diff --git a/CLOUDFLARE_SETUP.md b/CLOUDFLARE_SETUP.md new file mode 100644 index 0000000..c435b70 --- /dev/null +++ b/CLOUDFLARE_SETUP.md @@ -0,0 +1,46 @@ +# Cloudflare Pages Deployment Guide + +## Static Export Configuration + +This project is configured to build as a static export with `output: 'export'` in `next.config.mjs`. When you run `npm run build`, it will generate static files in the `/out` directory. + +## Cloudflare Pages Function (Waitlist API) + +The waitlist functionality uses a Cloudflare Pages Function located at `functions/api/waitlist.ts`. This will run serverlessly on Cloudflare's edge network. + +### Setting up KV Namespace + +The waitlist function requires a Cloudflare KV namespace to store emails: + +1. In your Cloudflare dashboard, go to Workers & Pages > KV +2. Create a new KV namespace called `WAITLIST` +3. In your Pages project settings, go to Settings > Functions > KV namespace bindings +4. Add a binding: + - Variable name: `WAITLIST` + - KV namespace: Select the `WAITLIST` namespace you created + +### Build Configuration + +Cloudflare Pages will automatically detect the Next.js static export. Use these settings: + +- **Build command**: `npm run build` +- **Build output directory**: `out` +- **Node version**: 18 or higher + +### Deployment + +1. Connect your GitHub repository to Cloudflare Pages +2. Configure the build settings as above +3. Add the KV namespace binding +4. Deploy! + +The site will be served statically from the `/out` directory, and the `/api/waitlist` endpoint will be handled by the Cloudflare Pages Function. + +## Alternative: Simple Form Submission + +If you prefer not to use Cloudflare KV, you can also use: +- A third-party service like Formspree, Tally, or Airtable +- A simple mailto link or Google Form +- Any webhook service + +Simply update the `handleSubmit` function in `components/waitlist-section.tsx` to point to your chosen service. diff --git a/README.md b/README.md index 7c6f48c..fb97b20 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ Continue building your app on: 1. Create and modify your project using [v0.app](https://v0.app) 2. Deploy your chats from the v0 interface 3. Changes are automatically pushed to this repository -4. Vercel deploys the latest version from this repository \ No newline at end of file +4. Vercel deploys the latest version from this repository diff --git a/app/api/waitlist/route.ts b/app/api/waitlist/route.ts deleted file mode 100644 index cb7d922..0000000 --- a/app/api/waitlist/route.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { NextResponse } from 'next/server' - -// This is a simple in-memory store for demo purposes -// In production, you'd want to use a database -const waitlist = new Set() - -export async function POST(request: Request) { - try { - const { email } = await request.json() - - if (!email || !email.includes('@')) { - return NextResponse.json( - { error: 'Valid email required' }, - { status: 400 } - ) - } - - if (waitlist.has(email)) { - return NextResponse.json( - { error: 'Already in the network' }, - { status: 400 } - ) - } - - waitlist.add(email) - - // In production, you'd save to a database here - // and potentially send a confirmation email - console.log('[v0] New waitlist signup:', email) - console.log('[v0] Total waitlist size:', waitlist.size) - - return NextResponse.json({ - success: true, - message: 'Welcome to the underground', - }) - } catch (error) { - console.error('[v0] Waitlist error:', error) - return NextResponse.json( - { error: 'Network error occurred' }, - { status: 500 } - ) - } -} - -// Optional: Add a GET endpoint to check waitlist status (for admin use) -export async function GET() { - return NextResponse.json({ - count: waitlist.size, - }) -} diff --git a/components/hero-section.tsx b/components/hero-section.tsx index 7f95286..e1eab8c 100644 --- a/components/hero-section.tsx +++ b/components/hero-section.tsx @@ -30,7 +30,9 @@ export function HeroSection() {

- myc0punkz + + myc0punkz +

diff --git a/functions/api/waitlist.ts b/functions/api/waitlist.ts new file mode 100644 index 0000000..934388e --- /dev/null +++ b/functions/api/waitlist.ts @@ -0,0 +1,64 @@ +// Cloudflare Pages Function for waitlist +// This will run on Cloudflare's edge network + +interface Env { + WAITLIST: KVNamespace +} + +export async function onRequestPost(context: { request: Request; env: Env }) { + try { + const { email } = await context.request.json() + + if (!email || !email.includes('@')) { + return new Response( + JSON.stringify({ error: 'Valid email required' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ) + } + + // Check if email already exists using Cloudflare KV + const existing = await context.env.WAITLIST?.get(email) + + if (existing) { + return new Response( + JSON.stringify({ error: 'Already in the network' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ) + } + + // Store email with timestamp in KV + await context.env.WAITLIST?.put(email, JSON.stringify({ + email, + timestamp: new Date().toISOString(), + })) + + return new Response( + JSON.stringify({ + success: true, + message: 'Welcome to the underground', + }), + { status: 200, headers: { 'Content-Type': 'application/json' } } + ) + } catch (error) { + return new Response( + JSON.stringify({ error: 'Network error occurred' }), + { status: 500, headers: { 'Content-Type': 'application/json' } } + ) + } +} + +// Optional: GET endpoint to check waitlist count +export async function onRequestGet(context: { env: Env }) { + try { + const list = await context.env.WAITLIST?.list() + return new Response( + JSON.stringify({ count: list?.keys.length || 0 }), + { status: 200, headers: { 'Content-Type': 'application/json' } } + ) + } catch (error) { + return new Response( + JSON.stringify({ error: 'Network error' }), + { status: 500, headers: { 'Content-Type': 'application/json' } } + ) + } +} diff --git a/next.config.mjs b/next.config.mjs index 5501ef9..4f5b548 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,12 +1,13 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + output: 'export', + distDir: 'out', typescript: { ignoreBuildErrors: true, }, images: { unoptimized: true, }, - } -export default nextConfig \ No newline at end of file +export default nextConfig diff --git a/package.json b/package.json index f34b692..4d82107 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@radix-ui/react-select": "2.1.4", "@radix-ui/react-separator": "1.1.1", "@radix-ui/react-slider": "1.2.2", - "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-slot": "latest", "@radix-ui/react-switch": "1.1.2", "@radix-ui/react-tabs": "1.1.2", "@radix-ui/react-toast": "1.2.4", @@ -47,7 +47,7 @@ "input-otp": "1.4.1", "lucide-react": "^0.454.0", "next": "16.0.3", - "next-themes": "^0.4.6", + "next-themes": "latest", "react": "19.2.0", "react-day-picker": "9.8.0", "react-dom": "19.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ecd937..ef13441 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,8 +72,8 @@ importers: specifier: 1.2.2 version: 1.2.2(@types/react-dom@19.0.0)(@types/react@19.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-slot': - specifier: 1.1.1 - version: 1.1.1(@types/react@19.0.0)(react@19.2.0) + specifier: latest + version: 1.2.4(@types/react@19.0.0)(react@19.2.0) '@radix-ui/react-switch': specifier: 1.1.2 version: 1.1.2(@types/react-dom@19.0.0)(@types/react@19.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -123,7 +123,7 @@ importers: specifier: 16.0.3 version: 16.0.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) next-themes: - specifier: ^0.4.6 + specifier: latest version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: specifier: 19.2.0