import { useState, useCallback, useEffect } from 'react'; import { useRequiredEndpointsRuntime } from '../contexts/endpoints-runtime-context'; import { useRouter } from '../embed-shims/next-navigation'; import { apiErrorMessage } from '../utils/common'; import { contentFetch } from '../utils/embed-content-fetch'; import { useToast } from './use-toast'; interface ContactSubmissionOptions { userId?: string; successRedirectUrl?: string; successToastMessage?: string; onSuccess?: () => void; } /** * Mirrors the API contract at POST /api/contact (see `ContactBaseSchema` in * the consuming app). Keep optional fields aligned with the Zod schema — * anything missing here OR there will be silently dropped on submission. * * Tracking fields (rdt_cid, utm_*, referrer_url) are forwarded as-is and * persisted by the API; they don't need to be declared on the form, but * the index signature below preserves any extra keys callers spread in. */ interface ContactFormData { name: string; email: string; helpCategory: string; message: string; linkedin_url?: string; companySize?: string; referralSource?: string; rdt_cid?: string; // Permissive index signature — tracking metadata, A/B variants, and // future per-form fields flow through unchanged. The API decides what // to keep via its Zod schema. [key: string]: unknown; } /** * useContactSubmission * -------------------- * Provides a helper for submitting contact form data to `/api/contact`. * Handles loading state, success detection, toast notifications, and redirect. * Follows the same pattern as useWaitlistRegistration for consistency. */ export function useContactSubmission(options: ContactSubmissionOptions = {}) { const { userId, successRedirectUrl, successToastMessage, onSuccess } = options; const { toast } = useToast(); const router = useRouter(); // Endpoint URL injected via context — hub provides hub default, embedded // app provides its proxied path. Throws if no provider is mounted. const { contactUrl } = useRequiredEndpointsRuntime(); const [isSubmitting, setIsSubmitting] = useState(false); const [isSuccess, setIsSuccess] = useState(false); const submit = useCallback( async (formData: ContactFormData) => { if (isSubmitting) return; setIsSubmitting(true); try { const response = await contentFetch(contactUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...formData, user_id: userId, }), }); // Read the body ONLY on failure — nothing here consumes the success // payload, and parsing it meant a 2xx with an empty (or non-JSON) // body threw and toasted a failure for a message that was delivered. if (!response.ok) { const failure: unknown = await response.json().catch(() => null); throw new Error(apiErrorMessage(failure, 'Failed to submit contact form')); } // Success setIsSuccess(true); const message = successToastMessage ? `Thank you! Your message has been sent. ${successToastMessage}` : 'Thank you! Your message has been sent successfully.'; toast({ title: 'Message sent!', description: message, variant: 'success', }); } catch (error) { const message = error instanceof Error ? error.message : 'Something went wrong. Please try again.'; toast({ title: 'Failed to send message', description: message, variant: 'destructive', }); throw error; // allow caller to handle if needed } finally { setIsSubmitting(false); } }, [isSubmitting, toast, userId, successToastMessage, contactUrl], ); // Handle redirect after success useEffect(() => { if (!isSuccess || !successRedirectUrl) return undefined; console.log('🚀 Contact submission successful, redirecting to:', successRedirectUrl); const timer = setTimeout(() => { console.log('🎯 Performing redirect now to:', successRedirectUrl); if (successRedirectUrl.startsWith('http')) { window.location.href = successRedirectUrl; } else { router.push(successRedirectUrl); } }, 1500); return () => clearTimeout(timer); }, [isSuccess, successRedirectUrl, router]); // Call onSuccess callback if provided useEffect(() => { if (isSuccess && onSuccess) { onSuccess(); } }, [isSuccess, onSuccess]); return { submit, isSubmitting, isSuccess, setIsSuccess } as const; }