feat: add password change and reset via email code

This commit is contained in:
Kirill
2026-05-22 14:12:29 +05:00
parent 22282c5f4e
commit ad43ff98b6
4 changed files with 259 additions and 2 deletions
+125 -2
View File
@@ -44,6 +44,9 @@ export function AuthPage() {
const [oauthError, setOauthError] = useState<string | null>(null)
const [tab, setTab] = useState(0)
const [isRegister, setIsRegister] = useState(false)
const [showForgot, setShowForgot] = useState(false)
const [forgotStep, setForgotStep] = useState(0)
const [forgotEmail, setForgotEmail] = useState('')
const [searchParams, setSearchParams] = useSearchParams()
const navigate = useNavigate()
const user = useUnit($user)
@@ -99,7 +102,7 @@ export function AuthPage() {
mutationFn: async () => {
await apiClient.post('auth/request-code', { email })
},
onSuccess: () => setMessage('Код отправлен. Проверьте почту (в dev может быть в логах сервера).'),
onSuccess: () => setMessage('Код отправлен. Проверьте почту.'),
})
const verifyCode = useMutation({
@@ -110,11 +113,38 @@ export function AuthPage() {
},
})
const forgotCode = useMutation({
mutationFn: async () => {
await apiClient.post('auth/forgot-password', { email: forgotEmail })
},
onSuccess: () => {
setForgotStep(1)
setMessage('Код отправлен на почту')
},
})
const resetPassword = useMutation({
mutationFn: async () => {
await apiClient.post('auth/reset-password', {
email: forgotEmail,
code,
newPassword: password,
})
},
onSuccess: () => {
setShowForgot(false)
setForgotStep(0)
setMessage('Пароль изменён. Войдите с новым паролем.')
},
})
const errMsg =
getApiErrorMessage(loginMutation.error) ||
getApiErrorMessage(registerMutation.error) ||
getApiErrorMessage(requestCode.error) ||
getApiErrorMessage(verifyCode.error)
getApiErrorMessage(verifyCode.error) ||
getApiErrorMessage(forgotCode.error) ||
getApiErrorMessage(resetPassword.error)
const passwordError = isRegister && passwordConfirm && password !== passwordConfirm ? 'Пароли не совпадают' : null
@@ -304,6 +334,99 @@ export function AuthPage() {
Войти
</Button>
)}
{!isRegister && !showForgot && (
<Button
variant="text"
size="small"
sx={{ textTransform: 'none', alignSelf: 'center', color: 'text.secondary' }}
onClick={() => {
setShowForgot(true)
setForgotStep(0)
setForgotEmail(email)
setMessage(null)
}}
>
Забыли пароль?
</Button>
)}
{showForgot && (
<>
<TextField
label="Email"
value={forgotEmail}
onChange={(e) => setForgotEmail(e.target.value)}
fullWidth
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">
<Mail size={18} />
</InputAdornment>
),
},
}}
/>
{forgotStep === 1 && (
<>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
<TextField
label="Код (6 цифр)"
inputMode="numeric"
value={code}
onChange={(e) => {
register('code').onChange(e)
}}
sx={{ flex: 1 }}
/>
<Button
variant="outlined"
onClick={() => forgotCode.mutate()}
disabled={!forgotEmail || forgotCode.isPending}
sx={{ whiteSpace: 'nowrap' }}
>
Отправить ещё раз
</Button>
</Stack>
<TextField label="Новый пароль" type="password" {...register('password')} fullWidth />
<Button
variant="contained"
disabled={
!code || code.length !== 6 || !password || password.length < 8 || resetPassword.isPending
}
onClick={() => resetPassword.mutate()}
>
Сменить пароль
</Button>
</>
)}
{forgotStep === 0 && (
<Button
variant="contained"
disabled={!forgotEmail || forgotCode.isPending}
onClick={() => forgotCode.mutate()}
>
Отправить код
</Button>
)}
<Button
variant="text"
size="small"
sx={{ textTransform: 'none', alignSelf: 'center' }}
onClick={() => {
setShowForgot(false)
setForgotStep(0)
setMessage(null)
}}
>
Назад к входу
</Button>
</>
)}
</Stack>
)}