Files
FrenoCorp/server/trpc/waitlist-router.ts
Michael Freno ec215ae426 Implement waitlist landing page FRE-656
- Add waitlist tRPC router with signup mutation and count query
- Add referral code generation and tracking
- Register waitlist router in app router
- Add useWaitlistSignup, useWaitlistCount, useReferralCount hooks
- Update landing page with email capture form, live waitlist counter, referral sharing
- Add waitlist and referral CSS styles

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-26 07:57:29 -04:00

89 lines
3.0 KiB
TypeScript

import { publicProcedure } from './router';
import { z } from 'zod';
import { eq, sql } from 'drizzle-orm';
import { waitlistSignups, waitlistEvents } from '../../src/db/schema';
function generateReferralCode(length = 8): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let code = '';
for (let i = 0; i < length; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length));
}
return code;
}
export const waitlistRouter = {
signup: publicProcedure
.input(z.object({
email: z.string().email(),
name: z.string().min(1).max(200).optional(),
source: z.string().max(100).optional().default('organic'),
referralCode: z.string().max(20).optional(),
}))
.mutation(async ({ input, ctx }) => {
const existingRows = await ctx.db!.select()
.from(waitlistSignups)
.where(eq(waitlistSignups.email, input.email.toLowerCase()));
const existing = existingRows[0];
if (existing) {
const metaStr = (existing as Record<string, unknown>).metadata as string | null;
const existingMeta = metaStr ? JSON.parse(metaStr) : {};
return { success: true, alreadyJoined: true, id: existing.id, referralCode: existingMeta.referralCode || null };
}
const metadata: Record<string, unknown> = {};
if (input.referralCode) {
metadata.referredBy = input.referralCode;
}
metadata.referralCode = generateReferralCode();
const result = await ctx.db!.insert(waitlistSignups)
.values({
email: input.email.toLowerCase(),
name: input.name ?? null,
source: input.source ?? 'organic',
metadata: JSON.stringify(metadata),
})
.returning();
const signup = result[0];
await ctx.db!.insert(waitlistEvents)
.values({
signupId: signup!.id,
eventType: 'signup',
eventData: JSON.stringify({ source: input.source, referralCode: input.referralCode }),
});
const referralCode = metadata.referralCode as string;
return { success: true, alreadyJoined: false, id: signup!.id, referralCode };
}),
getCount: publicProcedure
.query(async ({ ctx }) => {
const result = await ctx.db!.select({ count: sql<number>`count(*)` })
.from(waitlistSignups)
.where(eq(waitlistSignups.status, 'waitlist'));
return { count: Number(result[0]!.count) };
}),
getReferralCount: publicProcedure
.input(z.object({ referralCode: z.string().min(1).max(20) }))
.query(async ({ input, ctx }) => {
const rows = await ctx.db!.select({ id: waitlistSignups.id })
.from(waitlistSignups)
.where(eq(waitlistSignups.status, 'waitlist'));
let count = 0;
for (const row of rows) {
const metaStr = (row as Record<string, unknown>).metadata as string | null;
const meta = metaStr ? JSON.parse(metaStr) : {};
if (meta.referredBy === input.referralCode) {
count++;
}
}
return { count };
}),
};