fix: translate auth pages to Russian, fix backend error parsing, calendar timezone
- Translate forgot-password and reset-password pages to Russian
- Translate verify-email page to Russian
- Fix error message parsing: backend returns {error:{message}} not {message}
- Apply timezone fix in calendar (FullCalendar timeZone prop + fTime helper)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f6caa7df6b
commit
75d6072309
|
|
@ -23,8 +23,8 @@ import { requestPasswordReset } from 'src/auth/context/jwt';
|
||||||
const ForgotPasswordSchema = zod.object({
|
const ForgotPasswordSchema = zod.object({
|
||||||
email: zod
|
email: zod
|
||||||
.string()
|
.string()
|
||||||
.min(1, { message: 'Email is required!' })
|
.min(1, { message: 'Введите email!' })
|
||||||
.email({ message: 'Email must be a valid email address!' }),
|
.email({ message: 'Введите корректный email адрес!' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
@ -46,11 +46,11 @@ export function JwtForgotPasswordView() {
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
await requestPasswordReset({ email: data.email });
|
await requestPasswordReset({ email: data.email });
|
||||||
setSuccessMsg('Password reset instructions have been sent to your email.');
|
setSuccessMsg('Инструкции по сбросу пароля отправлены на ваш email.');
|
||||||
setErrorMsg('');
|
setErrorMsg('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
const msg = error?.response?.data?.message || error?.response?.data?.detail || 'Error sending request. Please check your email.';
|
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || 'Ошибка отправки запроса. Проверьте правильность email.';
|
||||||
setErrorMsg(msg);
|
setErrorMsg(msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -58,10 +58,10 @@ export function JwtForgotPasswordView() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
||||||
<Typography variant="h5">Forgot your password?</Typography>
|
<Typography variant="h5">Забыли пароль?</Typography>
|
||||||
|
|
||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
Enter your email address and we will send you a link to reset your password.
|
Введите ваш email и мы отправим вам ссылку для сброса пароля.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ export function JwtForgotPasswordView() {
|
||||||
{!successMsg && (
|
{!successMsg && (
|
||||||
<Form methods={methods} onSubmit={onSubmit}>
|
<Form methods={methods} onSubmit={onSubmit}>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Field.Text name="email" label="Email address" InputLabelProps={{ shrink: true }} />
|
<Field.Text name="email" label="Email" InputLabelProps={{ shrink: true }} />
|
||||||
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|
@ -89,9 +89,9 @@ export function JwtForgotPasswordView() {
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
loadingIndicator="Sending..."
|
loadingIndicator="Отправка..."
|
||||||
>
|
>
|
||||||
Send reset link
|
Отправить ссылку
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
@ -104,7 +104,7 @@ export function JwtForgotPasswordView() {
|
||||||
color="inherit"
|
color="inherit"
|
||||||
sx={{ display: 'block', mt: 3, textAlign: 'center' }}
|
sx={{ display: 'block', mt: 3, textAlign: 'center' }}
|
||||||
>
|
>
|
||||||
Back to sign in
|
Вернуться ко входу
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,12 @@ const ResetPasswordSchema = zod
|
||||||
.object({
|
.object({
|
||||||
newPassword: zod
|
newPassword: zod
|
||||||
.string()
|
.string()
|
||||||
.min(1, { message: 'Password is required!' })
|
.min(1, { message: 'Введите пароль!' })
|
||||||
.min(6, { message: 'Password must be at least 6 characters!' }),
|
.min(6, { message: 'Пароль должен содержать не менее 6 символов!' }),
|
||||||
newPasswordConfirm: zod.string().min(1, { message: 'Please confirm your password!' }),
|
newPasswordConfirm: zod.string().min(1, { message: 'Подтвердите пароль!' }),
|
||||||
})
|
})
|
||||||
.refine((data) => data.newPassword === data.newPasswordConfirm, {
|
.refine((data) => data.newPassword === data.newPasswordConfirm, {
|
||||||
message: 'Passwords do not match!',
|
message: 'Пароли не совпадают!',
|
||||||
path: ['newPasswordConfirm'],
|
path: ['newPasswordConfirm'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ function ResetPasswordContent() {
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
setErrorMsg('Reset link is missing. Please request a new password reset.');
|
setErrorMsg('Ссылка для сброса отсутствует. Запросите новый сброс пароля.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
@ -72,11 +72,11 @@ function ResetPasswordContent() {
|
||||||
newPassword: data.newPassword,
|
newPassword: data.newPassword,
|
||||||
newPasswordConfirm: data.newPasswordConfirm,
|
newPasswordConfirm: data.newPasswordConfirm,
|
||||||
});
|
});
|
||||||
setSuccessMsg('Password changed successfully. You can now sign in with your new password.');
|
setSuccessMsg('Пароль успешно изменён. Теперь вы можете войти с новым паролем.');
|
||||||
setErrorMsg('');
|
setErrorMsg('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
const msg = error?.response?.data?.message || error?.response?.data?.detail || 'Failed to reset password. The link may have expired — please request a new one.';
|
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || 'Не удалось сбросить пароль. Возможно, ссылка устарела — запросите новую.';
|
||||||
setErrorMsg(msg);
|
setErrorMsg(msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -85,13 +85,13 @@ function ResetPasswordContent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
||||||
<Typography variant="h5">Reset password</Typography>
|
<Typography variant="h5">Сброс пароля</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Alert severity="error" sx={{ mb: 3 }}>
|
<Alert severity="error" sx={{ mb: 3 }}>
|
||||||
Reset link is missing. Please follow the link from your email or request a new one.
|
Ссылка для сброса отсутствует. Перейдите по ссылке из письма или запросите новую.
|
||||||
</Alert>
|
</Alert>
|
||||||
<Link component={RouterLink} href={paths.auth.jwt.forgotPassword} variant="subtitle2">
|
<Link component={RouterLink} href={paths.auth.jwt.forgotPassword} variant="subtitle2">
|
||||||
Request new reset link
|
Запросить новую ссылку
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -101,13 +101,13 @@ function ResetPasswordContent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
||||||
<Typography variant="h5">Reset password</Typography>
|
<Typography variant="h5">Сброс пароля</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Alert severity="success" sx={{ mb: 3 }}>
|
<Alert severity="success" sx={{ mb: 3 }}>
|
||||||
{successMsg}
|
{successMsg}
|
||||||
</Alert>
|
</Alert>
|
||||||
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
|
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
|
||||||
Sign in
|
Войти
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -116,9 +116,9 @@ function ResetPasswordContent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
||||||
<Typography variant="h5">Set new password</Typography>
|
<Typography variant="h5">Новый пароль</Typography>
|
||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
Enter your new password below.
|
Введите новый пароль ниже.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|
@ -132,8 +132,8 @@ function ResetPasswordContent() {
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Field.Text
|
<Field.Text
|
||||||
name="newPassword"
|
name="newPassword"
|
||||||
label="New password"
|
label="Новый пароль"
|
||||||
placeholder="6+ characters"
|
placeholder="6+ символов"
|
||||||
type={newPassword.value ? 'text' : 'password'}
|
type={newPassword.value ? 'text' : 'password'}
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
|
|
@ -149,8 +149,8 @@ function ResetPasswordContent() {
|
||||||
|
|
||||||
<Field.Text
|
<Field.Text
|
||||||
name="newPasswordConfirm"
|
name="newPasswordConfirm"
|
||||||
label="Confirm new password"
|
label="Подтвердите пароль"
|
||||||
placeholder="6+ characters"
|
placeholder="6+ символов"
|
||||||
type={newPasswordConfirm.value ? 'text' : 'password'}
|
type={newPasswordConfirm.value ? 'text' : 'password'}
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
|
|
@ -171,9 +171,9 @@ function ResetPasswordContent() {
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
loadingIndicator="Saving..."
|
loadingIndicator="Сохранение..."
|
||||||
>
|
>
|
||||||
Save new password
|
Сохранить пароль
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
@ -185,7 +185,7 @@ function ResetPasswordContent() {
|
||||||
color="inherit"
|
color="inherit"
|
||||||
sx={{ display: 'block', mt: 3, textAlign: 'center' }}
|
sx={{ display: 'block', mt: 3, textAlign: 'center' }}
|
||||||
>
|
>
|
||||||
Back to sign in
|
Вернуться ко входу
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -195,7 +195,7 @@ function ResetPasswordContent() {
|
||||||
|
|
||||||
export function JwtResetPasswordView() {
|
export function JwtResetPasswordView() {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Typography variant="body2">Loading...</Typography>}>
|
<Suspense fallback={<Typography variant="body2">Загрузка...</Typography>}>
|
||||||
<ResetPasswordContent />
|
<ResetPasswordContent />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ export function JwtSignUpView() {
|
||||||
router.replace(paths.dashboard.root);
|
router.replace(paths.dashboard.root);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
const msg = error?.response?.data?.message || error?.response?.data?.detail || (error instanceof Error ? error.message : 'Ошибка регистрации. Проверьте введённые данные.');
|
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || (error instanceof Error ? error.message : 'Ошибка регистрации. Проверьте введённые данные.');
|
||||||
setErrorMsg(msg);
|
setErrorMsg(msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ function VerifyEmailContent() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
setMessage('Verification link is missing. Please check your email or request a new one.');
|
setMessage('Ссылка для подтверждения отсутствует. Проверьте письмо или запросите новое.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,16 +37,16 @@ function VerifyEmailContent() {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
if (res?.success) {
|
if (res?.success) {
|
||||||
setStatus('success');
|
setStatus('success');
|
||||||
setMessage('Email successfully verified. You can now sign in.');
|
setMessage('Email успешно подтверждён. Теперь вы можете войти.');
|
||||||
} else {
|
} else {
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
setMessage(res?.message || 'Failed to verify email.');
|
setMessage(res?.error?.message || res?.message || 'Не удалось подтвердить email.');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
const msg = err?.response?.data?.message || err?.response?.data?.detail || 'Invalid or expired link. Please request a new verification email.';
|
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || err?.response?.data?.detail || 'Ссылка недействительна или устарела. Запросите новое письмо с подтверждением.';
|
||||||
setMessage(msg);
|
setMessage(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -58,14 +58,14 @@ function VerifyEmailContent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
<Stack spacing={1.5} sx={{ mb: 5 }}>
|
||||||
<Typography variant="h5">Email verification</Typography>
|
<Typography variant="h5">Подтверждение email</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{status === 'loading' && (
|
{status === 'loading' && (
|
||||||
<Stack alignItems="center" sx={{ py: 4 }}>
|
<Stack alignItems="center" sx={{ py: 4 }}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
<Typography variant="body2" sx={{ mt: 2, color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ mt: 2, color: 'text.secondary' }}>
|
||||||
Verifying your email...
|
Подтверждение email...
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
@ -76,7 +76,7 @@ function VerifyEmailContent() {
|
||||||
{message}
|
{message}
|
||||||
</Alert>
|
</Alert>
|
||||||
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
|
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
|
||||||
Sign in to your account
|
Войти в аккаунт
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
@ -88,10 +88,10 @@ function VerifyEmailContent() {
|
||||||
</Alert>
|
</Alert>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
|
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
|
||||||
Back to sign in
|
Вернуться ко входу
|
||||||
</Link>
|
</Link>
|
||||||
<Link component={RouterLink} href={paths.auth.jwt.signUp} variant="body2" color="text.secondary">
|
<Link component={RouterLink} href={paths.auth.jwt.signUp} variant="body2" color="text.secondary">
|
||||||
Create a new account
|
Создать новый аккаунт
|
||||||
</Link>
|
</Link>
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
|
|
@ -104,7 +104,7 @@ function VerifyEmailContent() {
|
||||||
|
|
||||||
export function JwtVerifyEmailView() {
|
export function JwtVerifyEmailView() {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Typography variant="body2">Loading...</Typography>}>
|
<Suspense fallback={<Typography variant="body2">Загрузка...</Typography>}>
|
||||||
<VerifyEmailContent />
|
<VerifyEmailContent />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue