19.05.2026 okkk
This commit is contained in:
@@ -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: {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,6 +65,25 @@ export function RestorePasswordForm() {
|
|||||||
<h1 className={styles.title}>Восстановление пароля</h1>
|
<h1 className={styles.title}>Восстановление пароля</h1>
|
||||||
|
|
||||||
<div className={styles.fields}>
|
<div className={styles.fields}>
|
||||||
|
<div className={styles.emailRow}>
|
||||||
|
<FormField
|
||||||
|
label="E-Mail"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={setEmail}
|
||||||
|
placeholder="example@mail.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<PrimaryButton
|
||||||
|
label={isSendingCode ? 'Отправка...' : 'Получить код'}
|
||||||
|
disabled={isSendingCode || !email}
|
||||||
|
type="button"
|
||||||
|
onClick={handleSendCode}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{codeSent && (
|
||||||
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
label="Код с почты"
|
label="Код с почты"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -78,13 +108,17 @@ export function RestorePasswordForm() {
|
|||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && <p className={styles.error}>{error}</p>}
|
{error && <p className={styles.error}>{error}</p>}
|
||||||
|
|
||||||
|
{codeSent && (
|
||||||
<div className={styles.submitWrapper}>
|
<div className={styles.submitWrapper}>
|
||||||
<PrimaryButton label={isLoading ? 'Сохранение...' : 'Сохранить пароль'} disabled={isLoading} />
|
<PrimaryButton label={isLoading ? 'Сохранение...' : 'Изменить пароль'} disabled={isLoading} />
|
||||||
</div>
|
</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)}>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user