Feature: Add 24-hour cleanup for incomplete orders and update status display
- Create cleanupOrders utility to delete pending orders older than 24 hours - Update admin notifications to only count recent pending orders (< 24h old) - Automatically call cleanup when customer initiates checkout - Change status display from "PENDING" to "Waiting payment" for customer-facing views - Update both customer account page and admin orders page with new status label - Incomplete orders automatically purged, reducing admin clutter Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
fa202db4a7
commit
3e90928986
@@ -88,7 +88,8 @@
|
|||||||
"Bash(git commit -m 'Add: Prominent '\\\\''Shop plain items'\\\\'' section to homepage *)",
|
"Bash(git commit -m 'Add: Prominent '\\\\''Shop plain items'\\\\'' section to homepage *)",
|
||||||
"Bash(git commit -m 'Feature: Royal Mail shipping integration with weight-based postage *)",
|
"Bash(git commit -m 'Feature: Royal Mail shipping integration with weight-based postage *)",
|
||||||
"Bash(git commit -m 'Add: Weight field to product admin pages *)",
|
"Bash(git commit -m 'Add: Weight field to product admin pages *)",
|
||||||
"Bash(git commit -m 'Add: Shipping selector component and checkout integration *)"
|
"Bash(git commit -m 'Add: Shipping selector component and checkout integration *)",
|
||||||
|
"Bash(node clear-pending-orders.js)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ function formatPrice(cents: number) {
|
|||||||
function statusLabel(status: string) {
|
function statusLabel(status: string) {
|
||||||
if (status === 'PAID') return 'Confirmed';
|
if (status === 'PAID') return 'Confirmed';
|
||||||
if (status === 'FAILED') return 'Failed';
|
if (status === 'FAILED') return 'Failed';
|
||||||
|
if (status === 'PENDING') return 'Waiting payment';
|
||||||
return 'Processing';
|
return 'Processing';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ function formatPrice(cents: number) {
|
|||||||
return `£${(cents / 100).toFixed(2)}`;
|
return `£${(cents / 100).toFixed(2)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function statusLabel(status: string) {
|
||||||
|
if (status === 'PAID') return 'Confirmed';
|
||||||
|
if (status === 'FAILED') return 'Failed';
|
||||||
|
if (status === 'PENDING') return 'Waiting payment';
|
||||||
|
return 'Processing';
|
||||||
|
}
|
||||||
|
|
||||||
const STATUS_STYLES: Record<string, string> = {
|
const STATUS_STYLES: Record<string, string> = {
|
||||||
PAID: 'bg-splash-blue/10 text-splash-blue',
|
PAID: 'bg-splash-blue/10 text-splash-blue',
|
||||||
PENDING: 'bg-splash-orange/10 text-splash-orange',
|
PENDING: 'bg-splash-orange/10 text-splash-orange',
|
||||||
@@ -121,7 +128,7 @@ export default async function OrdersPage({ searchParams }: { searchParams: { arc
|
|||||||
)}
|
)}
|
||||||
<span className="font-mono text-sm">{formatPrice(order.total)}</span>
|
<span className="font-mono text-sm">{formatPrice(order.total)}</span>
|
||||||
<span className={`tag-label rounded-full px-3 py-1 ${STATUS_STYLES[order.status] ?? ''}`}>
|
<span className={`tag-label rounded-full px-3 py-1 ${STATUS_STYLES[order.status] ?? ''}`}>
|
||||||
{order.status}
|
{statusLabel(order.status)}
|
||||||
</span>
|
</span>
|
||||||
<form action={showArchived ? unarchiveOrder : archiveOrder}>
|
<form action={showArchived ? unarchiveOrder : archiveOrder}>
|
||||||
<input type="hidden" name="id" value={order.id} />
|
<input type="hidden" name="id" value={order.id} />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getEffectivePrice } from '@/lib/pricing';
|
|||||||
import { fetchActivePromotions } from '@/lib/promotions';
|
import { fetchActivePromotions } from '@/lib/promotions';
|
||||||
import { checkRateLimit, getClientIp } from '@/lib/rateLimit';
|
import { checkRateLimit, getClientIp } from '@/lib/rateLimit';
|
||||||
import { getRoyalMailShippingQuotes } from '@/lib/royalmail-shipping';
|
import { getRoyalMailShippingQuotes } from '@/lib/royalmail-shipping';
|
||||||
|
import { cleanupPendingOrders } from '@/lib/cleanupOrders';
|
||||||
|
|
||||||
type CheckoutCartItem = {
|
type CheckoutCartItem = {
|
||||||
productId: string;
|
productId: string;
|
||||||
@@ -51,6 +52,9 @@ export async function POST(req: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up old incomplete orders (non-blocking)
|
||||||
|
cleanupPendingOrders().catch((err) => console.error('Cleanup error:', err));
|
||||||
|
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
console.log('Checkout request received with items:', body.items?.length, 'customerId:', body.customerId);
|
console.log('Checkout request received with items:', body.items?.length, 'customerId:', body.customerId);
|
||||||
const items: CheckoutCartItem[] = body.items ?? [];
|
const items: CheckoutCartItem[] = body.items ?? [];
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
import { prisma } from '@/lib/prisma';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
import { cleanupPendingOrders } from '@/lib/cleanupOrders';
|
||||||
import AdminNotificationsBadge from './AdminNotificationsBadge';
|
import AdminNotificationsBadge from './AdminNotificationsBadge';
|
||||||
|
|
||||||
export default async function AdminNotifications() {
|
export default async function AdminNotifications() {
|
||||||
try {
|
try {
|
||||||
|
// Clean up old incomplete orders (older than 24 hours)
|
||||||
|
await cleanupPendingOrders();
|
||||||
|
|
||||||
|
// Only count pending orders that are less than 24 hours old (still waiting for payment)
|
||||||
|
const recentThreshold = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||||
const [
|
const [
|
||||||
pendingOrders,
|
pendingOrders,
|
||||||
designApprovalsInReview,
|
designApprovalsInReview,
|
||||||
photoRequests,
|
photoRequests,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
prisma.order.count({
|
prisma.order.count({
|
||||||
where: { status: 'PENDING' },
|
where: {
|
||||||
|
status: 'PENDING',
|
||||||
|
createdAt: { gte: recentThreshold },
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
prisma.designApproval.count({
|
prisma.designApproval.count({
|
||||||
where: { status: 'IN_REVIEW' },
|
where: { status: 'IN_REVIEW' },
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { prisma } from './prisma';
|
||||||
|
|
||||||
|
export async function cleanupPendingOrders() {
|
||||||
|
try {
|
||||||
|
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const result = await prisma.order.deleteMany({
|
||||||
|
where: {
|
||||||
|
status: 'PENDING',
|
||||||
|
createdAt: { lt: twentyFourHoursAgo },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.count > 0) {
|
||||||
|
console.log(`✓ Cleaned up ${result.count} incomplete orders older than 24 hours`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.count;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error cleaning up pending orders:', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user