base commit

This commit is contained in:
@kirill.komarov
2026-04-28 21:36:30 +05:00
parent 55480d4aa5
commit 2148fd7a12
24 changed files with 1578 additions and 121 deletions
+2
View File
@@ -0,0 +1,2 @@
export { MePage } from './ui/MePage'
+178
View File
@@ -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>
)
}