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",
|
||||
"version": "1.0.0-alpha8",
|
||||
"version": "1.0.0-alpha9",
|
||||
"license": "MIT",
|
||||
"description": "Static CMS core application.",
|
||||
"repository": "https://github.com/StaticJsCMS/static-cms",
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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],
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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...',
|
||||
|
Loading…
x
Reference in New Issue
Block a user