Initial commit: Craft2Prints with Stages 1-3

This commit is contained in:
Andymick
2026-07-15 18:09:59 +01:00
commit 41937ffc15
158 changed files with 16054 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
import { notFound } from 'next/navigation';
import { prisma } from '@/lib/prisma';
import AdminNav from '@/components/AdminNav';
import { archiveOrder, unarchiveOrder } from '../actions';
function formatPrice(cents: number) {
return `£${(cents / 100).toFixed(2)}`;
}
export default async function OrderDetailPage({ params }: { params: { id: string } }) {
const order = await prisma.order.findUnique({
where: { id: params.id },
include: { items: true },
});
if (!order) notFound();
const shipping = order.shippingAddress ? JSON.parse(order.shippingAddress) : null;
return (
<div className="mx-auto max-w-3xl px-6 py-12">
<AdminNav />
<div className="flex items-baseline justify-between gap-3">
<h1 className="font-display text-3xl">Order</h1>
<div className="flex items-center gap-3">
{order.archived && <span className="tag-label">Archived</span>}
<form action={order.archived ? unarchiveOrder : archiveOrder}>
<input type="hidden" name="id" value={order.id} />
<button type="submit" className="tag-label border border-line px-3 py-1.5 hover:border-clay hover:text-clay">
{order.archived ? 'Unarchive' : 'Archive'}
</button>
</form>
<span
className={`tag-label rounded-full px-3 py-1 ${
order.status === 'PAID'
? 'bg-splash-blue/10 text-splash-blue'
: order.status === 'FAILED'
? 'bg-splash-pink/10 text-splash-pink'
: 'bg-splash-orange/10 text-splash-orange'
}`}
>
{order.status}
</span>
</div>
</div>
<div className="mt-6 grid gap-6 border border-line bg-surface p-6 sm:grid-cols-2">
<div>
<p className="tag-label mb-1">Customer</p>
<p className="text-sm">{order.email ?? 'Not yet available'}</p>
</div>
<div>
<p className="tag-label mb-1">Placed</p>
<p className="text-sm">
{new Date(order.createdAt).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' })}
</p>
</div>
{shipping && (
<div className="sm:col-span-2">
<p className="tag-label mb-1">Shipping address</p>
<p className="text-sm">
{shipping.name}
<br />
{[shipping.address?.line1, shipping.address?.line2].filter(Boolean).join(', ')}
<br />
{[shipping.address?.city, shipping.address?.postal_code].filter(Boolean).join(', ')}
<br />
{shipping.address?.country}
</p>
</div>
)}
</div>
<div className="mt-10 divide-y divide-line border-t border-line">
{order.items.map((item) => {
const design = JSON.parse(item.designJson) as { front: unknown[]; back: unknown[] };
const hasFrontDesign = design.front.length > 0;
const hasBackDesign = design.back.length > 0;
const frontPlacement = item.placementPreviewUrl || item.designPreviewUrl;
return (
<div key={item.id} className="py-8">
<div className="flex flex-wrap items-baseline justify-between gap-2">
<p className="font-display text-xl">{item.productName}</p>
<p className="text-sm text-muted">
{item.color}
{item.size ? `, ${item.size}` : ''} · qty {item.quantity} ·{' '}
{formatPrice(item.unitPrice * item.quantity)}
</p>
</div>
<div className="mt-4 grid gap-6 sm:grid-cols-2">
<div>
<img
src={frontPlacement || undefined}
alt={`${item.productName} — where the design sits`}
className="w-full max-w-md border border-line bg-paper object-contain"
/>
<div className="mt-2 flex items-center justify-between">
<p className="tag-label">{item.placementPreviewUrl ? 'On product — front' : 'Front'}</p>
{hasFrontDesign && (
<a
href={item.designPreviewUrl}
download={`order-${order.id}-${item.id}-front.png`}
className="text-xs text-clay hover:text-clay-dark"
>
Download print file
</a>
)}
</div>
</div>
{item.placementPreviewUrlBack && (
<div>
<img
src={item.placementPreviewUrlBack}
alt={`${item.productName} back — where the design sits`}
className="w-full max-w-md border border-line bg-paper object-contain"
/>
<div className="mt-2 flex items-center justify-between">
<p className="tag-label">On product back</p>
{hasBackDesign && item.designPreviewUrlBack && (
<a
href={item.designPreviewUrlBack}
download={`order-${order.id}-${item.id}-back.png`}
className="text-xs text-clay hover:text-clay-dark"
>
Download print file
</a>
)}
</div>
</div>
)}
</div>
</div>
);
})}
</div>
<div className="mt-6 flex justify-end">
<div className="flex items-baseline gap-3">
<span className="tag-label">Total</span>
<span className="font-mono text-xl">{formatPrice(order.total)}</span>
</div>
</div>
</div>
);
}