From e8a7094e379c22399b020967490e3abf6c796b0a Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Tue, 28 Feb 2023 00:52:44 +0800 Subject: [PATCH] feat(console): allow editing basic userinfo in profile page --- .../src/components/TextLink/index.module.scss | 1 + .../AppLayout/components/UserInfo/index.tsx | 10 +- .../console/src/hooks/use-logto-userinfo.ts | 27 ++++++ .../BasicUserInfoSection/index.module.scss | 5 + .../components/BasicUserInfoSection/index.tsx | 97 +++++++++++++++++++ .../components/CardContent/index.module.scss | 5 + .../Profile/components/CardContent/index.tsx | 15 ++- .../components/PasswordSection/index.tsx | 35 +++++++ .../src/pages/Profile/index.module.scss | 6 -- packages/console/src/pages/Profile/index.tsx | 61 +++--------- .../modals/BasicUserInfoUpdateModal/index.tsx | 96 ++++++++++++++++++ .../ChangePasswordModal/index.module.scss | 17 ++++ .../modals/ChangePasswordModal/index.tsx | 77 +++++++++++++++ packages/core/src/routes-me/user.ts | 29 ++++++ .../de/translation/admin-console/profile.ts | 10 +- .../en/translation/admin-console/profile.ts | 10 +- .../fr/translation/admin-console/profile.ts | 10 +- .../ko/translation/admin-console/profile.ts | 10 +- .../translation/admin-console/profile.ts | 10 +- .../translation/admin-console/profile.ts | 10 +- .../translation/admin-console/profile.ts | 10 +- .../translation/admin-console/profile.ts | 12 ++- 22 files changed, 497 insertions(+), 66 deletions(-) create mode 100644 packages/console/src/hooks/use-logto-userinfo.ts create mode 100644 packages/console/src/pages/Profile/components/BasicUserInfoSection/index.module.scss create mode 100644 packages/console/src/pages/Profile/components/BasicUserInfoSection/index.tsx create mode 100644 packages/console/src/pages/Profile/components/PasswordSection/index.tsx create mode 100644 packages/console/src/pages/Profile/modals/BasicUserInfoUpdateModal/index.tsx create mode 100644 packages/console/src/pages/Profile/modals/ChangePasswordModal/index.module.scss create mode 100644 packages/console/src/pages/Profile/modals/ChangePasswordModal/index.tsx diff --git a/packages/console/src/components/TextLink/index.module.scss b/packages/console/src/components/TextLink/index.module.scss index 583d1385e..b9618c446 100644 --- a/packages/console/src/components/TextLink/index.module.scss +++ b/packages/console/src/components/TextLink/index.module.scss @@ -8,6 +8,7 @@ font: var(--font-body-2); color: var(--color-text-link); gap: _.unit(1); + cursor: pointer; &.trailingIcon { flex-direction: row-reverse; diff --git a/packages/console/src/containers/AppLayout/components/UserInfo/index.tsx b/packages/console/src/containers/AppLayout/components/UserInfo/index.tsx index 2ef929f97..4462e368b 100644 --- a/packages/console/src/containers/AppLayout/components/UserInfo/index.tsx +++ b/packages/console/src/containers/AppLayout/components/UserInfo/index.tsx @@ -1,5 +1,5 @@ import { builtInLanguageOptions as consoleBuiltInLanguageOptions } from '@logto/phrases'; -import type { IdTokenClaims } from '@logto/react'; +import type { UserInfoResponse } from '@logto/react'; import { useLogto } from '@logto/react'; import { AppearanceMode } from '@logto/schemas'; import classNames from 'classnames'; @@ -25,14 +25,14 @@ import UserInfoSkeleton from '../UserInfoSkeleton'; import * as styles from './index.module.scss'; const UserInfo = () => { - const { isAuthenticated, getIdTokenClaims, signOut } = useLogto(); + const { isAuthenticated, fetchUserInfo, signOut } = useLogto(); const navigate = useNavigate(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const anchorRef = useRef(null); const [showDropdown, setShowDropdown] = useState(false); const [user, setUser] = useState< - Pick & IdTokenClaims, 'username' | 'name' | 'picture' | 'email'> + Pick & UserInfoResponse, 'username' | 'name' | 'picture' | 'email'> >(); const [isLoading, setIsLoading] = useState(false); const { @@ -43,11 +43,11 @@ const UserInfo = () => { useEffect(() => { (async () => { if (isAuthenticated) { - const userInfo = await getIdTokenClaims(); + const userInfo = await fetchUserInfo(); setUser(userInfo ?? { name: '' }); // Provide a fallback to avoid infinite loading state } })(); - }, [isAuthenticated, getIdTokenClaims]); + }, [isAuthenticated, fetchUserInfo]); if (!user) { return ; diff --git a/packages/console/src/hooks/use-logto-userinfo.ts b/packages/console/src/hooks/use-logto-userinfo.ts new file mode 100644 index 000000000..dbbd564ad --- /dev/null +++ b/packages/console/src/hooks/use-logto-userinfo.ts @@ -0,0 +1,27 @@ +import type { UserInfoResponse } from '@logto/react'; +import { useLogto } from '@logto/react'; +import type { Optional } from '@silverhand/essentials'; +import { useCallback, useEffect, useState } from 'react'; + +const useLogtoUserInfo = (): [Optional, () => void, boolean] => { + const { fetchUserInfo, isLoading, isAuthenticated } = useLogto(); + const [user, setUser] = useState(); + + const fetch = useCallback(async () => { + if (isAuthenticated) { + const userInfo = await fetchUserInfo(); + setUser(userInfo); + } else { + // eslint-disable-next-line unicorn/no-useless-undefined + setUser(undefined); + } + }, [fetchUserInfo, isAuthenticated]); + + useEffect(() => { + void fetch(); + }, [fetch]); + + return [user, fetch, isLoading]; +}; + +export default useLogtoUserInfo; diff --git a/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.module.scss b/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.module.scss new file mode 100644 index 000000000..fc5343250 --- /dev/null +++ b/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.module.scss @@ -0,0 +1,5 @@ +.avatar { + width: 40px; + height: 40px; + border-radius: 6px; +} diff --git a/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.tsx b/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.tsx new file mode 100644 index 000000000..6d13c6b5b --- /dev/null +++ b/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.tsx @@ -0,0 +1,97 @@ +import type { UserInfoResponse } from '@logto/react'; +import type { Nullable } from '@silverhand/essentials'; +import { useState } from 'react'; + +import UserAvatar from '@/components/UserAvatar'; +import { isCloud } from '@/consts/cloud'; + +import type { BasicUserField } from '../../modals/BasicUserInfoUpdateModal'; +import BasicUserInfoUpdateModal from '../../modals/BasicUserInfoUpdateModal'; +import type { Row } from '../CardContent'; +import CardContent from '../CardContent'; +import Section from '../Section'; +import * as styles from './index.module.scss'; + +type Props = { + user: UserInfoResponse; + onUpdate?: () => void; +}; + +const BasicUserInfoSection = ({ user, onUpdate }: Props) => { + const [editingField, setEditingField] = useState(); + const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false); + + const { name, username, picture: avatar } = user; + + const conditionalUsername: Array | undefined>> = isCloud + ? [] + : [ + { + label: 'profile.settings.username', + value: username, + actionName: 'profile.change', + action: () => { + setEditingField('username'); + setIsUpdateModalOpen(true); + }, + }, + ]; + + // Get the value of the editing simple field (avatar, username or name) + const getSimpleFieldValue = (field?: BasicUserField): string => { + if (field === 'avatar') { + return avatar ?? ''; + } + + if (field === 'name') { + return name ?? ''; + } + + if (field === 'username') { + return username ?? ''; + } + + return ''; + }; + + return ( +
+ , + actionName: 'profile.change', + action: () => { + setEditingField('avatar'); + setIsUpdateModalOpen(true); + }, + }, + { + label: 'profile.settings.name', + value: name, + actionName: name ? 'profile.change' : 'profile.set_name', + action: () => { + setEditingField('name'); + setIsUpdateModalOpen(true); + }, + }, + ...conditionalUsername, + ]} + /> + { + setIsUpdateModalOpen(false); + onUpdate?.(); + }} + /> +
+ ); +}; + +export default BasicUserInfoSection; diff --git a/packages/console/src/pages/Profile/components/CardContent/index.module.scss b/packages/console/src/pages/Profile/components/CardContent/index.module.scss index b94b1c72f..7fafb44c1 100644 --- a/packages/console/src/pages/Profile/components/CardContent/index.module.scss +++ b/packages/console/src/pages/Profile/components/CardContent/index.module.scss @@ -22,6 +22,11 @@ &:first-child { width: 35%; } + + &:last-child { + text-align: right; + width: 30%; + } } tr:last-child td { diff --git a/packages/console/src/pages/Profile/components/CardContent/index.tsx b/packages/console/src/pages/Profile/components/CardContent/index.tsx index ee4956528..a23704af6 100644 --- a/packages/console/src/pages/Profile/components/CardContent/index.tsx +++ b/packages/console/src/pages/Profile/components/CardContent/index.tsx @@ -3,12 +3,16 @@ import type { Nullable } from '@silverhand/essentials'; import type { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; +import TextLink from '@/components/TextLink'; + import * as styles from './index.module.scss'; export type Row = { label: AdminConsoleKey; value: T; renderer?: (value: T) => ReactNode; + action: () => void; + actionName: AdminConsoleKey; }; type Props = { @@ -29,10 +33,19 @@ const CardContent = | undefined>({ title, data }: Pr
{t(title)}
- {data.map(({ label, value, renderer = defaultRenderer }) => ( + {data.map(({ label, value, renderer = defaultRenderer, actionName, action }) => ( + ))} diff --git a/packages/console/src/pages/Profile/components/PasswordSection/index.tsx b/packages/console/src/pages/Profile/components/PasswordSection/index.tsx new file mode 100644 index 000000000..ec9743bbc --- /dev/null +++ b/packages/console/src/pages/Profile/components/PasswordSection/index.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react'; + +import ChangePasswordModal from '../../modals/ChangePasswordModal'; +import CardContent from '../CardContent'; +import Section from '../Section'; + +const PasswordSection = () => { + const [isChangePasswordModalOpen, setIsChangePasswordModalOpen] = useState(false); + + return ( +
+ { + setIsChangePasswordModalOpen(true); + }, + }, + ]} + /> + { + setIsChangePasswordModalOpen(false); + }} + /> +
+ ); +}; + +export default PasswordSection; diff --git a/packages/console/src/pages/Profile/index.module.scss b/packages/console/src/pages/Profile/index.module.scss index 247fce8b8..03d3b7398 100644 --- a/packages/console/src/pages/Profile/index.module.scss +++ b/packages/console/src/pages/Profile/index.module.scss @@ -1,11 +1,5 @@ @use '@/scss/underscore' as _; -.avatar { - width: 40px; - height: 40px; - border-radius: 6px; -} - .deleteAccount { flex: 1; display: flex; diff --git a/packages/console/src/pages/Profile/index.tsx b/packages/console/src/pages/Profile/index.tsx index a2e90ea5c..5c691c257 100644 --- a/packages/console/src/pages/Profile/index.tsx +++ b/packages/console/src/pages/Profile/index.tsx @@ -1,78 +1,49 @@ -import type { IdTokenClaims } from '@logto/react'; -import { useLogto } from '@logto/react'; -import type { Nullable } from '@silverhand/essentials'; -import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import Button from '@/components/Button'; import CardTitle from '@/components/CardTitle'; -import UserAvatar from '@/components/UserAvatar'; import { isCloud } from '@/consts/cloud'; +import useLogtoUserInfo from '@/hooks/use-logto-userinfo'; import * as resourcesStyles from '@/scss/resources.module.scss'; -import type { Row } from './components/CardContent'; +import BasicUserInfoSection from './components/BasicUserInfoSection'; import CardContent from './components/CardContent'; +import PasswordSection from './components/PasswordSection'; import Section from './components/Section'; import * as styles from './index.module.scss'; const Profile = () => { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const { getIdTokenClaims } = useLogto(); - const [user, setUser] = useState(); - - useEffect(() => { - (async () => { - const claims = await getIdTokenClaims(); - - if (claims) { - setUser(claims); - } - })(); - }, [getIdTokenClaims]); + const [user, fetchUser] = useLogtoUserInfo(); if (!user) { return null; } - const { name, username, picture, email } = user; - - const conditionalUsername: Array | undefined>> = isCloud - ? [{ label: 'profile.settings.username', value: username }] - : []; - return (
-
- , - }, - { label: 'profile.settings.name', value: name }, - ...conditionalUsername, - ]} - /> -
+ {isCloud && (
{ + console.log('link email'); + }, + }, + ]} />
)} -
- -
+ {isCloud && (
diff --git a/packages/console/src/pages/Profile/modals/BasicUserInfoUpdateModal/index.tsx b/packages/console/src/pages/Profile/modals/BasicUserInfoUpdateModal/index.tsx new file mode 100644 index 000000000..d0a5a79ba --- /dev/null +++ b/packages/console/src/pages/Profile/modals/BasicUserInfoUpdateModal/index.tsx @@ -0,0 +1,96 @@ +import type { AdminConsoleKey } from '@logto/phrases'; +import { useEffect, useState } from 'react'; +import ReactModal from 'react-modal'; + +import Button from '@/components/Button'; +import ModalLayout from '@/components/ModalLayout'; +import TextInput from '@/components/TextInput'; +import { adminTenantEndpoint, meApi } from '@/consts'; +import { useStaticApi } from '@/hooks/use-api'; +import * as modalStyles from '@/scss/modal.module.scss'; + +export type BasicUserField = 'avatar' | 'username' | 'name'; + +type Props = { + field?: BasicUserField; + value: string; + isOpen?: boolean; + onClose: () => void; +}; + +const BasicUserInfoUpdateModal = ({ field, value: defaultValue, isOpen, onClose }: Props) => { + const api = useStaticApi({ prefixUrl: adminTenantEndpoint, resourceIndicator: meApi.indicator }); + const [value, setValue] = useState(defaultValue); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setValue(defaultValue); + }, [defaultValue]); + + if (!field) { + return null; + } + + const getModalTitle = (): AdminConsoleKey => { + if (field === 'avatar') { + return 'profile.change_avatar'; + } + + if (field === 'username') { + return 'profile.change_username'; + } + + return defaultValue ? 'profile.change_name' : 'profile.set_name'; + }; + + const onSubmit = async () => { + setLoading(true); + + try { + await api.patch(`me/user`, { json: { [field]: value } }).json(); + } finally { + setLoading(false); + onClose(); + } + }; + + return ( + { + onClose(); + }} + > + + } + onClose={() => { + onClose(); + }} + > +
+ { + setValue(event.currentTarget.value); + }} + /> +
+
+
+ ); +}; + +export default BasicUserInfoUpdateModal; diff --git a/packages/console/src/pages/Profile/modals/ChangePasswordModal/index.module.scss b/packages/console/src/pages/Profile/modals/ChangePasswordModal/index.module.scss new file mode 100644 index 000000000..e4efa88d6 --- /dev/null +++ b/packages/console/src/pages/Profile/modals/ChangePasswordModal/index.module.scss @@ -0,0 +1,17 @@ +@use '@/scss/underscore' as _; + +.changePassword { + border: 1px solid var(--color-divider); + border-radius: 8px; + padding: _.unit(4); + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + + .description { + font: var(--font-body-2); + color: var(--color-text); + margin-right: _.unit(4); + } +} diff --git a/packages/console/src/pages/Profile/modals/ChangePasswordModal/index.tsx b/packages/console/src/pages/Profile/modals/ChangePasswordModal/index.tsx new file mode 100644 index 000000000..29c41427a --- /dev/null +++ b/packages/console/src/pages/Profile/modals/ChangePasswordModal/index.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; +import ReactModal from 'react-modal'; + +import Button from '@/components/Button'; +import FormField from '@/components/FormField'; +import ModalLayout from '@/components/ModalLayout'; +import TextInput from '@/components/TextInput'; +import { adminTenantEndpoint, meApi } from '@/consts'; +import { useStaticApi } from '@/hooks/use-api'; +import * as modalStyles from '@/scss/modal.module.scss'; + +type FormFields = { + password: string; + confirmPassword: string; +}; + +type Props = { + isOpen: boolean; + onClose: () => void; +}; + +const ChangePasswordModal = ({ isOpen, onClose }: Props) => { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const { watch, register, reset } = useForm(); + const [isLoading, setIsLoading] = useState(false); + const api = useStaticApi({ prefixUrl: adminTenantEndpoint, resourceIndicator: meApi.indicator }); + const password = watch('password'); + const confirmPassword = watch('confirmPassword'); + const isDisabled = !password || password !== confirmPassword; + + const onSubmit = async () => { + setIsLoading(true); + await api.post(`me/password`, { json: { password } }).json(); + setIsLoading(false); + onClose(); + toast.success(t('settings.password_changed')); + reset({}); + }; + + return ( + + + } + onClose={onClose} + > +
+ + + + + + +
+
+
+ ); +}; + +export default ChangePasswordModal; diff --git a/packages/core/src/routes-me/user.ts b/packages/core/src/routes-me/user.ts index d731649d5..1f3eb1a9e 100644 --- a/packages/core/src/routes-me/user.ts +++ b/packages/core/src/routes-me/user.ts @@ -1,5 +1,6 @@ import { passwordRegEx } from '@logto/core-kit'; import { arbitraryObjectGuard } from '@logto/schemas'; +import { conditional } from '@silverhand/essentials'; import { object, string } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; @@ -15,6 +16,34 @@ export default function userRoutes( ) { const { findUserById, updateUserById } = tenant.queries.users; + router.patch( + '/user', + koaGuard({ + body: object({ + avatar: string().optional(), + name: string().optional(), + username: string().optional(), + }), + }), + async (ctx, next) => { + const { id: userId } = ctx.auth; + const { avatar, name, username } = ctx.guard.body; + + const user = await findUserById(userId); + assertThat(!user.isSuspended, new RequestError({ code: 'user.suspended', status: 401 })); + + await updateUserById(userId, { + ...conditional(avatar !== undefined && { avatar }), + ...conditional(name !== undefined && { name }), + ...conditional(username !== undefined && { username }), + }); + + ctx.status = 204; + + return next(); + } + ); + router.get('/custom-data', async (ctx, next) => { const { id: userId } = ctx.auth; const user = await findUserById(userId); diff --git a/packages/phrases/src/locales/de/translation/admin-console/profile.ts b/packages/phrases/src/locales/de/translation/admin-console/profile.ts index 440f47d9f..8281446b4 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/profile.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/profile.ts @@ -33,11 +33,19 @@ const profile = { dialog_paragraph_3: 'Thank you for choosing Logto Cloud. If you have any further questions or concerns, please do not hesitate to reach out to us.', // UNTRANSLATED }, - edit: 'Edit', // UNTRANSLATED + set: 'Set', // UNTRANSLATED change: 'Change', // UNTRANSLATED link: 'Link', // UNTRANSLATED unlink: 'Unlink', // UNTRANSLATED not_set: 'Not set', // UNTRANSLATED + change_avatar: 'Change avatar', // UNTRANSLATED + change_name: 'Change name', // UNTRANSLATED + change_username: 'Change username', // UNTRANSLATED + set_name: 'Set name', // UNTRANSLATED + set_password: 'Set password', // UNTRANSLATED + link_email: 'Link email', // UNTRANSLATED + enter_password: 'Enter password', // UNTRANSLATED + enter_verification_code: 'Enter verification code', // UNTRANSLATED }; export default profile; diff --git a/packages/phrases/src/locales/en/translation/admin-console/profile.ts b/packages/phrases/src/locales/en/translation/admin-console/profile.ts index c0e9defde..769f60cd2 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/profile.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/profile.ts @@ -33,11 +33,19 @@ const profile = { dialog_paragraph_3: 'Thank you for choosing Logto Cloud. If you have any further questions or concerns, please do not hesitate to reach out to us.', }, - edit: 'Edit', + set: 'Set', change: 'Change', link: 'Link', unlink: 'Unlink', not_set: 'Not set', + change_avatar: 'Change avatar', + change_name: 'Change name', + change_username: 'Change username', + set_name: 'Set name', + set_password: 'Set password', + link_email: 'Link email', + enter_password: 'Enter password', + enter_verification_code: 'Enter verification code', }; export default profile; diff --git a/packages/phrases/src/locales/fr/translation/admin-console/profile.ts b/packages/phrases/src/locales/fr/translation/admin-console/profile.ts index 440f47d9f..8281446b4 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/profile.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/profile.ts @@ -33,11 +33,19 @@ const profile = { dialog_paragraph_3: 'Thank you for choosing Logto Cloud. If you have any further questions or concerns, please do not hesitate to reach out to us.', // UNTRANSLATED }, - edit: 'Edit', // UNTRANSLATED + set: 'Set', // UNTRANSLATED change: 'Change', // UNTRANSLATED link: 'Link', // UNTRANSLATED unlink: 'Unlink', // UNTRANSLATED not_set: 'Not set', // UNTRANSLATED + change_avatar: 'Change avatar', // UNTRANSLATED + change_name: 'Change name', // UNTRANSLATED + change_username: 'Change username', // UNTRANSLATED + set_name: 'Set name', // UNTRANSLATED + set_password: 'Set password', // UNTRANSLATED + link_email: 'Link email', // UNTRANSLATED + enter_password: 'Enter password', // UNTRANSLATED + enter_verification_code: 'Enter verification code', // UNTRANSLATED }; export default profile; diff --git a/packages/phrases/src/locales/ko/translation/admin-console/profile.ts b/packages/phrases/src/locales/ko/translation/admin-console/profile.ts index 440f47d9f..8281446b4 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/profile.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/profile.ts @@ -33,11 +33,19 @@ const profile = { dialog_paragraph_3: 'Thank you for choosing Logto Cloud. If you have any further questions or concerns, please do not hesitate to reach out to us.', // UNTRANSLATED }, - edit: 'Edit', // UNTRANSLATED + set: 'Set', // UNTRANSLATED change: 'Change', // UNTRANSLATED link: 'Link', // UNTRANSLATED unlink: 'Unlink', // UNTRANSLATED not_set: 'Not set', // UNTRANSLATED + change_avatar: 'Change avatar', // UNTRANSLATED + change_name: 'Change name', // UNTRANSLATED + change_username: 'Change username', // UNTRANSLATED + set_name: 'Set name', // UNTRANSLATED + set_password: 'Set password', // UNTRANSLATED + link_email: 'Link email', // UNTRANSLATED + enter_password: 'Enter password', // UNTRANSLATED + enter_verification_code: 'Enter verification code', // UNTRANSLATED }; export default profile; diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/profile.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/profile.ts index 440f47d9f..8281446b4 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/profile.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/profile.ts @@ -33,11 +33,19 @@ const profile = { dialog_paragraph_3: 'Thank you for choosing Logto Cloud. If you have any further questions or concerns, please do not hesitate to reach out to us.', // UNTRANSLATED }, - edit: 'Edit', // UNTRANSLATED + set: 'Set', // UNTRANSLATED change: 'Change', // UNTRANSLATED link: 'Link', // UNTRANSLATED unlink: 'Unlink', // UNTRANSLATED not_set: 'Not set', // UNTRANSLATED + change_avatar: 'Change avatar', // UNTRANSLATED + change_name: 'Change name', // UNTRANSLATED + change_username: 'Change username', // UNTRANSLATED + set_name: 'Set name', // UNTRANSLATED + set_password: 'Set password', // UNTRANSLATED + link_email: 'Link email', // UNTRANSLATED + enter_password: 'Enter password', // UNTRANSLATED + enter_verification_code: 'Enter verification code', // UNTRANSLATED }; export default profile; diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/profile.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/profile.ts index 440f47d9f..8281446b4 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/profile.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/profile.ts @@ -33,11 +33,19 @@ const profile = { dialog_paragraph_3: 'Thank you for choosing Logto Cloud. If you have any further questions or concerns, please do not hesitate to reach out to us.', // UNTRANSLATED }, - edit: 'Edit', // UNTRANSLATED + set: 'Set', // UNTRANSLATED change: 'Change', // UNTRANSLATED link: 'Link', // UNTRANSLATED unlink: 'Unlink', // UNTRANSLATED not_set: 'Not set', // UNTRANSLATED + change_avatar: 'Change avatar', // UNTRANSLATED + change_name: 'Change name', // UNTRANSLATED + change_username: 'Change username', // UNTRANSLATED + set_name: 'Set name', // UNTRANSLATED + set_password: 'Set password', // UNTRANSLATED + link_email: 'Link email', // UNTRANSLATED + enter_password: 'Enter password', // UNTRANSLATED + enter_verification_code: 'Enter verification code', // UNTRANSLATED }; export default profile; diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/profile.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/profile.ts index 440f47d9f..8281446b4 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/profile.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/profile.ts @@ -33,11 +33,19 @@ const profile = { dialog_paragraph_3: 'Thank you for choosing Logto Cloud. If you have any further questions or concerns, please do not hesitate to reach out to us.', // UNTRANSLATED }, - edit: 'Edit', // UNTRANSLATED + set: 'Set', // UNTRANSLATED change: 'Change', // UNTRANSLATED link: 'Link', // UNTRANSLATED unlink: 'Unlink', // UNTRANSLATED not_set: 'Not set', // UNTRANSLATED + change_avatar: 'Change avatar', // UNTRANSLATED + change_name: 'Change name', // UNTRANSLATED + change_username: 'Change username', // UNTRANSLATED + set_name: 'Set name', // UNTRANSLATED + set_password: 'Set password', // UNTRANSLATED + link_email: 'Link email', // UNTRANSLATED + enter_password: 'Enter password', // UNTRANSLATED + enter_verification_code: 'Enter verification code', // UNTRANSLATED }; export default profile; diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/profile.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/profile.ts index 0eaa15bd6..0f7c716d9 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/profile.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/profile.ts @@ -31,11 +31,19 @@ const profile = { dialog_paragraph_3: '感谢您选择 Logto Cloud。如果您有任何进一步的问题或疑虑,请随时与我们联系。', }, - edit: '编辑', - change: '变更', + set: '设置', + change: '修改', link: '关联', unlink: '取消关联', not_set: '未设置', + change_avatar: '修改头像', + change_name: '修改姓名', + change_username: '修改用户名', + set_name: '设置姓名', + set_password: '设置密码', + link_email: '关联邮件', + enter_password: '输入密码', + enter_verification_code: '输入验证码', }; export default profile;
{t(label)} {renderer(value)} + { + action(); + }} + > + {t(actionName)} + +