0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-10 22:22:45 -05:00

refactor(console): make custom languages become available language options (#2038)

This commit is contained in:
Xiao Yijun 2022-10-08 17:45:55 +08:00 committed by GitHub
parent e3de677bd3
commit 4995ab9461
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 257 additions and 246 deletions

View file

@ -0,0 +1,37 @@
import { builtInLanguages as builtInUiLanguages } from '@logto/phrases-ui';
import { useMemo } from 'react';
import useSWR from 'swr';
import { CustomPhraseResponse } from '@/types/custom-phrase';
import { RequestError } from './use-api';
const useUiLanguages = () => {
const {
data: customPhraseList,
error,
mutate,
} = useSWR<CustomPhraseResponse[], RequestError>('/api/custom-phrases');
const languages = useMemo(
() =>
[
...new Set([
...builtInUiLanguages,
...(customPhraseList?.map(({ languageTag }) => languageTag) ?? []),
]),
]
.slice()
.sort(),
[customPhraseList]
);
return {
languages,
error,
isLoading: !customPhraseList && !error,
mutate,
};
};
export default useUiLanguages;

View file

@ -1,6 +1,6 @@
import { builtInLanguageOptions } from '@logto/phrases-ui';
import { languages as uiLanguageNameMapping } from '@logto/language-kit';
import classNames from 'classnames';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
@ -8,8 +8,9 @@ import FormField from '@/components/FormField';
import Select from '@/components/Select';
import Switch from '@/components/Switch';
import * as textButtonStyles from '@/components/TextButton/index.module.scss';
import useUiLanguages from '@/hooks/use-ui-languages';
import useCustomPhrasesContext from '../hooks/use-custom-phrases-context';
import useLanguageEditorContext from '../hooks/use-language-editor-context';
import { SignInExperienceForm } from '../types';
import ManageLanguageModal from './ManageLanguageModal';
import * as styles from './index.module.scss';
@ -23,9 +24,17 @@ const LanguagesForm = ({ isManageLanguageVisible = false }: Props) => {
const { watch, control, register } = useFormContext<SignInExperienceForm>();
const isAutoDetect = watch('languageInfo.autoDetect');
const [isManageLanguageFormOpen, setIsManageLanguageFormOpen] = useState(false);
const { languages } = useUiLanguages();
const { context: customPhrasesContext, Provider: CustomPhrasesContextProvider } =
useCustomPhrasesContext();
const languageOptions = useMemo(() => {
return languages.map((languageTag) => ({
value: languageTag,
title: uiLanguageNameMapping[languageTag],
}));
}, [languages]);
const { context: languageEditorContext, Provider: LanguageEditorContextProvider } =
useLanguageEditorContext(languages);
return (
<>
@ -51,7 +60,7 @@ const LanguagesForm = ({ isManageLanguageVisible = false }: Props) => {
name="languageInfo.fallbackLanguage"
control={control}
render={({ field: { value, onChange } }) => (
<Select value={value} options={builtInLanguageOptions} onChange={onChange} />
<Select value={value} options={languageOptions} onChange={onChange} />
)}
/>
<div className={styles.defaultLanguageDescription}>
@ -60,14 +69,15 @@ const LanguagesForm = ({ isManageLanguageVisible = false }: Props) => {
: t('sign_in_exp.others.languages.default_language_description_fixed')}
</div>
</FormField>
<CustomPhrasesContextProvider value={customPhrasesContext}>
<LanguageEditorContextProvider value={languageEditorContext}>
<ManageLanguageModal
isOpen={isManageLanguageFormOpen}
languageTags={languages}
onClose={() => {
setIsManageLanguageFormOpen(false);
}}
/>
</CustomPhrasesContextProvider>
</LanguageEditorContextProvider>
</>
);
};

View file

@ -7,14 +7,14 @@ import { useCallback, useContext, useEffect, useMemo } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
import useSWR, { useSWRConfig } from 'swr';
import Button from '@/components/Button';
import useApi, { RequestError } from '@/hooks/use-api';
import Delete from '@/icons/Delete';
import { CustomPhraseResponse } from '@/types/custom-phrase';
import { CustomPhrasesContext } from '../../hooks/use-custom-phrases-context';
import { CustomPhraseResponse } from '../../types';
import { LanguageEditorContext } from '../../hooks/use-language-editor-context';
import { createEmptyUiTranslation, flattenTranslation } from '../../utilities';
import EditSection from './EditSection';
import * as style from './LanguageEditor.module.scss';
@ -24,22 +24,17 @@ const emptyUiTranslation = createEmptyUiTranslation();
const LanguageEditor = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
selectedLanguageTag,
appendToCustomPhraseList,
setIsCurrentCustomPhraseDirty,
stopAddingLanguage,
} = useContext(CustomPhrasesContext);
const { selectedLanguage, setIsDirty, stopAddingLanguage } = useContext(LanguageEditorContext);
const isBuiltIn = isBuiltInLanguageTag(selectedLanguageTag);
const isBuiltIn = isBuiltInLanguageTag(selectedLanguage);
const translationEntries = useMemo(
() => Object.entries((isBuiltIn ? resource[selectedLanguageTag] : en).translation),
[isBuiltIn, selectedLanguageTag]
() => Object.entries((isBuiltIn ? resource[selectedLanguage] : en).translation),
[isBuiltIn, selectedLanguage]
);
const { data: customPhrase, mutate } = useSWR<CustomPhraseResponse, RequestError>(
`/api/custom-phrases/${selectedLanguageTag}`,
`/api/custom-phrases/${selectedLanguage}`,
{
shouldRetryOnError: (error: unknown) => {
if (error instanceof RequestError) {
@ -76,7 +71,7 @@ const LanguageEditor = () => {
* for the `isDirty` state does not work correctly when comparing form data with empty / undefined values.
* Reference: https://github.com/react-hook-form/react-hook-form/issues/4740
*/
setIsCurrentCustomPhraseDirty(isDirty && Object.keys(dirtyFields).length > 0);
setIsDirty(isDirty && Object.keys(dirtyFields).length > 0);
}, [
/**
* Note: `isDirty` is used to trigger this `useEffect`; for `dirtyFields` object only marks filed dirty at field level.
@ -84,9 +79,11 @@ const LanguageEditor = () => {
*/
isDirty,
dirtyFields,
setIsCurrentCustomPhraseDirty,
setIsDirty,
]);
const { mutate: globalMutate } = useSWRConfig();
const api = useApi();
const upsertCustomPhrase = useCallback(
@ -99,17 +96,17 @@ const LanguageEditor = () => {
})
.json<CustomPhraseResponse>();
appendToCustomPhraseList(updatedCustomPhrase);
void globalMutate('/api/custom-phrases');
stopAddingLanguage();
return updatedCustomPhrase;
},
[api, appendToCustomPhraseList, stopAddingLanguage]
[api, globalMutate, stopAddingLanguage]
);
const onSubmit = handleSubmit(async (formData: Translation) => {
const updatedCustomPhrase = await upsertCustomPhrase(selectedLanguageTag, formData);
const updatedCustomPhrase = await upsertCustomPhrase(selectedLanguage, formData);
void mutate(updatedCustomPhrase);
toast.success(t('general.saved'));
});
@ -118,10 +115,10 @@ const LanguageEditor = () => {
reset(defaultFormValues);
}, [
/**
* Note: trigger form reset when selectedLanguageTag changed,
* Note: trigger form reset when selectedLanguage changed,
* for the `defaultValues` will not change when switching between languages with unavailable custom phrases.
*/
selectedLanguageTag,
selectedLanguage,
defaultFormValues,
reset,
]);
@ -129,8 +126,8 @@ const LanguageEditor = () => {
return (
<div className={style.languageEditor}>
<div className={style.title}>
{languages[selectedLanguageTag]}
<span>{selectedLanguageTag}</span>
{languages[selectedLanguage]}
<span>{selectedLanguage}</span>
{isBuiltIn && (
<span className={style.builtInFlag}>
{t('sign_in_exp.others.manage_language.logto_provided')}

View file

@ -1,32 +1,36 @@
import { isLanguageTag, languages, LanguageTag } from '@logto/language-kit';
import {
isLanguageTag,
LanguageTag,
languages as uiLanguageNameMapping,
} from '@logto/language-kit';
import { useContext } from 'react';
import { CustomPhrasesContext } from '../../hooks/use-custom-phrases-context';
import { LanguageEditorContext } from '../../hooks/use-language-editor-context';
import AddLanguageSelector from './AddLanguageSelector';
import LanguageItem from './LanguageItem';
import * as style from './LanguageNav.module.scss';
const LanguageNav = () => {
const {
displayingLanguages,
selectedLanguageTag,
languages,
selectedLanguage,
isAddingLanguage,
isCurrentCustomPhraseDirty,
isDirty,
setConfirmationState,
setSelectedLanguageTag,
setPreSelectedLanguageTag,
setPreAddedLanguageTag,
setSelectedLanguage,
setPreSelectedLanguage,
setPreAddedLanguage,
startAddingLanguage,
} = useContext(CustomPhrasesContext);
} = useContext(LanguageEditorContext);
const languageOptions = Object.keys(languages).filter(
const languageOptions = Object.keys(uiLanguageNameMapping).filter(
(languageTag): languageTag is LanguageTag =>
isLanguageTag(languageTag) && !displayingLanguages.includes(languageTag)
isLanguageTag(languageTag) && !languages.includes(languageTag)
);
const onAddLanguage = (languageTag: LanguageTag) => {
if (isCurrentCustomPhraseDirty || isAddingLanguage) {
setPreAddedLanguageTag(languageTag);
if (isDirty || isAddingLanguage) {
setPreAddedLanguage(languageTag);
setConfirmationState('try-add-language');
return;
@ -36,25 +40,25 @@ const LanguageNav = () => {
};
const onSwitchLanguage = (languageTag: LanguageTag) => {
if (isCurrentCustomPhraseDirty || isAddingLanguage) {
setPreSelectedLanguageTag(languageTag);
if (isDirty || isAddingLanguage) {
setPreSelectedLanguage(languageTag);
setConfirmationState('try-switch-language');
return;
}
setSelectedLanguageTag(languageTag);
setSelectedLanguage(languageTag);
};
return (
<div className={style.languageNav}>
<AddLanguageSelector options={languageOptions} onSelect={onAddLanguage} />
<div className={style.languageItemList}>
{displayingLanguages.map((languageTag) => (
{languages.map((languageTag) => (
<LanguageItem
key={languageTag}
languageTag={languageTag}
isSelected={selectedLanguageTag === languageTag}
isSelected={selectedLanguage === languageTag}
onClick={() => {
onSwitchLanguage(languageTag);
}}

View file

@ -1,3 +1,4 @@
import { LanguageTag } from '@logto/language-kit';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import Modal from 'react-modal';
@ -6,42 +7,42 @@ import ConfirmModal from '@/components/ConfirmModal';
import ModalLayout from '@/components/ModalLayout';
import * as modalStyles from '@/scss/modal.module.scss';
import { CustomPhrasesContext } from '../../hooks/use-custom-phrases-context';
import { LanguageEditorContext } from '../../hooks/use-language-editor-context';
import LanguageEditor from './LanguageEditor';
import LanguageNav from './LanguageNav';
import * as style from './index.module.scss';
type ManageLanguageModalProps = {
isOpen: boolean;
languageTags: LanguageTag[];
onClose: () => void;
};
const ManageLanguageModal = ({ isOpen, onClose }: ManageLanguageModalProps) => {
const ManageLanguageModal = ({ isOpen, languageTags, onClose }: ManageLanguageModalProps) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
preSelectedLanguageTag,
preAddedLanguageTag,
preSelectedLanguage,
preAddedLanguage,
isAddingLanguage,
isCurrentCustomPhraseDirty,
isDirty,
confirmationState,
setSelectedLanguageTag,
setPreSelectedLanguageTag,
setSelectedLanguage,
setPreSelectedLanguage,
setConfirmationState,
startAddingLanguage,
stopAddingLanguage,
resetSelectedLanguageTag,
} = useContext(CustomPhrasesContext);
} = useContext(LanguageEditorContext);
const onCloseModal = () => {
if (isAddingLanguage || isCurrentCustomPhraseDirty) {
if (isAddingLanguage || isDirty) {
setConfirmationState('try-close');
return;
}
onClose();
resetSelectedLanguageTag();
setSelectedLanguage(languageTags[0] ?? 'en');
};
const onConfirmUnsavedChanges = () => {
@ -51,13 +52,13 @@ const ManageLanguageModal = ({ isOpen, onClose }: ManageLanguageModalProps) => {
onClose();
}
if (confirmationState === 'try-switch-language' && preSelectedLanguageTag) {
setSelectedLanguageTag(preSelectedLanguageTag);
setPreSelectedLanguageTag(undefined);
if (confirmationState === 'try-switch-language' && preSelectedLanguage) {
setSelectedLanguage(preSelectedLanguage);
setPreSelectedLanguage(undefined);
}
if (confirmationState === 'try-add-language' && preAddedLanguageTag) {
startAddingLanguage(preAddedLanguageTag);
if (confirmationState === 'try-add-language' && preAddedLanguage) {
startAddingLanguage(preAddedLanguage);
}
setConfirmationState('none');

View file

@ -1,5 +1,4 @@
import { LanguageTag } from '@logto/language-kit';
import { builtInLanguageOptions } from '@logto/phrases-ui';
import { LanguageTag, languages as uiLanguageNameMapping } from '@logto/language-kit';
import {
AppearanceMode,
ConnectorResponse,
@ -17,6 +16,7 @@ import Card from '@/components/Card';
import Select from '@/components/Select';
import TabNav, { TabNavItem } from '@/components/TabNav';
import { RequestError } from '@/hooks/use-api';
import useUiLanguages from '@/hooks/use-ui-languages';
import PhoneInfo from '@/icons/PhoneInfo';
import * as styles from './Preview.module.scss';
@ -34,6 +34,8 @@ const Preview = ({ signInExperience, className }: Props) => {
const { data: allConnectors } = useSWR<ConnectorResponse[], RequestError>('/api/connectors');
const previewRef = useRef<HTMLIFrameElement>(null);
const { languages } = useUiLanguages();
const modeOptions = useMemo(() => {
const light = { value: AppearanceMode.LightMode, title: t('sign_in_exp.preview.light') };
const dark = { value: AppearanceMode.DarkMode, title: t('sign_in_exp.preview.dark') };
@ -56,14 +58,18 @@ const Preview = ({ signInExperience, className }: Props) => {
}, [modeOptions, mode]);
const availableLanguageOptions = useMemo(() => {
if (signInExperience && !signInExperience.languageInfo.autoDetect) {
return builtInLanguageOptions.filter(
({ value }) => value === signInExperience.languageInfo.fallbackLanguage
);
}
const availableLanguageTags =
signInExperience && !signInExperience.languageInfo.autoDetect
? languages.filter(
(languageTag) => languageTag === signInExperience.languageInfo.fallbackLanguage
)
: languages;
return builtInLanguageOptions;
}, [signInExperience]);
return availableLanguageTags.map((languageTag) => ({
value: languageTag,
title: uiLanguageNameMapping[languageTag],
}));
}, [languages, signInExperience]);
useEffect(() => {
if (!availableLanguageOptions[0]) {

View file

@ -1,165 +0,0 @@
import { LanguageTag } from '@logto/language-kit';
import { builtInLanguages as builtInUiLanguages } from '@logto/phrases-ui';
import { createContext, useCallback, useEffect, useMemo, useState } from 'react';
import useSWR from 'swr';
import { RequestError } from '@/hooks/use-api';
import { CustomPhraseResponse } from '../types';
const noop = () => {
throw new Error('Context provider not found');
};
export type ConfirmationState = 'none' | 'try-close' | 'try-switch-language' | 'try-add-language';
export type Context = {
displayingLanguages: LanguageTag[];
selectedLanguageTag: LanguageTag;
preSelectedLanguageTag: LanguageTag | undefined;
preAddedLanguageTag: LanguageTag | undefined;
isAddingLanguage: boolean;
isCurrentCustomPhraseDirty: boolean;
confirmationState: ConfirmationState;
setSelectedLanguageTag: React.Dispatch<React.SetStateAction<LanguageTag>>;
resetSelectedLanguageTag: () => void;
setPreSelectedLanguageTag: React.Dispatch<React.SetStateAction<LanguageTag | undefined>>;
setPreAddedLanguageTag: React.Dispatch<React.SetStateAction<LanguageTag | undefined>>;
setIsCurrentCustomPhraseDirty: React.Dispatch<React.SetStateAction<boolean>>;
appendToCustomPhraseList: (customPhrase: CustomPhraseResponse) => void;
setConfirmationState: React.Dispatch<React.SetStateAction<ConfirmationState>>;
startAddingLanguage: (languageTag: LanguageTag) => void;
stopAddingLanguage: (isCanceled?: boolean) => void;
};
export const CustomPhrasesContext = createContext<Context>({
displayingLanguages: [],
selectedLanguageTag: 'en',
preSelectedLanguageTag: undefined,
preAddedLanguageTag: undefined,
isAddingLanguage: false,
isCurrentCustomPhraseDirty: false,
confirmationState: 'none',
setSelectedLanguageTag: noop,
resetSelectedLanguageTag: noop,
setPreSelectedLanguageTag: noop,
setPreAddedLanguageTag: noop,
setIsCurrentCustomPhraseDirty: noop,
appendToCustomPhraseList: noop,
setConfirmationState: noop,
startAddingLanguage: noop,
stopAddingLanguage: noop,
});
const useCustomPhrasesContext = () => {
const { data: customPhraseList, mutate: mutateCustomPhraseList } = useSWR<
CustomPhraseResponse[],
RequestError
>('/api/custom-phrases');
const existedLanguageTags = useMemo(
() =>
[
...new Set([
...builtInUiLanguages,
...(customPhraseList?.map(({ languageTag }) => languageTag) ?? []),
]),
]
.slice()
.sort(),
[customPhraseList]
);
const [displayingLanguages, setDisplayingLanguages] =
useState<LanguageTag[]>(existedLanguageTags);
useEffect(() => {
setDisplayingLanguages(existedLanguageTags);
}, [existedLanguageTags]);
const defaultLanguageTag = useMemo(() => existedLanguageTags[0] ?? 'en', [existedLanguageTags]);
const [selectedLanguageTag, setSelectedLanguageTag] = useState<LanguageTag>(defaultLanguageTag);
const [preSelectedLanguageTag, setPreSelectedLanguageTag] = useState<LanguageTag>();
const [preAddedLanguageTag, setPreAddedLanguageTag] = useState<LanguageTag>();
const [isAddingLanguage, setIsAddingLanguage] = useState(false);
const [isCurrentCustomPhraseDirty, setIsCurrentCustomPhraseDirty] = useState(false);
const [confirmationState, setConfirmationState] = useState<ConfirmationState>('none');
const appendToCustomPhraseList = useCallback(
(customPhrase: CustomPhraseResponse) => {
void mutateCustomPhraseList([
customPhrase,
...(customPhraseList?.filter(
({ languageTag }) => languageTag !== customPhrase.languageTag
) ?? []),
]);
},
[customPhraseList, mutateCustomPhraseList]
);
const startAddingLanguage = useCallback(
(languageTag: LanguageTag) => {
setDisplayingLanguages([...new Set([languageTag, ...existedLanguageTags])].slice().sort());
setSelectedLanguageTag(languageTag);
setIsAddingLanguage(true);
},
[existedLanguageTags]
);
const stopAddingLanguage = useCallback(
(isCanceled = false) => {
if (isAddingLanguage) {
if (isCanceled) {
setDisplayingLanguages(existedLanguageTags);
}
setIsAddingLanguage(false);
}
},
[existedLanguageTags, isAddingLanguage]
);
const resetSelectedLanguageTag = useCallback(() => {
setSelectedLanguageTag(defaultLanguageTag);
}, [defaultLanguageTag]);
const context = useMemo<Context>(() => {
return {
displayingLanguages,
selectedLanguageTag,
preSelectedLanguageTag,
preAddedLanguageTag,
isAddingLanguage,
isCurrentCustomPhraseDirty,
confirmationState,
setSelectedLanguageTag,
resetSelectedLanguageTag,
setPreSelectedLanguageTag,
setPreAddedLanguageTag,
setIsCurrentCustomPhraseDirty,
appendToCustomPhraseList,
setConfirmationState,
startAddingLanguage,
stopAddingLanguage,
};
}, [
displayingLanguages,
selectedLanguageTag,
preSelectedLanguageTag,
preAddedLanguageTag,
isAddingLanguage,
isCurrentCustomPhraseDirty,
confirmationState,
resetSelectedLanguageTag,
appendToCustomPhraseList,
startAddingLanguage,
stopAddingLanguage,
]);
return {
context,
Provider: CustomPhrasesContext.Provider,
};
};
export default useCustomPhrasesContext;

View file

@ -0,0 +1,115 @@
import { LanguageTag } from '@logto/language-kit';
import { createContext, useCallback, useEffect, useMemo, useState } from 'react';
const noop = () => {
throw new Error('Context provider not found');
};
export type ConfirmationState = 'none' | 'try-close' | 'try-switch-language' | 'try-add-language';
export type Context = {
languages: LanguageTag[];
selectedLanguage: LanguageTag;
preSelectedLanguage?: LanguageTag;
preAddedLanguage?: LanguageTag;
isAddingLanguage: boolean;
isDirty: boolean;
confirmationState: ConfirmationState;
setSelectedLanguage: React.Dispatch<React.SetStateAction<LanguageTag>>;
setPreSelectedLanguage: React.Dispatch<React.SetStateAction<LanguageTag | undefined>>;
setPreAddedLanguage: React.Dispatch<React.SetStateAction<LanguageTag | undefined>>;
setIsDirty: React.Dispatch<React.SetStateAction<boolean>>;
setConfirmationState: React.Dispatch<React.SetStateAction<ConfirmationState>>;
startAddingLanguage: (languageTag: LanguageTag) => void;
stopAddingLanguage: (isCanceled?: boolean) => void;
};
export const LanguageEditorContext = createContext<Context>({
languages: [],
selectedLanguage: 'en',
preSelectedLanguage: undefined,
preAddedLanguage: undefined,
isAddingLanguage: false,
isDirty: false,
confirmationState: 'none',
setSelectedLanguage: noop,
setPreSelectedLanguage: noop,
setPreAddedLanguage: noop,
setIsDirty: noop,
setConfirmationState: noop,
startAddingLanguage: noop,
stopAddingLanguage: noop,
});
const useLanguageEditorContext = (defaultLanguages: LanguageTag[]) => {
const [languages, setLanguages] = useState(defaultLanguages);
useEffect(() => {
setLanguages(defaultLanguages);
}, [defaultLanguages]);
const [selectedLanguage, setSelectedLanguage] = useState<LanguageTag>(languages[0] ?? 'en');
const [preSelectedLanguage, setPreSelectedLanguage] = useState<LanguageTag>();
const [preAddedLanguage, setPreAddedLanguage] = useState<LanguageTag>();
const [isAddingLanguage, setIsAddingLanguage] = useState(false);
const [isDirty, setIsDirty] = useState(false);
const [confirmationState, setConfirmationState] = useState<ConfirmationState>('none');
const startAddingLanguage = useCallback(
(language: LanguageTag) => {
setLanguages([...new Set([language, ...defaultLanguages])].slice().sort());
setSelectedLanguage(language);
setIsAddingLanguage(true);
},
[defaultLanguages]
);
const stopAddingLanguage = useCallback(
(isCanceled = false) => {
if (isAddingLanguage) {
if (isCanceled) {
setLanguages(defaultLanguages);
}
setIsAddingLanguage(false);
}
},
[defaultLanguages, isAddingLanguage]
);
const context = useMemo<Context>(
() => ({
languages,
selectedLanguage,
preSelectedLanguage,
preAddedLanguage,
isAddingLanguage,
isDirty,
confirmationState,
setSelectedLanguage,
setPreSelectedLanguage,
setPreAddedLanguage,
setIsDirty,
setConfirmationState,
startAddingLanguage,
stopAddingLanguage,
}),
[
confirmationState,
isAddingLanguage,
isDirty,
languages,
preAddedLanguage,
preSelectedLanguage,
selectedLanguage,
startAddingLanguage,
stopAddingLanguage,
]
);
return {
context,
Provider: LanguageEditorContext.Provider,
};
};
export default useLanguageEditorContext;

View file

@ -14,6 +14,7 @@ import ConfirmModal from '@/components/ConfirmModal';
import TabNav, { TabNavItem } from '@/components/TabNav';
import useApi, { RequestError } from '@/hooks/use-api';
import useSettings from '@/hooks/use-settings';
import useUiLanguages from '@/hooks/use-ui-languages';
import * as detailsStyles from '@/scss/details.module.scss';
import Preview from './components/Preview';
@ -33,6 +34,7 @@ const SignInExperience = () => {
const { tab } = useParams();
const { data, error, mutate } = useSWR<SignInExperienceType, RequestError>('/api/sign-in-exp');
const { settings, error: settingsError, updateSettings, mutate: mutateSettings } = useSettings();
const { error: languageError, isLoading: isLoadingLanguages } = useUiLanguages();
const [dataToCompare, setDataToCompare] = useState<SignInExperienceType>();
const methods = useForm<SignInExperienceForm>();
@ -90,7 +92,7 @@ const SignInExperience = () => {
await saveData();
});
if ((!settings && !settingsError) || (!data && !error)) {
if ((!settings && !settingsError) || (!data && !error) || isLoadingLanguages) {
return <Skeleton />;
}
@ -98,6 +100,10 @@ const SignInExperience = () => {
return <div>{settingsError.body?.message ?? settingsError.message}</div>;
}
if (languageError) {
return <div>{languageError.body?.message ?? languageError.message}</div>;
}
if (!settings?.signInExperienceCustomized) {
return (
<Welcome

View file

@ -1,6 +1,4 @@
import type { LanguageTag } from '@logto/language-kit';
import { SignInExperience, SignInMethodKey } from '@logto/schemas';
import type { Translation } from '@logto/schemas';
export type SignInExperienceForm = Omit<SignInExperience, 'signInMethods'> & {
signInMethods: {
@ -13,8 +11,3 @@ export type SignInExperienceForm = Omit<SignInExperience, 'signInMethods'> & {
};
createAccountEnabled: boolean;
};
export type CustomPhraseResponse = {
languageTag: LanguageTag;
translation: Translation;
};

View file

@ -0,0 +1,7 @@
import { LanguageTag } from '@logto/language-kit';
import { Translation } from '@logto/schemas';
export type CustomPhraseResponse = {
languageTag: LanguageTag;
translation: Translation;
};