e273c29c36
- Extract ProfileSection (45 lines): display name form with save button - Extract AvatarSection (114 lines): avatar preview, style selector, generate/save/cancel - Extract AuthMethodsSection (204 lines): auth methods list, set/change password forms - Rewrite SettingsPage as composer (41 lines): composes 3 sections with dividers - Add tests for all 3 sections
46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
import Box from '@mui/material/Box'
|
||
import Button from '@mui/material/Button'
|
||
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 { $user, updateProfileFx } from '@/shared/model/auth'
|
||
|
||
export function ProfileSection() {
|
||
const user = useUnit($user)
|
||
const pendingProfile = useUnit(updateProfileFx.pending)
|
||
|
||
const profileForm = useForm<{ displayName: string }>({
|
||
defaultValues: { displayName: user?.displayName ? String(user.displayName) : '' },
|
||
mode: 'onChange',
|
||
})
|
||
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" gutterBottom>
|
||
Профиль
|
||
</Typography>
|
||
<Stack spacing={2}>
|
||
<TextField
|
||
label="Имя или ник"
|
||
helperText="До 40 символов"
|
||
slotProps={{ htmlInput: { maxLength: 40 } }}
|
||
{...profileForm.register('displayName')}
|
||
/>
|
||
<Button
|
||
variant="contained"
|
||
disabled={pendingProfile}
|
||
onClick={() => {
|
||
const raw = profileForm.getValues('displayName')
|
||
const name = raw.trim()
|
||
updateProfileFx({ displayName: name.length ? name : null })
|
||
}}
|
||
>
|
||
Сохранить
|
||
</Button>
|
||
</Stack>
|
||
</Box>
|
||
)
|
||
}
|