diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..76ec4f3 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,13 @@ +# SMTP Configuration for Email Notifications +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_SECURE=false +SMTP_USER=your-email@gmail.com +SMTP_PASSWORD=your-app-password +SMTP_FROM=noreply@clearance.dev + +# Admin Email +ADMIN_EMAIL=admin@clearance.dev + +# Database Configuration (optional for future database integration) +DATABASE_URL= diff --git a/package.json b/package.json index 6dfa71b..cbc9576 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,12 @@ { - "name": "webild-components-2", - "version": "0.1.0", - "private": true, + "name": "clearance-landing", "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbopack", - "build": "next build --turbopack", - "typecheck": "tsc --noEmit", - "lint": "eslint . --ext .ts,.tsx", - "start": "next start" + "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { - "@gsap/react": "^2.1.2", - "@react-three/drei": "^10.7.7", - "@react-three/fiber": "^9.4.0", - "@rive-app/react-canvas": "^4.26.2", - "@tsparticles/engine": "^3.9.1", - "@tsparticles/react": "^3.0.0", - "@tsparticles/slim": "^3.9.1", - "clsx": "^2.1.1", - "cobe": "^0.6.5", - "embla-carousel-auto-scroll": "^8.6.0", - "embla-carousel-react": "^8.6.0", - "gsap": "^3.13.0", - "lenis": "^1.3.15", - "lucide-react": "^0.555.0", - "motion-number": "^1.0.0", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1", - "react-fast-marquee": "^1.6.5", - "recharts": "^3.6.0", - "tailwind-merge": "^3.4.0", - "three": "^0.181.2" + "react": "^18", "react-dom": "^18", "next": "^14", "lucide-react": "latest", "nodemailer": "^6.9.7" }, "devDependencies": { - "@eslint/eslintrc": "^3", - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "^9", - "eslint-config-next": "16.0.7", - "tailwindcss": "^4", - "typescript": "^5" + "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "@types/nodemailer": "^6.4.14", "autoprefixer": "^10", "postcss": "^8", "tailwindcss": "^3" } } diff --git a/src/app/api/waitlist/route.ts b/src/app/api/waitlist/route.ts index fd22071..110c164 100644 --- a/src/app/api/waitlist/route.ts +++ b/src/app/api/waitlist/route.ts @@ -1,86 +1,143 @@ import { NextRequest, NextResponse } from 'next/server'; +import nodemailer from 'nodemailer'; -interface WaitlistEntry { +interface WaitlistFormData { email: string; instagram?: string; tiktok?: string; - createdAt: string; } -// In-memory storage for demonstration -// In production, replace with actual database (MongoDB, PostgreSQL, etc.) -const waitlistEntries: WaitlistEntry[] = []; +// In-memory storage for demo purposes. Replace with database in production. +const waitlistDatabase: WaitlistFormData[] = []; + +// Configure your email service here +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST || 'smtp.gmail.com', + port: parseInt(process.env.SMTP_PORT || '587'), + secure: process.env.SMTP_SECURE === 'true', // true for 465, false for other ports + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD, + }, +}); export async function POST(request: NextRequest) { try { - const body = await request.json(); - const { email, instagram, tiktok } = body; + const body: WaitlistFormData = await request.json(); // Validate email - if (!email || typeof email !== 'string' || !email.includes('@')) { + if (!body.email || !body.email.includes('@')) { return NextResponse.json( - { error: 'Valid email is required' }, + { error: 'Invalid email address' }, { status: 400 } ); } - // Check for duplicate email - if (waitlistEntries.some(entry => entry.email === email)) { + // Check if email already exists + if (waitlistDatabase.some(entry => entry.email === body.email)) { return NextResponse.json( - { error: 'Email already registered' }, + { error: 'Email already on waitlist' }, { status: 409 } ); } - // Create waitlist entry - const entry: WaitlistEntry = { - email, - instagram: instagram || undefined, - tiktok: tiktok || undefined, - createdAt: new Date().toISOString() - }; + // Add to database + waitlistDatabase.push(body); - // Add to in-memory storage - waitlistEntries.push(entry); + // Send confirmation email to user + const userEmailContent = ` + + +
+

Welcome to Clearance Waitlist!

+

Hi ${body.email.split('@')[0]},

+

Thank you for joining our waitlist. You're now part of an exclusive group of creators and agencies who will get early access to Clearance.

+

What to expect:

+ + ${body.instagram ? `

Instagram: ${body.instagram}

` : ''} + ${body.tiktok ? `

TikTok: ${body.tiktok}

` : ''} +

We'll be in touch soon with more details!

+

Best regards,
The Clearance Team

+
+ + + `; - // TODO: In production, save to database here - // Example for MongoDB: - // await db.collection('waitlist').insertOne(entry); - - // TODO: Send confirmation email - // Example: - // await sendEmail({ - // to: email, - // subject: 'Welcome to Clearance Waitlist', - // template: 'waitlist-confirmation' - // }); + // Send notification email to admin + const adminEmailContent = ` + + +
+

New Waitlist Submission

+

Email: ${body.email}

+ ${body.instagram ? `

Instagram: ${body.instagram}

` : ''} + ${body.tiktok ? `

TikTok: ${body.tiktok}

` : ''} +

Submitted at: ${new Date().toISOString()}

+

Total waitlist entries: ${waitlistDatabase.length}

+
+ + + `; - console.log('Waitlist entry created:', entry); + // Send emails in parallel + const emailPromises = []; + + // Send confirmation to user + if (process.env.SMTP_USER) { + emailPromises.push( + transporter.sendMail({ + from: process.env.SMTP_FROM || process.env.SMTP_USER, + to: body.email, + subject: 'Welcome to Clearance Waitlist - Confirmation', + html: userEmailContent, + }) + ); + + // Send notification to admin + emailPromises.push( + transporter.sendMail({ + from: process.env.SMTP_FROM || process.env.SMTP_USER, + to: process.env.ADMIN_EMAIL || process.env.SMTP_USER, + subject: 'New Waitlist Submission', + html: adminEmailContent, + }) + ); + } + + // Wait for all emails to send + if (emailPromises.length > 0) { + await Promise.all(emailPromises); + } return NextResponse.json( { success: true, - message: 'Successfully joined the waitlist', - data: entry + message: 'Successfully added to waitlist', + data: { + email: body.email, + timestamp: new Date().toISOString(), + }, }, { status: 201 } ); } catch (error) { - console.error('Waitlist API error:', error); + console.error('Waitlist submission error:', error); return NextResponse.json( - { error: 'Internal server error' }, + { error: 'Failed to submit waitlist form' }, { status: 500 } ); } } export async function GET() { - // Optional: Get all waitlist entries (add authentication in production) - return NextResponse.json( - { - count: waitlistEntries.length, - entries: waitlistEntries - }, - { status: 200 } - ); + // Admin endpoint to view waitlist (add authentication in production) + return NextResponse.json({ + total: waitlistDatabase.length, + entries: waitlistDatabase, + }); } diff --git a/src/app/page.tsx b/src/app/page.tsx index d3c697f..151b4f4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; import NavbarLayoutFloatingOverlay from '@/components/navbar/NavbarLayoutFloatingOverlay/NavbarLayoutFloatingOverlay'; @@ -137,7 +137,7 @@ export default function LandingPage() { id: "2", value: "You Miss Renewal Invoices", title: "Nobody Remembers to Bill", items: ["Manual renewal follow-ups", "Easy to forget", "Lost revenue disappears"] }, { - id: "3", value: "Revenue Slips Away Silently", title: "Money Lost to Expired Deals", items: ["Average loss: $1,200/creator", "Expires quarterly", "Compounds over time"] + id: "3", value: "Revenue Slips Away Silently", title: "Money Lost to Expired Deals", items: ["Average loss: $1,200/creator", "Expires quarterly", "Compunds over time"] } ]} />