Design Canvas (Primary) - FabricDesignCanvasV2: Fabric.js-based product customization canvas - Lazy-loads Fabric.js to minimize bundle impact - Renders product mockup with print area guide (magenta dashed box) - Supports front/back view switching with correct product images - Color swatch selection for garment customization - Text element creation with selection handles and transform controls - Fixed React dependency array to prevent infinite renders Support Components - DesignCanvasWrapper: Client-side wrapper for SSR compatibility - DesignCanvasErrorBoundary: Error boundary for canvas failures - ThemeInitializer: Theme detection and application Configuration - Updated .claude/launch.json with dev server settings - Updated .claude/settings.local.json with local preferences - Updated next.config.mjs for production build optimization - Dependency updates in package.json and package-lock.json Known Issues & TODO - Image element rendering disabled (Fabric.js compatibility investigation) - Text editing UI controls needed (font, size, color pickers) - Delete/undo functionality pending - Print area alignment needs visual verification - "Add to Cart" integration with design serialization pending Testing Artifacts - check_*.js: Product verification scripts for debugging Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
97 lines
3.6 KiB
TypeScript
97 lines
3.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import dynamic from 'next/dynamic';
|
|
import type { ProductDTO } from '@/lib/types';
|
|
|
|
interface DesignApproval {
|
|
id: string;
|
|
customerEmail: string;
|
|
customerName: string | null;
|
|
productId: string;
|
|
product: ProductDTO;
|
|
designJson: string;
|
|
color: string;
|
|
size: string | null;
|
|
status: string;
|
|
}
|
|
|
|
const DesignCanvas = dynamic(() => import('@/components/DesignCanvas'), { ssr: false });
|
|
|
|
export default function EditDesignPage({ params }: { params: { id: string } }) {
|
|
const router = useRouter();
|
|
const [design, setDesign] = useState<DesignApproval | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [sending, setSending] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetch(`/api/designs/${params.id}`)
|
|
.then((r) => r.json())
|
|
.then((data) => {
|
|
if (data.product) {
|
|
const parseIfString = (val: any) => typeof val === 'string' ? JSON.parse(val) : val;
|
|
data.product.colors = Array.isArray(data.product.colors) ? data.product.colors : parseIfString(data.product.colors || '[]');
|
|
data.product.sizes = Array.isArray(data.product.sizes) ? data.product.sizes : (data.product.sizes ? parseIfString(data.product.sizes) : []);
|
|
data.product.colorPhotos = typeof data.product.colorPhotos === 'object' ? data.product.colorPhotos : (data.product.colorPhotos ? parseIfString(data.product.colorPhotos) : {});
|
|
data.product.colorPhotosBack = typeof data.product.colorPhotosBack === 'object' ? data.product.colorPhotosBack : (data.product.colorPhotosBack ? parseIfString(data.product.colorPhotosBack) : {});
|
|
data.product.printArea = typeof data.product.printArea === 'object' ? data.product.printArea : parseIfString(data.product.printArea || '{"xPct":0,"yPct":0,"wPct":1,"hPct":1}');
|
|
if (data.product.printAreaBack) {
|
|
data.product.printAreaBack = typeof data.product.printAreaBack === 'object' ? data.product.printAreaBack : parseIfString(data.product.printAreaBack);
|
|
}
|
|
}
|
|
setDesign(data);
|
|
setLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
console.error('Error fetching design:', err);
|
|
setLoading(false);
|
|
});
|
|
}, [params.id]);
|
|
|
|
const handleSendToCustomer = async () => {
|
|
setSending(true);
|
|
try {
|
|
const res = await fetch(`/api/designs/${params.id}/send-to-customer`, {
|
|
method: 'POST',
|
|
});
|
|
if (res.ok) {
|
|
router.push(`/admin/designs/${params.id}`);
|
|
} else {
|
|
alert('Failed to send to customer');
|
|
}
|
|
} finally {
|
|
setSending(false);
|
|
}
|
|
};
|
|
|
|
if (loading) return <div className="p-6">Loading...</div>;
|
|
if (!design) return <div className="p-6 text-sm text-muted">Design not found.</div>;
|
|
|
|
return (
|
|
<div className="min-h-screen bg-paper">
|
|
<div className="mx-auto max-w-7xl px-6 py-8">
|
|
<Link href={`/admin/designs/${params.id}`} className="text-sm text-clay hover:text-clay-dark mb-4 inline-block">
|
|
← Back to design approval
|
|
</Link>
|
|
|
|
<div className="mb-6">
|
|
<h1 className="font-display text-3xl mb-2">{design.product.name}</h1>
|
|
<p className="text-sm text-muted">Editing design for {design.customerName || design.customerEmail}</p>
|
|
</div>
|
|
|
|
{/* Full Design Canvas */}
|
|
<DesignCanvas
|
|
product={design.product}
|
|
isAdminEditing={true}
|
|
initialDesignJson={design.designJson}
|
|
initialColor={design.color}
|
|
initialSize={design.size}
|
|
designId={params.id}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|