Files
FrenoCorp/src/routes/beta/BetaSignup.tsx
Michael Freno bef1d7f829 FRE-750: Break infinite recovery cascade, reassign FRE-620 to Founding Engineer
- Cancelled 700+ runaway recovery issues (FRE-767 through FRE-2000+)
- Reassigned FRE-620 (analytics setup) from error-state Senior Engineer to available Founding Engineer
- Removed blocker chain that was preventing FRE-620 from progressing
- Documented system bug: recovery system creates recovery issues for cancelled recovery issues

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-27 01:22:34 -04:00

434 lines
16 KiB
TypeScript

import { Component, createSignal } from 'solid-js';
import { A } from '@solidjs/router';
import { useBetaSignup } from '../../lib/api/trpc-hooks';
import '../../styles/beta-signup.css';
export const BetaSignup: Component = () => {
const [formData, setFormData] = createSignal({
name: '',
email: '',
primaryRole: '',
scriptsWritten: '',
currentSoftware: '',
softwareLove: '',
softwareFrustrate: '',
hoursPerWeek: '',
willingFeedback: '',
joinDiscord: '',
discordUsername: '',
excitedFeatures: [] as string[],
heardAbout: '',
additionalInfo: '',
utmSource: '',
utmMedium: '',
utmCampaign: '',
utmContent: '',
utmTerm: '',
});
const [submitted, setSubmitted] = createSignal(false);
const [error, setError] = createSignal('');
const [isSubmitting, setIsSubmitting] = createSignal(false);
const betaSignup = useBetaSignup();
const captureUTMParams = () => {
if (typeof window === 'undefined') return {
utmSource: '',
utmMedium: '',
utmCampaign: '',
utmContent: '',
utmTerm: '',
};
const params = new URLSearchParams(window.location.search);
return {
utmSource: params.get('utm_source') || '',
utmMedium: params.get('utm_medium') || '',
utmCampaign: params.get('utm_campaign') || '',
utmContent: params.get('utm_content') || '',
utmTerm: params.get('utm_term') || '',
};
};
const utmParams = captureUTMParams();
const updateField = (field: string, value: any) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const toggleFeature = (feature: string) => {
setFormData((prev) => {
const features = prev.excitedFeatures.includes(feature)
? prev.excitedFeatures.filter((f) => f !== feature)
: [...prev.excitedFeatures, feature];
return { ...prev, excitedFeatures: features };
});
};
const handleSubmit = async (e: Event) => {
e.preventDefault();
setError('');
const data = formData();
if (!data.name.trim() || !data.email.trim()) {
setError('Name and email are required.');
return;
}
if (!data.primaryRole) {
setError('Please select your primary role.');
return;
}
if (!data.willingFeedback || data.willingFeedback === 'No, just want early access') {
setError('Beta access requires willingness to provide weekly feedback.');
return;
}
setIsSubmitting(true);
try {
await betaSignup.mutateAsync({
name: data.name.trim(),
email: data.email.trim(),
primaryRole: data.primaryRole,
scriptsWritten: data.scriptsWritten,
currentSoftware: data.currentSoftware,
softwareLove: data.softwareLove,
softwareFrustrate: data.softwareFrustrate,
hoursPerWeek: data.hoursPerWeek,
willingFeedback: data.willingFeedback,
joinDiscord: data.joinDiscord,
discordUsername: data.discordUsername,
excitedFeatures: data.excitedFeatures,
heardAbout: data.heardAbout,
additionalInfo: data.additionalInfo,
utmSource: utmParams.utmSource,
utmMedium: utmParams.utmMedium,
utmCampaign: utmParams.utmCampaign,
utmContent: utmParams.utmContent,
utmTerm: utmParams.utmTerm,
});
setSubmitted(true);
} catch (err: any) {
setError(err?.message || 'Something went wrong. Please try again.');
} finally {
setIsSubmitting(false);
}
};
return (
<div class="beta-signup-page">
<nav class="landing-nav">
<div class="nav-container">
<div class="nav-logo">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M16 2L4 8V24L16 30L28 24V8L16 2Z" fill="#518ac8"/>
<path d="M16 6L8 10V22L16 26L24 22V10L16 6Z" fill="#76b3e1"/>
</svg>
<span class="logo-text">Scripter</span>
</div>
<div class="nav-links">
<a href="/">Home</a>
<a href="/features">Features</a>
<a href="/pricing">Pricing</a>
</div>
</div>
</nav>
<div class="beta-hero">
<h1>Join the Scripter Beta</h1>
<p>Help us build the future of screenwriting. We're looking for 500 active writers to test Scripter before our public launch.</p>
<div class="beta-badges">
<span class="beta-badge">🎬 3-week beta program</span>
<span class="beta-badge">📝 Weekly feedback (5 min)</span>
<span class="beta-badge">💬 Discord community</span>
</div>
</div>
{submitted() ? (
<div class="beta-success">
<div class="success-icon">🎉</div>
<h2>Application Submitted!</h2>
<p>Thanks for applying to the Scripter beta. We're reviewing applications and will get back to you within 48 hours.</p>
<div class="success-next-steps">
<h3>What happens next:</h3>
<ol>
<li>We'll review your application</li>
<li>If accepted, you'll get beta access + Discord invite</li>
<li>Beta starts April 26 - get ready to write!</li>
</ol>
</div>
<div class="success-actions">
<a href="https://twitter.com/ScripterApp" target="_blank" rel="noopener noreferrer" class="btn-secondary">
Follow us on Twitter
</a>
<A href="/" class="btn-primary">Back to Home</A>
</div>
</div>
) : (
<form onSubmit={handleSubmit} class="beta-form">
{error() && <div class="form-error-banner">{error()}</div>}
<section class="form-section">
<h2>Section 1: About You</h2>
<div class="form-group">
<label for="name">What's your name? *</label>
<input
type="text"
id="name"
value={formData().name}
onInput={(e) => updateField('name', e.currentTarget.value)}
required
/>
</div>
<div class="form-group">
<label for="email">What's your email address? *</label>
<input
type="email"
id="email"
value={formData().email}
onInput={(e) => updateField('email', e.currentTarget.value)}
required
/>
</div>
<div class="form-group">
<label for="primaryRole">What's your primary role? *</label>
<select
id="primaryRole"
value={formData().primaryRole}
onChange={(e) => updateField('primaryRole', e.currentTarget.value)}
required
>
<option value="">Select your role</option>
<option value="Screenwriter (feature films)">Screenwriter (feature films)</option>
<option value="Screenwriter (TV/Streaming)">Screenwriter (TV/Streaming)</option>
<option value="Writer/Director">Writer/Director</option>
<option value="Producer">Producer</option>
<option value="Student">Student</option>
<option value="Other">Other</option>
</select>
</div>
<div class="form-group">
<label for="scriptsWritten">How many scripts have you written?</label>
<select
id="scriptsWritten"
value={formData().scriptsWritten}
onChange={(e) => updateField('scriptsWritten', e.currentTarget.value)}
>
<option value="">Select an option</option>
<option value="0-1 (just starting)">0-1 (just starting)</option>
<option value="2-5 (developing craft)">2-5 (developing craft)</option>
<option value="6-10 (working writer)">6-10 (working writer)</option>
<option value="10+ (professional)">10+ (professional)</option>
</select>
</div>
</section>
<section class="form-section">
<h2>Section 2: Current Tools</h2>
<div class="form-group">
<label for="currentSoftware">What screenwriting software do you currently use?</label>
<select
id="currentSoftware"
value={formData().currentSoftware}
onChange={(e) => updateField('currentSoftware', e.currentTarget.value)}
>
<option value="">Select software</option>
<option value="Final Draft">Final Draft</option>
<option value="WriterDuet">WriterDuet</option>
<option value="Celtx">Celtx</option>
<option value="Fade In">Fade In</option>
<option value="Arc Studio">Arc Studio</option>
<option value="Google Docs">Google Docs</option>
<option value="Microsoft Word">Microsoft Word</option>
<option value="Other">Other</option>
</select>
</div>
<div class="form-group">
<label for="softwareLove">What do you love about your current tool? *</label>
<textarea
id="softwareLove"
value={formData().softwareLove}
onInput={(e) => updateField('softwareLove', e.currentTarget.value)}
rows={3}
required
/>
</div>
<div class="form-group">
<label for="softwareFrustrate">What frustrates you about your current tool? *</label>
<textarea
id="softwareFrustrate"
value={formData().softwareFrustrate}
onInput={(e) => updateField('softwareFrustrate', e.currentTarget.value)}
rows={3}
required
/>
</div>
</section>
<section class="form-section">
<h2>Section 3: Beta Commitment</h2>
<div class="form-group">
<label for="hoursPerWeek">How many hours per week do you spend screenwriting?</label>
<select
id="hoursPerWeek"
value={formData().hoursPerWeek}
onChange={(e) => updateField('hoursPerWeek', e.currentTarget.value)}
>
<option value="">Select an option</option>
<option value="0-5 (hobbyist)">0-5 (hobbyist)</option>
<option value="5-10 (serious amateur)">5-10 (serious amateur)</option>
<option value="10-20 (working writer)">10-20 (working writer)</option>
<option value="20+ (professional)">20+ (professional)</option>
</select>
</div>
<div class="form-group">
<label for="willingFeedback">Are you willing to provide weekly feedback (5-min survey)? *</label>
<select
id="willingFeedback"
value={formData().willingFeedback}
onChange={(e) => updateField('willingFeedback', e.currentTarget.value)}
required
>
<option value="">Select an option</option>
<option value="Yes, absolutely">Yes, absolutely (required to join beta)</option>
<option value="No, just want early access">No, just want early access</option>
<option value="Maybe, depends on my schedule">Maybe, depends on my schedule</option>
</select>
</div>
<div class="form-group">
<label for="joinDiscord">Will you join our Discord community?</label>
<select
id="joinDiscord"
value={formData().joinDiscord}
onChange={(e) => updateField('joinDiscord', e.currentTarget.value)}
>
<option value="">Select an option</option>
<option value="Yes, I'll join">Yes, I'll join</option>
<option value="No, email is fine">No, email is fine</option>
<option value="Maybe">Maybe</option>
</select>
</div>
<div class="form-group">
<label for="discordUsername">Discord username (if joining)</label>
<input
type="text"
id="discordUsername"
value={formData().discordUsername}
onInput={(e) => updateField('discordUsername', e.currentTarget.value)}
placeholder="username#1234"
/>
</div>
</section>
<section class="form-section">
<h2>Section 4: Use Cases</h2>
<div class="form-group">
<label>What features are you most excited about?</label>
<div class="checkbox-group">
{['Real-time collaboration', 'AI writing assistant', 'Cloud sync across devices', 'Affordable pricing', 'Modern interface', 'Export options (PDF, FDX, etc.)'].map((feature) => (
<label class="checkbox-label">
<input
type="checkbox"
checked={formData().excitedFeatures.includes(feature)}
onChange={() => toggleFeature(feature)}
/>
{feature}
</label>
))}
</div>
</div>
<div class="form-group">
<label for="heardAbout">How did you hear about Scripter?</label>
<select
id="heardAbout"
value={formData().heardAbout}
onChange={(e) => updateField('heardAbout', e.currentTarget.value)}
>
<option value="">Select an option</option>
<option value="Product Hunt">Product Hunt</option>
<option value="Reddit">Reddit</option>
<option value="Twitter/X">Twitter/X</option>
<option value="YouTube">YouTube</option>
<option value="Friend/colleague">Friend/colleague</option>
<option value="Google search">Google search</option>
<option value="Other">Other</option>
</select>
</div>
<div class="form-group">
<label for="additionalInfo">Anything else you'd like us to know?</label>
<textarea
id="additionalInfo"
value={formData().additionalInfo}
onInput={(e) => updateField('additionalInfo', e.currentTarget.value)}
rows={3}
placeholder="Optional"
/>
</div>
</section>
<div class="form-submit">
<button type="submit" class="btn-primary btn-large" disabled={isSubmitting()}>
{isSubmitting() ? 'Submitting...' : 'Submit Application'}
</button>
<p class="form-note">
By submitting, you agree to provide weekly feedback during the 3-week beta period.
</p>
</div>
</form>
)}
<footer class="landing-footer">
<div class="footer-content">
<div class="footer-brand">
<div class="nav-logo">
<svg width="24" height="24" viewBox="0 0 32 32" fill="none">
<path d="M16 2L4 8V24L16 30L28 24V8L16 2Z" fill="#518ac8"/>
</svg>
<span>Scripter</span>
</div>
<p>Write Faster.</p>
</div>
<div class="footer-links">
<div class="footer-col">
<h4>Product</h4>
<a href="/features">Features</a>
<a href="/pricing">Pricing</a>
<a href="/blog">Blog</a>
</div>
<div class="footer-col">
<h4>Company</h4>
<a href="/about">About</a>
<a href="/faq">FAQ</a>
</div>
<div class="footer-col">
<h4>Legal</h4>
<a href="/terms">Terms</a>
<a href="/privacy">Privacy</a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2026 Scripter. All rights reserved.</p>
</div>
</footer>
</div>
);
};