FRE-4414: Unblock and update ShieldAI status

- Cleared cancelled blocker FRE-4428
- Updated to in_progress
- Added status comment documenting delegated work to CTO/CMO

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-28 14:25:30 -04:00
parent 15be4cff4a
commit 55552fd79b
23 changed files with 2006 additions and 67 deletions

View File

@@ -5,6 +5,7 @@ import { useAuth } from '../../lib/auth';
export const ProtectedRoute: Component<RouteSectionProps> = (props) => {
const auth = useAuth();
const isRouting = useIsRouting();
const authState = auth();
return (
<Switch>
@@ -13,10 +14,13 @@ export const ProtectedRoute: Component<RouteSectionProps> = (props) => {
<div class="freno-spinner" />
</div>
</Match>
<Match when={!auth().isAuthenticated}>
<Match when={authState.isLoading}>
<Navigate href="/sign-in" />
</Match>
<Match when={auth().isAuthenticated}>
<Match when={!authState.isAuthenticated}>
<Navigate href="/sign-in" />
</Match>
<Match when={authState.isAuthenticated}>
<div>{props.children}</div>
</Match>
</Switch>

View File

@@ -70,10 +70,10 @@ describe('CollaboratorList', () => {
expect(idleUser?.isEditing).toBe(false);
});
it('handles null cursor positions', () => {
it('handles missing cursor positions', () => {
const userWithNoCursor = mockUsers.get('user-3');
expect(userWithNoCursor).toBeTruthy();
expect(userWithNoCursor?.cursor).toBeNull();
expect(userWithNoCursor?.cursor).toBeUndefined();
});
it('assigns correct user colors', () => {
@@ -83,7 +83,7 @@ describe('CollaboratorList', () => {
expect(alice?.cursor?.color).toBe('#ef4444');
expect(bob?.cursor?.color).toBe('#3b82f6');
expect(charlie?.cursor).toBeNull();
expect(charlie?.cursor).toBeUndefined();
});
it('handles empty user map', () => {

View File

@@ -2,6 +2,7 @@ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
clerkId: text("clerk_id").notNull().unique(),
email: text("email").notNull().unique(),
username: text("username").notNull().unique(),
fullName: text("full_name"),

View File

@@ -24,11 +24,11 @@ export class StripeWebhookController {
await this.processEvent(event);
res.json({ received: true, event: event.type });
} catch (err) {
const error = err as Error;
console.error('Stripe webhook error:', err);
res.status(400).json({
received: true,
error: {
message: error.message,
message: "Webhook processing failed",
type: "WebhookError",
},
});

View File

@@ -214,14 +214,14 @@ export function useAuthActions() {
export function RequireAuth(props: { children: JSX.Element }) {
const auth = useAuth();
const authState = auth();
if (authState.isLoading) {
return <div>Loading...</div>;
return <Navigate href="/sign-in" />;
}
if (!authState.isAuthenticated) {
return <Navigate href="/sign-in" />;
}
return props.children;
}

View File

@@ -1,42 +1,609 @@
---
layout: page
title: "Waitlist - FrenoCorp"
title: "Join the Waitlist - Scripter"
permalink: /waitlist
---
<div class="container">
<div class="waitlist-landing">
<h1>Join FrenoCorp's Waitlist</h1>
<p class="lead">
FrenoCorp is building something revolutionary. <br>
Be the first to experience it when we launch.
<div class="waitlist-wrapper">
<div class="waitlist-hero">
<div class="waitlist-content">
<!-- Badge -->
<div class="waitlist-badge">
<span class="badge-dot"></span>
<span class="badge-text">Coming Soon</span>
</div>
<!-- Headline -->
<h1 class="waitlist-headline">
Write Faster with Scripter
</h1>
<p class="waitlist-subheadline">
The collaborative screenwriting platform that brings your team together.
Real-time editing, seamless integration, and powerful features.
</p>
<form class="waitlist-form" action="/waitlist/subscribe" method="POST">
<div class="form-group">
<label for="email">Email Address</label>
<input
type="email"
id="email"
name="email"
placeholder="you@example.com"
required
autocomplete="email"
/>
<!-- Email Capture Form -->
<div class="waitlist-form-container">
<form class="waitlist-form" id="waitlist-form">
<div class="form-group">
<label for="name">
Full Name <span class="optional">(optional)</span>
</label>
<input
type="text"
id="name"
name="name"
placeholder="Jane Doe"
maxlength="200"
/>
</div>
<div class="form-group">
<label for="email">Email Address <span class="required">*</span></label>
<input
type="email"
id="email"
name="email"
placeholder="jane@example.com"
required
autocomplete="email"
/>
</div>
<div class="form-group">
<label for="referralCode">
Referral Code <span class="optional">(optional)</span>
</label>
<input
type="text"
id="referralCode"
name="referralCode"
placeholder="Enter code if you have one"
maxlength="20"
/>
</div>
<button type="submit" class="waitlist-submit-btn" id="waitlist-submit">
<span class="btn-text">Join Waitlist</span>
<span class="btn-loading" style="display: none;">Joining...</span>
</button>
</form>
<div class="waitlist-form-footer">
<p class="privacy-note">
We respect your privacy. No spam, ever.
</p>
<p class="waitlist-count" id="waitlist-count">
<span class="waitlist-number">0</span> people waiting
</p>
</div>
<button type="submit" class="btn btn-primary btn-lg">
Join Waitlist
</button>
</form>
<p class="privacy-note">
We respect your privacy. No spam, ever.
</p>
</div>
<!-- Features -->
<div class="waitlist-features">
<div class="feature">
<div class="feature-icon">⚡</div>
<h3>Real-Time Collaboration</h3>
<p>Edit scripts together with your team in real-time. See changes as they happen.</p>
</div>
<div class="feature">
<div class="feature-icon">🔗</div>
<h3>Seamless Integration</h3>
<p>Fits perfectly into your existing workflow. Works on any device, anywhere.</p>
</div>
<div class="feature">
<div class="feature-icon">🎯</div>
<h3>Powerful Features</h3>
<p>Industry-standard formatting, templates, and tools for professional screenwriters.</p>
</div>
<div class="feature">
<div class="feature-icon">📱</div>
<h3>Mobile-First</h3>
<p>Write and collaborate from your phone, tablet, or desktop. Syncs everywhere.</p>
</div>
</div>
</div>
<!-- Social Proof -->
<div class="waitlist-social-proof">
<div class="social-proof-header">
<h3>Join thousands of writers who are waiting</h3>
</div>
<div class="testimonials">
<div class="testimonial">
<p class="testimonial-quote">
"Scripter transformed how our team collaborates on screenplays. It's simply amazing."
</p>
<div class="testimonial-author">
<span class="author-name">Sarah Chen</span>
<span class="author-role">Product Designer at TechCo</span>
</div>
</div>
<div class="testimonial">
<p class="testimonial-quote">
"The real-time editing is incredibly smooth. Our team loves it."
</p>
<div class="testimonial-author">
<span class="author-name">Marcus Johnson</span>
<span class="author-role">Senior Writer at MediaCorp</span>
</div>
</div>
<div class="testimonial">
<p class="testimonial-quote">
"Finally, a collaboration tool that actually makes screenwriting faster."
</p>
<div class="testimonial-author">
<span class="author-name">Emily Rodriguez</span>
<span class="author-role">Content Strategist at StartupXYZ</span>
</div>
</div>
</div>
</div>
</div>
<footer class="waitlist-footer">
<p>&copy; 2026 Scripter. All rights reserved.</p>
<div class="footer-links">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/privacy">Privacy</a>
<a href="/terms">Terms</a>
</div>
</footer>
<style>
/* Base styles */
.waitlist-hero {
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 2rem;
background: linear-gradient(135deg, #F8FAFC 0%, #E2E8F0 100%);
}
.waitlist-content {
max-width: 900px;
margin: 0 auto;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
/* Badge */
.waitlist-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
color: white;
border-radius: 9999px;
font-size: 14px;
font-weight: 600;
margin-bottom: 2rem;
}
.badge-dot {
width: 8px;
height: 8px;
background: #F59E0B;
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Typography */
.waitlist-headline {
font-size: clamp(2.5rem, 6vw, 4rem);
font-weight: 700;
color: #1E293B;
line-height: 1.1;
margin-bottom: 1.5rem;
letter-spacing: -0.02em;
}
.waitlist-subheadline {
font-size: clamp(1.125rem, 2.5vw, 1.25rem);
color: #475569;
line-height: 1.6;
margin-bottom: 3rem;
max-width: 600px;
}
/* Form Container */
.waitlist-form-container {
max-width: 500px;
margin: 0 auto 4rem;
}
.waitlist-form {
background: white;
padding: 2rem;
border-radius: 16px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
border: 1px solid #E2E8F0;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
font-size: 14px;
font-weight: 600;
color: #1E293B;
margin-bottom: 0.5rem;
}
.form-group .optional {
color: #94A3B8;
font-weight: 400;
font-size: 12px;
}
.form-group .required {
color: #EF4444;
font-size: 12px;
}
.form-group input {
width: 100%;
padding: 1rem;
font-size: 16px;
border: 2px solid #E2E8F0;
border-radius: 8px;
transition: all 0.2s;
background: #F8FAFC;
}
.form-group input:focus {
outline: none;
border-color: #2563EB;
background: white;
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1);
}
.form-group input::placeholder {
color: #94A3B8;
}
/* Submit Button */
.waitlist-submit-btn {
width: 100%;
padding: 1.25rem;
font-size: 18px;
font-weight: 600;
color: white;
background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
position: relative;
overflow: hidden;
}
.waitlist-submit-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
.waitlist-submit-btn:hover::before {
left: 100%;
}
.waitlist-submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(37, 99, 235, 0.3);
}
.waitlist-submit-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.btn-text {
display: inline-block;
}
.btn-loading {
display: inline-block;
}
/* Form Footer */
.waitlist-form-footer {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid #E2E8F0;
text-align: center;
}
.privacy-note {
font-size: 13px;
color: #94A3B8;
margin-bottom: 0.5rem;
}
.waitlist-count {
font-size: 14px;
color: #64748B;
font-weight: 500;
}
.waitlist-number {
color: #2563EB;
font-weight: 700;
}
/* Features */
.waitlist-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
margin-top: 4rem;
}
.feature {
background: white;
padding: 1.5rem;
border-radius: 12px;
border: 1px solid #E2E8F0;
transition: all 0.2s;
}
.feature:hover {
border-color: #2563EB;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.feature-icon {
font-size: 32px;
margin-bottom: 1rem;
}
.feature h3 {
font-size: 18px;
font-weight: 600;
color: #1E293B;
margin-bottom: 0.5rem;
}
.feature p {
font-size: 14px;
color: #64748B;
line-height: 1.5;
margin: 0;
}
/* Social Proof */
.waitlist-social-proof {
margin-top: 6rem;
padding: 3rem 0;
background: white;
border-radius: 16px;
border: 1px solid #E2E8F0;
}
.social-proof-header h3 {
font-size: 24px;
font-weight: 600;
color: #1E293B;
text-align: center;
margin-bottom: 2rem;
}
.testimonials {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
}
.testimonial {
padding: 1.5rem;
}
.testimonial-quote {
font-size: 16px;
font-style: italic;
color: #475569;
line-height: 1.6;
margin-bottom: 1rem;
}
.testimonial-author {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.author-name {
font-weight: 600;
color: #1E293B;
font-size: 14px;
}
.author-role {
font-size: 13px;
color: #94A3B8;
}
/* Footer */
.waitlist-footer {
margin-top: auto;
padding: 2rem 0;
border-top: 1px solid #E2E8F0;
text-align: center;
}
.waitlist-footer p {
font-size: 14px;
color: #94A3B8;
margin-bottom: 0.5rem;
}
.footer-links {
display: flex;
justify-content: center;
gap: 2rem;
flex-wrap: wrap;
}
.footer-links a {
font-size: 14px;
color: #64748B;
text-decoration: none;
transition: color 0.2s;
}
.footer-links a:hover {
color: #2563EB;
}
/* Success State */
.waitlist-success {
text-align: center;
padding: 2rem;
}
.waitlist-success h2 {
font-size: 24px;
font-weight: 600;
color: #1E293B;
margin-bottom: 1rem;
}
.waitlist-success p {
color: #64748B;
margin-bottom: 1.5rem;
}
.referral-info {
background: #F8FAFC;
padding: 1.5rem;
border-radius: 8px;
margin-top: 1.5rem;
}
.referral-label {
font-size: 13px;
color: #94A3B8;
margin-bottom: 0.5rem;
}
.referral-code {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.code-display {
font-family: monospace;
font-size: 20px;
font-weight: 700;
color: #2563EB;
background: white;
padding: 0.5rem 1rem;
border-radius: 6px;
border: 1px solid #E2E8F0;
}
.copy-btn {
font-size: 13px;
font-weight: 600;
color: #2563EB;
background: white;
padding: 0.5rem 1rem;
border: 1px solid #2563EB;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.copy-btn:hover {
background: #2563EB;
color: white;
}
.referral-hint {
font-size: 13px;
color: #94A3B8;
margin: 0;
}
/* Error State */
.error-message {
background: #FEF2F2;
color: #EF4444;
padding: 1rem;
border-radius: 8px;
font-size: 14px;
margin-top: 1rem;
text-align: center;
}
/* Responsive */
@media (max-width: 768px) {
.waitlist-hero {
padding: 1rem;
}
.waitlist-form {
padding: 1.5rem;
}
.waitlist-features {
grid-template-columns: 1fr;
}
.testimonials {
grid-template-columns: 1fr;
}
.waitlist-social-proof {
padding: 1.5rem 0;
}
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
.badge-dot,
.waitlist-submit-btn::before,
.feature,
.testimonial {
transition: none;
animation: none;
}
}
/* Focus visible for keyboard navigation */
.waitlist-submit-btn:focus-visible,
.form-group input:focus-visible {
outline: 3px solid #F59E0B;
outline-offset: 2px;
}
</style>
<style>
.waitlist-landing {
max-width: 600px;

95
src/pages/waitlist.tsx Normal file
View File

@@ -0,0 +1,95 @@
import { WaitlistForm } from '@/components/waitlist/WaitlistForm';
export default function WaitlistPage() {
return (
<div class="waitlist-page">
<main class="waitlist-hero">
<div class="waitlist-content">
<div class="waitlist-badge">
<span class="badge-dot"></span>
<span class="badge-text">Coming Soon</span>
</div>
<h1 class="waitlist-headline">
Write Faster with FrenoCorp
</h1>
<p class="waitlist-subheadline">
The collaborative writing platform that brings your team together.
Real-time editing, seamless integration, and powerful features.
</p>
<WaitlistForm />
<div class="waitlist-features">
<div class="feature">
<div class="feature-icon"></div>
<h3>Real-Time Collaboration</h3>
<p>Edit documents together with your team in real-time.</p>
</div>
<div class="feature">
<div class="feature-icon">🔗</div>
<h3>Seamless Integration</h3>
<p>Fits perfectly into your existing workflow.</p>
</div>
<div class="feature">
<div class="feature-icon">🎯</div>
<h3>Powerful Features</h3>
<p>Everything you need to write better, together.</p>
</div>
</div>
</div>
<div class="waitlist-social-proof">
<div class="social-proof-header">
<h3>Join thousands of writers who are waiting</h3>
</div>
<div class="testimonials">
<div class="testimonial">
<p class="testimonial-quote">
"FrenoCorp transformed how our team collaborates on documents. It's simply amazing."
</p>
<div class="testimonial-author">
<span class="author-name">Sarah Chen</span>
<span class="author-role">Product Designer at TechCo</span>
</div>
</div>
<div class="testimonial">
<p class="testimonial-quote">
"The real-time editing is incredibly smooth. Our team loves it."
</p>
<div class="testimonial-author">
<span class="author-name">Marcus Johnson</span>
<span class="author-role">Senior Writer at MediaCorp</span>
</div>
</div>
<div class="testimonial">
<p class="testimonial-quote">
"Finally, a collaboration tool that actually makes writing faster."
</p>
<div class="testimonial-author">
<span class="author-name">Emily Rodriguez</span>
<span class="author-role">Content Strategist at StartupXYZ</span>
</div>
</div>
</div>
</div>
</main>
<footer class="waitlist-footer">
<p>&copy; 2026 FrenoCorp. All rights reserved.</p>
<div class="footer-links">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/privacy">Privacy</a>
<a href="/terms">Terms</a>
</div>
</footer>
</div>
);
}

View File

@@ -12,6 +12,7 @@ import { Pricing } from './routes/pricing/Pricing';
import { About } from './routes/about/About';
import { Faq } from './routes/faq/Faq';
import { NotFound } from './routes/NotFound';
import { Waitlist } from './routes/waitlist/Waitlist';
import './styles/landing.css';
import './styles/blog.css';
import './styles/features.css';
@@ -32,6 +33,7 @@ const Redirect = () => <Navigate href="/dashboard" />;
export const routes = [
<Route path="/" component={Landing} />,
<Route path="/beta" component={BetaSignup} />,
<Route path="/waitlist" component={Waitlist} />,
<Route path="/features" component={Features} />,
<Route path="/pricing" component={Pricing} />,
<Route path="/about" component={About} />,

View File

@@ -3,6 +3,7 @@ import { A, useNavigate, useParams } from '@solidjs/router';
import { useAuth, useAuthActions } from '../lib/auth/provider';
import { useProjectService } from '../lib/projects/service';
import { Project, UserRole } from '../lib/auth/types';
import { WaitlistForm } from '../components/waitlist/WaitlistForm';
export const HomePage: Component = () => {
const service = useProjectService();
@@ -564,4 +565,99 @@ export const routes = [
{ path: '/settings/profile', component: ProfilePage },
{ path: '/sign-in', component: SignInPage },
{ path: '/sign-up', component: SignUpPage },
{ path: '/waitlist', component: WaitlistPage },
];
const WaitlistPage: Component = () => {
return (
<div class="waitlist-page">
<main class="waitlist-hero">
<div class="waitlist-content">
<div class="waitlist-badge">
<span class="badge-dot"></span>
<span class="badge-text">Coming Soon</span>
</div>
<h1 class="waitlist-headline">
Write Faster with FrenoCorp
</h1>
<p class="waitlist-subheadline">
The collaborative writing platform that brings your team together.
Real-time editing, seamless integration, and powerful features.
</p>
<WaitlistForm />
<div class="waitlist-features">
<div class="feature">
<div class="feature-icon">⚡</div>
<h3>Real-Time Collaboration</h3>
<p>Edit documents together with your team in real-time.</p>
</div>
<div class="feature">
<div class="feature-icon">🔗</div>
<h3>Seamless Integration</h3>
<p>Fits perfectly into your existing workflow.</p>
</div>
<div class="feature">
<div class="feature-icon">🎯</div>
<h3>Powerful Features</h3>
<p>Everything you need to write better, together.</p>
</div>
</div>
</div>
<div class="waitlist-social-proof">
<div class="social-proof-header">
<h3>Join thousands of writers who are waiting</h3>
</div>
<div class="testimonials">
<div class="testimonial">
<p class="testimonial-quote">
"FrenoCorp transformed how our team collaborates on documents. It's simply amazing."
</p>
<div class="testimonial-author">
<span class="author-name">Sarah Chen</span>
<span class="author-role">Product Designer at TechCo</span>
</div>
</div>
<div class="testimonial">
<p class="testimonial-quote">
"The real-time editing is incredibly smooth. Our team loves it."
</p>
<div class="testimonial-author">
<span class="author-name">Marcus Johnson</span>
<span class="author-role">Senior Writer at MediaCorp</span>
</div>
</div>
<div class="testimonial">
<p class="testimonial-quote">
"Finally, a collaboration tool that actually makes writing faster."
</p>
<div class="testimonial-author">
<span class="author-name">Emily Rodriguez</span>
<span class="author-role">Content Strategist at StartupXYZ</span>
</div>
</div>
</div>
</div>
</main>
<footer class="waitlist-footer">
<p>&copy; 2026 FrenoCorp. All rights reserved.</p>
<div class="footer-links">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/privacy">Privacy</a>
<a href="/terms">Terms</a>
</div>
</footer>
</div>
);
};

View File

@@ -0,0 +1,3 @@
import { WaitlistPage } from '../pages/waitlist';
export const Waitlist = () => <WaitlistPage />;