@@ -24,6 +24,17 @@ interface IRegisterEmailFormProps { successLogin: () => void } +function messageFromRegisterResponse(payload: unknown): string { + if (payload == null || typeof payload !== 'object') return 'Не удалось зарегистрироваться' + const detail = (payload as { detail?: unknown }).detail + if (typeof detail === 'string') return detail + if (Array.isArray(detail)) { + const item = detail[0] as { msg?: string } | undefined + if (item?.msg && typeof item.msg === 'string') return item.msg + } + return 'Не удалось зарегистрироваться' +} + export const RegisterEmailForm: React.FC = ({ successLogin }) => { const { register, handleSubmit, reset, watch } = useForm() @@ -64,22 +75,28 @@ export const RegisterEmailForm: React.FC = ({ successLo req_data.referer = localStorage.getItem('referral') } - const { status, data: result } = await axios.post(getApiUrl() + '/auth/register', req_data) + try { + const { status, data: result } = await axios.post(getApiUrl() + '/auth/register', req_data) - if (status !== 201) { + if (status !== 201) { + setLoading(false) + showMessage(messageFromRegisterResponse(result)) + return + } + + setLoading(false) + reset() + successLogin() + setTimeout(() => push('/login'), 7000) + } catch { setLoading(false) - showMessage(result.detail) - return + showMessage('Не удалось связаться с сервером. Проверьте подключение и попробуйте снова.') } - - setLoading(false) - reset() - successLogin() - setTimeout(() => push('/login'), 7000) } - const checkError: SubmitErrorHandler = (data) => { - showMessage(Object.values(data)[0].message || 'Неверные данные') + const checkError: SubmitErrorHandler = (errors) => { + const first = Object.values(errors)[0] as { message?: string } | undefined + showMessage(first?.message || 'Неверные данные') } const handleInputChangeTrim = (event: any) => { @@ -88,7 +105,7 @@ export const RegisterEmailForm: React.FC = ({ successLo const handleKeyDown = (event: any) => { if (event.key === 'Enter') { - handleSubmit(makeNewAccount)() + handleSubmit(makeNewAccount, checkError)() } } @@ -3,6 +3,13 @@ import { getEnv } from '#/shared/lib/env-store' export const getApiHost = (): string => { return (getEnv()?.NEXT_PUBLIC_MAIN_URL || process.env.NEXT_PUBLIC_MAIN_URL) ?? '' } + +/** Публичная реферальная ссылка без двойных слэшей (если MAIN_URL заканчивается на `/`). */ +export const getReferralInviteUrl = (referrerIdentifier: string): string => { + const base = getApiHost().replace(/\/+$/, '') + const segment = encodeURIComponent(referrerIdentifier) + return `${base}/r/${segment}` +} export const getApiUrl = (): string => { return (getEnv()?.NEXT_PUBLIC_API_HOST || process.env.NEXT_PUBLIC_API_HOST) ?? '' } @@ -1,6 +1,7 @@ export { getApiHost, getApiUrl, + getReferralInviteUrl, ModelPagesList, surpriseMePrompts, } from './constants' @@ -3,9 +3,14 @@ import { useDispatch } from 'react-redux' import { Box } from '@mui/material' import CircularProgress from '@mui/material/CircularProgress' import { useRouter } from 'next/router' +import { useSession } from 'next-auth/react' import { addReferral } from '#/entities/user-account/model/user-type-slice' import { NextPageWithLayout } from '#/pages/_app' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' + +const REFERRAL_BLOCKED_MESSAGE = + 'Вы не можете повторно зарегистрироваться по реферальной ссылке или стать рефералом после регистрации' export interface ReferralRegisterProps { email: string @@ -13,13 +18,23 @@ export interface ReferralRegisterProps { const ReferralRegister: NextPageWithLayout = ({ email }) => { const dispatch = useDispatch() - const { push } = useRouter() + const { push, replace } = useRouter() + const { status } = useSession() + const { showMessage } = useShowDataStore() useEffect(() => { + if (status === 'loading') return + + if (status === 'authenticated') { + showMessage(REFERRAL_BLOCKED_MESSAGE, 'error') + replace('/account?scope=setting') + return + } + localStorage.setItem('referral', email) dispatch(addReferral(email)) push('/register') - }, []) + }, [status, email, dispatch, push, replace, showMessage]) return ( @@ -5,7 +5,7 @@ import { useRouter } from 'next/router' import { useSession } from 'next-auth/react' import { getReferral, IReferral } from '#/shared/api/endpoints' -import { getApiHost } from '#/shared/lib/constants' +import { getReferralInviteUrl } from '#/shared/lib/constants' import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' import { useAppSelector } from '#/app/store/store' interface IProps { @@ -15,7 +15,7 @@ interface IProps { export const Referral = ({ device }: IProps) => { const email = useAppSelector((state) => state.user.email) const { push } = useRouter() - const url = getApiHost() + '/r/' + email + const url = getReferralInviteUrl(email) const { showMessage } = useShowDataStore() const { data } = useSession() @@ -7,6 +7,14 @@ export const deprecatedPaths = ['/admin'] export async function middleware(req: NextRequest) { const { pathname } = req.nextUrl + // Ссылки вида https://host//r/... (двойной слэш после домена) иначе не матчятся с pages/r/[email] + const normalizedPathname = pathname.replace(/\/{2,}/g, '/') + if (normalizedPathname !== pathname) { + const url = req.nextUrl.clone() + url.pathname = normalizedPathname + return NextResponse.redirect(url) + } + if (deprecatedPaths.includes(pathname)) { return NextResponse.redirect(new URL('/', req.url)) }