feat(waitlist): made waitlist function, added waitlist table

This commit is contained in:
Waleed Latif
2025-02-16 23:21:29 -08:00
parent a27ef94597
commit 7fabe4c83c
7 changed files with 577 additions and 12 deletions
+2 -12
View File
@@ -1,7 +1,6 @@
import Image from 'next/image'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import HeroWorkflowProvider from './hero-workflow'
import WaitlistForm from './waitlist-form'
export default function Landing() {
return (
@@ -49,16 +48,7 @@ export default function Landing() {
user-friendly environment for devs and agents
</p>
<div className="flex gap-3 justify-center max-w-lg mx-auto mt-8">
<Input
type="email"
placeholder="you@example.com"
className="flex-1 text-lg bg-[#020817] border-white/20 focus:border-white/30 focus:ring-white/30 rounded-md h-12"
/>
<Button className="bg-white text-black hover:bg-gray-100 rounded-md px-8 h-12">
Join waitlist
</Button>
</div>
<WaitlistForm />
<div className="mt-10 -mx-4">
<HeroWorkflowProvider />
+81
View File
@@ -0,0 +1,81 @@
'use client'
import { useState } from 'react'
import { z } from 'zod'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
const emailSchema = z.string().email('Please enter a valid email')
export default function WaitlistForm() {
const [email, setEmail] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [message, setMessage] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setMessage('')
try {
// Validate email
emailSchema.parse(email)
setIsSubmitting(true)
const response = await fetch('/api/waitlist', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
})
const data = await response.json()
if (!response.ok) {
setMessage(data.message || 'Failed to join waitlist')
return
}
setMessage('Thanks for joining our waitlist!')
setEmail('')
} catch (error) {
if (error instanceof z.ZodError) {
setMessage('Please enter a valid email')
} else {
setMessage(error instanceof Error ? error.message : 'Something went wrong')
}
} finally {
setIsSubmitting(false)
}
}
return (
<form
onSubmit={handleSubmit}
className="flex flex-col gap-3 items-center max-w-lg mx-auto mt-8"
>
<div className="flex w-full gap-3">
<Input
type="email"
placeholder="you@example.com"
className="flex-1 text-lg bg-[#020817] border-white/20 focus:border-white/30 focus:ring-white/30 rounded-md h-12"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isSubmitting}
/>
<Button
type="submit"
className="bg-white text-black hover:bg-gray-100 rounded-md px-8 h-12"
disabled={isSubmitting}
>
{isSubmitting ? 'Joining...' : 'Join waitlist'}
</Button>
</div>
{message && (
<p className={`text-sm ${message.includes('Thanks') ? 'text-green-400' : 'text-red-400'}`}>
{message}
</p>
)}
</form>
)
}
+41
View File
@@ -0,0 +1,41 @@
import { NextResponse } from 'next/server'
import { eq } from 'drizzle-orm'
import { nanoid } from 'nanoid'
import { z } from 'zod'
import { db } from '@/db'
import { waitlist } from '@/db/schema'
const waitlistSchema = z.object({
email: z.string().email(),
})
export async function POST(request: Request) {
try {
const body = await request.json()
const { email } = waitlistSchema.parse(body)
// Check if email already exists
const existingEntry = await db
.select()
.from(waitlist)
.where(eq(waitlist.email, email))
.execute()
if (existingEntry.length > 0) {
return NextResponse.json({ message: 'Email already registered' }, { status: 400 })
}
// Add to waitlist
await db.insert(waitlist).values({
id: nanoid(),
email,
createdAt: new Date(),
updatedAt: new Date(),
})
return NextResponse.json({ message: 'Successfully joined waitlist' }, { status: 200 })
} catch (error) {
console.error('Waitlist error:', error)
return NextResponse.json({ message: 'Failed to join waitlist' }, { status: 500 })
}
}
+8
View File
@@ -0,0 +1,8 @@
CREATE TABLE "waitlist" (
"id" text PRIMARY KEY NOT NULL,
"email" text NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "waitlist_email_unique" UNIQUE("email")
);
+430
View File
@@ -0,0 +1,430 @@
{
"id": "80542c3c-48a3-41e5-b911-fe51152efccc",
"prevId": "a17085d8-94da-40fe-b71c-e143059e3f80",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"account_user_id_user_id_fk": {
"name": "account_user_id_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"session_user_id_user_id_fk": {
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"session_token_unique": {
"name": "session_token_unique",
"nullsNotDistinct": false,
"columns": ["token"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": ["email"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.verification": {
"name": "verification",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'pending'"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": ["email"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.workflow": {
"name": "workflow",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_synced": {
"name": "last_synced",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"workflow_user_id_user_id_fk": {
"name": "workflow_user_id_user_id_fk",
"tableFrom": "workflow",
"tableTo": "user",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+7
View File
@@ -15,6 +15,13 @@
"when": 1739773751302,
"tag": "0001_foamy_dakota_north",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1739776634326,
"tag": "0002_previous_xavin",
"breakpoints": true
}
]
}
+8
View File
@@ -62,3 +62,11 @@ export const workflow = pgTable('workflow', {
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull(),
})
export const waitlist = pgTable('waitlist', {
id: text('id').primaryKey(),
email: text('email').notNull().unique(),
status: text('status').notNull().default('pending'), // pending, approved, rejected
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
})