diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 63fa45163..cbb3655d7 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -552,6 +552,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s setting: { profile: 'Profile', avatar: 'Avatar', + avatarTip: 'This will be displayed on your profile.', profileDescription: 'Update your photo and personal details here.', maxTokens: 'Max Tokens', maxTokensMessage: 'Max Tokens is required', @@ -584,6 +585,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s currentPassword: 'Current password', currentPasswordMessage: 'Please input your password!', newPassword: 'New password', + changePassword: 'Change Password', newPasswordMessage: 'Please input your password!', newPasswordDescription: 'Your new password must be more than 8 characters.', diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index 923dc1c0e..da38bdef3 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -535,6 +535,7 @@ export default { setting: { profile: '概述', avatar: '头像', + avatarTip: '這會在你的個人主頁展示', profileDescription: '在此更新您的照片和個人詳細信息。', maxTokens: '最大token數', maxTokensMessage: '最大token數是必填項', @@ -567,6 +568,7 @@ export default { currentPassword: '當前密碼', currentPasswordMessage: '請輸入當前密碼', newPassword: '新密碼', + changePassword: '修改密碼', newPasswordMessage: '請輸入新密碼', newPasswordDescription: '您的新密碼必須超過 8 個字符。', confirmPassword: '確認新密碼', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 2bbb025c1..c9ebfdb42 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -556,6 +556,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 setting: { profile: '概要', avatar: '头像', + avatarTip: '这会在你的个人主页展示', profileDescription: '在此更新您的照片和个人详细信息。', maxTokens: '最大token数', maxTokensMessage: '最大token数是必填项', @@ -588,6 +589,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 currentPassword: '当前密码', currentPasswordMessage: '请输入当前密码', newPassword: '新密码', + changePassword: '修改密码', newPasswordMessage: '请输入新密码', newPasswordDescription: '您的新密码必须超过 8 个字符。', confirmPassword: '确认新密码', diff --git a/web/src/pages/profile-setting/profile/index.tsx b/web/src/pages/profile-setting/profile/index.tsx index 7d7416637..ca3a891ab 100644 --- a/web/src/pages/profile-setting/profile/index.tsx +++ b/web/src/pages/profile-setting/profile/index.tsx @@ -28,68 +28,71 @@ import { Loader2Icon, Pencil, Upload } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; +function defineSchema( + t: TFunction<'translation', string>, + showPasswordForm = false, +) { + const baseSchema = z.object({ + userName: z + .string() + .min(1, { message: t('usernameMessage') }) + .trim(), + avatarUrl: z.string().trim(), + timeZone: z + .string() + .trim() + .min(1, { message: t('timezonePlaceholder') }), + email: z + .string({ required_error: 'Please select an email to display.' }) + .trim() + .regex(/^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/, { + message: 'Enter a valid email address.', + }), + }); -function defineSchema(t: TFunction<'translation', string>) { - return z - .object({ - userName: z - .string() - .min(1, { - message: t('usernameMessage'), - }) - .trim(), - avatarUrl: z.string().trim(), - timeZone: z - .string() - .trim() - .min(1, { - message: t('timezonePlaceholder'), - }), - email: z - .string({ - required_error: 'Please select an email to display.', - }) - .trim() - .regex( - /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/, - { - message: 'Enter a valid email address.', - }, - ), - currPasswd: z - .string() - .trim() - .min(1, { - message: t('currentPasswordMessage'), - }), - newPasswd: z - .string() - .trim() - .min(8, { - message: t('confirmPasswordMessage'), - }), - confirmPasswd: z - .string() - .trim() - .min(8, { - message: t('newPasswordDescription'), - }), - }) - .refine((data) => data.newPasswd === data.confirmPasswd, { - message: t('confirmPasswordNonMatchMessage'), - path: ['confirmPasswd'], - }); + if (showPasswordForm) { + return baseSchema + .extend({ + currPasswd: z + .string({ + required_error: t('currentPasswordMessage'), + }) + .trim() + .min(1, { message: t('currentPasswordMessage') }), + newPasswd: z + .string({ + required_error: t('confirmPasswordMessage'), + }) + .trim() + .min(8, { message: t('confirmPasswordMessage') }), + confirmPasswd: z + .string({ + required_error: t('newPasswordDescription'), + }) + .trim() + .min(8, { message: t('newPasswordDescription') }), + }) + .refine((data) => data.newPasswd === data.confirmPasswd, { + message: t('confirmPasswordNonMatchMessage'), + path: ['confirmPasswd'], + }); + } + + return baseSchema; } - export default function Profile() { const [avatarFile, setAvatarFile] = useState(null); const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64 const { data: userInfo } = useFetchUserInfo(); - const { saveSetting, loading: submitLoading } = useSaveSetting(); + const { + saveSetting, + loading: submitLoading, + data: saveUserData, + } = useSaveSetting(); const { t } = useTranslate('setting'); - const FormSchema = defineSchema(t); - + const [showPasswordForm, setShowPasswordForm] = useState(false); + const FormSchema = defineSchema(t, showPasswordForm); const form = useForm>({ resolver: zodResolver(FormSchema), defaultValues: { @@ -97,10 +100,11 @@ export default function Profile() { avatarUrl: '', timeZone: '', email: '', - currPasswd: '', - newPasswd: '', - confirmPasswd: '', + // currPasswd: '', + // newPasswd: '', + // confirmPasswd: '', }, + shouldUnregister: true, }); useEffect(() => { @@ -108,10 +112,20 @@ export default function Profile() { form.setValue('email', userInfo?.email); // email form.setValue('userName', userInfo?.nickname); // nickname form.setValue('timeZone', userInfo?.timezone); // time zone - form.setValue('currPasswd', ''); // current password + // form.setValue('currPasswd', ''); // current password setAvatarBase64Str(userInfo?.avatar ?? ''); }, [userInfo]); + useEffect(() => { + if (saveUserData === 0) { + setShowPasswordForm(false); + form.resetField('currPasswd'); + form.resetField('newPasswd'); + form.resetField('confirmPasswd'); + } + console.log('saveUserData', saveUserData); + }, [saveUserData]); + useEffect(() => { if (avatarFile) { // make use of img compression transformFile2Base64 @@ -122,24 +136,34 @@ export default function Profile() { }, [avatarFile]); function onSubmit(data: z.infer) { - // toast('You submitted the following values', { - // description: ( - //
-    //       {JSON.stringify(data, null, 2)}
-    //     
- // ), - // }); - // console.log('data=', data); - // final submit form - saveSetting({ + const payload: Partial<{ + nickname: string; + password: string; + new_password: string; + avatar: string; + timezone: string; + }> = { nickname: data.userName, - password: rsaPsw(data.currPasswd) as string, - new_password: rsaPsw(data.newPasswd) as string, avatar: avatarBase64Str, timezone: data.timeZone, - }); + }; + + if (showPasswordForm && 'currPasswd' in data && 'newPasswd' in data) { + payload.password = rsaPsw(data.currPasswd!) as string; + payload.new_password = rsaPsw(data.newPasswd!) as string; + } + saveSetting(payload); } + useEffect(() => { + if (showPasswordForm) { + form.register('currPasswd'); + form.register('newPasswd'); + form.register('confirmPasswd'); + } else { + form.unregister(['currPasswd', 'newPasswd', 'confirmPasswd']); + } + }, [showPasswordForm]); return (

{t('profile')}

@@ -152,12 +176,13 @@ export default function Profile() { onSubmit={form.handleSubmit(onSubmit)} className="block space-y-6" > + {/* Username Field */} ( -
+
* {t('username')} @@ -170,24 +195,26 @@ export default function Profile() { />
-
+
)} /> + + {/* Avatar Field */} ( -
+
Avatar - <> +
{!avatarBase64Str ? (
@@ -198,18 +225,18 @@ export default function Profile() {
) : (
- + - +
@@ -234,22 +261,27 @@ export default function Profile() { }} />
- +
+ {t('avatarTip')} +
+
-
+
)} /> + + {/* Time Zone Field */} ( -
+
* {t('timezone')} @@ -269,37 +301,35 @@ export default function Profile() {
-
+
)} /> + + {/* Email Address Field */} (
-
+
{t('email')} - + <>{field.value}
-
+
-
+

 

{t('emailDescription')} @@ -308,92 +338,110 @@ export default function Profile() {

)} /> -
+ + {/* Password Section */}
-

{t('password')}

+
+

{t('password')}

+ +
{t('passwordDescription')}
-
- -
- ( - -
- - * - {t('currentPassword')} - - - - -
-
-
- -
-
- )} - /> - ( - -
- - * - {t('newPassword')} - - - - -
-
-
- -
-
- )} - /> - ( - -
- - * - {t('confirmPassword')} - - - { - form.trigger('confirmPasswd'); - }} - onChange={(ev) => { - form.setValue( - 'confirmPasswd', - ev.target.value.trim(), - ); - }} - /> - -
-
-
 
- -
-
- )} - /> -
- + {/* Password Form */} + {showPasswordForm && ( + <> + ( + +
+ + * + {t('currentPassword')} + + + + +
+
+
+ +
+
+ )} + /> + ( + +
+ + * + {t('newPassword')} + + + + +
+
+
+ +
+
+ )} + /> + ( + +
+ + * + {t('confirmPassword')} + + + { + form.trigger('confirmPasswd'); + }} + onChange={(ev) => { + form.setValue( + 'confirmPasswd', + ev.target.value.trim(), + ); + }} + /> + +
+
+
+   +
+ +
+
+ )} + /> + + )} +
+