From 440a735b8a062ed3d5f185a6f80ac50ebc3f1449 Mon Sep 17 00:00:00 2001 From: Andymick Date: Sat, 18 Jul 2026 20:34:19 +0100 Subject: [PATCH] Feature: Add order tracking and shipping status updates - Add carrier, trackingNumber, and estimatedDeliveryDate fields to Order model - Update order statuses: PENDING, PAID, PRINTING, PRINTED, SHIPPED, DELIVERED, FAILED - Add admin UI on order detail page to update tracking information - Display tracking info on customer account page in order history - Server action to update order tracking and status Customers can now see their shipping status and tracking details from their account page. Admins can update order status and tracking info from the order detail page. --- .claude/settings.local.json | 3 +- .../migration.sql | 4 ++ prisma/schema.prisma | 8 ++- src/app/account/page.tsx | 47 ++++++++----- src/app/admin/orders/[id]/page.tsx | 68 ++++++++++++++++++- src/app/admin/orders/actions.ts | 22 ++++++ 6 files changed, 133 insertions(+), 19 deletions(-) create mode 100644 prisma/migrations/20260718193316_add_order_tracking/migration.sql diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7ee78c5..d10072e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -75,7 +75,8 @@ "Bash(git commit -m 'Fix: Handle undefined designElements in design spec SVG rendering *)", "Bash(git commit -m 'Fix: Ensure cart is completely cleared after payment success *)", "Bash(git commit -m 'Fix: Update order status to PAID on checkout success page *)", - "Bash(git commit -m 'Improve: Cart clearing on success page with better state management *)" + "Bash(git commit -m 'Improve: Cart clearing on success page with better state management *)", + "Bash(git commit -m 'Feature: Add order tracking and shipping status updates *)" ] } } diff --git a/prisma/migrations/20260718193316_add_order_tracking/migration.sql b/prisma/migrations/20260718193316_add_order_tracking/migration.sql new file mode 100644 index 0000000..165eab2 --- /dev/null +++ b/prisma/migrations/20260718193316_add_order_tracking/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "Order" ADD COLUMN "carrier" TEXT, +ADD COLUMN "estimatedDeliveryDate" TIMESTAMP(3), +ADD COLUMN "trackingNumber" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 18f48bd..5a6a892 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -218,9 +218,15 @@ model Order { stripeSessionId String? @unique email String? shippingAddress String? // JSON — collected by Stripe Checkout, saved once payment completes - status String @default("PENDING") // PENDING | PAID | FAILED + status String @default("PENDING") // PENDING | PAID | PRINTING | PRINTED | SHIPPED | DELIVERED | FAILED archived Boolean @default(false) total Int // cents + + // Shipping & tracking info + carrier String? // e.g. "Royal Mail", "DPD", "Courier" + trackingNumber String? // tracking number from carrier + estimatedDeliveryDate DateTime? // estimated delivery date + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx index ad1593c..550938b 100644 --- a/src/app/account/page.tsx +++ b/src/app/account/page.tsx @@ -90,22 +90,37 @@ export default async function AccountPage() { ) : (
{orders.map((order) => ( - -
-

- {new Date(order.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })} ·{' '} - {order.items.length} item{order.items.length === 1 ? '' : 's'} -

- - {statusLabel(order.status)} - -
- {formatPrice(order.total)} - +
+ +
+

+ {new Date(order.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })} ·{' '} + {order.items.length} item{order.items.length === 1 ? '' : 's'} +

+ + {statusLabel(order.status)} + +
+ {formatPrice(order.total)} + + + {/* Tracking Information */} + {order.carrier && order.trackingNumber && ( +
+

+ Tracking: {order.carrier} · {order.trackingNumber} +

+ {order.estimatedDeliveryDate && ( +

+ Est. delivery: {new Date(order.estimatedDeliveryDate).toLocaleDateString(undefined, { dateStyle: 'medium' })} +

+ )} +
+ )} +
))}
)} diff --git a/src/app/admin/orders/[id]/page.tsx b/src/app/admin/orders/[id]/page.tsx index f37eadc..cb1f20c 100644 --- a/src/app/admin/orders/[id]/page.tsx +++ b/src/app/admin/orders/[id]/page.tsx @@ -1,7 +1,7 @@ import { notFound } from 'next/navigation'; import { prisma } from '@/lib/prisma'; import AdminNav from '@/components/AdminNav'; -import { archiveOrder, unarchiveOrder } from '../actions'; +import { archiveOrder, unarchiveOrder, updateOrderTracking } from '../actions'; function formatPrice(cents: number) { return `£${(cents / 100).toFixed(2)}`; @@ -76,6 +76,72 @@ export default async function OrderDetailPage({ params }: { params: { id: string )} + {/* Tracking Information */} +
+

Shipping & Tracking

+
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+
{order.items.map((item) => { let hasFrontDesign = false; diff --git a/src/app/admin/orders/actions.ts b/src/app/admin/orders/actions.ts index 1b93e4e..518640b 100644 --- a/src/app/admin/orders/actions.ts +++ b/src/app/admin/orders/actions.ts @@ -35,3 +35,25 @@ export async function unarchiveOrder(formData: FormData) { await prisma.order.update({ where: { id }, data: { archived: false } }); redirect('/admin/orders?archived=1'); } + +export async function updateOrderTracking(formData: FormData) { + const id = String(formData.get('orderId') ?? ''); + const status = String(formData.get('status') ?? 'PENDING'); + const carrier = formData.get('carrier') ? String(formData.get('carrier')) : null; + const trackingNumber = formData.get('trackingNumber') ? String(formData.get('trackingNumber')) : null; + const estimatedDeliveryDate = formData.get('estimatedDeliveryDate') ? new Date(String(formData.get('estimatedDeliveryDate'))) : null; + + if (!id) return; + + await prisma.order.update({ + where: { id }, + data: { + status, + carrier, + trackingNumber, + estimatedDeliveryDate, + }, + }); + + // TODO: Send email notification to customer when status changes +}