diff --git a/sim/app/(auth)/signup/signup-form.tsx b/sim/app/(auth)/signup/signup-form.tsx index 04551454c3..0636353d91 100644 --- a/sim/app/(auth)/signup/signup-form.tsx +++ b/sim/app/(auth)/signup/signup-form.tsx @@ -56,6 +56,7 @@ function SignupFormContent({ const [passwordErrors, setPasswordErrors] = useState([]) const [showValidationError, setShowValidationError] = useState(false) const [email, setEmail] = useState('') + const [waitlistToken, setWaitlistToken] = useState('') useEffect(() => { setMounted(true) @@ -63,8 +64,38 @@ function SignupFormContent({ if (emailParam) { setEmail(emailParam) } + + // Check for waitlist token + const tokenParam = searchParams.get('token') + if (tokenParam) { + setWaitlistToken(tokenParam) + // Verify the token and get the email + verifyWaitlistToken(tokenParam) + } }, [searchParams]) + // Verify waitlist token and pre-fill email + const verifyWaitlistToken = async (token: string) => { + try { + const response = await fetch('/api/auth/verify-waitlist-token', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token }), + }) + + const data = await response.json() + + if (data.success && data.email) { + setEmail(data.email) + } + } catch (error) { + console.error('Error verifying waitlist token:', error) + // Continue regardless of errors - we don't want to block sign up + } + } + // Validate password and return array of error messages const validatePassword = (passwordValue: string): string[] => { const errors: string[] = [] diff --git a/sim/app/admin/waitlist/stores/store.ts b/sim/app/admin/waitlist/stores/store.ts index 4b417bce81..e0a31f8593 100644 --- a/sim/app/admin/waitlist/stores/store.ts +++ b/sim/app/admin/waitlist/stores/store.ts @@ -1,7 +1,7 @@ import { create } from 'zustand' // Define types inline since types.ts was deleted -export type WaitlistStatus = 'pending' | 'approved' | 'rejected' +export type WaitlistStatus = 'pending' | 'approved' | 'rejected' | 'signed_up' export interface WaitlistEntry { id: string diff --git a/sim/app/admin/waitlist/waitlist-table.tsx b/sim/app/admin/waitlist/waitlist-table.tsx index a16dc41b3b..4b3e37a8d4 100644 --- a/sim/app/admin/waitlist/waitlist-table.tsx +++ b/sim/app/admin/waitlist/waitlist-table.tsx @@ -346,50 +346,66 @@ export function WaitlistTable() { {/* Filter bar - similar to logs.tsx */}
- handleStatusChange('all')} - icon={} - label="All" - className={ - status === 'all' - ? 'bg-blue-100 text-blue-900 hover:bg-blue-200 hover:text-blue-900' - : '' - } - /> - handleStatusChange('pending')} - icon={} - label="Pending" - className={ - status === 'pending' - ? 'bg-amber-100 text-amber-900 hover:bg-amber-200 hover:text-amber-900' - : '' - } - /> - handleStatusChange('approved')} - icon={} - label="Approved" - className={ - status === 'approved' - ? 'bg-green-100 text-green-900 hover:bg-green-200 hover:text-green-900' - : '' - } - /> - handleStatusChange('rejected')} - icon={} - label="Rejected" - className={ - status === 'rejected' - ? 'bg-red-100 text-red-900 hover:bg-red-200 hover:text-red-900' - : '' - } - /> +
+ {/* Filter buttons */} +
+ handleStatusChange('all')} + icon={} + label="All" + className={ + status === 'all' + ? 'bg-blue-100 text-blue-900 hover:bg-blue-200 hover:text-blue-900' + : '' + } + /> + handleStatusChange('pending')} + icon={} + label="Pending" + className={ + status === 'pending' + ? 'bg-amber-100 text-amber-900 hover:bg-amber-200 hover:text-amber-900' + : '' + } + /> + handleStatusChange('approved')} + icon={} + label="Approved" + className={ + status === 'approved' + ? 'bg-green-100 text-green-900 hover:bg-green-200 hover:text-green-900' + : '' + } + /> + handleStatusChange('rejected')} + icon={} + label="Rejected" + className={ + status === 'rejected' + ? 'bg-red-100 text-red-900 hover:bg-red-200 hover:text-red-900' + : '' + } + /> + handleStatusChange('signed_up')} + icon={} + label="Signed Up" + className={ + status === 'signed_up' + ? 'bg-purple-100 text-purple-900 hover:bg-purple-200 hover:text-purple-900' + : '' + } + /> +
+
@@ -557,24 +573,45 @@ export function WaitlistTable() { - - {entry.status} - + {/* Status badge */} +
+ {entry.status === 'pending' && ( + + + Pending + + )} + {entry.status === 'approved' && ( + + + Approved + + )} + {entry.status === 'rejected' && ( + + + Rejected + + )} + {entry.status === 'signed_up' && ( + + + Signed Up + + )} +
diff --git a/sim/app/api/admin/waitlist/route.ts b/sim/app/api/admin/waitlist/route.ts index b33e76307d..ddad8d867f 100644 --- a/sim/app/api/admin/waitlist/route.ts +++ b/sim/app/api/admin/waitlist/route.ts @@ -9,7 +9,7 @@ const logger = new Logger('WaitlistAPI') const getQuerySchema = z.object({ page: z.coerce.number().optional().default(1), limit: z.coerce.number().optional().default(20), - status: z.enum(['all', 'pending', 'approved', 'rejected']).optional(), + status: z.enum(['all', 'pending', 'approved', 'rejected', 'signed_up']).optional(), search: z.string().optional(), }) diff --git a/sim/app/api/auth/verify-waitlist-token/route.ts b/sim/app/api/auth/verify-waitlist-token/route.ts new file mode 100644 index 0000000000..771e916fee --- /dev/null +++ b/sim/app/api/auth/verify-waitlist-token/route.ts @@ -0,0 +1,73 @@ +import { NextRequest, NextResponse } from 'next/server' +import { Logger } from '@/lib/logs/console-logger' +import { markWaitlistUserAsSignedUp } from '@/lib/waitlist/service' +import { verifyToken } from '@/lib/waitlist/token' + +const logger = new Logger('VerifyWaitlistToken') + +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { token } = body + + if (!token) { + return NextResponse.json( + { + success: false, + message: 'Token is required', + }, + { status: 400 } + ) + } + + // Verify token + const decodedToken = await verifyToken(token) + + if (!decodedToken) { + return NextResponse.json( + { + success: false, + message: 'Invalid or expired token', + }, + { status: 400 } + ) + } + + // Check if it's a waitlist approval token + if (decodedToken.type !== 'waitlist-approval') { + return NextResponse.json( + { + success: false, + message: 'Invalid token type', + }, + { status: 400 } + ) + } + + const email = decodedToken.email + + // Mark the user as signed up + const result = await markWaitlistUserAsSignedUp(email) + + if (!result.success) { + logger.warn(`Failed to mark user as signed up: ${result.message}`, { email }) + // Continue even if this fails - we still want to allow the user to sign up + } else { + logger.info(`Successfully marked waitlist user as signed up`, { email }) + } + + return NextResponse.json({ + success: true, + email: email, + }) + } catch (error) { + logger.error('Error verifying waitlist token:', error) + return NextResponse.json( + { + success: false, + message: 'An error occurred while verifying the token', + }, + { status: 500 } + ) + } +} diff --git a/sim/lib/waitlist/service.ts b/sim/lib/waitlist/service.ts index 922fb9207d..a6bf0b1496 100644 --- a/sim/lib/waitlist/service.ts +++ b/sim/lib/waitlist/service.ts @@ -11,7 +11,7 @@ import { db } from '@/db' import { waitlist } from '@/db/schema' // Define types for better type safety -export type WaitlistStatus = 'pending' | 'approved' | 'rejected' +export type WaitlistStatus = 'pending' | 'approved' | 'rejected' | 'signed_up' export interface WaitlistEntry { id: string @@ -310,3 +310,46 @@ export async function verifyWaitlistToken( return { valid: false } } } + +// Mark a user as signed up after they create an account +export async function markWaitlistUserAsSignedUp( + email: string +): Promise<{ success: boolean; message: string }> { + try { + const { user, normalizedEmail } = await findUserByEmail(email) + + if (!user) { + return { + success: false, + message: 'User not found in waitlist', + } + } + + if (user.status !== 'approved') { + return { + success: false, + message: 'User is not in approved status', + } + } + + // Update status to signed_up + await db + .update(waitlist) + .set({ + status: 'signed_up', + updatedAt: new Date(), + }) + .where(eq(waitlist.email, normalizedEmail)) + + return { + success: true, + message: 'User marked as signed up', + } + } catch (error) { + console.error('Error marking waitlist user as signed up:', error) + return { + success: false, + message: 'An error occurred while updating user status', + } + } +}