"use client"; import { useState, useEffect, useRef } from "react"; import Link from "next/link"; declare global { interface Window { turnstile?: { render: ( container: HTMLElement, options: { sitekey: string; callback: (token: string) => void; "expired-callback"?: () => void; "error-callback"?: () => void; } ) => string; reset: (widgetId: string) => void; }; } } interface Props { showTermsCheckbox: boolean; privacyPolicyUrl: string; termsUrl: string; offerUrl: string; archetype?: string; utmSource?: string; utmMedium?: string; utmCampaign?: string; } export function RegisterForm({ showTermsCheckbox, privacyPolicyUrl, termsUrl, offerUrl, archetype, utmSource, utmMedium, utmCampaign }: Props) { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [honeypot, setHoneypot] = useState(""); const [termsAccepted, setTermsAccepted] = useState(false); const [turnstileToken, setTurnstileToken] = useState(null); const [error, setError] = useState(""); const [emailTaken, setEmailTaken] = useState(false); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); const turnstileContainerRef = useRef(null); const turnstileWidgetId = useRef(null); const siteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY; useEffect(() => { if (!siteKey || !turnstileContainerRef.current) return; const script = document.createElement("script"); script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js"; script.async = true; script.defer = true; document.head.appendChild(script); script.onload = () => { if (!turnstileContainerRef.current || !window.turnstile) return; turnstileWidgetId.current = window.turnstile.render(turnstileContainerRef.current, { sitekey: siteKey, callback: (token) => setTurnstileToken(token), "expired-callback": () => setTurnstileToken(null), "error-callback": () => setTurnstileToken(null), }); }; return () => { if (document.head.contains(script)) document.head.removeChild(script); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const inputStyle = { border: "2px solid var(--border)", background: "var(--background)", outline: "none", width: "100%", padding: "0.5rem 0.75rem", fontSize: "16px", fontFamily: "inherit", } as React.CSSProperties; const legalLinks = [ { url: privacyPolicyUrl, label: "Политику конфиденциальности" }, { url: termsUrl, label: "Согласие на обработку данных" }, { url: offerUrl, label: "Договор-оферту" }, ].filter((l) => l.url); function resetTurnstile() { if (turnstileWidgetId.current) { window.turnstile?.reset(turnstileWidgetId.current); setTurnstileToken(null); } } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (showTermsCheckbox && !termsAccepted) { setError("Необходимо принять условия для продолжения"); return; } if (siteKey && !turnstileToken) { setError("Пожалуйста, подтвердите, что вы не робот"); return; } setError(""); setEmailTaken(false); setLoading(true); // 1. Сетевой слой: настоящий обрыв связи — отдельно от ошибок сервера let res: Response; try { res = await fetch("/api/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, email, password, website: honeypot, cfTurnstileResponse: turnstileToken, archetype, utmSource, utmMedium, utmCampaign, }), }); } catch { setError("Не удалось связаться с сервером. Проверьте интернет-соединение и попробуйте ещё раз."); setLoading(false); return; } // 2. Тело может быть не-JSON (502 при передеплое, HTML от прокси) let data: { message?: string; code?: string } = {}; try { data = await res.json() as { message?: string; code?: string }; } catch { setError( res.ok ? "Сервер вернул неожиданный ответ. Попробуйте ещё раз." : "Сервис временно недоступен. Попробуйте через минуту." ); resetTurnstile(); setLoading(false); return; } // 3. Осмысленные ошибки сервера if (!res.ok) { if (res.status === 409 || data.code === "EMAIL_TAKEN") { setEmailTaken(true); } else { setError(data.message ?? "Не удалось зарегистрироваться. Попробуйте ещё раз."); } resetTurnstile(); setLoading(false); return; } setSuccess(true); setLoading(false); } if (success) { return (
✉️

Проверьте почту

Мы отправили письмо на {email} для подтверждения аккаунта.

Вернуться к входу
); } return (
{/* Honeypot — hidden from humans, visible to bots */}
setName(e.target.value)} required style={inputStyle} onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")} onBlur={(e) => (e.currentTarget.style.borderColor = "var(--border)")} placeholder="Иван Иванов" />
setEmail(e.target.value)} required style={inputStyle} onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")} onBlur={(e) => (e.currentTarget.style.borderColor = "var(--border)")} placeholder="you@example.com" />
setPassword(e.target.value)} required minLength={8} style={inputStyle} onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")} onBlur={(e) => (e.currentTarget.style.borderColor = "var(--border)")} placeholder="Минимум 8 символов" />
{siteKey &&
} {showTermsCheckbox && ( )} {emailTaken && (

Аккаунт с таким email уже зарегистрирован.

Войти {" · "} Забыли пароль?

)} {error && !emailTaken && (

{error}

)}

Уже есть аккаунт?{" "} Войти

); }