0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

feat(console): delete custom phrases (#2065)

This commit is contained in:
Xiao Yijun 2022-10-09 12:08:50 +08:00 committed by GitHub
parent 4995ab9461
commit 68e88840bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 160 additions and 26 deletions

View file

@ -1,13 +1,16 @@
import { languages as uiLanguageNameMapping } from '@logto/language-kit';
import { SignInExperience } from '@logto/schemas';
import classNames from 'classnames';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
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 { RequestError } from '@/hooks/use-api';
import useUiLanguages from '@/hooks/use-ui-languages';
import useLanguageEditorContext from '../hooks/use-language-editor-context';
@ -21,8 +24,10 @@ type Props = {
const LanguagesForm = ({ isManageLanguageVisible = false }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { watch, control, register } = useFormContext<SignInExperienceForm>();
const { data: signInExperience } = useSWR<SignInExperience, RequestError>('/api/sign-in-exp');
const { watch, control, register, setValue } = useFormContext<SignInExperienceForm>();
const isAutoDetect = watch('languageInfo.autoDetect');
const selectedDefaultLanguage = watch('languageInfo.fallbackLanguage');
const [isManageLanguageFormOpen, setIsManageLanguageFormOpen] = useState(false);
const { languages } = useUiLanguages();
@ -36,6 +41,15 @@ const LanguagesForm = ({ isManageLanguageVisible = false }: Props) => {
const { context: languageEditorContext, Provider: LanguageEditorContextProvider } =
useLanguageEditorContext(languages);
useEffect(() => {
if (!languages.includes(selectedDefaultLanguage)) {
setValue(
'languageInfo.fallbackLanguage',
signInExperience?.languageInfo.fallbackLanguage ?? 'en'
);
}
}, [languages, selectedDefaultLanguage, setValue, signInExperience]);
return (
<>
<div className={styles.title}>{t('sign_in_exp.others.languages.title')}</div>

View file

@ -7,20 +7,27 @@
padding: _.unit(6) _.unit(5);
font: var(--font-title-large);
color: var(--color-text);
display: flex;
justify-content: space-between;
> span {
margin-left: _.unit(2);
font: var(--font-body-medium);
color: var(--color-caption);
}
.languageInfo {
display: flex;
align-items: center;
.builtInFlag {
display: inline-block;
font: var(--font-label-medium);
color: var(--color-text);
background-color: var(--color-surface-variant);
padding: _.unit(0.5) _.unit(2);
border-radius: 10px;
> span {
margin-left: _.unit(2);
font: var(--font-body-medium);
color: var(--color-caption);
}
.builtInFlag {
display: inline-block;
font: var(--font-label-medium);
color: var(--color-text);
background-color: var(--color-surface-variant);
padding: _.unit(0.5) _.unit(2);
border-radius: 10px;
}
}
}

View file

@ -1,15 +1,17 @@
import { languages, LanguageTag } from '@logto/language-kit';
import { languages as uiLanguageNameMapping, LanguageTag } from '@logto/language-kit';
import resource, { isBuiltInLanguageTag } from '@logto/phrases-ui';
import en from '@logto/phrases-ui/lib/locales/en';
import { Translation } from '@logto/schemas';
import { SignInExperience, Translation } from '@logto/schemas';
import cleanDeep from 'clean-deep';
import { useCallback, useContext, useEffect, useMemo } from 'react';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import useSWR, { useSWRConfig } from 'swr';
import Button from '@/components/Button';
import ConfirmModal from '@/components/ConfirmModal';
import IconButton from '@/components/IconButton';
import useApi, { RequestError } from '@/hooks/use-api';
import Delete from '@/icons/Delete';
import { CustomPhraseResponse } from '@/types/custom-phrase';
@ -24,10 +26,17 @@ const emptyUiTranslation = createEmptyUiTranslation();
const LanguageEditor = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { selectedLanguage, setIsDirty, stopAddingLanguage } = useContext(LanguageEditorContext);
const { data: signInExperience } = useSWR<SignInExperience, RequestError>('/api/sign-in-exp');
const { languages, selectedLanguage, setIsDirty, setSelectedLanguage, stopAddingLanguage } =
useContext(LanguageEditorContext);
const [isDeletionAlertOpen, setIsDeletionAlertOpen] = useState(false);
const isBuiltIn = isBuiltInLanguageTag(selectedLanguage);
const isDefaultLanguage = signInExperience?.languageInfo.fallbackLanguage === selectedLanguage;
const translationEntries = useMemo(
() => Object.entries((isBuiltIn ? resource[selectedLanguage] : en).translation),
[isBuiltIn, selectedLanguage]
@ -105,6 +114,39 @@ const LanguageEditor = () => {
[api, globalMutate, stopAddingLanguage]
);
const onDelete = useCallback(() => {
if (!customPhrase && !isDefaultLanguage) {
stopAddingLanguage(true);
setSelectedLanguage(
languages.find((languageTag) => languageTag !== selectedLanguage) ?? 'en'
);
return;
}
setIsDeletionAlertOpen(true);
}, [
customPhrase,
isDefaultLanguage,
languages,
selectedLanguage,
setSelectedLanguage,
stopAddingLanguage,
]);
const onConfirmDeletion = useCallback(async () => {
setIsDeletionAlertOpen(false);
if (isDefaultLanguage) {
return;
}
await api.delete(`/api/custom-phrases/${selectedLanguage}`);
await globalMutate('/api/custom-phrases');
setSelectedLanguage(languages.find((languageTag) => languageTag !== selectedLanguage) ?? 'en');
}, [api, globalMutate, isDefaultLanguage, languages, selectedLanguage, setSelectedLanguage]);
const onSubmit = handleSubmit(async (formData: Translation) => {
const updatedCustomPhrase = await upsertCustomPhrase(selectedLanguage, formData);
void mutate(updatedCustomPhrase);
@ -126,12 +168,19 @@ const LanguageEditor = () => {
return (
<div className={style.languageEditor}>
<div className={style.title}>
{languages[selectedLanguage]}
<span>{selectedLanguage}</span>
{isBuiltIn && (
<span className={style.builtInFlag}>
{t('sign_in_exp.others.manage_language.logto_provided')}
</span>
<div className={style.languageInfo}>
{uiLanguageNameMapping[selectedLanguage]}
<span>{selectedLanguage}</span>
{isBuiltIn && (
<span className={style.builtInFlag}>
{t('sign_in_exp.others.manage_language.logto_provided')}
</span>
)}
</div>
{!isBuiltIn && (
<IconButton onClick={onDelete}>
<Delete />
</IconButton>
)}
</div>
<form
@ -187,6 +236,28 @@ const LanguageEditor = () => {
/>
</div>
</form>
<ConfirmModal
isOpen={isDeletionAlertOpen}
title={
isDefaultLanguage
? 'sign_in_exp.others.manage_language.default_language_deletion_title'
: 'sign_in_exp.others.manage_language.deletion_title'
}
confirmButtonText={
isDefaultLanguage ? 'sign_in_exp.others.manage_language.got_it' : 'general.delete'
}
confirmButtonType={isDefaultLanguage ? 'primary' : 'danger'}
onCancel={() => {
setIsDeletionAlertOpen(false);
}}
onConfirm={onConfirmDeletion}
>
{isDefaultLanguage
? t('sign_in_exp.others.manage_language.default_language_deletion_description', {
language: uiLanguageNameMapping[selectedLanguage],
})
: t('sign_in_exp.others.manage_language.deletion_description')}
</ConfirmModal>
</div>
);
};

View file

@ -23,7 +23,12 @@ const LanguageItem = ({ languageTag, isSelected, onClick }: Props) => {
<div
ref={itemRef}
className={classNames(style.languageItem, isSelected && style.selected)}
onClick={onClick}
onClick={() => {
if (isSelected) {
return;
}
onClick();
}}
>
<div className={style.languageName}>{languages[languageTag]}</div>
<div className={style.languageTag}>{languageTag}</div>

View file

@ -69,11 +69,12 @@ const useLanguageEditorContext = (defaultLanguages: LanguageTag[]) => {
if (isAddingLanguage) {
if (isCanceled) {
setLanguages(defaultLanguages);
setSelectedLanguage(languages[0] ?? 'en');
}
setIsAddingLanguage(false);
}
},
[defaultLanguages, isAddingLanguage]
[defaultLanguages, isAddingLanguage, languages]
);
const context = useMemo<Context>(

View file

@ -92,6 +92,13 @@ const sign_in_exp = {
custom_values: 'Custom values',
clear_all: 'Clear all',
unsaved_description: 'Changes wont be saved if you leave this page without saving.',
deletion_title: 'Do you want to delete the added language?',
deletion_description:
'After deletion, your users wont be able to browse in that language again.',
default_language_deletion_title: 'Default language cant be deleted.',
default_language_deletion_description:
'{{language}} is set as your default language and cant be deleted. ',
got_it: 'Got It',
},
authentication: {
title: 'AUTHENTICATION',

View file

@ -94,6 +94,13 @@ const sign_in_exp = {
custom_values: 'Custom values', // UNTRANSLATED
clear_all: 'Clear all', // UNTRANSLATED
unsaved_description: 'Changes wont be saved if you leave this page without saving.', // UNTRANSLATED
deletion_title: 'Do you want to delete the added language?', // UNTRANSLATED
deletion_description:
'After deletion, your users wont be able to browse in that language again.', // UNTRANSLATED
default_language_deletion_title: 'Default language cant be deleted.', // UNTRANSLATED
default_language_deletion_description:
'{{language}} is set as your default language and cant be deleted. ', // UNTRANSLATED
got_it: 'Got It', // UNTRANSLATED
},
authentication: {
title: 'AUTHENTICATION',

View file

@ -89,6 +89,13 @@ const sign_in_exp = {
custom_values: 'Custom values', // UNTRANSLATED
clear_all: 'Clear all', // UNTRANSLATED
unsaved_description: 'Changes wont be saved if you leave this page without saving.', // UNTRANSLATED
deletion_title: 'Do you want to delete the added language?', // UNTRANSLATED
deletion_description:
'After deletion, your users wont be able to browse in that language again.', // UNTRANSLATED
default_language_deletion_title: 'Default language cant be deleted.', // UNTRANSLATED
default_language_deletion_description:
'{{language}} is set as your default language and cant be deleted. ', // UNTRANSLATED
got_it: 'Got It', // UNTRANSLATED
},
authentication: {
title: 'AUTHENTICATION',

View file

@ -92,6 +92,13 @@ const sign_in_exp = {
custom_values: 'Custom values', // UNTRANSLATED
clear_all: 'Clear all', // UNTRANSLATED
unsaved_description: 'Changes wont be saved if you leave this page without saving.', // UNTRANSLATED
deletion_title: 'Do you want to delete the added language?', // UNTRANSLATED
deletion_description:
'After deletion, your users wont be able to browse in that language again.', // UNTRANSLATED
default_language_deletion_title: 'Default language cant be deleted.', // UNTRANSLATED
default_language_deletion_description:
'{{language}} is set as your default language and cant be deleted. ', // UNTRANSLATED
got_it: 'Got It', // UNTRANSLATED
},
authentication: {
title: 'AUTENTICAÇÃO',

View file

@ -99,6 +99,7 @@ const sign_in_exp = {
default_language_deletion_title: 'Default language cant be deleted.', // UNTRANSLATED
default_language_deletion_description:
'{{language}} is set as your default language and cant be deleted. ', // UNTRANSLATED
got_it: 'Got It', // UNTRANSLATED
},
authentication: {
title: 'AUTHENTICATION',

View file

@ -89,6 +89,13 @@ const sign_in_exp = {
custom_values: 'Custom values', // UNTRANSLATED
clear_all: 'Clear all', // UNTRANSLATED
unsaved_description: 'Changes wont be saved if you leave this page without saving.', // UNTRANSLATED
deletion_title: 'Do you want to delete the added language?', // UNTRANSLATED
deletion_description:
'After deletion, your users wont be able to browse in that language again.', // UNTRANSLATED
default_language_deletion_title: 'Default language cant be deleted.', // UNTRANSLATED
default_language_deletion_description:
'{{language}} is set as your default language and cant be deleted. ', // UNTRANSLATED
got_it: 'Got It', // UNTRANSLATED
},
authentication: {
title: '身份验证',