- Created complete Next.js 15 teaser website with CHORUS brand styling - Implemented mobile-responsive 3D logo (128px mobile, 512px desktop) - Added proper Exo font loading via Next.js Google Fonts for iOS/Chrome compatibility - Built comprehensive early access form with GDPR compliance and rate limiting - Integrated PostgreSQL database with complete schema for lead capture - Added scroll indicators that auto-hide when scrolling begins - Optimized mobile modal forms with proper scrolling and submit button access - Deployed via Docker Swarm with Traefik SSL termination at chorus.services - Includes database migrations, consent tracking, and email notifications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
121 lines
3.2 KiB
TypeScript
121 lines
3.2 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useCallback } from 'react'
|
|
|
|
export type LeadSourceType = 'early_access_waitlist' | 'request_early_access'
|
|
|
|
export interface EarlyAccessLead {
|
|
firstName: string
|
|
lastName: string
|
|
email: string
|
|
companyName?: string
|
|
companyRole?: string
|
|
interestLevel: 'technical_evaluation' | 'strategic_demo' | 'general_interest'
|
|
leadSource: LeadSourceType
|
|
gdprConsent: boolean
|
|
marketingConsent: boolean
|
|
}
|
|
|
|
export interface EarlyAccessResponse {
|
|
success: boolean
|
|
message: string
|
|
leadId?: string
|
|
error?: string
|
|
}
|
|
|
|
export function useEarlyAccessCapture() {
|
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
|
const [currentLeadSource, setCurrentLeadSource] = useState<LeadSourceType>('early_access_waitlist')
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle')
|
|
const [errorMessage, setErrorMessage] = useState('')
|
|
|
|
const openModal = useCallback((leadSource: LeadSourceType) => {
|
|
setCurrentLeadSource(leadSource)
|
|
setIsModalOpen(true)
|
|
setSubmitStatus('idle')
|
|
setErrorMessage('')
|
|
}, [])
|
|
|
|
const closeModal = useCallback(() => {
|
|
setIsModalOpen(false)
|
|
setSubmitStatus('idle')
|
|
setErrorMessage('')
|
|
}, [])
|
|
|
|
const submitEarlyAccess = useCallback(async (leadData: EarlyAccessLead): Promise<EarlyAccessResponse> => {
|
|
setIsSubmitting(true)
|
|
setSubmitStatus('idle')
|
|
setErrorMessage('')
|
|
|
|
try {
|
|
const response = await fetch('/api/early-access', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
...leadData,
|
|
countryCode: 'AU', // Default to Australia based on business location
|
|
customMessage: `Interest Level: ${leadData.interestLevel}`,
|
|
}),
|
|
})
|
|
|
|
const data = await response.json()
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || `HTTP error! status: ${response.status}`)
|
|
}
|
|
|
|
setSubmitStatus('success')
|
|
|
|
// Auto-close modal after success
|
|
setTimeout(() => {
|
|
closeModal()
|
|
}, 2000)
|
|
|
|
return {
|
|
success: true,
|
|
message: data.message || 'Successfully joined the waitlist!',
|
|
leadId: data.leadId,
|
|
}
|
|
} catch (error) {
|
|
const errorMsg = error instanceof Error ? error.message : 'Failed to submit request'
|
|
setErrorMessage(errorMsg)
|
|
setSubmitStatus('error')
|
|
|
|
return {
|
|
success: false,
|
|
message: 'Failed to join waitlist',
|
|
error: errorMsg,
|
|
}
|
|
} finally {
|
|
setIsSubmitting(false)
|
|
}
|
|
}, [closeModal])
|
|
|
|
const handleSuccess = useCallback((leadId: string) => {
|
|
console.log('Early access lead captured:', leadId)
|
|
|
|
// Track analytics event
|
|
if (typeof window !== 'undefined' && (window as any).gtag) {
|
|
(window as any).gtag('event', 'early_access_signup', {
|
|
event_category: 'conversion',
|
|
event_label: 'early_access_waitlist',
|
|
value: 1
|
|
})
|
|
}
|
|
}, [])
|
|
|
|
return {
|
|
isModalOpen,
|
|
currentLeadSource,
|
|
isSubmitting,
|
|
submitStatus,
|
|
errorMessage,
|
|
openModal,
|
|
closeModal,
|
|
submitEarlyAccess,
|
|
handleSuccess,
|
|
}
|
|
} |