335 lines
11 KiB
TypeScript
335 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api";
|
|
|
|
interface GeneratedDesign {
|
|
slug: string;
|
|
name: string;
|
|
image_url: string;
|
|
status: string;
|
|
}
|
|
|
|
export default function DesignPage() {
|
|
const [name, setName] = useState("");
|
|
const [concept, setConcept] = useState("");
|
|
const [tags, setTags] = useState("");
|
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [generatedDesign, setGeneratedDesign] = useState<GeneratedDesign | null>(null);
|
|
const [isActivating, setIsActivating] = useState(false);
|
|
|
|
const handleGenerate = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setIsGenerating(true);
|
|
setError(null);
|
|
setGeneratedDesign(null);
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/design/generate`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
name,
|
|
concept,
|
|
tags: tags.split(",").map((t) => t.trim()).filter(Boolean),
|
|
product_type: "sticker",
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json();
|
|
throw new Error(data.detail || "Failed to generate design");
|
|
}
|
|
|
|
const design = await response.json();
|
|
setGeneratedDesign(design);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "An error occurred");
|
|
} finally {
|
|
setIsGenerating(false);
|
|
}
|
|
};
|
|
|
|
const handleActivate = async () => {
|
|
if (!generatedDesign) return;
|
|
|
|
setIsActivating(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`${API_URL}/design/${generatedDesign.slug}/activate`,
|
|
{
|
|
method: "POST",
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json();
|
|
throw new Error(data.detail || "Failed to activate design");
|
|
}
|
|
|
|
setGeneratedDesign({ ...generatedDesign, status: "active" });
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "An error occurred");
|
|
} finally {
|
|
setIsActivating(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
if (!generatedDesign) return;
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`${API_URL}/design/${generatedDesign.slug}`,
|
|
{
|
|
method: "DELETE",
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json();
|
|
throw new Error(data.detail || "Failed to delete design");
|
|
}
|
|
|
|
setGeneratedDesign(null);
|
|
setName("");
|
|
setConcept("");
|
|
setTags("");
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "An error occurred");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="max-w-4xl mx-auto">
|
|
<h1 className="text-3xl font-bold mb-2">Design Swag</h1>
|
|
<p className="text-muted-foreground mb-8">
|
|
Create custom mycopunk merchandise using AI. Describe your vision and
|
|
we'll generate a unique design.
|
|
</p>
|
|
|
|
<div className="grid md:grid-cols-2 gap-8">
|
|
{/* Form */}
|
|
<div>
|
|
<form onSubmit={handleGenerate} className="space-y-6">
|
|
<div>
|
|
<label
|
|
htmlFor="name"
|
|
className="block text-sm font-medium mb-2"
|
|
>
|
|
Design Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="name"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="e.g., Mycelial Revolution"
|
|
className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
|
|
required
|
|
disabled={isGenerating}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
htmlFor="concept"
|
|
className="block text-sm font-medium mb-2"
|
|
>
|
|
Design Concept
|
|
</label>
|
|
<textarea
|
|
id="concept"
|
|
value={concept}
|
|
onChange={(e) => setConcept(e.target.value)}
|
|
placeholder="Describe your design idea... e.g., A mushroom growing through cracked concrete, symbolizing nature reclaiming urban spaces. Include the phrase 'NATURE WINS' in bold letters."
|
|
rows={4}
|
|
className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-primary resize-none"
|
|
required
|
|
disabled={isGenerating}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
htmlFor="tags"
|
|
className="block text-sm font-medium mb-2"
|
|
>
|
|
Tags (comma-separated)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="tags"
|
|
value={tags}
|
|
onChange={(e) => setTags(e.target.value)}
|
|
placeholder="mycopunk, nature, urban, punk"
|
|
className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
|
|
disabled={isGenerating}
|
|
/>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="p-4 bg-red-50 border border-red-200 rounded-md text-red-700">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={isGenerating || !name || !concept}
|
|
className="w-full px-6 py-3 bg-primary text-primary-foreground rounded-md font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{isGenerating ? (
|
|
<span className="flex items-center justify-center gap-2">
|
|
<svg
|
|
className="animate-spin h-5 w-5"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
Generating Design...
|
|
</span>
|
|
) : (
|
|
"Generate Design"
|
|
)}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
{/* Preview */}
|
|
<div>
|
|
<h2 className="text-lg font-semibold mb-4">Preview</h2>
|
|
<div className="aspect-square border-2 border-dashed rounded-lg flex items-center justify-center bg-muted/30">
|
|
{generatedDesign ? (
|
|
<img
|
|
src={`${API_URL.replace("/api", "")}${generatedDesign.image_url}`}
|
|
alt={generatedDesign.name}
|
|
className="w-full h-full object-contain rounded-lg"
|
|
/>
|
|
) : isGenerating ? (
|
|
<div className="text-center text-muted-foreground">
|
|
<svg
|
|
className="animate-spin h-12 w-12 mx-auto mb-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
<p>Creating your design...</p>
|
|
<p className="text-sm">This may take a moment</p>
|
|
</div>
|
|
) : (
|
|
<p className="text-muted-foreground">
|
|
Your design will appear here
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{generatedDesign && (
|
|
<div className="mt-4 space-y-4">
|
|
<div className="p-4 bg-muted/50 rounded-lg">
|
|
<p className="font-medium">{generatedDesign.name}</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
Status:{" "}
|
|
<span
|
|
className={
|
|
generatedDesign.status === "active"
|
|
? "text-green-600"
|
|
: "text-yellow-600"
|
|
}
|
|
>
|
|
{generatedDesign.status}
|
|
</span>
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
{generatedDesign.status === "draft" ? (
|
|
<>
|
|
<button
|
|
onClick={handleActivate}
|
|
disabled={isActivating}
|
|
className="flex-1 px-4 py-2 bg-green-600 text-white rounded-md font-medium hover:bg-green-700 transition-colors disabled:opacity-50"
|
|
>
|
|
{isActivating ? "Activating..." : "Add to Store"}
|
|
</button>
|
|
<button
|
|
onClick={handleDelete}
|
|
className="px-4 py-2 border border-red-300 text-red-600 rounded-md font-medium hover:bg-red-50 transition-colors"
|
|
>
|
|
Discard
|
|
</button>
|
|
</>
|
|
) : (
|
|
<Link
|
|
href="/products"
|
|
className="flex-1 px-4 py-2 bg-primary text-primary-foreground rounded-md font-medium hover:bg-primary/90 transition-colors text-center"
|
|
>
|
|
View in Store
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tips */}
|
|
<div className="mt-12 p-6 bg-muted/30 rounded-lg">
|
|
<h3 className="font-semibold mb-3">Design Tips</h3>
|
|
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
<li>
|
|
• Be specific about text you want included - the AI will try to
|
|
render it in the design
|
|
</li>
|
|
<li>
|
|
• Mention colors, mood, and style preferences in your concept
|
|
</li>
|
|
<li>
|
|
• Mycopunk themes work great: mycelium networks, mushrooms, punk
|
|
aesthetics, decentralization
|
|
</li>
|
|
<li>
|
|
• Generated designs start as drafts - preview before adding to the
|
|
store
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|