Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
.next
.DS_Store
.env

# Ignore archive copy bundled in repo
"aigiftgen_v101 2/"
105 changes: 105 additions & 0 deletions app/GiftForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
'use client';

import { FormEvent, useState } from 'react';
import Idea from './Idea';

const DEFAULT_TAG = process.env.NEXT_PUBLIC_AMAZON_TAG || 'yourtag-21';

export default function GiftForm() {
const [who, setWho] = useState('my outdoorsy sister who loves hiking');
const [ideas, setIdeas] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [affiliateTag, setAffiliateTag] = useState(DEFAULT_TAG);

async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!who.trim()) {
setError('Tell AI Santa who you are shopping for first.');
return;
}

setLoading(true);
setError('');

try {
const resp = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ who }),
});

if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(data.error || 'Something went wrong. Please try again.');
}

const data = await resp.json();
setIdeas(Array.isArray(data.ideas) ? data.ideas : []);
} catch (err: any) {
setError(err.message || 'Unable to fetch ideas right now.');
setIdeas([]);
} finally {
setLoading(false);
}
}

return (
<section className="card p-6 md:p-10">
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="who" className="block text-sm font-semibold uppercase tracking-wide text-[var(--muted)]">
Who are you shopping for?
</label>
<textarea
id="who"
name="who"
value={who}
onChange={(event) => setWho(event.target.value)}
rows={3}
className="input min-h-[120px] resize-y"
placeholder="e.g. my best friend who is learning to cook"
/>
<p className="small mt-2">
Be descriptive: mention hobbies, inside jokes, favourite snacks—AI Santa thrives on the details.
</p>
</div>

<div className="grid gap-4 md:grid-cols-[2fr,1fr]">
<div>
<label htmlFor="affiliate" className="block text-sm font-semibold uppercase tracking-wide text-[var(--muted)]">
Optional Amazon affiliate tag
</label>
<input
id="affiliate"
name="affiliate"
value={affiliateTag}
onChange={(event) => setAffiliateTag(event.target.value)}
className="input"
placeholder="yourtag-21"
/>
<p className="small mt-2">
We&apos;ll append this tag to Amazon search links so you can earn commission on any purchases.
</p>
</div>
<div className="flex items-end">
<button type="submit" className="btn btn-primary w-full md:w-auto px-8 py-3 text-lg" disabled={loading}>
{loading ? 'Summoning ideas…' : 'Generate gift ideas'}
</button>
</div>
</div>
</form>

{error && <p className="mt-6 text-red-300">{error}</p>}

{ideas.length > 0 && (
<div className="mt-8 space-y-4">
<h3 className="text-xl font-semibold">AI Santa suggests:</h3>
{ideas.map((idea) => (
<Idea key={idea} idea={idea} affiliateTag={affiliateTag.trim() || 'yourtag-21'} />
))}
</div>
)}
</section>
);
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
19 changes: 19 additions & 0 deletions app/legal/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default function Legal() {
return (
<main className="max-w-2xl mx-auto p-6">
<h1 className="text-3xl font-bold mt-12">Legal &amp; Privacy</h1>
<section className="mt-6">
<h2 className="text-xl font-semibold">Affiliate Disclosure</h2>
<p className="mt-2">
Some links are affiliate links. As an Amazon Associate, this site earns from qualifying purchases.
</p>
</section>
<section className="mt-6">
<h2 className="text-xl font-semibold">Privacy</h2>
<p className="mt-2">
We do not collect personal data. If analytics are enabled in the future, we will update this page.
</p>
</section>
</main>
);
}
132 changes: 132 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import Link from 'next/link';
import GiftForm from './GiftForm';

const samplePrompts = [
'My partner who is obsessed with cosy sci-fi reading nooks',
'A Secret Santa gift for a developer who loves retro gaming',
'My mum who just retired and wants to travel more',
];

const features = [
{
title: 'Story-driven prompts',
description: 'Describe the recipient in vivid detail. AI Santa uses the nuance to tailor thoughtful ideas.'
},
{
title: 'Affiliate ready',
description: 'Add your Amazon UK Associate tag and instantly share monetisable suggestion lists.'
},
{
title: 'Shareable in seconds',
description: 'One-click links for X, Reddit and WhatsApp help you crowdsource opinions from friends.'
},
];

const faqs = [
{
q: 'Is this free to use?',
a: 'Yes. Generate as many gift brainstorms as you like. Heavy usage may be throttled to keep AI costs sane.'
},
{
q: 'Do you store my prompts or personal data?',
a: 'No. Requests are processed live via the OpenAI API and not persisted, beyond anonymous analytics (if enabled).'
},
{
q: 'Can I use my own affiliate tag?',
a: 'Absolutely. Paste your Amazon UK tag before generating ideas. We append it to every shopping link.'
}
];

export default function Home() {
return (
<main className="min-h-screen px-6 pb-20">
<div className="mx-auto max-w-5xl">
<header className="py-16 text-center space-y-6">
<span className="inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-1 text-sm tracking-wide text-[var(--muted)]">
🎄 Meet AI Santa
</span>
<h1 className="text-4xl font-bold sm:text-5xl md:text-6xl">
Find the perfect gift in minutes, not hours
</h1>
<p className="mx-auto max-w-2xl text-lg text-[var(--muted)]">
Describe who you&apos;re shopping for and let our playful-yet-practical AI brainstorm gifts across price points.
Each idea links straight to Amazon UK searches—add your affiliate tag to monetise the inspiration.
</p>
</header>

<div className="grid gap-12 lg:grid-cols-[1.1fr,0.9fr] items-start">
<GiftForm />

<aside className="space-y-10">
<section className="card p-6">
<h2 className="text-xl font-semibold">Try these starter prompts</h2>
<ul className="mt-4 space-y-3 text-[var(--muted)]">
{samplePrompts.map((prompt) => (
<li key={prompt} className="rounded-xl bg-white/5 px-4 py-3 text-sm">
{prompt}
</li>
))}
</ul>
</section>

<section className="card p-6 space-y-4">
<h2 className="text-xl font-semibold">How it works</h2>
<ol className="space-y-3 text-[var(--muted)]">
<li>
<span className="font-semibold text-white">1.</span> Paint a vivid picture of the recipient—their hobbies, quirks, upcoming milestones.
</li>
<li>
<span className="font-semibold text-white">2.</span> Click generate. AI Santa blends humour and utility to produce 3-5 shoppable ideas.
</li>
<li>
<span className="font-semibold text-white">3.</span> Share or shop instantly using the Amazon, X, Reddit or WhatsApp buttons.
</li>
</ol>
</section>

<section className="card p-6 space-y-4">
<h2 className="text-xl font-semibold">Why gift hunters love it</h2>
<ul className="space-y-4 text-[var(--muted)]">
{features.map((feature) => (
<li key={feature.title}>
<h3 className="text-lg font-semibold text-white">{feature.title}</h3>
<p className="mt-1">{feature.description}</p>
</li>
))}
</ul>
</section>
</aside>
</div>

<section className="mt-16 grid gap-6 rounded-3xl border border-white/10 bg-white/5 p-8 text-center md:grid-cols-3">
{['Gifting pros', 'Busy parents', 'Office Secret Santas'].map((segment) => (
<div key={segment} className="space-y-2">
<div className="text-3xl">✨</div>
<h3 className="text-xl font-semibold">{segment}</h3>
<p className="text-sm text-[var(--muted)]">
Saves hours of browsing with ideas that feel tailored and personal.
</p>
</div>
))}
</section>

<section className="mt-16 grid gap-6 lg:grid-cols-3">
{faqs.map((faq) => (
<article key={faq.q} className="card p-6 text-left">
<h3 className="text-lg font-semibold">{faq.q}</h3>
<p className="mt-2 text-sm text-[var(--muted)]">{faq.a}</p>
</article>
))}
</section>

<footer className="mt-20 flex flex-col items-center gap-4 text-sm text-[var(--muted)]">
<p>
Built with ❤️ by AI Santa. Need the legal bits?{' '}
<Link href="/legal" className="link">Legal &amp; privacy</Link>.
</p>
<p>Spread the cheer—share your favourite ideas and credit your tag when you do!</p>
</footer>
</div>
</main>
);
}
File renamed without changes.
15 changes: 0 additions & 15 deletions page.tsx

This file was deleted.