19.05.2026 okkk

This commit is contained in:
2026-05-19 15:50:47 +03:00
parent 36196a882f
commit 370141c83b
5 changed files with 86 additions and 37 deletions

View File

@@ -69,14 +69,20 @@ export async function uploadAvatar(payload: UploadAvatarPayload): Promise<MeResp
return data return data
} }
export async function passwordResetStart(): Promise<void> { export interface PasswordResetStartPayload {
email: string
}
export async function passwordResetStart(payload: PasswordResetStartPayload): Promise<void> {
const csrf = await getCsrfToken() const csrf = await getCsrfToken()
const res = await fetch(`${USERS_API_URL}/me/settings/password/start`, { const res = await fetch(`${USERS_API_URL}/me/settings/password/forgot/start`, {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf, 'X-CSRF-Token': csrf,
}, },
body: JSON.stringify(payload),
}) })
if (!res.ok) { if (!res.ok) {
const data = await res.json().catch(() => ({})) const data = await res.json().catch(() => ({}))
@@ -92,7 +98,7 @@ export interface PasswordResetCompletePayload {
export async function passwordResetComplete(payload: PasswordResetCompletePayload): Promise<void> { export async function passwordResetComplete(payload: PasswordResetCompletePayload): Promise<void> {
const csrf = await getCsrfToken() const csrf = await getCsrfToken()
const res = await fetch(`${USERS_API_URL}/me/settings/password/complete`, { const res = await fetch(`${USERS_API_URL}/me/settings/password/forgot/complete`, {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
headers: { headers: {

View File

@@ -32,6 +32,12 @@
gap: 20px; gap: 20px;
} }
.emailRow {
display: flex;
flex-direction: column;
gap: 12px;
}
.error { .error {
color: #ff5a5a; color: #ff5a5a;
font-size: 13px; font-size: 13px;

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react' import { useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { FormField } from '@shared/ui' import { FormField } from '@shared/ui'
import { PrimaryButton } from '@shared/ui' import { PrimaryButton } from '@shared/ui'
@@ -11,6 +11,9 @@ import styles from './RestorePasswordForm.module.css'
type NotificationState = { message: string; status: 'success' | 'error' } type NotificationState = { message: string; status: 'success' | 'error' }
export function RestorePasswordForm() { export function RestorePasswordForm() {
const [email, setEmail] = useState('')
const [codeSent, setCodeSent] = useState(false)
const [isSendingCode, setIsSendingCode] = useState(false)
const [code, setCode] = useState('') const [code, setCode] = useState('')
const [newPassword, setNewPassword] = useState('') const [newPassword, setNewPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('') const [confirmPassword, setConfirmPassword] = useState('')
@@ -19,11 +22,19 @@ export function RestorePasswordForm() {
const [notification, setNotification] = useState<NotificationState | null>(null) const [notification, setNotification] = useState<NotificationState | null>(null)
const navigate = useNavigate() const navigate = useNavigate()
useEffect(() => { async function handleSendCode() {
passwordResetStart() setError('')
.then(() => setNotification({ status: 'success', message: 'На вашу почту отправлено письмо с кодом' })) setIsSendingCode(true)
.catch(() => setNotification({ status: 'error', message: 'Не удалось отправить письмо. Попробуйте позже.' })) try {
}, []) await passwordResetStart({ email })
setCodeSent(true)
setNotification({ status: 'success', message: `Код отправлен на ${email}` })
} catch {
setNotification({ status: 'error', message: 'Не удалось отправить письмо. Проверьте email и попробуйте снова.' })
} finally {
setIsSendingCode(false)
}
}
async function handleSubmit(e: React.FormEvent) { async function handleSubmit(e: React.FormEvent) {
e.preventDefault() e.preventDefault()
@@ -54,37 +65,60 @@ export function RestorePasswordForm() {
<h1 className={styles.title}>Восстановление пароля</h1> <h1 className={styles.title}>Восстановление пароля</h1>
<div className={styles.fields}> <div className={styles.fields}>
<FormField <div className={styles.emailRow}>
label="Код с почты" <FormField
type="text" label="E-Mail"
value={code} type="email"
onChange={setCode} value={email}
placeholder="000 000" onChange={setEmail}
required placeholder="example@mail.com"
/> required
<FormField />
label="Новый пароль" <PrimaryButton
type="password" label={isSendingCode ? 'Отправка...' : 'Получить код'}
value={newPassword} disabled={isSendingCode || !email}
onChange={setNewPassword} type="button"
placeholder="••••••••" onClick={handleSendCode}
required />
/> </div>
<FormField
label="Повторить пароль" {codeSent && (
type="password" <>
value={confirmPassword} <FormField
onChange={setConfirmPassword} label="Код с почты"
placeholder="••••••••" type="text"
required value={code}
/> onChange={setCode}
placeholder="000 000"
required
/>
<FormField
label="Новый пароль"
type="password"
value={newPassword}
onChange={setNewPassword}
placeholder="••••••••"
required
/>
<FormField
label="Повторить пароль"
type="password"
value={confirmPassword}
onChange={setConfirmPassword}
placeholder="••••••••"
required
/>
</>
)}
</div> </div>
{error && <p className={styles.error}>{error}</p>} {error && <p className={styles.error}>{error}</p>}
<div className={styles.submitWrapper}> {codeSent && (
<PrimaryButton label={isLoading ? 'Сохранение...' : 'Сохранить пароль'} disabled={isLoading} /> <div className={styles.submitWrapper}>
</div> <PrimaryButton label={isLoading ? 'Сохранение...' : 'Изменить пароль'} disabled={isLoading} />
</div>
)}
<div className={styles.footer}> <div className={styles.footer}>
<a className={styles.back} onClick={() => navigate(ROUTES.LOGIN)}> <a className={styles.back} onClick={() => navigate(ROUTES.LOGIN)}>

View File

@@ -140,6 +140,9 @@
} }
.display { .display {
flex: 1;
min-width: 0;
overflow: hidden;
font-weight: 700; font-weight: 700;
white-space: nowrap; white-space: nowrap;
line-height: 1; line-height: 1;

View File

@@ -25,7 +25,7 @@ export function SwapCard({
onTokenChange, onAmountChange, onSetPercent, onTokenChange, onAmountChange, onSetPercent,
selectedNetwork, onNetworkChange, hideNetworkSelect, selectedNetwork, onNetworkChange, hideNetworkSelect,
}: Props) { }: Props) {
const [intPart, decPart] = amount.split('.') const [intPart, decPart] = truncateDecimals(amount, 8).split('.')
const pills = onSetPercent && ( const pills = onSetPercent && (
<> <>