base commit
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Alert from '@mui/material/Alert'
|
||||
import Box from '@mui/material/Box'
|
||||
import Button from '@mui/material/Button'
|
||||
@@ -21,6 +21,7 @@ import TableRow from '@mui/material/TableRow'
|
||||
import TextField from '@mui/material/TextField'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { Controller, useForm } from 'react-hook-form'
|
||||
import {
|
||||
createCategory,
|
||||
createProduct,
|
||||
@@ -55,14 +56,32 @@ const emptyForm = (): FormState => ({
|
||||
|
||||
export function AdminPage() {
|
||||
const queryClient = useQueryClient()
|
||||
const [tokenInput, setTokenInput] = useState('')
|
||||
const [token, setToken] = useState<string | null>(() => getAdminToken())
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
const [editing, setEditing] = useState<Product | null>(null)
|
||||
const [form, setForm] = useState<FormState>(emptyForm)
|
||||
const [catOpen, setCatOpen] = useState(false)
|
||||
const [catName, setCatName] = useState('')
|
||||
const [catSlug, setCatSlug] = useState('')
|
||||
|
||||
const tokenForm = useForm<{ token: string }>({
|
||||
defaultValues: { token: '' },
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const productForm = useForm<FormState>({
|
||||
defaultValues: emptyForm(),
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const categoryForm = useForm<{ name: string; slug: string }>({
|
||||
defaultValues: { name: '', slug: '' },
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const titleValue = productForm.watch('title')
|
||||
const categoryIdValue = productForm.watch('categoryId')
|
||||
|
||||
useEffect(() => {
|
||||
tokenForm.reset({ token: '' })
|
||||
}, [token, tokenForm])
|
||||
|
||||
const categoriesQuery = useQuery({
|
||||
queryKey: ['categories'],
|
||||
@@ -76,7 +95,7 @@ export function AdminPage() {
|
||||
})
|
||||
|
||||
const saveToken = () => {
|
||||
const t = tokenInput.trim()
|
||||
const t = tokenForm.getValues('token').trim()
|
||||
if (!t) {
|
||||
clearAdminToken()
|
||||
setToken(null)
|
||||
@@ -88,13 +107,13 @@ export function AdminPage() {
|
||||
|
||||
const openCreate = () => {
|
||||
setEditing(null)
|
||||
setForm(emptyForm())
|
||||
productForm.reset(emptyForm())
|
||||
setDialogOpen(true)
|
||||
}
|
||||
|
||||
const openEdit = (p: Product) => {
|
||||
setEditing(p)
|
||||
setForm({
|
||||
productForm.reset({
|
||||
title: p.title,
|
||||
slug: p.slug,
|
||||
description: p.description ?? '',
|
||||
@@ -108,6 +127,7 @@ export function AdminPage() {
|
||||
|
||||
const createMut = useMutation({
|
||||
mutationFn: async () => {
|
||||
const form = productForm.getValues()
|
||||
const priceCents = Math.round(Number(form.priceRub.replace(',', '.')) * 100)
|
||||
if (!Number.isFinite(priceCents) || priceCents < 0) throw new Error('Некорректная цена')
|
||||
await createProduct(token!, {
|
||||
@@ -129,6 +149,7 @@ export function AdminPage() {
|
||||
|
||||
const updateMut = useMutation({
|
||||
mutationFn: async () => {
|
||||
const form = productForm.getValues()
|
||||
const priceCents = Math.round(Number(form.priceRub.replace(',', '.')) * 100)
|
||||
if (!Number.isFinite(priceCents) || priceCents < 0) throw new Error('Некорректная цена')
|
||||
await updateProduct(token!, editing!.id, {
|
||||
@@ -157,16 +178,17 @@ export function AdminPage() {
|
||||
})
|
||||
|
||||
const createCategoryMut = useMutation({
|
||||
mutationFn: () =>
|
||||
createCategory(token!, {
|
||||
name: catName.trim(),
|
||||
slug: catSlug.trim() || undefined,
|
||||
}),
|
||||
mutationFn: () => {
|
||||
const v = categoryForm.getValues()
|
||||
return createCategory(token!, {
|
||||
name: v.name.trim(),
|
||||
slug: v.slug.trim() || undefined,
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['categories'] })
|
||||
setCatOpen(false)
|
||||
setCatName('')
|
||||
setCatSlug('')
|
||||
categoryForm.reset({ name: '', slug: '' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -191,13 +213,18 @@ export function AdminPage() {
|
||||
</Typography>
|
||||
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ mb: 3 }}>
|
||||
<TextField
|
||||
label="Токен (Bearer)"
|
||||
type="password"
|
||||
fullWidth
|
||||
value={tokenInput}
|
||||
onChange={(e) => setTokenInput(e.target.value)}
|
||||
placeholder={token ? '••••••••' : ''}
|
||||
<Controller
|
||||
control={tokenForm.control}
|
||||
name="token"
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
label="Токен (Bearer)"
|
||||
type="password"
|
||||
fullWidth
|
||||
{...field}
|
||||
placeholder={token ? '••••••••' : ''}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Button variant="contained" onClick={saveToken} sx={{ minWidth: 140 }}>
|
||||
Сохранить
|
||||
@@ -267,63 +294,63 @@ export function AdminPage() {
|
||||
<DialogTitle>{editing ? 'Редактировать товар' : 'Новый товар'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
label="Название"
|
||||
fullWidth
|
||||
required
|
||||
value={form.title}
|
||||
onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="title"
|
||||
render={({ field }) => <TextField label="Название" fullWidth required {...field} />}
|
||||
/>
|
||||
<TextField
|
||||
label="Slug (URL)"
|
||||
fullWidth
|
||||
value={form.slug}
|
||||
onChange={(e) => setForm((f) => ({ ...f, slug: e.target.value }))}
|
||||
helperText="Можно оставить пустым при создании — сгенерируется из названия"
|
||||
/>
|
||||
<TextField
|
||||
label="Описание"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
value={form.description}
|
||||
onChange={(e) => setForm((f) => ({ ...f, description: e.target.value }))}
|
||||
/>
|
||||
<TextField
|
||||
label="Цена, ₽"
|
||||
fullWidth
|
||||
value={form.priceRub}
|
||||
onChange={(e) => setForm((f) => ({ ...f, priceRub: e.target.value }))}
|
||||
/>
|
||||
<TextField
|
||||
label="Ссылка на изображение"
|
||||
fullWidth
|
||||
value={form.imageUrl}
|
||||
onChange={(e) => setForm((f) => ({ ...f, imageUrl: e.target.value }))}
|
||||
/>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel id="cat-label">Категория</InputLabel>
|
||||
<Select
|
||||
labelId="cat-label"
|
||||
label="Категория"
|
||||
value={form.categoryId}
|
||||
onChange={(e) => setForm((f) => ({ ...f, categoryId: String(e.target.value) }))}
|
||||
>
|
||||
{(categoriesQuery.data ?? []).map((c) => (
|
||||
<MenuItem key={c.id} value={c.id}>
|
||||
{c.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={form.published}
|
||||
onChange={(e) => setForm((f) => ({ ...f, published: e.target.checked }))}
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="slug"
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
label="Slug (URL)"
|
||||
fullWidth
|
||||
{...field}
|
||||
helperText="Можно оставить пустым при создании — сгенерируется из названия"
|
||||
/>
|
||||
}
|
||||
label="Показывать в каталоге"
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="description"
|
||||
render={({ field }) => <TextField label="Описание" fullWidth multiline minRows={2} {...field} />}
|
||||
/>
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="priceRub"
|
||||
render={({ field }) => <TextField label="Цена, ₽" fullWidth {...field} />}
|
||||
/>
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="imageUrl"
|
||||
render={({ field }) => <TextField label="Ссылка на изображение" fullWidth {...field} />}
|
||||
/>
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="categoryId"
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel id="cat-label">Категория</InputLabel>
|
||||
<Select labelId="cat-label" label="Категория" {...field}>
|
||||
{(categoriesQuery.data ?? []).map((c) => (
|
||||
<MenuItem key={c.id} value={c.id}>
|
||||
{c.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="published"
|
||||
render={({ field }) => (
|
||||
<FormControlLabel
|
||||
control={<Switch checked={field.value} onChange={(_, v) => field.onChange(v)} />}
|
||||
label="Показывать в каталоге"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
@@ -332,7 +359,7 @@ export function AdminPage() {
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSubmit}
|
||||
disabled={!form.title.trim() || !form.categoryId || createMut.isPending || updateMut.isPending}
|
||||
disabled={!titleValue.trim() || !categoryIdValue || createMut.isPending || updateMut.isPending}
|
||||
>
|
||||
{editing ? 'Сохранить' : 'Создать'}
|
||||
</Button>
|
||||
@@ -343,19 +370,22 @@ export function AdminPage() {
|
||||
<DialogTitle>Новая категория</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
label="Название"
|
||||
fullWidth
|
||||
required
|
||||
value={catName}
|
||||
onChange={(e) => setCatName(e.target.value)}
|
||||
<Controller
|
||||
control={categoryForm.control}
|
||||
name="name"
|
||||
render={({ field }) => <TextField label="Название" fullWidth required {...field} />}
|
||||
/>
|
||||
<TextField
|
||||
label="Slug"
|
||||
fullWidth
|
||||
value={catSlug}
|
||||
onChange={(e) => setCatSlug(e.target.value)}
|
||||
helperText="Необязательно — можно сгенерировать из названия на сервере"
|
||||
<Controller
|
||||
control={categoryForm.control}
|
||||
name="slug"
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
label="Slug"
|
||||
fullWidth
|
||||
{...field}
|
||||
helperText="Необязательно — можно сгенерировать из названия на сервере"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
@@ -363,7 +393,7 @@ export function AdminPage() {
|
||||
<Button onClick={() => setCatOpen(false)}>Отмена</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!catName.trim() || createCategoryMut.isPending}
|
||||
disabled={!categoryForm.watch('name').trim() || createCategoryMut.isPending}
|
||||
onClick={() => createCategoryMut.mutate()}
|
||||
>
|
||||
Создать
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { AuthPage } from './ui/AuthPage'
|
||||
@@ -0,0 +1,139 @@
|
||||
import { useState } from 'react'
|
||||
import Alert from '@mui/material/Alert'
|
||||
import Box from '@mui/material/Box'
|
||||
import Button from '@mui/material/Button'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import TextField from '@mui/material/TextField'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
import { tokenSet } from '@/shared/model/auth'
|
||||
|
||||
type AuthResponse = { token: string; user: { id: string; email: string } }
|
||||
|
||||
function getApiErrorMessage(err: unknown): string | null {
|
||||
if (!err || typeof err !== 'object') return null
|
||||
const anyErr = err as Record<string, unknown>
|
||||
const response = anyErr.response as Record<string, unknown> | undefined
|
||||
const data = response?.data as Record<string, unknown> | undefined
|
||||
const msg = data?.error
|
||||
return typeof msg === 'string' ? msg : null
|
||||
}
|
||||
|
||||
export function AuthPage() {
|
||||
const [message, setMessage] = useState<string | null>(null)
|
||||
const { register, watch } = useForm<{
|
||||
email: string
|
||||
code: string
|
||||
password: string
|
||||
}>({
|
||||
defaultValues: { email: '', code: '', password: '' },
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const email = watch('email')
|
||||
const code = watch('code')
|
||||
const password = watch('password')
|
||||
|
||||
const requestCode = useMutation({
|
||||
mutationFn: async () => {
|
||||
await apiClient.post('auth/request-code', { email })
|
||||
},
|
||||
onSuccess: () => setMessage('Код отправлен. Проверьте почту (в dev может быть в логах сервера).'),
|
||||
})
|
||||
|
||||
const verifyCode = useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiClient.post<AuthResponse>('auth/verify-code', { email, code })
|
||||
tokenSet(data.token)
|
||||
setMessage(`Вход выполнен: ${data.user.email}`)
|
||||
},
|
||||
})
|
||||
|
||||
const registerPassword = useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiClient.post<AuthResponse>('auth/register', { email, password })
|
||||
tokenSet(data.token)
|
||||
setMessage(`Регистрация выполнена: ${data.user.email}`)
|
||||
},
|
||||
})
|
||||
|
||||
const loginPassword = useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiClient.post<AuthResponse>('auth/login', { email, password })
|
||||
tokenSet(data.token)
|
||||
setMessage(`Вход выполнен: ${data.user.email}`)
|
||||
},
|
||||
})
|
||||
|
||||
const errMsg = getApiErrorMessage(
|
||||
requestCode.error || verifyCode.error || registerPassword.error || loginPassword.error,
|
||||
)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Вход / регистрация
|
||||
</Typography>
|
||||
|
||||
{message && (
|
||||
<Alert severity="success" sx={{ mb: 2 }}>
|
||||
{message}
|
||||
</Alert>
|
||||
)}
|
||||
{errMsg && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{errMsg}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Stack spacing={2} sx={{ maxWidth: 520 }}>
|
||||
<TextField label="Email" {...register('email')} fullWidth />
|
||||
|
||||
<Typography variant="h6">Вариант 1: Email + код</Typography>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
||||
<Button variant="outlined" onClick={() => requestCode.mutate()} disabled={!email || requestCode.isPending}>
|
||||
Отправить код
|
||||
</Button>
|
||||
<TextField label="Код (6 цифр)" inputMode="numeric" {...register('code')} />
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => verifyCode.mutate()}
|
||||
disabled={!email || code.length !== 6 || verifyCode.isPending}
|
||||
>
|
||||
Войти
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Typography variant="h6">Вариант 2: Email + пароль</Typography>
|
||||
<TextField
|
||||
label="Пароль"
|
||||
type="password"
|
||||
{...register('password')}
|
||||
fullWidth
|
||||
helperText="Минимум 8 символов для регистрации"
|
||||
/>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => registerPassword.mutate()}
|
||||
disabled={!email || password.length < 8 || registerPassword.isPending}
|
||||
>
|
||||
Зарегистрироваться
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => loginPassword.mutate()}
|
||||
disabled={!email || !password || loginPassword.isPending}
|
||||
>
|
||||
Войти
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { MePage } from './ui/MePage'
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import Alert from '@mui/material/Alert'
|
||||
import Box from '@mui/material/Box'
|
||||
import Button from '@mui/material/Button'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import TextField from '@mui/material/TextField'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { useUnit } from 'effector-react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import {
|
||||
$changePasswordError,
|
||||
$requestEmailChangeCodeError,
|
||||
$updateProfileError,
|
||||
$user,
|
||||
$verifyEmailChangeError,
|
||||
changePasswordFx,
|
||||
requestEmailChangeCodeFx,
|
||||
updateProfileFx,
|
||||
verifyEmailChangeFx,
|
||||
} from '@/shared/model/auth'
|
||||
|
||||
export function MePage() {
|
||||
const user = useUnit($user)
|
||||
const pendingPassword = useUnit(changePasswordFx.pending)
|
||||
const pendingEmailReq = useUnit(requestEmailChangeCodeFx.pending)
|
||||
const pendingEmailVerify = useUnit(verifyEmailChangeFx.pending)
|
||||
const pendingProfile = useUnit(updateProfileFx.pending)
|
||||
const errorPassword = useUnit($changePasswordError)
|
||||
const errorEmailReq = useUnit($requestEmailChangeCodeError)
|
||||
const errorProfile = useUnit($updateProfileError)
|
||||
const errorEmailVerify = useUnit($verifyEmailChangeError)
|
||||
|
||||
const passwordForm = useForm<{ currentPassword: string; newPassword: string }>({
|
||||
defaultValues: { currentPassword: '', newPassword: '' },
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const emailForm = useForm<{ newEmail: string; code: string }>({
|
||||
defaultValues: { newEmail: '', code: '' },
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const profileForm = useForm<{ name: string }>({
|
||||
defaultValues: { name: user?.name ? String(user.name) : '' },
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const passwordErrorMsg =
|
||||
(errorPassword as any)?.response?.data?.error ? String((errorPassword as any).response.data.error) : null
|
||||
const emailErrorMsg =
|
||||
(errorEmailReq as any)?.response?.data?.error
|
||||
? String((errorEmailReq as any).response.data.error)
|
||||
: (errorEmailVerify as any)?.response?.data?.error
|
||||
? String((errorEmailVerify as any).response.data.error)
|
||||
: null
|
||||
const profileErrorMsg =
|
||||
(errorProfile as any)?.response?.data?.error ? String((errorProfile as any).response.data.error) : null
|
||||
|
||||
if (!user) {
|
||||
return <Alert severity="info">Нужно войти. Перейдите на страницу «Вход».</Alert>
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Профиль
|
||||
</Typography>
|
||||
<Typography color="text.secondary" sx={{ mb: 3 }}>
|
||||
Текущая почта: <b>{user.email}</b>
|
||||
</Typography>
|
||||
|
||||
{passwordErrorMsg && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{passwordErrorMsg}
|
||||
</Alert>
|
||||
)}
|
||||
{emailErrorMsg && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{emailErrorMsg}
|
||||
</Alert>
|
||||
)}
|
||||
{profileErrorMsg && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{profileErrorMsg}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Stack spacing={3} sx={{ maxWidth: 560 }}>
|
||||
<Box>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Имя / ник
|
||||
</Typography>
|
||||
<Stack spacing={2}>
|
||||
<TextField
|
||||
label="Имя или ник"
|
||||
helperText="До 40 символов"
|
||||
slotProps={{ htmlInput: { maxLength: 40 } }}
|
||||
{...profileForm.register('name')}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={pendingProfile}
|
||||
onClick={() => {
|
||||
const raw = profileForm.getValues('name')
|
||||
const name = raw.trim()
|
||||
updateProfileFx({ name: name.length ? name : null })
|
||||
}}
|
||||
>
|
||||
Сохранить имя
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Смена почты
|
||||
</Typography>
|
||||
<Stack spacing={2}>
|
||||
<TextField label="Новая почта" {...emailForm.register('newEmail')} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
disabled={!emailForm.watch('newEmail') || pendingEmailReq}
|
||||
onClick={() => requestEmailChangeCodeFx(emailForm.getValues('newEmail').trim())}
|
||||
>
|
||||
Отправить код на новую почту
|
||||
</Button>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
||||
<TextField label="Код (6 цифр)" inputMode="numeric" {...emailForm.register('code')} />
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={emailForm.watch('code').trim().length !== 6 || pendingEmailVerify}
|
||||
onClick={() =>
|
||||
verifyEmailChangeFx({
|
||||
newEmail: emailForm.getValues('newEmail').trim(),
|
||||
code: emailForm.getValues('code').trim(),
|
||||
})
|
||||
}
|
||||
>
|
||||
Подтвердить
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Смена пароля
|
||||
</Typography>
|
||||
<Stack spacing={2}>
|
||||
<TextField
|
||||
label="Текущий пароль (если установлен)"
|
||||
type="password"
|
||||
{...passwordForm.register('currentPassword')}
|
||||
/>
|
||||
<TextField label="Новый пароль" type="password" {...passwordForm.register('newPassword')} />
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={passwordForm.watch('newPassword').length < 8 || pendingPassword}
|
||||
onClick={() =>
|
||||
changePasswordFx({
|
||||
currentPassword: passwordForm.getValues('currentPassword') || undefined,
|
||||
newPassword: passwordForm.getValues('newPassword'),
|
||||
})
|
||||
}
|
||||
>
|
||||
Сохранить пароль
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user