"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { Loader2 } from "lucide-react"; import { useTranslations } from "next-intl"; import Image from "next/image"; import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; import { v4 } from "uuid"; import { z } from "zod"; import { getPublicApiUrl } from "../../../../client/config"; import { errorToast, FormInput, FormPassword } from "../../../../components"; import { getRegistrationMode, isDiscordAuthEnabled, isGoogleAuthEnabled } from "../../../../login/config"; import { Button, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Form, Link, } from "../../../../shadcnui"; import { WaitlistService } from "../../../waitlist/data/WaitlistService"; import { useAuthContext } from "../../contexts"; import { AuthService } from "../../data/auth.service"; import { AuthComponent } from "../../enums"; import { GdprConsentSection } from "../GdprConsentSection"; // Referral cookie utilities const REFERRAL_COOKIE_NAME = "referral_code"; function getReferralCode(): string | null { if (typeof document === "undefined") return null; const cookies = document.cookie.split("; "); for (const cookie of cookies) { const [name, value] = cookie.split("="); if (name === REFERRAL_COOKIE_NAME && value) { return decodeURIComponent(value); } } return null; } function clearReferralCode(): void { if (typeof document === "undefined") return; document.cookie = `${REFERRAL_COOKIE_NAME}=; path=/; max-age=0; SameSite=Lax`; } // Helper function to build OAuth URLs with both invite and referral codes function buildOAuthQueryParams(inviteCode?: string | null, referralCode?: string | null): string { const params = new URLSearchParams(); if (inviteCode) params.set("invite", inviteCode); if (referralCode) params.set("referral", referralCode); return params.toString() ? `?${params.toString()}` : ""; } export default function Register() { const t = useTranslations(); const { setComponentType } = useAuthContext(); const searchParams = useSearchParams(); const inviteCode = searchParams.get("invite"); const registrationMode = getRegistrationMode(); const [showConfirmation, setShowConfirmation] = useState(false); const [inviteValidated, setInviteValidated] = useState(false); const [inviteError, setInviteError] = useState(""); // Initialize loading state to true if we have an invite code in waitlist mode const [isValidatingInvite, setIsValidatingInvite] = useState( registrationMode === "waitlist" && !!inviteCode, ); // Store referral code from cookie on mount (must use useState to trigger re-render for OAuth links) const [referralCode, setReferralCode] = useState(null); useEffect(() => { const code = getReferralCode(); setReferralCode(code); }, []); const formSchema = z.object({ company: z.string().min(1, { message: t(`common.errors.missing_company_name`), }), name: z.string().min(1, { message: t("common.errors.missing_name"), }), email: z.string().email({ message: t(`common.errors.invalid_email`), }), password: z .string() .min(8, t(`auth.errors.password_too_short`)) .regex(/^(?=.*[0-9])(?=.*[^a-zA-Z0-9]).*$/, { message: t(`auth.errors.password_invalid_format`), }), termsAccepted: z.literal(true, { message: t("auth.gdpr.terms_required"), }), marketingConsent: z.boolean().optional(), }); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { company: "", name: "", email: "", password: "", termsAccepted: false as unknown as true, marketingConsent: false, }, }); useEffect(() => { async function validateInvite() { if (registrationMode !== "waitlist" || !inviteCode) { return; } setIsValidatingInvite(true); try { const result = await WaitlistService.validateInvite(inviteCode); if (result && result.valid) { setInviteValidated(true); form.setValue("email", result.email); } else { const errorMsg = result ? t("waitlist.invite.error_expired") : t("waitlist.invite.error_invalid"); setInviteError(errorMsg); } } catch (_error) { setInviteError(t("waitlist.invite.error_validation_failed")); } finally { setIsValidatingInvite(false); } } validateInvite(); }, [registrationMode, inviteCode, form, t]); const onSubmit: SubmitHandler> = async (values: z.infer) => { try { const payload = { id: v4(), companyName: values.company, name: values.name, email: values.email, password: values.password, termsAcceptedAt: new Date().toISOString(), marketingConsent: values.marketingConsent ?? false, marketingConsentAt: values.marketingConsent ? new Date().toISOString() : null, inviteCode: inviteCode ?? undefined, referralCode: referralCode ?? undefined, }; await AuthService.register(payload); // Clear referral cookie after successful registration clearReferralCode(); setShowConfirmation(true); } catch (e) { errorToast({ error: e }); } }; // Show loading state while validating invite if (registrationMode === "waitlist" && inviteCode && isValidatingInvite) { return ( <> Logo {t("waitlist.invite.validating_title")}

{t("waitlist.invite.validating_description")}

); } // Show error if invite validation failed if (registrationMode === "waitlist" && inviteCode && inviteError) { return ( <> Logo {t("waitlist.invite.invalid_title")}

{inviteError}

{t("waitlist.invite.join_prompt")}

{t("waitlist.invite.join_link")}
); } // Show waitlist message if in waitlist mode without invite if (registrationMode === "waitlist" && !inviteCode) { return ( <> Logo {t("waitlist.invite.registration_title")}

{t("waitlist.invite.registration_description")}

{t("waitlist.invite.registration_hint")}

{t("waitlist.invite.join_link")}
setComponentType(AuthComponent.Login)}> {t("auth.buttons.login")} ); } return ( <> Logo {t(`auth.buttons.register`)} {showConfirmation ? <> : <>{t(`auth.register_description`)}} {showConfirmation ? ( {t("auth.register_confirmation")} ) : (
{/* OAuth options - shown unless in waitlist mode without valid invite */} {(registrationMode !== "waitlist" || inviteValidated) && (isGoogleAuthEnabled() || isDiscordAuthEnabled()) && (
{t("auth.buttons.or")}
{isGoogleAuthEnabled() && ( )} {isDiscordAuthEnabled() && ( )}
)}
setComponentType(AuthComponent.Login)} > {t(`auth.buttons.login`)} setComponentType(AuthComponent.ForgotPassword)} > {t(`auth.buttons.forgot_password`)}
)} ); }