Remove deprecated fields, fix loader styles, fix snackbars not appearing on login page

This commit is contained in:
Daniel Lautzenheiser 2022-10-26 10:57:40 -04:00
parent 40a2a20b97
commit c96c5b4e70
6 changed files with 100 additions and 189 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@staticcms/core",
"version": "1.0.0-alpha8",
"version": "1.0.0-alpha9",
"license": "MIT",
"description": "Static CMS core application.",
"repository": "https://github.com/StaticJsCMS/static-cms",

View File

@ -14,14 +14,14 @@ import { getIntegrations, selectIntegration } from '../reducers/integrations';
import type { AnyAction } from 'redux';
import type { ThunkDispatch } from 'redux-thunk';
import type {
BaseField,
Collection,
Config,
Field,
BaseField,
ListField,
ObjectField,
I18nInfo,
ListField,
LocalBackend,
ObjectField,
} from '../interface';
import type { RootState } from '../store';
@ -73,35 +73,6 @@ function setDefaultPublicFolderForField<T extends Field>(field: T) {
return field;
}
// Mapping between existing camelCase and its snake_case counterpart
const WIDGET_KEY_MAP = {
dateFormat: 'date_format',
timeFormat: 'time_format',
pickerUtc: 'picker_utc',
editorComponents: 'editor_components',
valueType: 'value_type',
valueField: 'value_field',
searchFields: 'search_fields',
displayFields: 'display_fields',
optionsLength: 'options_length',
} as const;
function setSnakeCaseConfig<T extends Field>(field: T) {
const deprecatedKeys = Object.keys(WIDGET_KEY_MAP).filter(
camel => camel in field,
) as ReadonlyArray<keyof typeof WIDGET_KEY_MAP>;
const snakeValues = deprecatedKeys.map(camel => {
const snake = WIDGET_KEY_MAP[camel];
console.warn(
`Field ${field.name} is using a deprecated configuration '${camel}'. Please use '${snake}'`,
);
return { [snake]: (field as unknown as Record<string, unknown>)[camel] };
});
return Object.assign({}, field, ...snakeValues) as T;
}
function setI18nField<T extends Field>(field: T) {
if (field[I18N] === true) {
return { ...field, [I18N]: I18N_FIELD.TRANSLATE };
@ -161,32 +132,6 @@ function hasIntegration(config: Config, collection: Collection) {
return !!integration;
}
export function normalizeConfig(config: Config) {
const { collections = [] } = config;
const normalizedCollections = collections.map(collection => {
const { fields, files } = collection;
let normalizedCollection = collection;
if (fields) {
const normalizedFields = traverseFieldsJS(fields, setSnakeCaseConfig);
normalizedCollection = { ...normalizedCollection, fields: normalizedFields };
}
if (files) {
const normalizedFiles = files.map(file => {
const normalizedFileFields = traverseFieldsJS(file.fields, setSnakeCaseConfig);
return { ...file, fields: normalizedFileFields };
});
normalizedCollection = { ...normalizedCollection, files: normalizedFiles };
}
return normalizedCollection;
});
return { ...config, collections: normalizedCollections };
}
export function applyDefaults(originalConfig: Config) {
return produce(originalConfig, config => {
config.slug = config.slug || {};
@ -451,9 +396,7 @@ export function loadConfig(manualConfig: Config | undefined, onLoad: () => unkno
validateConfig(mergedConfig);
const withLocalBackend = await handleLocalBackend(mergedConfig);
const normalizedConfig = normalizeConfig(withLocalBackend);
const config = applyDefaults(normalizedConfig);
const config = applyDefaults(withLocalBackend);
dispatch(configLoaded(config));

View File

@ -3,7 +3,6 @@ import { styled } from '@mui/material/styles';
import TextField from '@mui/material/TextField';
import React, { useCallback, useEffect, useState } from 'react';
import alert from '../../components/UI/Alert';
import AuthenticationPage from '../../components/UI/AuthenticationPage';
import { colors } from '../../components/UI/styles';
@ -57,37 +56,17 @@ const GitGatewayAuthenticationPage = ({
}>({});
useEffect(() => {
try {
if (!loggedIn && window.netlifyIdentity && window.netlifyIdentity.currentUser()) {
onLogin(window.netlifyIdentity.currentUser());
window.netlifyIdentity.close();
}
} catch (e: unknown) {
console.error(e);
if (e instanceof Error) {
alert({
title: 'auth.errors.authTitle',
body: { key: 'auth.errors.authBody', options: { details: e.message } },
});
}
if (!loggedIn && window.netlifyIdentity && window.netlifyIdentity.currentUser()) {
onLogin(window.netlifyIdentity.currentUser());
window.netlifyIdentity.close();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleIdentityLogin = useCallback(
(user: User) => {
try {
onLogin(user);
window.netlifyIdentity?.close();
} catch (e: unknown) {
console.error(e);
if (e instanceof Error) {
alert({
title: 'auth.errors.authTitle',
body: { key: 'auth.errors.authBody', options: { details: e.message } },
});
}
}
onLogin(user);
window.netlifyIdentity?.close();
},
[onLogin],
);
@ -113,21 +92,11 @@ const GitGatewayAuthenticationPage = ({
useNetlifyIdentifyEvent('error', handleIdentityError);
const handleIdentity = useCallback(() => {
try {
const user = window.netlifyIdentity?.currentUser();
if (user) {
onLogin(user);
} else {
window.netlifyIdentity?.open();
}
} catch (e: unknown) {
console.error(e);
if (e instanceof Error) {
alert({
title: 'auth.errors.authTitle',
body: { key: 'auth.errors.authBody', options: { details: e.message } },
});
}
const user = window.netlifyIdentity?.currentUser();
if (user) {
onLogin(user);
} else {
window.netlifyIdentity?.open();
}
}, [onLogin]);
@ -173,17 +142,7 @@ const GitGatewayAuthenticationPage = ({
return;
}
try {
onLogin(response);
} catch (e: unknown) {
console.error(e);
if (e instanceof Error) {
alert({
title: 'auth.errors.authTitle',
body: { key: 'auth.errors.authBody', options: { details: e.message } },
});
}
}
onLogin(response);
},
[email, handleAuth, onLogin, password, t],
);

View File

@ -1,6 +1,6 @@
import { styled } from '@mui/material/styles';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import Fab from '@mui/material/Fab';
import { styled } from '@mui/material/styles';
import React, { useCallback, useMemo } from 'react';
import { translate } from 'react-polyglot';
import { connect } from 'react-redux';
@ -92,18 +92,21 @@ const App = ({
t,
scrollSyncEnabled,
}: TranslatedProps<AppProps>) => {
const configError = useCallback(() => {
return (
<ErrorContainer>
<h1>{t('app.app.errorHeader')}</h1>
<div>
<strong>{t('app.app.configErrors')}:</strong>
<ErrorCodeBlock>{config.error}</ErrorCodeBlock>
<span>{t('app.app.checkConfigYml')}</span>
</div>
</ErrorContainer>
);
}, [config.error, t]);
const configError = useCallback(
(error?: string) => {
return (
<ErrorContainer>
<h1>{t('app.app.errorHeader')}</h1>
<div>
<strong>{t('app.app.configErrors')}:</strong>
<ErrorCodeBlock>{error ?? config.error}</ErrorCodeBlock>
<span>{t('app.app.checkConfigYml')}</span>
</div>
</ErrorContainer>
);
},
[config.error, t],
);
const handleLogin = useCallback(
(credentials: Credentials) => {
@ -154,8 +157,59 @@ const App = ({
const defaultPath = useMemo(() => getDefaultPath(collections), [collections]);
const content = useMemo(() => {
if (!user) {
return authenticationPage;
}
return (
<>
{isFetching && <TopBarProgress />}
<Routes>
<Route path="/" element={<Navigate to={defaultPath} />} />
<Route path="/search" element={<Navigate to={defaultPath} />} />
<Route path="/collections/:name/search/" element={<CollectionSearchRedirect />} />
<Route
path="/error=access_denied&error_description=Signups+not+allowed+for+this+instance"
element={<Navigate to={defaultPath} />}
/>
<Route path="/collections" element={<CollectionRoute collections={collections} />} />
<Route
path="/collections/:name"
element={<CollectionRoute collections={collections} />}
/>
<Route
path="/collections/:name/new"
element={<EditorRoute collections={collections} newRecord />}
/>
<Route
path="/collections/:name/entries/:slug"
element={<EditorRoute collections={collections} />}
/>
<Route
path="/collections/:name/search/:searchTerm"
element={
<CollectionRoute collections={collections} isSearchResults isSingleSearchResult />
}
/>
<Route
path="/collections/:name/filter/:filterTerm"
element={<CollectionRoute collections={collections} />}
/>
<Route
path="/search/:searchTerm"
element={<CollectionRoute collections={collections} isSearchResults />}
/>
<Route path="/edit/:name/:entryName" element={<EditEntityRedirect />} />
<Route element={<NotFoundPage />} />
</Routes>
{useMediaLibrary ? <MediaLibrary /> : null}
</>
);
}, [authenticationPage, collections, defaultPath, isFetching, useMediaLibrary, user]);
if (!config.config) {
return null;
return configError(t('app.app.configNotFound'));
}
if (config.error) {
@ -166,71 +220,21 @@ const App = ({
return <Loader>{t('app.app.loadingConfig')}</Loader>;
}
if (!user) {
return authenticationPage;
}
return (
<>
<GlobalStyles />
<ScrollSync enabled={scrollSyncEnabled}>
<GlobalStyles key="global-styles" />
<ScrollSync key="scroll-sync" enabled={scrollSyncEnabled}>
<>
<div id="back-to-top-anchor" />
<AppRoot id="cms-root">
<AppWrapper className="cms-wrapper">
<Snackbars />
{isFetching && <TopBarProgress />}
<Routes>
<Route path="/" element={<Navigate to={defaultPath} />} />
<Route path="/search" element={<Navigate to={defaultPath} />} />
<Route path="/collections/:name/search/" element={<CollectionSearchRedirect />} />
<Route
path="/error=access_denied&error_description=Signups+not+allowed+for+this+instance"
element={<Navigate to={defaultPath} />}
/>
<Route
path="/collections"
element={<CollectionRoute collections={collections} />}
/>
<Route
path="/collections/:name"
element={<CollectionRoute collections={collections} />}
/>
<Route
path="/collections/:name/new"
element={<EditorRoute collections={collections} newRecord />}
/>
<Route
path="/collections/:name/entries/:slug"
element={<EditorRoute collections={collections} />}
/>
<Route
path="/collections/:name/search/:searchTerm"
element={
<CollectionRoute
collections={collections}
isSearchResults
isSingleSearchResult
/>
}
/>
<Route
path="/collections/:name/filter/:filterTerm"
element={<CollectionRoute collections={collections} />}
/>
<Route
path="/search/:searchTerm"
element={<CollectionRoute collections={collections} isSearchResults />}
/>
<Route path="/edit/:name/:entryName" element={<EditEntityRedirect />} />
<Route element={<NotFoundPage />} />
</Routes>
{useMediaLibrary ? <MediaLibrary /> : null}
<Alert />
<Confirm />
<div key="back-to-top-anchor" id="back-to-top-anchor" />
<AppRoot key="cms-root" id="cms-root">
<AppWrapper key="cms-wrapper" className="cms-wrapper">
<Snackbars key="snackbars" />
{content}
<Alert key="alert" />
<Confirm key="confirm" />
</AppWrapper>
</AppRoot>
<ScrollTop>
<ScrollTop key="scroll-to-top">
<Fab size="small" aria-label="scroll back to top">
<KeyboardArrowUpIcon />
</Fab>

View File

@ -4,8 +4,12 @@ import Typography from '@mui/material/Typography';
import React, { useEffect, useMemo, useState } from 'react';
const StyledLoader = styled('div')`
position: fixed;
display: flex;
width: 100%;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
flex-direction: column;
align-items: center;
justify-content: center;

View File

@ -27,6 +27,7 @@ const en: LocalePhrasesRoot = {
app: {
errorHeader: 'Error loading the CMS configuration',
configErrors: 'Config Errors',
configNotFound: 'Config not found',
checkConfigYml: 'Check your config.yml file.',
loadingConfig: 'Loading configuration...',
waitingBackend: 'Waiting for backend...',