Merge version_12 into main #12
13
.env.local.example
Normal file
13
.env.local.example
Normal file
@@ -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=
|
||||
43
package.json
43
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; color: #333;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<h1 style="color: #0d5238;">Welcome to Clearance Waitlist!</h1>
|
||||
<p>Hi ${body.email.split('@')[0]},</p>
|
||||
<p>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.</p>
|
||||
<p><strong>What to expect:</strong></p>
|
||||
<ul>
|
||||
<li>Early access to Clearance platform</li>
|
||||
<li>Lifetime discount for early adopters</li>
|
||||
<li>1-on-1 onboarding call with our team</li>
|
||||
<li>Direct access to our founder for feedback</li>
|
||||
</ul>
|
||||
${body.instagram ? `<p><strong>Instagram:</strong> ${body.instagram}</p>` : ''}
|
||||
${body.tiktok ? `<p><strong>TikTok:</strong> ${body.tiktok}</p>` : ''}
|
||||
<p>We'll be in touch soon with more details!</p>
|
||||
<p>Best regards,<br/>The Clearance Team</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// 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 = `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; color: #333;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<h1>New Waitlist Submission</h1>
|
||||
<p><strong>Email:</strong> ${body.email}</p>
|
||||
${body.instagram ? `<p><strong>Instagram:</strong> ${body.instagram}</p>` : ''}
|
||||
${body.tiktok ? `<p><strong>TikTok:</strong> ${body.tiktok}</p>` : ''}
|
||||
<p><strong>Submitted at:</strong> ${new Date().toISOString()}</p>
|
||||
<p><strong>Total waitlist entries:</strong> ${waitlistDatabase.length}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user