Files
chorus-services/modules/teaser/components/EarlyAccessForm.tsx
tony c8fb816775 feat: Add CHORUS teaser website with mobile-responsive design
- 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>
2025-08-26 13:57:30 +10:00

276 lines
9.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState, FormEvent } from 'react'
import { EarlyAccessLead, useEarlyAccessCapture } from '../hooks/useEarlyAccessCapture'
import { LeadSourceType } from '../hooks/useEarlyAccessCapture'
interface EarlyAccessFormProps {
isOpen: boolean
onClose: () => void
leadSource: LeadSourceType
}
export default function EarlyAccessForm({ isOpen, onClose, leadSource }: EarlyAccessFormProps) {
const { submitEarlyAccess, isSubmitting, submitStatus, errorMessage } = useEarlyAccessCapture()
const [formData, setFormData] = useState<EarlyAccessLead>({
firstName: '',
lastName: '',
email: '',
companyName: '',
companyRole: '',
interestLevel: 'general_interest',
leadSource: leadSource,
gdprConsent: false,
marketingConsent: false,
})
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
if (!formData.gdprConsent) {
alert('Please accept the privacy policy to continue.')
return
}
const result = await submitEarlyAccess(formData)
if (result.success) {
// Reset form on success
setFormData({
firstName: '',
lastName: '',
email: '',
companyName: '',
companyRole: '',
interestLevel: 'general_interest',
leadSource: leadSource,
gdprConsent: false,
marketingConsent: false,
})
}
}
const handleInputChange = (field: keyof EarlyAccessLead, value: string | boolean) => {
setFormData(prev => ({
...prev,
[field]: value
}))
}
if (!isOpen) return null
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm overflow-y-auto"
onClick={onClose}
>
<div
className="relative w-full max-w-md bg-gradient-to-b from-mulberry-900 to-carbon-900 text-white p-4 sm:p-chorus-xl rounded-lg border border-mulberry-700/50 shadow-2xl my-8 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
{/* Close Button */}
<button
onClick={onClose}
className="absolute top-4 right-4 text-mulberry-300 hover:text-white text-2xl font-light transition-colors duration-200"
>
×
</button>
{/* Form Header */}
<h3 className="text-h3 font-semibold text-white mb-chorus-sm">
{leadSource === 'request_early_access'
? 'Request Early Access to CHORUS'
: 'Join the CHORUS Waitlist'}
</h3>
<p className="text-sm text-mulberry-200 font-light mb-4 sm:mb-chorus-xl">
{leadSource === 'request_early_access'
? 'Get priority access to contextual AI orchestration'
: 'Be notified when CHORUS becomes available'}
</p>
{/* Success State */}
{submitStatus === 'success' ? (
<div className="text-center py-chorus-xl">
<div className="text-5xl text-eucalyptus-400 mb-chorus-lg animate-bounce">
</div>
<h4 className="text-lg font-semibold text-eucalyptus-300 mb-chorus-sm">
{leadSource === 'request_early_access'
? 'Request submitted successfully!'
: 'Welcome to the waitlist!'}
</h4>
<p className="text-sm text-mulberry-200 font-light">
{leadSource === 'request_early_access'
? 'We\'ll prioritize your request and contact you soon.'
: 'We\'ll notify you when CHORUS becomes available.'}
</p>
</div>
) : (
/* Form */
<form onSubmit={handleSubmit}>
{/* Name Fields */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-chorus-sm sm:gap-chorus-md mb-chorus-md">
<div>
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
First Name *
</label>
<input
type="text"
required
value={formData.firstName}
onChange={(e) => handleInputChange('firstName', e.target.value)}
className="form-input"
placeholder="John"
/>
</div>
<div>
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Last Name *
</label>
<input
type="text"
required
value={formData.lastName}
onChange={(e) => handleInputChange('lastName', e.target.value)}
className="form-input"
placeholder="Doe"
/>
</div>
</div>
{/* Email */}
<div className="mb-chorus-md">
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Email Address *
</label>
<input
type="email"
required
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
className="form-input"
placeholder="john@company.com"
/>
</div>
{/* Company Information */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-chorus-sm sm:gap-chorus-md mb-chorus-md">
<div>
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Company
</label>
<input
type="text"
value={formData.companyName || ''}
onChange={(e) => handleInputChange('companyName', e.target.value)}
className="form-input"
placeholder="Company Name"
/>
</div>
<div>
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Role
</label>
<input
type="text"
value={formData.companyRole || ''}
onChange={(e) => handleInputChange('companyRole', e.target.value)}
className="form-input"
placeholder="CTO, Director, etc."
/>
</div>
</div>
{/* Interest Level */}
<div className="mb-4 sm:mb-chorus-lg">
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Primary Interest
</label>
<select
value={formData.interestLevel}
onChange={(e) => handleInputChange('interestLevel', e.target.value as any)}
className="form-input"
>
<option value="general_interest">General Interest</option>
<option value="technical_evaluation">Technical Evaluation</option>
<option value="strategic_demo">Strategic Demo</option>
</select>
</div>
{/* GDPR Consent */}
<div style={{ marginBottom: '1.5rem', fontSize: '0.85rem' }}>
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.5rem', marginBottom: '0.75rem' }}>
<input
type="checkbox"
checked={formData.gdprConsent}
onChange={(e) => handleInputChange('gdprConsent', e.target.checked)}
required
/>
<span>
I agree to the privacy policy and consent to processing my personal data for early access communications. *
</span>
</label>
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.5rem' }}>
<input
type="checkbox"
checked={formData.marketingConsent}
onChange={(e) => handleInputChange('marketingConsent', e.target.checked)}
/>
<span>
I would like to receive updates about CHORUS Services and related products.
</span>
</label>
</div>
{/* Error Message */}
{submitStatus === 'error' && errorMessage && (
<div style={{
padding: '0.75rem',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
border: '1px solid #dc2626',
color: '#fca5a5',
fontSize: '0.85rem',
marginBottom: '1rem'
}}>
{errorMessage}
</div>
)}
{/* Submit Button */}
<button
type="submit"
disabled={isSubmitting || !formData.gdprConsent}
className="btn-primary"
style={{
width: '100%',
opacity: (isSubmitting || !formData.gdprConsent) ? 0.5 : 1,
cursor: (isSubmitting || !formData.gdprConsent) ? 'not-allowed' : 'pointer'
}}
>
{isSubmitting
? (leadSource === 'request_early_access' ? 'Submitting Request...' : 'Joining Waitlist...')
: (leadSource === 'request_early_access' ? 'Submit Request' : 'Join Waitlist')
}
</button>
</form>
)}
{/* Footer */}
<p style={{
fontSize: '0.75rem',
opacity: 0.6,
textAlign: 'center',
marginTop: '1.5rem',
paddingTop: '1.5rem',
borderTop: '1px solid #444'
}}>
By joining our waitlist, you'll receive exclusive early access and product updates.
We respect your privacy and won't spam you.
</p>
</div>
</div>
)
}