Remove deprecated fields, fix loader styles, fix snackbars not appearing on login page
This commit is contained in:
parent
40a2a20b97
commit
c96c5b4e70
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@staticcms/core",
|
"name": "@staticcms/core",
|
||||||
"version": "1.0.0-alpha8",
|
"version": "1.0.0-alpha9",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Static CMS core application.",
|
"description": "Static CMS core application.",
|
||||||
"repository": "https://github.com/StaticJsCMS/static-cms",
|
"repository": "https://github.com/StaticJsCMS/static-cms",
|
||||||
|
@ -14,14 +14,14 @@ import { getIntegrations, selectIntegration } from '../reducers/integrations';
|
|||||||
import type { AnyAction } from 'redux';
|
import type { AnyAction } from 'redux';
|
||||||
import type { ThunkDispatch } from 'redux-thunk';
|
import type { ThunkDispatch } from 'redux-thunk';
|
||||||
import type {
|
import type {
|
||||||
|
BaseField,
|
||||||
Collection,
|
Collection,
|
||||||
Config,
|
Config,
|
||||||
Field,
|
Field,
|
||||||
BaseField,
|
|
||||||
ListField,
|
|
||||||
ObjectField,
|
|
||||||
I18nInfo,
|
I18nInfo,
|
||||||
|
ListField,
|
||||||
LocalBackend,
|
LocalBackend,
|
||||||
|
ObjectField,
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
import type { RootState } from '../store';
|
import type { RootState } from '../store';
|
||||||
|
|
||||||
@ -73,35 +73,6 @@ function setDefaultPublicFolderForField<T extends Field>(field: T) {
|
|||||||
return field;
|
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) {
|
function setI18nField<T extends Field>(field: T) {
|
||||||
if (field[I18N] === true) {
|
if (field[I18N] === true) {
|
||||||
return { ...field, [I18N]: I18N_FIELD.TRANSLATE };
|
return { ...field, [I18N]: I18N_FIELD.TRANSLATE };
|
||||||
@ -161,32 +132,6 @@ function hasIntegration(config: Config, collection: Collection) {
|
|||||||
return !!integration;
|
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) {
|
export function applyDefaults(originalConfig: Config) {
|
||||||
return produce(originalConfig, config => {
|
return produce(originalConfig, config => {
|
||||||
config.slug = config.slug || {};
|
config.slug = config.slug || {};
|
||||||
@ -451,9 +396,7 @@ export function loadConfig(manualConfig: Config | undefined, onLoad: () => unkno
|
|||||||
validateConfig(mergedConfig);
|
validateConfig(mergedConfig);
|
||||||
|
|
||||||
const withLocalBackend = await handleLocalBackend(mergedConfig);
|
const withLocalBackend = await handleLocalBackend(mergedConfig);
|
||||||
const normalizedConfig = normalizeConfig(withLocalBackend);
|
const config = applyDefaults(withLocalBackend);
|
||||||
|
|
||||||
const config = applyDefaults(normalizedConfig);
|
|
||||||
|
|
||||||
dispatch(configLoaded(config));
|
dispatch(configLoaded(config));
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import { styled } from '@mui/material/styles';
|
|||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import alert from '../../components/UI/Alert';
|
|
||||||
import AuthenticationPage from '../../components/UI/AuthenticationPage';
|
import AuthenticationPage from '../../components/UI/AuthenticationPage';
|
||||||
import { colors } from '../../components/UI/styles';
|
import { colors } from '../../components/UI/styles';
|
||||||
|
|
||||||
@ -57,37 +56,17 @@ const GitGatewayAuthenticationPage = ({
|
|||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
if (!loggedIn && window.netlifyIdentity && window.netlifyIdentity.currentUser()) {
|
||||||
if (!loggedIn && window.netlifyIdentity && window.netlifyIdentity.currentUser()) {
|
onLogin(window.netlifyIdentity.currentUser());
|
||||||
onLogin(window.netlifyIdentity.currentUser());
|
window.netlifyIdentity.close();
|
||||||
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 } },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleIdentityLogin = useCallback(
|
const handleIdentityLogin = useCallback(
|
||||||
(user: User) => {
|
(user: User) => {
|
||||||
try {
|
onLogin(user);
|
||||||
onLogin(user);
|
window.netlifyIdentity?.close();
|
||||||
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],
|
[onLogin],
|
||||||
);
|
);
|
||||||
@ -113,21 +92,11 @@ const GitGatewayAuthenticationPage = ({
|
|||||||
useNetlifyIdentifyEvent('error', handleIdentityError);
|
useNetlifyIdentifyEvent('error', handleIdentityError);
|
||||||
|
|
||||||
const handleIdentity = useCallback(() => {
|
const handleIdentity = useCallback(() => {
|
||||||
try {
|
const user = window.netlifyIdentity?.currentUser();
|
||||||
const user = window.netlifyIdentity?.currentUser();
|
if (user) {
|
||||||
if (user) {
|
onLogin(user);
|
||||||
onLogin(user);
|
} else {
|
||||||
} else {
|
window.netlifyIdentity?.open();
|
||||||
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 } },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [onLogin]);
|
}, [onLogin]);
|
||||||
|
|
||||||
@ -173,17 +142,7 @@ const GitGatewayAuthenticationPage = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
onLogin(response);
|
||||||
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 } },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[email, handleAuth, onLogin, password, t],
|
[email, handleAuth, onLogin, password, t],
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
|
||||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||||
import Fab from '@mui/material/Fab';
|
import Fab from '@mui/material/Fab';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { translate } from 'react-polyglot';
|
import { translate } from 'react-polyglot';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@ -92,18 +92,21 @@ const App = ({
|
|||||||
t,
|
t,
|
||||||
scrollSyncEnabled,
|
scrollSyncEnabled,
|
||||||
}: TranslatedProps<AppProps>) => {
|
}: TranslatedProps<AppProps>) => {
|
||||||
const configError = useCallback(() => {
|
const configError = useCallback(
|
||||||
return (
|
(error?: string) => {
|
||||||
<ErrorContainer>
|
return (
|
||||||
<h1>{t('app.app.errorHeader')}</h1>
|
<ErrorContainer>
|
||||||
<div>
|
<h1>{t('app.app.errorHeader')}</h1>
|
||||||
<strong>{t('app.app.configErrors')}:</strong>
|
<div>
|
||||||
<ErrorCodeBlock>{config.error}</ErrorCodeBlock>
|
<strong>{t('app.app.configErrors')}:</strong>
|
||||||
<span>{t('app.app.checkConfigYml')}</span>
|
<ErrorCodeBlock>{error ?? config.error}</ErrorCodeBlock>
|
||||||
</div>
|
<span>{t('app.app.checkConfigYml')}</span>
|
||||||
</ErrorContainer>
|
</div>
|
||||||
);
|
</ErrorContainer>
|
||||||
}, [config.error, t]);
|
);
|
||||||
|
},
|
||||||
|
[config.error, t],
|
||||||
|
);
|
||||||
|
|
||||||
const handleLogin = useCallback(
|
const handleLogin = useCallback(
|
||||||
(credentials: Credentials) => {
|
(credentials: Credentials) => {
|
||||||
@ -154,8 +157,59 @@ const App = ({
|
|||||||
|
|
||||||
const defaultPath = useMemo(() => getDefaultPath(collections), [collections]);
|
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) {
|
if (!config.config) {
|
||||||
return null;
|
return configError(t('app.app.configNotFound'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.error) {
|
if (config.error) {
|
||||||
@ -166,71 +220,21 @@ const App = ({
|
|||||||
return <Loader>{t('app.app.loadingConfig')}</Loader>;
|
return <Loader>{t('app.app.loadingConfig')}</Loader>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return authenticationPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GlobalStyles />
|
<GlobalStyles key="global-styles" />
|
||||||
<ScrollSync enabled={scrollSyncEnabled}>
|
<ScrollSync key="scroll-sync" enabled={scrollSyncEnabled}>
|
||||||
<>
|
<>
|
||||||
<div id="back-to-top-anchor" />
|
<div key="back-to-top-anchor" id="back-to-top-anchor" />
|
||||||
<AppRoot id="cms-root">
|
<AppRoot key="cms-root" id="cms-root">
|
||||||
<AppWrapper className="cms-wrapper">
|
<AppWrapper key="cms-wrapper" className="cms-wrapper">
|
||||||
<Snackbars />
|
<Snackbars key="snackbars" />
|
||||||
{isFetching && <TopBarProgress />}
|
{content}
|
||||||
<Routes>
|
<Alert key="alert" />
|
||||||
<Route path="/" element={<Navigate to={defaultPath} />} />
|
<Confirm key="confirm" />
|
||||||
<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 />
|
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
</AppRoot>
|
</AppRoot>
|
||||||
<ScrollTop>
|
<ScrollTop key="scroll-to-top">
|
||||||
<Fab size="small" aria-label="scroll back to top">
|
<Fab size="small" aria-label="scroll back to top">
|
||||||
<KeyboardArrowUpIcon />
|
<KeyboardArrowUpIcon />
|
||||||
</Fab>
|
</Fab>
|
||||||
|
@ -4,8 +4,12 @@ import Typography from '@mui/material/Typography';
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
const StyledLoader = styled('div')`
|
const StyledLoader = styled('div')`
|
||||||
|
position: fixed;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -27,6 +27,7 @@ const en: LocalePhrasesRoot = {
|
|||||||
app: {
|
app: {
|
||||||
errorHeader: 'Error loading the CMS configuration',
|
errorHeader: 'Error loading the CMS configuration',
|
||||||
configErrors: 'Config Errors',
|
configErrors: 'Config Errors',
|
||||||
|
configNotFound: 'Config not found',
|
||||||
checkConfigYml: 'Check your config.yml file.',
|
checkConfigYml: 'Check your config.yml file.',
|
||||||
loadingConfig: 'Loading configuration...',
|
loadingConfig: 'Loading configuration...',
|
||||||
waitingBackend: 'Waiting for backend...',
|
waitingBackend: 'Waiting for backend...',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user