From 3b37011b7f071cd75399d1b3350ff9c2e69c280b Mon Sep 17 00:00:00 2001 From: attaboy11 Date: Thu, 6 Nov 2025 21:58:20 +0000 Subject: [PATCH] Restore Next.js app structure --- .gitignore | 4 + Idea.tsx | 16 ---- ShareButtons.tsx | 12 --- aigiftgen_v101 2/README.md | 64 -------------- aigiftgen_v101 2/app/page.tsx | 63 -------------- aigiftgen_v101 2/components/Idea.tsx | 16 ---- aigiftgen_v101 2/components/ShareButtons.tsx | 12 --- aigiftgen_v101 2/next-env.d.ts | 5 -- aigiftgen_v101 2/next.config.js | 4 - aigiftgen_v101 2/package.json | 29 ------- aigiftgen_v101 2/postcss.config.js | 6 -- aigiftgen_v101 2/public/og.png | 0 aigiftgen_v101 2/tailwind.config.ts | 13 --- aigiftgen_v101 2/tsconfig.json | 36 -------- .../app => app}/api/gifts/route.ts | 0 {aigiftgen_v101 2/app => app}/clone/page.tsx | 0 {aigiftgen_v101 2/app => app}/globals.css | 0 {aigiftgen_v101 2/app => app}/layout.tsx | 0 {aigiftgen_v101 2/app => app}/legal/page.tsx | 0 app/page.tsx | 87 +++++++++++++++++++ components/Idea.tsx | 33 +++++++ components/ShareButtons.tsx | 40 +++++++++ globals.css | 42 --------- layout.tsx | 30 ------- page.tsx | 15 ---- route.ts | 87 ------------------- tsconfig.json | 5 +- 27 files changed, 168 insertions(+), 451 deletions(-) create mode 100644 .gitignore delete mode 100644 Idea.tsx delete mode 100644 ShareButtons.tsx delete mode 100644 aigiftgen_v101 2/README.md delete mode 100644 aigiftgen_v101 2/app/page.tsx delete mode 100644 aigiftgen_v101 2/components/Idea.tsx delete mode 100644 aigiftgen_v101 2/components/ShareButtons.tsx delete mode 100644 aigiftgen_v101 2/next-env.d.ts delete mode 100644 aigiftgen_v101 2/next.config.js delete mode 100644 aigiftgen_v101 2/package.json delete mode 100644 aigiftgen_v101 2/postcss.config.js delete mode 100644 aigiftgen_v101 2/public/og.png delete mode 100644 aigiftgen_v101 2/tailwind.config.ts delete mode 100644 aigiftgen_v101 2/tsconfig.json rename {aigiftgen_v101 2/app => app}/api/gifts/route.ts (100%) rename {aigiftgen_v101 2/app => app}/clone/page.tsx (100%) rename {aigiftgen_v101 2/app => app}/globals.css (100%) rename {aigiftgen_v101 2/app => app}/layout.tsx (100%) rename {aigiftgen_v101 2/app => app}/legal/page.tsx (100%) create mode 100644 app/page.tsx create mode 100644 components/Idea.tsx create mode 100644 components/ShareButtons.tsx delete mode 100644 globals.css delete mode 100644 layout.tsx delete mode 100644 page.tsx delete mode 100644 route.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97db373 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.next/ +.DS_Store +.env* diff --git a/Idea.tsx b/Idea.tsx deleted file mode 100644 index 0875b25..0000000 --- a/Idea.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import ShareButtons from "./ShareButtons"; - -export default function Idea({ idea, affiliateTag }: { idea: string; affiliateTag: string; }) { - const q = encodeURIComponent(idea.replace(/[^a-zA-Z0-9\s]/g, ' ').trim()); - const url = `https://www.amazon.co.uk/s?k=${q}&tag=${affiliateTag||'yourtag-21'}`; - return ( -
-
{idea}
-
- View options on Amazon - Affiliate link -
- -
- ); -} diff --git a/ShareButtons.tsx b/ShareButtons.tsx deleted file mode 100644 index d678205..0000000 --- a/ShareButtons.tsx +++ /dev/null @@ -1,12 +0,0 @@ -'use client'; -export default function ShareButtons({ text, url }: { text: string; url: string }) { - const shareText = encodeURIComponent(text); - const shareUrl = encodeURIComponent(url); - return ( -
- Share on X - Reddit - WhatsApp -
- ); -} diff --git a/aigiftgen_v101 2/README.md b/aigiftgen_v101 2/README.md deleted file mode 100644 index 726f4dc..0000000 --- a/aigiftgen_v101 2/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# 🎅 AI Gift Generator (Next.js + Serverless) - -Deterministic £1 machine → scalable affiliate engine. - -## ✨ What it does -- One-page app where users describe the gift recipient. -- Serverless API asks **AI Santa** for 3–5 witty, specific gift ideas. -- Each idea links to **Amazon UK** search with your **affiliate tag**. -- Footer invites visitors to **clone the site** → virality + compounding. - -## 🧱 Stack -- Next.js (App Router) + TailwindCSS -- Vercel Serverless (API) -- OpenAI (your key) -- Optional: Vercel KV for analytics (not required) - -## 🚀 Deploy (Vercel, 5 minutes) -1. Create a new GitHub repo and upload this folder. -2. Go to **Vercel → New Project → Import** your repo. -3. Add **Environment Variables**: - - `OPENAI_API_KEY` = your OpenAI key - - `NEXT_PUBLIC_AFFILIATE_TAG` = e.g. `yourtag-21` -4. Deploy. Then **Domains → Add** `aigiftgen.co.uk` (or use the free `.vercel.app` domain). - -> If using the custom domain, point your registrar to Vercel’s DNS or add CNAME per Vercel instructions. - -## 🧪 Local dev -```bash -npm i -npm run dev -# open http://localhost:3000 -``` - -## 🪄 Customise -- Adjust the default input text in `app/page.tsx`. -- Tweak prompt / model in `app/api/gifts/route.ts`. -- Change styling in `app/globals.css`. - -## 🔗 Affiliate link format -We link to Amazon UK search with your tag: -``` -https://www.amazon.co.uk/s?k={QUERY}&tag=YOUR_TAG -``` - -## 🧬 Clone Network (optional) -Keep the `/clone` page live so others can fork and deploy. -When they do, your site gains distribution via social shares. - -## 🛡️ Safety / Compliance -- Mark links as affiliate where appropriate. -- Respect Amazon Associates Program Policies. -- Add a simple privacy notice if you enable analytics. - ---- - -### Why this can guarantee £1 by Christmas -- You control at least one user (you); a single qualifying purchase or bounty will surpass £1. -- Minimal cost-to-deploy → near-zero risk. -- Tiny push (share once) → high chance of 10+ outbound clicks. - -**Build fast. Iterate. Then seasonal clones = £££.** - -## 📜 Legal -- See `/legal` for affiliate disclosure and privacy notice. diff --git a/aigiftgen_v101 2/app/page.tsx b/aigiftgen_v101 2/app/page.tsx deleted file mode 100644 index 6059797..0000000 --- a/aigiftgen_v101 2/app/page.tsx +++ /dev/null @@ -1,63 +0,0 @@ -'use client'; -import { useState } from "react"; -import Idea from "@/components/Idea"; - -export default function Home() { - const [who, setWho] = useState('my brother who loves hiking and bad puns'); - const [ideas, setIdeas] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const affiliateTag = process.env.NEXT_PUBLIC_AFFILIATE_TAG || 'yourtag-21'; - - async function generate() { - setLoading(true); setError(null); - try { - const res = await fetch('/api/gifts', { - method: 'POST', - headers: {'Content-Type':'application/json'}, - body: JSON.stringify({ who }) - }); - if(!res.ok) throw new Error('Failed to fetch gift ideas'); - const data = await res.json(); - setIdeas(data.ideas || []); - } catch (e:any) { - setError(e.message); - } finally { - setLoading(false); - } - } - - return ( -
-
-

🎅 AI Gift Generator

-

Tell AI Santa who you're shopping for. Get 3–5 witty, spot‑on gift ideas.

-
- -
- - setWho(e.target.value)} - placeholder="e.g., my dad who grills in winter and collects jazz vinyl" /> -
- - Clone this site → earn too -
-
- - {error &&
Error: {error}
} - -
- {ideas.map((idea, i)=>( - - ))} -
- - -
- ); -} diff --git a/aigiftgen_v101 2/components/Idea.tsx b/aigiftgen_v101 2/components/Idea.tsx deleted file mode 100644 index 0875b25..0000000 --- a/aigiftgen_v101 2/components/Idea.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import ShareButtons from "./ShareButtons"; - -export default function Idea({ idea, affiliateTag }: { idea: string; affiliateTag: string; }) { - const q = encodeURIComponent(idea.replace(/[^a-zA-Z0-9\s]/g, ' ').trim()); - const url = `https://www.amazon.co.uk/s?k=${q}&tag=${affiliateTag||'yourtag-21'}`; - return ( -
-
{idea}
-
- View options on Amazon - Affiliate link -
- -
- ); -} diff --git a/aigiftgen_v101 2/components/ShareButtons.tsx b/aigiftgen_v101 2/components/ShareButtons.tsx deleted file mode 100644 index d678205..0000000 --- a/aigiftgen_v101 2/components/ShareButtons.tsx +++ /dev/null @@ -1,12 +0,0 @@ -'use client'; -export default function ShareButtons({ text, url }: { text: string; url: string }) { - const shareText = encodeURIComponent(text); - const shareUrl = encodeURIComponent(url); - return ( -
- Share on X - Reddit - WhatsApp -
- ); -} diff --git a/aigiftgen_v101 2/next-env.d.ts b/aigiftgen_v101 2/next-env.d.ts deleted file mode 100644 index 4f11a03..0000000 --- a/aigiftgen_v101 2/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/aigiftgen_v101 2/next.config.js b/aigiftgen_v101 2/next.config.js deleted file mode 100644 index 658404a..0000000 --- a/aigiftgen_v101 2/next.config.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {}; - -module.exports = nextConfig; diff --git a/aigiftgen_v101 2/package.json b/aigiftgen_v101 2/package.json deleted file mode 100644 index bfd027b..0000000 --- a/aigiftgen_v101 2/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "ai-gift-generator", - "version": "1.0.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0", - "openai": "^4.58.1", - "zod": "^3.23.8" - }, - "devDependencies": { - "@types/node": "20.11.30", - "@types/react": "18.2.66", - "@types/react-dom": "18.2.22", - "autoprefixer": "10.4.19", - "eslint": "8.57.0", - "eslint-config-next": "14.2.5", - "postcss": "8.4.38", - "tailwindcss": "3.4.10", - "typescript": "5.4.5" - } -} diff --git a/aigiftgen_v101 2/postcss.config.js b/aigiftgen_v101 2/postcss.config.js deleted file mode 100644 index 33ad091..0000000 --- a/aigiftgen_v101 2/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/aigiftgen_v101 2/public/og.png b/aigiftgen_v101 2/public/og.png deleted file mode 100644 index e69de29..0000000 diff --git a/aigiftgen_v101 2/tailwind.config.ts b/aigiftgen_v101 2/tailwind.config.ts deleted file mode 100644 index 5c88f51..0000000 --- a/aigiftgen_v101 2/tailwind.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Config } from "tailwindcss"; - -const config: Config = { - content: [ - "./app/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}" - ], - theme: { - extend: {}, - }, - plugins: [], -}; -export default config; diff --git a/aigiftgen_v101 2/tsconfig.json b/aigiftgen_v101 2/tsconfig.json deleted file mode 100644 index 6ca3d3e..0000000 --- a/aigiftgen_v101 2/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "lib": [ - "dom", - "dom.iterable", - "es2020" - ], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "baseUrl": "." - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/aigiftgen_v101 2/app/api/gifts/route.ts b/app/api/gifts/route.ts similarity index 100% rename from aigiftgen_v101 2/app/api/gifts/route.ts rename to app/api/gifts/route.ts diff --git a/aigiftgen_v101 2/app/clone/page.tsx b/app/clone/page.tsx similarity index 100% rename from aigiftgen_v101 2/app/clone/page.tsx rename to app/clone/page.tsx diff --git a/aigiftgen_v101 2/app/globals.css b/app/globals.css similarity index 100% rename from aigiftgen_v101 2/app/globals.css rename to app/globals.css diff --git a/aigiftgen_v101 2/app/layout.tsx b/app/layout.tsx similarity index 100% rename from aigiftgen_v101 2/app/layout.tsx rename to app/layout.tsx diff --git a/aigiftgen_v101 2/app/legal/page.tsx b/app/legal/page.tsx similarity index 100% rename from aigiftgen_v101 2/app/legal/page.tsx rename to app/legal/page.tsx diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..b7c325d --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { useState } from "react"; + +import Idea from "@/components/Idea"; + +export default function Home() { + const [who, setWho] = useState("my brother who loves hiking and bad puns"); + const [ideas, setIdeas] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const affiliateTag = process.env.NEXT_PUBLIC_AFFILIATE_TAG || "yourtag-21"; + + async function generate() { + setLoading(true); + setError(null); + + try { + const res = await fetch("/api/gifts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ who }), + }); + + if (!res.ok) { + throw new Error("Failed to fetch gift ideas"); + } + + const data = await res.json(); + setIdeas(data.ideas || []); + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + } + + return ( +
+
+

🎅 AI Gift Generator

+

+ Tell AI Santa who you're shopping for. Get 3–5 witty, spot‑on gift + ideas. +

+
+ +
+ + setWho(event.target.value)} + placeholder="e.g., my dad who grills in winter and collects jazz vinyl" + /> +
+ + + Clone this site → earn too + +
+
+ + {error &&
Error: {error}
} + +
+ {ideas.map((idea, index) => ( + + ))} +
+ + +
+ ); +} diff --git a/components/Idea.tsx b/components/Idea.tsx new file mode 100644 index 0000000..07f47b3 --- /dev/null +++ b/components/Idea.tsx @@ -0,0 +1,33 @@ +import ShareButtons from "./ShareButtons"; + +type IdeaProps = { + idea: string; + affiliateTag: string; +}; + +export default function Idea({ idea, affiliateTag }: IdeaProps) { + const sanitized = idea.replace(/[^a-zA-Z0-9\s]/g, " ").trim(); + const query = encodeURIComponent(sanitized); + const tag = affiliateTag || "yourtag-21"; + const url = `https://www.amazon.co.uk/s?k=${query}&tag=${tag}`; + + return ( +
+
{idea}
+ +
+ + View options on Amazon + + Affiliate link +
+ + +
+ ); +} diff --git a/components/ShareButtons.tsx b/components/ShareButtons.tsx new file mode 100644 index 0000000..1514662 --- /dev/null +++ b/components/ShareButtons.tsx @@ -0,0 +1,40 @@ +'use client'; + +type ShareButtonsProps = { + text: string; + url: string; +}; + +export default function ShareButtons({ text, url }: ShareButtonsProps) { + const shareText = encodeURIComponent(text); + const shareUrl = encodeURIComponent(url); + + return ( + + ); +} diff --git a/globals.css b/globals.css deleted file mode 100644 index 472ebe5..0000000 --- a/globals.css +++ /dev/null @@ -1,42 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --bg: #0b1220; - --card: #121a2b; - --text: #e8eefc; - --muted: #9bb0d3; -} - -html, body, #__next { - height: 100%; -} - -body { - background: radial-gradient(1200px 600px at 20% -10%, #14213d 0%, transparent 60%), - radial-gradient(800px 400px at 80% 0%, #0b3b5e 0%, transparent 60%), - var(--bg); - color: var(--text); -} -.btn { - @apply px-4 py-2 rounded-2xl shadow-md hover:shadow-lg transition font-medium; -} -.btn-primary { - @apply bg-blue-600 hover:bg-blue-500; -} -.btn-ghost { - @apply bg-white/5 hover:bg-white/10; -} -.card { - @apply bg-[var(--card)]/80 backdrop-blur-md border border-white/10 rounded-2xl shadow-lg; -} -.input { - @apply w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 outline-none focus:border-blue-500; -} -.link { - @apply underline decoration-dotted underline-offset-4 hover:text-blue-300; -} -.small { - @apply text-sm text-[var(--muted)]; -} diff --git a/layout.tsx b/layout.tsx deleted file mode 100644 index 903ee64..0000000 --- a/layout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import "./globals.css"; -import type { Metadata } from "next"; - -export const metadata: Metadata = { - title: "AI Gift Generator – Find the perfect gift (with AI Santa)", - description: "Type who you're shopping for. AI Santa suggests witty, spot-on gifts with direct links.", - openGraph: { - title: "AI Gift Generator – Find the perfect gift (with AI Santa)", - description: "Type who you're shopping for. AI Santa suggests witty, spot-on gifts with direct links.", - url: "https://aigiftgen.co.uk", - siteName: "AI Gift Generator", - images: [{ url: "/og.png", width: 1200, height: 630 }], - locale: "en_GB", - type: "website", - }, - twitter: { - card: "summary_large_image", - title: "AI Gift Generator – Find the perfect gift (with AI Santa)", - description: "Type who you're shopping for. AI Santa suggests witty, spot-on gifts with direct links.", - images: ["/og.png"] - } -}; - -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} diff --git a/page.tsx b/page.tsx deleted file mode 100644 index 7f962ae..0000000 --- a/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export default function Legal() { - return ( -
-

Legal & Privacy

-
-

Affiliate Disclosure

-

Some links are affiliate links. As an Amazon Associate, this site earns from qualifying purchases.

-
-
-

Privacy

-

We do not collect personal data. If analytics are enabled in the future, we will update this page.

-
-
- ); -} diff --git a/route.ts b/route.ts deleted file mode 100644 index 6bc1b66..0000000 --- a/route.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { z } from "zod"; -import OpenAI from "openai"; - -const schema = z.object({ - who: z.string().min(3).max(300) -}); - -// Use the official OpenAI SDK; expect OPENAI_API_KEY to be provided in Vercel env -const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); -function extractJsonArray(text: string): string[] { - // Try direct parse - try { - const maybe = JSON.parse(text); - if (Array.isArray(maybe)) return maybe; - } catch {} - // Try to find the first [...] block - const m = text.match(/\[[\s\S]*\]/); - if (m) { - try { - const maybe = JSON.parse(m[0]); - if (Array.isArray(maybe)) return maybe; - } catch {} - } - // Fallback: line split - return text.split(/\n|\r/).map(s => s.trim()).filter(Boolean).slice(0,5); -} - - -export async function POST(req: NextRequest) { - try { - const body = await req.json(); - const { who } = schema.parse(body); - - // Prompt engineering: humorous + useful hybrid - const sys = `You are 'AI Santa'—a witty but helpful gift recommender. -Return ONLY a JSON array of 3-5 concise gift ideas. Each idea should be 8-20 words, -specific enough to search on Amazon UK. Avoid brand names unless iconic. No extra commentary.`; - - const user = `Who: ${who} -Constraints: -- Mix fun + practicality -- Vary price points -- Include at least one small stocking stuffer -Output: JSON array of strings only.`; - - // Use Responses API (compatible with gpt-4.1/gpt-4o/gpt-5 when available) - const resp = await client.chat.completions.create({ - model: "gpt-4o-mini", - messages: [ - { role: "system", content: sys }, - { role: "user", content: user } - ], - temperature: 0.8, - max_tokens: 300 - }); - - const text = resp.choices?.[0]?.message?.content || "[]"; - // Best-effort JSON parse - let ideas: string[] = []; - try { - ideas = JSON.parse(text); - if (!Array.isArray(ideas)) ideas = []; - } catch { - // fallback: split lines if LLM didn't return JSON - ideas = text.split(/\n|\r/).filter(Boolean).slice(0,5); - } - - // Basic sanitization & cap - ideas = ideas.map(s => String(s).trim()).filter(Boolean).slice(0,5); - if (ideas.length < 3) { - // Force a minimal list if the model misbehaves - ideas = [ - "Insulated hiking mug with clip carabiner", - "Waterproof phone pouch for trail photos", - "Compact headlamp with rechargeable battery" - ]; - } - - // TODO: optional: log to KV (requires Vercel KV or Upstash Redis) - // Skipped by default for zero-config deploy - - return NextResponse.json({ ideas }); - } catch (err: any) { - return NextResponse.json({ error: err.message || "Bad request" }, { status: 400 }); - } -} diff --git a/tsconfig.json b/tsconfig.json index 6ca3d3e..acad97f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,10 @@ "name": "next" } ], - "baseUrl": "." + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } }, "include": [ "next-env.d.ts",