- Add query for approved designs (status = 'APPROVED') - Display 'Ready to order' section in customer account - Link directly to checkout with design pre-selected - Show estimated delivery date if set - Add success message when admin approves design Now when admin approves a design with expected delivery date: 1. Customer sees it in 'Ready to order' section 2. Can click to view and proceed to payment 3. Can see estimated delivery date Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
222 lines
9.1 KiB
TypeScript
222 lines
9.1 KiB
TypeScript
import Link from 'next/link';
|
|
import { redirect } from 'next/navigation';
|
|
import { getCurrentCustomer } from '@/lib/auth';
|
|
import { prisma } from '@/lib/prisma';
|
|
import { logoutCustomer } from './actions';
|
|
import AddressFormSection from '@/components/AddressFormSection';
|
|
import OlderOrders from '@/components/OlderOrders';
|
|
|
|
function formatPrice(cents: number) {
|
|
return `£${(cents / 100).toFixed(2)}`;
|
|
}
|
|
|
|
function statusLabel(status: string, trackingNumber?: string | null, collectedByCustomer?: boolean) {
|
|
if (status === 'DELIVERED') return collectedByCustomer ? 'Collected' : 'Delivered';
|
|
if (status === 'PAID') return trackingNumber ? 'Dispatched' : 'Awaiting dispatch';
|
|
if (status === 'FAILED') return 'Failed';
|
|
if (status === 'PENDING') return 'Waiting payment';
|
|
return 'Processing';
|
|
}
|
|
|
|
function statusClasses(status: string, trackingNumber?: string | null) {
|
|
if (status === 'DELIVERED') return 'bg-green-500/10 text-green-600';
|
|
if (status === 'PAID') return trackingNumber ? 'bg-splash-blue/10 text-splash-blue' : 'bg-splash-orange/10 text-splash-orange';
|
|
if (status === 'FAILED') return 'bg-splash-pink/10 text-splash-pink';
|
|
return 'bg-splash-orange/10 text-splash-orange';
|
|
}
|
|
|
|
const SUCCESS_MESSAGES: Record<string, string> = {
|
|
password_changed: 'Password updated successfully.',
|
|
};
|
|
|
|
export default async function AccountPage({ searchParams }: { searchParams: { success?: string } }) {
|
|
const customer = await getCurrentCustomer();
|
|
if (!customer) redirect('/account/login');
|
|
|
|
const success = searchParams.success ? SUCCESS_MESSAGES[searchParams.success] : null;
|
|
|
|
const orders = await prisma.order.findMany({
|
|
where: { customerId: customer.id },
|
|
orderBy: { createdAt: 'desc' },
|
|
include: { items: true },
|
|
});
|
|
|
|
const designReviews = await prisma.designApproval.findMany({
|
|
where: { customerEmail: customer.email, status: 'IN_REVIEW' },
|
|
include: { product: true },
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
|
|
const approvedDesigns = await prisma.designApproval.findMany({
|
|
where: { customerEmail: customer.email, status: 'APPROVED' },
|
|
include: { product: true },
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
|
|
// Separate recent (< 7 days) and older (>= 7 days) orders
|
|
const now = new Date();
|
|
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
|
|
const recentOrders = orders.filter(order => new Date(order.createdAt) > sevenDaysAgo);
|
|
const olderOrders = orders.filter(order => new Date(order.createdAt) <= sevenDaysAgo);
|
|
|
|
return (
|
|
<div className="mx-auto max-w-3xl px-6 py-12">
|
|
<div className="mb-6 border border-green-500/40 bg-green-500/10 px-4 py-3 text-sm text-green-700">
|
|
✓ Welcome back{customer.name ? `, ${customer.name}` : ''}!
|
|
</div>
|
|
|
|
{success && (
|
|
<div className="mb-6 border border-green-500/40 bg-green-500/10 px-4 py-3 text-sm text-green-700">
|
|
✓ {success}
|
|
</div>
|
|
)}
|
|
<div className="flex items-baseline justify-between gap-3">
|
|
<div>
|
|
<h1 className="font-display text-3xl">My account</h1>
|
|
<p className="mt-1 text-sm text-muted">
|
|
{customer.name ? `${customer.name} · ` : ''}
|
|
{customer.email}
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-3">
|
|
<Link
|
|
href="/account/change-password"
|
|
className="tag-label border border-line px-3 py-1.5 hover:border-clay hover:text-clay"
|
|
>
|
|
Change password
|
|
</Link>
|
|
<form action={logoutCustomer}>
|
|
<button
|
|
type="submit"
|
|
className="tag-label border border-line px-3 py-1.5 hover:border-clay hover:text-clay"
|
|
>
|
|
Log out
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<AddressFormSection customer={customer} />
|
|
|
|
<h2 className="mt-10 font-display text-xl">Design reviews</h2>
|
|
{designReviews.length === 0 ? (
|
|
<p className="mt-3 text-sm text-muted">No designs awaiting review.</p>
|
|
) : (
|
|
<div className="mt-4 divide-y divide-line border-t border-line">
|
|
{designReviews.map((design) => (
|
|
<Link
|
|
key={design.id}
|
|
href={`/designs/${design.id}/review`}
|
|
className="flex items-center gap-4 py-4 hover:bg-surface"
|
|
>
|
|
<img
|
|
src={design.designPreviewUrl}
|
|
alt="design preview"
|
|
className="h-14 w-14 border border-line object-cover"
|
|
/>
|
|
<div className="flex-1">
|
|
<p className="font-medium">{design.product.name}</p>
|
|
<p className="text-sm text-muted">
|
|
{new Date(design.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })}
|
|
</p>
|
|
</div>
|
|
<span className="text-sm text-clay">Review →</span>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<h2 className="mt-10 font-display text-xl">Ready to order</h2>
|
|
{approvedDesigns.length === 0 ? (
|
|
<p className="mt-3 text-sm text-muted">No approved designs ready to order.</p>
|
|
) : (
|
|
<div className="mt-4 divide-y divide-line border-t border-line">
|
|
{approvedDesigns.map((design) => (
|
|
<Link
|
|
key={design.id}
|
|
href={`/made-to-order/${design.product.slug}?design=${design.id}`}
|
|
className="flex items-center gap-4 py-4 hover:bg-surface"
|
|
>
|
|
<img
|
|
src={design.designPreviewUrl}
|
|
alt="design preview"
|
|
className="h-14 w-14 border border-line object-cover"
|
|
/>
|
|
<div className="flex-1">
|
|
<p className="font-medium">{design.product.name}</p>
|
|
<p className="text-sm text-muted">
|
|
Approved {new Date(design.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })}
|
|
</p>
|
|
{design.expectedDeliveryDate && (
|
|
<p className="text-xs text-muted mt-1">
|
|
Est. delivery: {new Date(design.expectedDeliveryDate).toLocaleDateString(undefined, { dateStyle: 'medium' })}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<span className="text-sm text-clay">Order →</span>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<h2 className="mt-10 font-display text-xl">Order history</h2>
|
|
{orders.length === 0 ? (
|
|
<p className="mt-3 text-sm text-muted">No orders yet.</p>
|
|
) : (
|
|
<>
|
|
{recentOrders.length > 0 && (
|
|
<div className="mt-4 divide-y divide-line border-t border-line">
|
|
{recentOrders.map((order) => (
|
|
<div key={order.id} className="border-b border-line py-4 last:border-b-0">
|
|
<Link
|
|
href={`/account/orders/${order.id}`}
|
|
className="flex flex-wrap items-center justify-between gap-2 hover:opacity-80"
|
|
>
|
|
<div>
|
|
<p className="text-sm">
|
|
{new Date(order.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })} ·{' '}
|
|
{order.items.length} item{order.items.length === 1 ? '' : 's'}
|
|
</p>
|
|
<span className={`tag-label mt-1 inline-block rounded-full px-3 py-1 ${statusClasses(order.status, order.trackingNumber)}`}>
|
|
{statusLabel(order.status, order.trackingNumber, order.collectedByCustomer)}
|
|
</span>
|
|
</div>
|
|
<span className="font-mono text-sm">{formatPrice(order.total)}</span>
|
|
</Link>
|
|
|
|
{/* Collection Ready Notification */}
|
|
{order.isCollectionPickup && order.readyForCollection && (
|
|
<div className="mt-3 border-t border-line pt-3 bg-green-500/5 p-3 rounded border border-green-500/30 text-sm">
|
|
<p className="text-green-700 font-medium">✓ Your order is ready for collection</p>
|
|
<p className="text-muted text-xs mt-1">Please come collect your order at your earliest convenience</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tracking Information */}
|
|
{order.carrier && order.trackingNumber && (
|
|
<div className="mt-3 border-t border-line pt-3 text-sm">
|
|
<p>
|
|
<strong>Tracking:</strong> {order.carrier} · {order.trackingNumber}
|
|
</p>
|
|
{order.estimatedDeliveryDate && (
|
|
<p className="text-muted">
|
|
Est. delivery: {new Date(order.estimatedDeliveryDate).toLocaleDateString(undefined, { dateStyle: 'medium' })}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{olderOrders.length > 0 && (
|
|
<OlderOrders orders={olderOrders} formatPrice={formatPrice} statusLabel={statusLabel} statusClasses={statusClasses} />
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|