Add design approval workflow and per-image dimension tracking

- Add DesignApproval and DesignApprovalMessage models for design review workflow
- Add design approval admin interface (list, detail, chat, approve/reject)
- Add per-image dimension tracking to OrderItem
- Add design submission API endpoint
- Add AdminDesignEditor for admin-side design modifications
- Add DesignApprovalChat component for customer-admin communication
- Update design specification generation with image dimension details
- Add design persistence across login with next parameter redirect
- Add font properties UI with font size and color display

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Andymick
2026-07-18 16:21:19 +01:00
co-authored by Claude Haiku 4.5
parent 10764f3a5b
commit 5c4774be84
16 changed files with 935 additions and 1 deletions
+70
View File
@@ -0,0 +1,70 @@
import Link from 'next/link';
import { prisma } from '@/lib/prisma';
import AdminNav from '@/components/AdminNav';
export default async function DesignsPage() {
const designs = await prisma.designApproval.findMany({
include: {
product: true,
messages: { orderBy: { createdAt: 'desc' }, take: 1 },
},
orderBy: { createdAt: 'desc' },
});
const pending = designs.filter((d) => d.status === 'PENDING');
const approved = designs.filter((d) => d.status === 'APPROVED');
const rejected = designs.filter((d) => d.status === 'REJECTED');
const DesignList = ({ items, status }: { items: typeof designs; status: string }) => (
<div className="mt-8">
<h2 className="font-display text-lg">{status}</h2>
{items.length === 0 ? (
<p className="mt-4 text-sm text-muted">No designs.</p>
) : (
<div className="mt-4 divide-y divide-line border-t border-line">
{items.map((d) => {
const last = d.messages[0];
return (
<Link
key={d.id}
href={`/admin/designs/${d.id}`}
className="flex items-center gap-4 py-4 hover:bg-surface"
>
<img
src={d.designPreviewUrl}
alt="design preview"
className="h-14 w-14 border border-line object-cover"
/>
<div className="flex-1">
<p className="font-medium">{d.customerName || d.customerEmail}</p>
<p className="truncate text-sm text-muted">
{last ? (
<>
{last.sender === 'ADMIN' ? 'You: ' : ''}
{last.body}
</>
) : (
'No messages yet'
)}
</p>
</div>
<span className="tag-label">{d.product.name}</span>
</Link>
);
})}
</div>
)}
</div>
);
return (
<div className="mx-auto max-w-4xl px-6 py-12">
<AdminNav />
<h1 className="font-display text-3xl">Design Approvals</h1>
<DesignList items={pending} status={`Pending (${pending.length})`} />
<DesignList items={approved} status={`Approved (${approved.length})`} />
<DesignList items={rejected} status={`Rejected (${rejected.length})`} />
</div>
);
}