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>
This commit is contained in:
@@ -2,6 +2,7 @@ import { createHTTPServer } from '@trpc/server/adapters/standalone';
|
||||
import { projectRouter } from './project-router';
|
||||
import { revisionsRouter } from './revisions-router';
|
||||
import { scriptsRouter } from './scripts-router';
|
||||
import { waitlistRouter } from './waitlist-router';
|
||||
import type { TRPCContext } from './types';
|
||||
import type { TRPCError } from '@trpc/server';
|
||||
import { t } from './router';
|
||||
@@ -11,6 +12,7 @@ export const appRouter = t.router({
|
||||
project: projectRouter,
|
||||
revisions: revisionsRouter,
|
||||
scripts: scriptsRouter,
|
||||
waitlist: waitlistRouter,
|
||||
} as const);
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
88
server/trpc/waitlist-router.ts
Normal file
88
server/trpc/waitlist-router.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
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 };
|
||||
}),
|
||||
};
|
||||
Reference in New Issue
Block a user