@@ -64,6 +64,23 @@ export const authOptions: NextAuthOptions = { return (await data) as User }, }), + CredentialsProvider({ + id: 'tokens', + type: 'credentials', + credentials: {}, + async authorize(credentials) { + const { access, refresh } = credentials as { access: string; refresh: string } + + if (!access || !refresh) { + throw new Error('invalid_credentials') + } + + return { + id: 'tokens', + token: { access, refresh }, + } as User + }, + }), ], callbacks: { async jwt({ token, user, account, trigger }) { @@ -1,3 +1,26 @@ +export const getApiDetail = (error: unknown): string => { + const data = (error as { response?: { data?: { detail?: unknown; message?: string } } })?.response?.data + if (!data) return '' + + if (typeof data.message === 'string') return data.message + + const detail = data.detail + if (typeof detail === 'string') return detail + if (Array.isArray(detail)) { + return detail + .map((item) => + typeof item === 'string' + ? item + : typeof item === 'object' && item !== null && 'msg' in item + ? String((item as { msg: unknown }).msg) + : String(item) + ) + .join(', ') + } + + return '' +} + export const decodeError = (err: any, defaultErr: string) => { if (err.response?.data.detail?.includes('Token balance')) { return 'Не хватает ' + err.response.data.detail.split(' ').filter(Boolean).at(-2) + ' токена' @@ -3,33 +3,66 @@ import { Box, Typography } from '@mui/material' import axios from 'axios' import Link from 'next/link' import { useRouter } from 'next/router' +import { signIn } from 'next-auth/react' import { getApiUrl } from '#/shared/lib/constants' +import { getApiDetail } from '#/shared/lib/helpers/decode-error' import { NextPageWithLayout } from '#/pages/_app' -import { signIn } from 'next-auth/react' + +const CONFIRM_FAILED_MESSAGE = + 'Не удалось подтвердить почту. Попробуйте позже или войдите вручную.' const Confirm: NextPageWithLayout = () => { const { push } = useRouter() const [success, setSuccess] = useState(null) + const [errorMessage, setErrorMessage] = useState(null) + + const showConfirmError = (message?: string | null) => { + setSuccess(false) + setErrorMessage(message?.trim() || CONFIRM_FAILED_MESSAGE) + } useEffect(() => { - let formData: any = new FormData() - const tokenData = window.location.search.slice(1).split('=')[1] - formData.append("token", tokenData) + const params = new URLSearchParams(window.location.search) + const tokenData = params.get('token') ?? params.get('email_token') + + if (!tokenData) { + showConfirmError('Ссылка для подтверждения недействительна') + return + } + + const confirmEmail = async () => { + try { + const { data } = await axios.post<{ access?: string; refresh?: string }>( + getApiUrl() + '/v2/auth/confirm', + { token: tokenData }, + { headers: { 'Content-Type': 'application/json' } } + ) + + if (!data?.access || !data?.refresh) { + showConfirmError() + return + } + + const resp = await signIn('tokens', { + access: data.access, + refresh: data.refresh, + redirect: false, + }) + + if (resp?.ok) { + setSuccess(true) + push('/') + return + } + + showConfirmError() + } catch (err) { + showConfirmError(getApiDetail(err)) + } + } - axios.post(getApiUrl() + '/auth/confirm', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, validateStatus: (status) => status < 400 - }).then(() => { - setSuccess(true) - setTimeout(() => signIn('email_token', { token: tokenData, redirect: false }) - .then(() => push('/')) - .catch((err) => push({ pathname: '/login', query: { error: err ?? '' } })), 5000) - }).catch((err) => { - setSuccess(false) - setTimeout(() => push({ pathname: '/login', query: { error: err?.response.data.detail ?? '' } }), 5000) - }) + void confirmEmail() }, []) return ( @@ -56,9 +89,7 @@ const Confirm: NextPageWithLayout = () => { ) : success !== null && !success ? ( <> - Произошла проблема при потверждении почты -
- Уже получили сообщение об ошибке, пожалуйста - повторите попытку позже + {errorMessage ?? CONFIRM_FAILED_MESSAGE}
Вернуться к авторизации @@ -50,8 +50,21 @@ const Login: NextPageWithLayout = () => { if (!error || error === '') return - const expectedError = ERRROR_YANDEX_TRANSLATE_MAPPING[error] - showMessage(expectedError ?? 'Не удалось выполнить вход, попробуйте позже') + const yandexError = ERRROR_YANDEX_TRANSLATE_MAPPING[error] + if (yandexError) { + showMessage(yandexError) + return + } + + const authError = Object.fromEntries( + Object.entries(ERROR_MAPPING).map(([message, slug]) => [slug, message]) + )[error] + if (authError) { + showMessage(authError) + return + } + + showMessage(error) }, []) async function onSubmit(data: Record) {