пва
This commit is contained in:
@@ -8,8 +8,8 @@ import Stack from '@mui/material/Stack'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { Link as RouterLink } from 'react-router-dom'
|
||||
import { AppHeader } from '@/app/layout/AppHeader'
|
||||
import { STORE_EMAIL, STORE_NAME, STORE_PHONE, VK_URL } from '@/shared/config'
|
||||
import vkLogoSrc from '@/shared/assets/vk-logo.svg'
|
||||
import { STORE_EMAIL, STORE_NAME, STORE_PHONE, VK_URL } from '@/shared/config'
|
||||
import { ScrollOnNavigate } from '@/shared/ui/ScrollOnNavigate'
|
||||
import { ScrollToTop } from '@/shared/ui/ScrollToTop'
|
||||
|
||||
@@ -91,12 +91,7 @@ export function MainLayout({ children }: PropsWithChildren) {
|
||||
color="text.secondary"
|
||||
sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5, '&:hover': { color: '#4A76A8' } }}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={vkLogoSrc}
|
||||
alt="VK"
|
||||
sx={{ width: 20, height: 20 }}
|
||||
/>
|
||||
<Box component="img" src={vkLogoSrc} alt="VK" sx={{ width: 20, height: 20 }} />
|
||||
VK
|
||||
</Link>
|
||||
</Stack>
|
||||
|
||||
@@ -9,10 +9,12 @@ import Typography from '@mui/material/Typography'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useUnit } from 'effector-react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import {
|
||||
$user,
|
||||
changePasswordFx,
|
||||
fetchAuthMethodsFx,
|
||||
requestEmailChangeFx,
|
||||
setPasswordFx,
|
||||
unlinkOAuthFx,
|
||||
type AuthMethod,
|
||||
@@ -77,11 +79,78 @@ export function AuthMethodsSection() {
|
||||
return authMethods.filter((m) => m.active).length
|
||||
}, [authMethods])
|
||||
|
||||
const [searchParams] = useSearchParams()
|
||||
const emailVerified = searchParams.get('emailVerified')
|
||||
|
||||
const emailForm = useForm<{ email: string }>({
|
||||
defaultValues: { email: '' },
|
||||
})
|
||||
const [emailChangeError, setEmailChangeError] = useState<string | null>(null)
|
||||
const [verificationUrl, setVerificationUrl] = useState<string | null>(null)
|
||||
|
||||
const emailChangeMutation = useMutation({
|
||||
mutationFn: async (email: string) => {
|
||||
setEmailChangeError(null)
|
||||
const url = await requestEmailChangeFx(email)
|
||||
return url
|
||||
},
|
||||
onSuccess: (url) => setVerificationUrl(url),
|
||||
onError: (err) => setEmailChangeError(err?.message || 'Не удалось сменить email'),
|
||||
})
|
||||
|
||||
if (!user) return null
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Почта
|
||||
</Typography>
|
||||
|
||||
{emailVerified === '1' && (
|
||||
<Alert severity="success" sx={{ mb: 2 }}>
|
||||
Почта успешно подтверждена
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Typography sx={{ mb: 2 }} color="text.secondary">
|
||||
{user.email}
|
||||
</Typography>
|
||||
|
||||
{!verificationUrl && (
|
||||
<Stack direction="row" spacing={1} sx={{ mb: 2 }}>
|
||||
<TextField
|
||||
label="Новая почта"
|
||||
type="email"
|
||||
size="small"
|
||||
{...emailForm.register('email')}
|
||||
error={Boolean(emailChangeError)}
|
||||
helperText={emailChangeError}
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
disabled={!emailForm.watch('email') || emailChangeMutation.isPending}
|
||||
onClick={() => {
|
||||
const email = emailForm.getValues('email')
|
||||
if (email) emailChangeMutation.mutate(email)
|
||||
}}
|
||||
>
|
||||
Сменить
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{verificationUrl && (
|
||||
<Alert severity="info" sx={{ mb: 2 }}>
|
||||
<Stack spacing={1} direction="row" sx={{ alignItems: 'center' }}>
|
||||
<span>Ссылка подтверждения готова.</span>
|
||||
<Button size="small" variant="contained" href={verificationUrl}>
|
||||
Подтвердить email
|
||||
</Button>
|
||||
</Stack>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
|
||||
Методы входа
|
||||
</Typography>
|
||||
{fetchError && (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { AuthMethodsSection } from '../AuthMethodsSection'
|
||||
|
||||
@@ -15,6 +16,7 @@ vi.mock('@/shared/model/auth', () => ({
|
||||
fetchAuthMethodsFx: vi.fn().mockResolvedValue([]),
|
||||
setPasswordFx: vi.fn(),
|
||||
unlinkOAuthFx: vi.fn(),
|
||||
requestEmailChangeFx: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/shared/api/client', () => ({ apiClient: { post: vi.fn() } }))
|
||||
@@ -29,7 +31,9 @@ function renderSection() {
|
||||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
|
||||
return render(
|
||||
<QueryClientProvider client={qc}>
|
||||
<AuthMethodsSection />
|
||||
<MemoryRouter>
|
||||
<AuthMethodsSection />
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,6 +101,11 @@ export const changePasswordFx = createEffect(async (params: { oldPassword: strin
|
||||
await apiClient.post('me/change-password', params)
|
||||
})
|
||||
|
||||
export const requestEmailChangeFx = createEffect(async (email: string) => {
|
||||
const { data } = await apiClient.patch<{ verificationUrl: string }>('me/email', { email })
|
||||
return data.verificationUrl
|
||||
})
|
||||
|
||||
// ----- Error stores -----
|
||||
|
||||
export const $updateProfileError = createErrorStore(updateProfileFx).$error
|
||||
|
||||
Reference in New Issue
Block a user