359 lines
18 KiB
JavaScript

import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Wand2, Dice5, Download, Printer } from 'lucide-react';
import html2canvas from 'html2canvas';
import { jsPDF } from 'jspdf';
import { Button } from './ui/Button';
import { Input } from './ui/Input';
import { sections } from '../data/mockData';
export const Hero = ({ onGenerate, generatedSheet, isGenerating }) => {
const [theme, setTheme] = useState('');
const [age, setAge] = useState(5);
const [selectedSection, setSelectedSection] = useState('coloring');
// Sync state when a sheet is restored
React.useEffect(() => {
if (generatedSheet) {
setTheme(generatedSheet.title || '');
if (generatedSheet.age) setAge(generatedSheet.age);
// If sections exist, restore the first one's type as selected
if (generatedSheet.sections && generatedSheet.sections.length > 0) {
// Map section type back to ID if possible, or fallback
const secType = generatedSheet.sections[0].type; // e.g. "coloring"
// Our IDs match the types mostly, but let's be safe
// IDs: coloring, numbers, writing, find-the-way, counting
// Types from prompt: coloring|numbers|writing|find-the-way|counting
// So direct mapping should work.
// But wait, finding the ID that matches the type/id from result
// In App.jsx we assume result has sections.
// The prompt returns 'type' and we use that? No, prompt returns 'id' and 'type'.
// Let's use the first section's ID if valid.
const secId = generatedSheet.sections[0].id; // e.g. "coloring-123"
// This ID from AI might be "coloring-main". We need to map it to our internal IDs: 'coloring', 'writing', 'numbers', etc.
// Internal IDs in Hero are: 'coloring', 'numbers', 'writing', 'find-way' (wait, it's 'find-way' not 'find-the-way'!), 'counting'.
// Let's check mockData for IDs.
// sections: [{id: 'find-way', label: 'Find the Way', ...}]
// AI prompt was: "find-the-way" for type.
// We need to map "find-the-way" -> "find-way".
const typeToId = {
'coloring': 'coloring',
'numbers': 'numbers',
'writing': 'writing',
'find-the-way': 'find-way',
'counting': 'counting'
};
if (typeToId[secType]) {
setSelectedSection(typeToId[secType]);
}
}
}
}, [generatedSheet]);
const handleSectionChange = (id) => {
if (!isGenerating) {
setSelectedSection(id);
}
};
const handleSurpriseMe = async () => {
if (isGenerating) return;
try {
// Optional: indicate "thinking" state here if needed, or rely on parent loading state
// since we don't have a separate loading state, we can reuse isGenerating logic or add one.
// For now, we'll just set the values when they arrive.
const res = await fetch('http://localhost:3001/api/surprise', { method: 'POST' });
if (!res.ok) throw new Error('Surprise failed');
const idea = await res.json();
// Validate section ID exists in our map, else fallback
const validSection = sections.find(s => s.id === idea.section) ? idea.section : 'coloring';
setTheme(idea.theme);
setAge(idea.age);
setSelectedSection(validSection);
// Auto-generate for surprise
if (onGenerate) {
onGenerate({
theme: idea.theme,
age: idea.age,
sections: [validSection]
});
}
} catch (err) {
console.error(err);
// Fallback to local random if network/AI fails
const funnyThemes = ['Dinosaur Disco', 'Unicorn Space Party', 'Pirate Picnic', 'Robot Garden'];
const randomTheme = funnyThemes[Math.floor(Math.random() * funnyThemes.length)];
setTheme(randomTheme);
setAge(Math.floor(Math.random() * 5) + 3);
const randomSection = sections[Math.floor(Math.random() * sections.length)].id;
setSelectedSection(randomSection);
if (onGenerate) {
onGenerate({
theme: randomTheme,
age: Math.floor(Math.random() * 5) + 3,
sections: [randomSection]
});
}
}
};
const handleCreate = () => {
if (onGenerate && !isGenerating) {
onGenerate({ theme, age, sections: [selectedSection] });
}
};
// Use generated result or default preview
const previewTheme = generatedSheet ? generatedSheet.title : (theme || 'Unicorn & Rainbow');
const previewSubtitle = generatedSheet ? generatedSheet.subtitle : 'Coloring & Counting';
const generatePDF = async () => {
const element = document.getElementById('sheet-preview');
const canvas = await html2canvas(element);
const data = canvas.toDataURL('image/png');
const pdf = new jsPDF();
const imgProperties = pdf.getImageProperties(data);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (imgProperties.height * pdfWidth) / imgProperties.width;
pdf.addImage(data, 'PNG', 0, 0, pdfWidth, pdfHeight);
return pdf;
};
const handleDownload = async () => {
try {
const pdf = await generatePDF();
pdf.save(`${previewTheme.replace(/\s+/g, '_')}.pdf`);
} catch (err) {
console.error("PDF generation failed:", err);
}
};
const handlePrint = async () => {
try {
const pdf = await generatePDF();
pdf.autoPrint();
window.open(pdf.output('bloburl'), '_blank');
} catch (err) {
console.error("PDF print failed:", err);
}
};
return (
<div className="grid lg:grid-cols-2 gap-12 items-center py-12">
<div className="space-y-8">
{/* ... Left side controls remain unchanged ... */}
<div className="space-y-4">
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-5xl font-extrabold tracking-tight text-slate-900 leading-[1.1]"
>
Instantly create personalized learning pages for your <span className="text-primary-600">kid</span>.
</motion.h1>
<p className="text-xl text-slate-600 max-w-lg">
Just add imagination! We use AI to generate educational worksheets tailored to your child's interests.
</p>
</div>
<div className="space-y-6 bg-white p-1 rounded-3xl">
{/* Theme Input */}
<div className="space-y-2">
<Input
icon={Wand2}
placeholder="Theme (e.g., Unicorns, Space, Underwater)"
value={theme}
readOnly={isGenerating}
onChange={(e) => setTheme(e.target.value)}
/>
</div>
{/* Age Slider */}
<div className="space-y-2">
<div className="flex justify-between items-center px-1">
<label className="font-semibold text-slate-700">Age: {age}</label>
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">3 to 7 years</span>
</div>
<input
type="range"
min="3"
max="7"
value={age}
disabled={isGenerating}
onChange={(e) => setAge(parseInt(e.target.value))}
className="w-full h-3 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-primary-600 disabled:opacity-50"
/>
<div className="flex justify-between text-xs text-slate-400 px-1">
<span>3</span><span>4</span><span>5</span><span>6</span><span>7</span>
</div>
</div>
{/* Sections */}
<div className="space-y-3">
<label className="font-semibold text-slate-700 block">Activity Type</label>
<div className="flex flex-wrap gap-3">
{sections.map(section => {
const isSelected = selectedSection === section.id;
return (
<button
key={section.id}
onClick={() => handleSectionChange(section.id)}
disabled={isGenerating}
className={`
flex items-center gap-2 px-4 py-2 rounded-xl border-2 transition-all font-medium text-sm
${isSelected
? section.color + ' ring-2 ring-offset-1 ring-primary-200'
: 'bg-white border-slate-200 text-slate-600 hover:border-slate-300'
}
${isGenerating ? 'opacity-50 cursor-not-allowed' : ''}
`}
>
<span>{section.icon}</span>
{section.label}
</button>
)
})}
</div>
</div>
{/* Actions */}
<div className="flex gap-4 pt-4">
<Button size="lg" className="flex-1" onClick={handleCreate} disabled={isGenerating}>
{isGenerating ? (
<>
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2" />
Creating...
</>
) : (
<>
<Wand2 className="mr-2" size={20} />
Create Sheet
</>
)}
</Button>
<Button size="lg" variant="secondary" className="flex-1" onClick={handleSurpriseMe} disabled={isGenerating}>
{isGenerating ? (
<>
<div className="w-5 h-5 border-2 border-slate-400 border-t-slate-800 rounded-full animate-spin mr-2" />
Surprising...
</>
) : (
<>
<Dice5 className="mr-2" size={20} />
Surprise Me
</>
)}
</Button>
</div>
</div>
</div>
{/* Right Side - Preview */}
<div className="relative hidden lg:block">
<div className="absolute inset-0 bg-gradient-to-tr from-primary-100 to-indigo-50 rounded-full blur-3xl opacity-60 transform -translate-y-12 translate-x-12" />
<div id="sheet-wrapper" className="relative bg-white rounded-[2rem] shadow-2xl shadow-slate-200/50 border-8 border-white p-4 rotate-3 hover:rotate-0 transition-transform duration-500 ease-out z-10">
<div id="sheet-preview" className={`aspect-[1/1.4] bg-white rounded-xl overflow-hidden relative border flex flex-col ${generatedSheet?.sections?.length === 1 ? 'p-4' : 'p-6'}`}>
<AnimatePresence>
{isGenerating && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 z-50 bg-white/80 backdrop-blur-sm flex flex-col items-center justify-center"
>
<div className="w-16 h-16 border-4 border-primary-200 border-t-primary-600 rounded-full animate-spin mb-4" />
<motion.p
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="text-lg font-bold text-slate-700"
>
Designing your sheet...
</motion.p>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.5, duration: 2, repeat: Infinity, repeatType: "reverse" }}
className="text-sm text-slate-500 mt-2"
>
Adding some magic ✨
</motion.p>
</motion.div>
)}
</AnimatePresence>
<div className="flex-1 flex flex-col items-center text-center bg-white h-full">
<h3 className="font-outfit font-bold text-2xl mb-1 text-slate-800">{previewTheme}</h3>
<p className="text-slate-500 uppercase tracking-widest text-[10px] font-bold mb-2">{previewSubtitle}</p>
{generatedSheet ? (
<div className={`w-full flex-1 flex flex-col gap-2 ${generatedSheet.sections?.length === 1 ? 'justify-center' : ''}`}>
{generatedSheet.sections && generatedSheet.sections.map((sec, idx) => (
<div
key={idx}
className={`
border-2 border-dashed border-slate-200 rounded-xl flex flex-col items-center
${generatedSheet.sections.length === 1 ? 'flex-1 justify-center p-2 w-full h-full' : 'p-2 flex-1'}
`}
>
<div className="text-[10px] font-bold text-slate-400 uppercase mb-1 w-full text-left">{sec.type}</div>
{/* Handle both new content_svg and legacy content fields */}
{(sec.content_svg || sec.content || '').trim().startsWith('<svg') ? (
<div
className={`w-full flex-1 flex items-center justify-center ${generatedSheet.sections.length === 1 ? 'max-h-full' : ''}`}
dangerouslySetInnerHTML={{ __html: sec.content_svg || sec.content }}
/>
) : (
<div className="text-slate-700 font-medium">{sec.content_svg || sec.content}</div>
)}
</div>
))}
</div>
) : (
<>
{/* Mock Content for Default View */}
<div className="w-48 h-48 border-4 border-dashed border-slate-300 rounded-full flex items-center justify-center mb-8 mt-12">
<span className="text-6xl grayscale opacity-20">🦄</span>
</div>
<div className="flex gap-4 justify-center">
{[1, 2, 3, 4, 5].map(n => (
<div key={n} className="w-12 h-16 border-2 border-slate-800 rounded-lg flex items-center justify-center text-2xl font-bold text-slate-800 outline-dashed outline-2 outline-slate-200 -outline-offset-4">
<span className="opacity-20">{n}</span>
</div>
))}
</div>
</>
)}
</div>
</div>
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 shadow-xl shadow-primary-900/20 z-10 flex gap-2 w-max">
<Button className="bg-slate-900 text-white hover:bg-slate-800" onClick={handleDownload}>
<Download className="mr-2" size={18} />
Download PDF
</Button>
<Button className="bg-white text-slate-900 hover:bg-slate-100 border border-slate-200" onClick={handlePrint}>
<Printer className="mr-2" size={18} />
Print
</Button>
</div>
</div>
</div>
</div>
);
};