138 lines
4.1 KiB
TypeScript
138 lines
4.1 KiB
TypeScript
import { Component, createSignal, onMount } from 'solid-js';
|
|
import { trackWaitlistSignup } from '../hooks/useAnalytics';
|
|
|
|
interface WaitlistFormProps {
|
|
variant?: 'hero' | 'inline';
|
|
placeholder?: string;
|
|
buttonText?: string;
|
|
}
|
|
|
|
function getUtmParams() {
|
|
if (typeof window === 'undefined') return {};
|
|
const params = new URLSearchParams(window.location.search);
|
|
return {
|
|
utmSource: params.get('utm_source') || undefined,
|
|
utmMedium: params.get('utm_medium') || undefined,
|
|
utmCampaign: params.get('utm_campaign') || undefined,
|
|
};
|
|
}
|
|
|
|
const WaitlistForm: Component<WaitlistFormProps> = (props) => {
|
|
const [email, setEmail] = createSignal('');
|
|
const [name, setName] = createSignal('');
|
|
const [tier, setTier] = createSignal('basic');
|
|
const [utm, setUtm] = createSignal<Record<string, string | undefined>>({});
|
|
const [submitted, setSubmitted] = createSignal(false);
|
|
const [loading, setLoading] = createSignal(false);
|
|
const [error, setError] = createSignal('');
|
|
|
|
onMount(() => {
|
|
setUtm(getUtmParams());
|
|
});
|
|
|
|
const variant = props.variant || 'hero';
|
|
|
|
const handleSubmit = async (e: Event) => {
|
|
e.preventDefault();
|
|
setError('');
|
|
|
|
if (!email() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email())) {
|
|
setError('Please enter a valid email');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
|
|
try {
|
|
const res = await fetch('/api/waitlist/signup', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email: email(),
|
|
name: name() || undefined,
|
|
tier: tier() !== 'basic' ? tier() : undefined,
|
|
...utm(),
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const data = await res.json();
|
|
throw new Error(data.error || 'Signup failed');
|
|
}
|
|
|
|
trackWaitlistSignup(email(), 'landing_page', tier());
|
|
setSubmitted(true);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Something went wrong');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
if (submitted()) {
|
|
return (
|
|
<div class="waitlist-success">
|
|
<div class="success-icon">✓</div>
|
|
<h3>You're on the list!</h3>
|
|
<p>We'll keep you updated on our launch and send early access invites.</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (variant === 'hero') {
|
|
return (
|
|
<form class="waitlist-form hero-form" onSubmit={handleSubmit}>
|
|
<div class="form-row">
|
|
<input
|
|
type="email"
|
|
value={email()}
|
|
onInput={(e) => setEmail(e.currentTarget.value)}
|
|
placeholder={props.placeholder || 'Enter your email'}
|
|
required
|
|
aria-label="Email address"
|
|
/>
|
|
<button type="submit" disabled={loading()}>
|
|
{loading() ? 'Joining...' : props.buttonText || 'Join Waitlist'}
|
|
</button>
|
|
</div>
|
|
<div class="form-row secondary">
|
|
<input
|
|
type="text"
|
|
value={name()}
|
|
onInput={(e) => setName(e.currentTarget.value)}
|
|
placeholder="Your name (optional)"
|
|
aria-label="Your name"
|
|
/>
|
|
<select value={tier()} onChange={(e) => setTier(e.currentTarget.value)} aria-label="Interest level">
|
|
<option value="basic">Free — Basic Protection</option>
|
|
<option value="plus">Plus — $9.99/mo</option>
|
|
<option value="premium">Premium — $24.99/mo</option>
|
|
</select>
|
|
</div>
|
|
{error() && <p class="form-error">{error()}</p>}
|
|
</form>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<form class="waitlist-form inline-form" onSubmit={handleSubmit}>
|
|
<div class="form-row">
|
|
<input
|
|
type="email"
|
|
value={email()}
|
|
onInput={(e) => setEmail(e.currentTarget.value)}
|
|
placeholder={props.placeholder || 'Your email'}
|
|
required
|
|
aria-label="Email address"
|
|
/>
|
|
<button type="submit" disabled={loading()}>
|
|
{loading() ? '...' : props.buttonText || 'Sign Up'}
|
|
</button>
|
|
</div>
|
|
{error() && <p class="form-error">{error()}</p>}
|
|
</form>
|
|
);
|
|
};
|
|
|
|
export default WaitlistForm;
|