"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import Link from "next/link"; import { getSpaceIdFromCookie } from "@/lib/spaces"; import { MOCKUP_CONFIGS, generateMockup } from "@/lib/mockups"; import type { SpaceConfig } from "@/lib/spaces"; const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api"; interface UploadedDesign { slug: string; name: string; image_url: string; status: string; products: { type: string; price: number }[]; } export default function UploadPage() { const [file, setFile] = useState(null); const [preview, setPreview] = useState(null); const [name, setName] = useState(""); const [tags, setTags] = useState(""); const [isDragging, setIsDragging] = useState(false); const [mockups, setMockups] = useState>({}); const [isUploading, setIsUploading] = useState(false); const [error, setError] = useState(null); const [uploadedDesign, setUploadedDesign] = useState(null); const [isActivating, setIsActivating] = useState(false); const [spaceConfig, setSpaceConfig] = useState(null); const fileInputRef = useRef(null); useEffect(() => { const spaceId = getSpaceIdFromCookie(); fetch(`${API_URL}/spaces/${spaceId}`) .then((res) => (res.ok ? res.json() : null)) .then(setSpaceConfig) .catch(() => {}); }, []); // Generate mockups when preview changes useEffect(() => { if (!preview) { setMockups({}); return; } MOCKUP_CONFIGS.forEach(async (config) => { try { const result = await generateMockup(preview, config); setMockups((prev) => ({ ...prev, [config.productType]: result })); } catch { // Template load failure — fallback handled in render } }); }, [preview]); const handleFile = useCallback((f: File) => { if (!f.type.startsWith("image/")) { setError("Please select an image file (PNG, JPEG, or WebP)"); return; } if (f.size > 10 * 1024 * 1024) { setError("File must be under 10 MB"); return; } setError(null); setFile(f); setPreview(URL.createObjectURL(f)); if (!name) { // Auto-fill name from filename const baseName = f.name.replace(/\.[^.]+$/, "").replace(/[-_]/g, " "); setName(baseName.charAt(0).toUpperCase() + baseName.slice(1)); } }, [name]); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const droppedFile = e.dataTransfer.files[0]; if (droppedFile) handleFile(droppedFile); }, [handleFile] ); const handleUpload = async (e: React.FormEvent) => { e.preventDefault(); if (!file || !name) return; setIsUploading(true); setError(null); try { const formData = new FormData(); formData.append("file", file); formData.append("name", name); formData.append("space", getSpaceIdFromCookie()); if (tags) formData.append("tags", tags); const response = await fetch(`${API_URL}/design/upload`, { method: "POST", body: formData, }); if (!response.ok) { const data = await response.json(); throw new Error(data.detail || "Upload failed"); } const design = await response.json(); setUploadedDesign(design); } catch (err) { setError(err instanceof Error ? err.message : "An error occurred"); } finally { setIsUploading(false); } }; const handleActivate = async () => { if (!uploadedDesign) return; setIsActivating(true); setError(null); try { const response = await fetch( `${API_URL}/design/${uploadedDesign.slug}/activate`, { method: "POST" } ); if (!response.ok) { const data = await response.json(); throw new Error(data.detail || "Failed to activate design"); } setUploadedDesign({ ...uploadedDesign, status: "active" }); } catch (err) { setError(err instanceof Error ? err.message : "An error occurred"); } finally { setIsActivating(false); } }; const handleDelete = async () => { if (!uploadedDesign) return; try { const response = await fetch( `${API_URL}/design/${uploadedDesign.slug}`, { method: "DELETE" } ); if (!response.ok) { const data = await response.json(); throw new Error(data.detail || "Failed to delete design"); } resetForm(); } catch (err) { setError(err instanceof Error ? err.message : "An error occurred"); } }; const resetForm = () => { setFile(null); setPreview(null); setName(""); setTags(""); setMockups({}); setUploadedDesign(null); setError(null); }; return (

Upload Swag

Upload your own design and preview it on{" "} {spaceConfig?.name || "rSwag"} merchandise. See how it looks on shirts, stickers, and prints before ordering.

{/* Left: Upload Form */}
setName(e.target.value)} placeholder="e.g., My Custom Logo" className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-primary" required disabled={isUploading || !!uploadedDesign} />
{/* Drag & Drop Zone */}
{ e.preventDefault(); setIsDragging(true); }} onDragLeave={() => setIsDragging(false)} onDrop={handleDrop} onClick={() => !uploadedDesign && fileInputRef.current?.click() } className={`relative border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${ isDragging ? "border-primary bg-primary/5" : preview ? "border-primary/50" : "border-muted-foreground/30 hover:border-primary/50" } ${uploadedDesign ? "pointer-events-none opacity-60" : ""}`} > { const f = e.target.files?.[0]; if (f) handleFile(f); }} className="hidden" /> {preview ? (
Preview

{file?.name} ( {((file?.size || 0) / 1024 / 1024).toFixed(1)} MB)

{!uploadedDesign && ( )}
) : (

Drag & drop your design here, or{" "} browse

PNG, JPEG, or WebP. Max 10 MB. Min 500x500px.

)}
setTags(e.target.value)} placeholder="logo, custom, brand" className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-primary" disabled={isUploading || !!uploadedDesign} />
{error && (
{error}
)} {!uploadedDesign && ( )}
{/* Post-upload actions */} {uploadedDesign && (

{uploadedDesign.name}

Status:{" "} {uploadedDesign.status}

{uploadedDesign.status === "draft" ? ( <> ) : ( <> View in Store )}
)}
{/* Right: Mockup Previews */}

Product Previews

{preview ? (
{MOCKUP_CONFIGS.map((config) => (
{mockups[config.productType] ? ( {`${config.label} ) : (
)}
{config.label} from ${config.price.toFixed(2)}
))}
) : (

Upload a design to see product previews

)}
{/* Tips */}

Upload Tips

  • • Use a high-resolution image (at least 2000x2000px for best print quality)
  • • PNG with transparency works best for stickers and shirts
  • • Keep important elements centered — edges may be cropped on some products
  • • Designs start as drafts — preview before adding to the store
); }