From 83df8a73739df45d60854683d346a616f82bef26 Mon Sep 17 00:00:00 2001 From: "guangfei.zhao" Date: Wed, 19 Nov 2025 17:43:45 +0800 Subject: [PATCH] feat(auth): enhance password change flow and error handling - Return response from useProfileSetting hook after password change --- src/components/Provider/DialogComponent.tsx | 2 +- src/hooks/login-hooks.ts | 4 ++-- src/hooks/setting-hooks.ts | 1 + src/locales/en.ts | 7 +++++- src/locales/zh.ts | 7 +++++- src/pages/login/Login.tsx | 2 +- .../components/ChangePasswordDialog.tsx | 18 +++++++-------- src/pages/setting/profile.tsx | 7 ++++++ src/pages/setting/teams.tsx | 10 ++++++++- src/utils/request.ts | 22 ++++++++++++++----- 10 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/components/Provider/DialogComponent.tsx b/src/components/Provider/DialogComponent.tsx index 6644779..c775ef4 100644 --- a/src/components/Provider/DialogComponent.tsx +++ b/src/components/Provider/DialogComponent.tsx @@ -120,7 +120,7 @@ const DialogComponent: React.FC = ({ dialog, onClose }) => {getDialogIcon()} - {config.title || t('dialog.defaultTitle')} + {config.title || t('dialog.confirm')} { password: { required: t('login.passwordPlaceholder'), minLength: { - value: 8, + value: 6, message: t('setting.passwordMinLength') } }, confirmPassword: { - required: t('confirmPasswordRequired'), + required: t('login.confirmPasswordRequired'), validate: (value: string) => { const pwd = registerForm.getValues('password'); return value === pwd || t('setting.passwordMismatch'); diff --git a/src/hooks/setting-hooks.ts b/src/hooks/setting-hooks.ts index 6f729ad..8002a0b 100644 --- a/src/hooks/setting-hooks.ts +++ b/src/hooks/setting-hooks.ts @@ -42,6 +42,7 @@ export function useProfileSetting() { password: oldPassword, new_password: newPassword, }); + return res; } catch (error) { throw error; } diff --git a/src/locales/en.ts b/src/locales/en.ts index 528e729..6b0632e 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -262,6 +262,11 @@ export default { emailInvalid: 'Invalid email address', passwordLabel: 'Password', passwordPlaceholder: 'Please input password', + confirmPasswordRequired: 'Please confirm your password', + confirmPassword: 'Confirm password', + confirmPasswordMessage: 'Please confirm your password!', + confirmPasswordNonMatchMessage: + 'The confirm password that you entered do not match!', rememberMe: 'Remember me', signInTip: 'Don’t have an account?', signUpTip: 'Already have an account?', @@ -942,7 +947,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s serverTypeRequired: 'Please select server type', testConnection: 'Test Connection', testing: 'Testing...', - connectionSuccess: 'Connection successful! Found {count} tools', + connectionSuccess: 'Connection successful! Found {{count}} tools', availableTools: 'Available Tools', connectionFailed: 'Connection failed', testFailed: 'Test failed', diff --git a/src/locales/zh.ts b/src/locales/zh.ts index e81f9c5..7a4d380 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -244,6 +244,11 @@ export default { emailInvalid: '无效的邮箱地址', passwordLabel: '密码', passwordPlaceholder: '请输入密码', + confirmPasswordRequired: '请确认您的密码', + confirmPassword: '确认密码', + confirmPasswordMessage: '请确认您的密码!', + confirmPasswordNonMatchMessage: + '确认密码与密码不匹配!', rememberMe: '记住我', signInTip: '没有帐户?', signUpTip: '已经有帐户?', @@ -941,7 +946,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 serverTypeRequired: '请选择服务器类型', testConnection: '测试连接', testing: '测试中...', - connectionSuccess: '连接成功!发现 {count} 个工具', + connectionSuccess: '连接成功!发现 {{count}} 个工具', availableTools: '可用工具', connectionFailed: '连接失败', testFailed: '测试失败', diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index 11ead59..664a941 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -224,7 +224,7 @@ const Login = () => { fullWidth id="register-confirm-password" type={showConfirmPassword ? 'text' : 'password'} - placeholder={t('confirmPassword')} + placeholder={t('login.confirmPassword')} autoComplete="new-password" {...registerForm.register('confirmPassword', registerValidation.confirmPassword)} error={!!registerForm.formState.errors.confirmPassword} diff --git a/src/pages/setting/components/ChangePasswordDialog.tsx b/src/pages/setting/components/ChangePasswordDialog.tsx index e32d2af..e529a71 100644 --- a/src/pages/setting/components/ChangePasswordDialog.tsx +++ b/src/pages/setting/components/ChangePasswordDialog.tsx @@ -20,7 +20,8 @@ import logger from '@/utils/logger'; interface ChangePasswordDialogProps { open: boolean; onClose: () => void; - changeUserPassword: (data: { password: string; new_password: string }) => Promise; + onSuccess: () => void; + changeUserPassword: (data: { password: string; new_password: string }) => Promise; } interface PasswordFormData { @@ -32,7 +33,7 @@ interface PasswordFormData { /** * 修改密码对话框 */ -function ChangePasswordDialog({ open, onClose, changeUserPassword }: ChangePasswordDialogProps) { +function ChangePasswordDialog({ open, onClose, onSuccess, changeUserPassword }: ChangePasswordDialogProps) { const { t } = useTranslation(); const { showMessage } = useSnackbar(); @@ -133,20 +134,19 @@ function ChangePasswordDialog({ open, onClose, changeUserPassword }: ChangePassw setLoading(true); try { - await changeUserPassword({ + const res = await changeUserPassword({ password: formData.currentPassword, new_password: formData.newPassword }); + + logger.info('修改密码成功:', res); showMessage.success(t('setting.passwordChangeSuccess')); handleClose(); + // delay 1000 ms + setTimeout(() => onSuccess(), 1000); } catch (error: any) { logger.error('修改密码失败:', error); - if (error.response?.status === 400) { - showMessage.error(t('setting.currentPasswordIncorrect')); - } else { - showMessage.error(t('setting.passwordChangeError')); - } } finally { setLoading(false); } @@ -217,7 +217,7 @@ function ChangePasswordDialog({ open, onClose, changeUserPassword }: ChangePassw value={formData.newPassword} onChange={handleInputChange('newPassword')} error={!!errors.newPassword} - helperText={errors.newPassword || '密码长度至少6位'} + helperText={errors.newPassword} InputProps={{ endAdornment: ( diff --git a/src/pages/setting/profile.tsx b/src/pages/setting/profile.tsx index 7d1758c..f5de339 100644 --- a/src/pages/setting/profile.tsx +++ b/src/pages/setting/profile.tsx @@ -7,11 +7,14 @@ import ChangePasswordDialog from "./components/ChangePasswordDialog"; import { useProfileSetting } from "@/hooks/setting-hooks"; import logger from "@/utils/logger"; import { useUserData } from "@/hooks/useUserData"; +import { useAuth } from '@/hooks/login-hooks'; +import { useNavigate } from "react-router-dom"; function ProfileSetting() { const { t } = useTranslation(); const { userInfo, updateUserInfo: updateUserInfoFunc, changeUserPassword: changeUserPasswordFunc } = useProfileSetting(); const { refreshUserData } = useUserData(); + const { logout } = useAuth(); const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); logger.debug('userInfo', userInfo); @@ -66,6 +69,10 @@ function ProfileSetting() { { + // 使用 useAuth 的 logout,确保清除本地存储令牌并跳转登录 + logout(); + }} changeUserPassword={changeUserPasswordFunc} /> diff --git a/src/pages/setting/teams.tsx b/src/pages/setting/teams.tsx index 139ff7b..12353c5 100644 --- a/src/pages/setting/teams.tsx +++ b/src/pages/setting/teams.tsx @@ -34,6 +34,7 @@ import { ExitToApp as ExitToAppIcon } from '@mui/icons-material'; import { useTeamSetting } from '@/hooks/setting-hooks'; +import { useDialog } from '@/hooks/useDialog'; interface TenantUser { id: string; @@ -88,8 +89,15 @@ function TeamsSetting() { } }; + const dialog = useDialog(); + const handleDeleteUser = async (userId: string) => { - await deleteUser(userId); + dialog.confirm({ + content: t('setting.sureDelete'), + onConfirm: async () => { + await deleteUser(userId); + } + }); }; const handleAgreeTenant = async (tenantId: string) => { diff --git a/src/utils/request.ts b/src/utils/request.ts index d6e5334..3ccc5bf 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -184,17 +184,29 @@ request.interceptors.response.use( }); const { status, data } = error.response || {}; - if (status == 401) { - logger.info('401', data) + if (status == 401 || status == 400) { + logger.info('401 || 400', data) const detail = data['detail'] if (detail) { const error = new CustomError(detail || i18n.t('message.requestError')); - if (detail.includes('not registered')) { + if (status == 401) { + const arr = ['not registered', 'Password error', 'Email and password do not match'] + const redirectArr = ['Invalid or expired token', 'No Authorization'] + if (arr.some(item => detail.includes(item))) { + error.code = data?.code || -1; + error.response = data; + snackbar.error(detail); + } else if (redirectArr.some(item => detail.includes(item))) { + redirectToLogin(); + } else { + error.code = data?.code || -1; + error.response = data; + snackbar.error(detail); + } + } else { error.code = data?.code || -1; error.response = data; snackbar.error(detail); - } else { - redirectToLogin(); } return Promise.reject(error); }