"use client" import { OpenFrameLogo } from '../icons' import { Button } from '../ui/button' import { CheckboxBlock } from '../ui/checkbox-block' import { Input } from '../ui/input' import { PhoneInput } from '../ui/phone-input' import { HoneypotField } from '../ui/honeypot-field' import { useHumanitySignals } from '../../hooks/use-humanity-signals' import type { HumanitySignals } from '../../utils/humanity-signals' import { useToast } from '../../hooks/use-toast' import { cn } from '../../utils/cn' import { hasGenericEmailDomain } from '../../utils/generic-domain-utils' import { formatPhoneE164 } from '../../utils/country-phone-utils' import { getCountries } from 'libphonenumber-js' import type { CountryCode } from 'libphonenumber-js' import { useEffect, useState } from 'react' export interface WaitlistFormProps { /** Optional ID for the form container (for anchor links) */ id?: string /** Optional CSS classes for the container */ className?: string /** * Registration handler — called with email, optional E.164 phone, and the * invisible bot-protection signals (honeypot + timing) to forward into the * POST body. Must throw on failure (toast is handled by the form). `signals` * is optional for backward compatibility with older callers. */ onRegister: (email: string, phone?: string, signals?: HumanitySignals) => Promise /** Whether a registration request is currently in flight */ isSubmitting?: boolean /** Whether registration completed successfully */ isSuccess?: boolean /** Pre-filled email (e.g. from auth context) */ defaultEmail?: string /** Pre-filled phone (e.g. from user profile) */ defaultPhone?: string /** Geo-detection API endpoint. Defaults to "/api/geo". Set to null to disable. */ geoApiUrl?: string | null /** Label on the submit button. Defaults to "Get Beta Access" */ submitLabel?: string /** Label shown after success. Defaults to "You're in!" */ successLabel?: string /** Label shown on the SMS consent checkbox */ smsCheckboxLabel?: string /** Warning shown when a generic email domain is detected */ genericEmailHint?: string /** Warning shown when phone validation fails */ invalidPhoneHint?: string /** URL for the Terms of Service link in the consent text */ termsOfServiceUrl?: string /** URL for the Privacy Policy link in the consent text */ privacyPolicyUrl?: string /** SMS consent text shown below the checkbox label */ consentText?: string } /** * WaitlistForm * * Platform-agnostic waitlist registration form. * All app-specific logic (auth, API calls, platform detection) is injected via props. * * Features: * - Email + optional phone with country code selector * - Generic email domain warning * - Phone validation warning * - Auto geo-detection for country code * - Enter key support * - Loading and success states * - Hydration-safe skeleton */ export function WaitlistForm({ id = "waitlist-form", className, onRegister, isSubmitting = false, isSuccess = false, defaultEmail = '', defaultPhone = '', geoApiUrl = '/api/geo', submitLabel = 'Get Beta Access', successLabel = "You're in!", smsCheckboxLabel = "Send me an SMS if my email gets caught by spam filters", genericEmailHint = "Use a work email \u2014 personal emails may not be verified or approved.", invalidPhoneHint = "Invalid phone number format.", termsOfServiceUrl, privacyPolicyUrl, consentText = "I agree to receive recurring automated text messages at the phone number provided. Msg & data rates may apply. Msg frequency varies. Reply HELP for help and STOP to cancel.", }: WaitlistFormProps) { const [email, setEmail] = useState(defaultEmail) const [phone, setPhone] = useState(defaultPhone) const [countryCode, setCountryCode] = useState('US') const { toast } = useToast() const { honeypotInputProps, getSignals, resetSignals } = useHumanitySignals() const [smsConsent, setSmsConsent] = useState(false) const [isClient, setIsClient] = useState(false) const [isPhoneInvalid, setIsPhoneInvalid] = useState(false) const [showConsentError, setShowConsentError] = useState(false) const isMailDomainGeneric = hasGenericEmailDomain(email) // Sync defaultEmail when it changes (e.g. auth loads) useEffect(() => { if (defaultEmail) { setEmail(defaultEmail) } }, [defaultEmail]) // Client-side hydration + geo detection useEffect(() => { setIsClient(true) if (!geoApiUrl) return const supportedCountries = new Set(getCountries()) fetch(geoApiUrl) .then(res => res.json()) .then(({ country }) => { if (country && supportedCountries.has(country)) { setCountryCode(country as CountryCode) } }) .catch(() => { /* keep default US */ }) }, [geoApiUrl]) const handleSubmit = async () => { if (isSubmitting) return if (!email.trim()) { toast({ title: 'Email required', description: 'Please enter a valid email address.', variant: 'destructive' }) return } if (phone.trim() && !smsConsent) { setShowConsentError(true) return } const finalPhone = phone ? formatPhoneE164(phone, countryCode) : undefined try { await onRegister(email, finalPhone, getSignals()) resetSignals() } catch { // caller's onRegister should handle its own error toasts if needed } } if (!isClient) { return (
{/* Email input skeleton */}
{/* Phone input skeleton */}
{/* Disclaimer + button skeleton */}
) } const showEmailWarning = isMailDomainGeneric const showPhoneWarning = isPhoneInvalid const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault() handleSubmit() } } return (
{/* Invisible honeypot — real users never fill it; bots that fill every field trip it. */} {/* Email Input */} setEmail(e.target.value)} required disabled={isSubmitting} placeholder="Enter your Business Email" onKeyDown={handleKeyDown} error={showEmailWarning ? genericEmailHint : undefined} errorVariant="warning" /> {/* Phone Input */}
{ setPhone(val) if (!val.trim()) setShowConsentError(false) }} onCountryChange={setCountryCode} onValidationChange={setIsPhoneInvalid} disabled={isSubmitting} placeholder="Phone (optional)" onKeyDown={handleKeyDown} /> {showPhoneWarning && (

{invalidPhoneHint}

)}
{/* SMS Consent + Button Section */}
{/* SMS Consent Checkbox */} { setSmsConsent(checked as boolean) if (checked) setShowConsentError(false) }} error={showConsentError ? "Please agree to SMS notifications to continue." : undefined} disabled={isSubmitting} label={smsCheckboxLabel} description={ <> {consentText}{' View our '} e.stopPropagation()} > Terms of Service {' and '} e.stopPropagation()} > Privacy Policy . } /> {/* Submit Button — right-aligned */}
) }