update storefront page
This commit is contained in:
@@ -93,6 +93,27 @@ const getStatusVariant = (status: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusStyle = (status: string) => {
|
||||
switch (status) {
|
||||
case 'acknowledged':
|
||||
return 'bg-purple-500/10 text-purple-500 border-purple-500/20';
|
||||
case 'paid':
|
||||
return 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20';
|
||||
case 'shipped':
|
||||
return 'bg-blue-500/10 text-blue-500 border-blue-500/20';
|
||||
case 'completed':
|
||||
return 'bg-green-500/10 text-green-500 border-green-500/20';
|
||||
case 'cancelled':
|
||||
return 'bg-red-500/10 text-red-500 border-red-500/20';
|
||||
case 'unpaid':
|
||||
return 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20';
|
||||
case 'confirming':
|
||||
return 'bg-orange-500/10 text-orange-500 border-orange-500/20';
|
||||
default:
|
||||
return 'bg-gray-500/10 text-gray-500 border-gray-500/20';
|
||||
}
|
||||
};
|
||||
|
||||
export default function OrderDetailsPage() {
|
||||
const [order, setOrder] = useState<Order | null>(null);
|
||||
const [trackingNumber, setTrackingNumber] = useState("");
|
||||
@@ -471,292 +492,255 @@ export default function OrderDetailsPage() {
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-4">
|
||||
<h1 className="text-3xl font-bold">Order Details: {order?.orderId}</h1>
|
||||
<div className="space-y-6">
|
||||
{/* Header Section */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<h1 className="text-2xl font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<Package className="mr-2 h-6 w-6" />
|
||||
Order {order?.orderId}
|
||||
</h1>
|
||||
<div className={`px-3 py-1 rounded-full border ${getStatusStyle(order?.status || '')}`}>
|
||||
{order?.status?.toUpperCase()}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant={getStatusVariant(order?.status || '')}
|
||||
className={`${
|
||||
order?.status === 'acknowledged'
|
||||
? '!bg-purple-600 hover:!bg-purple-700 text-white'
|
||||
: order?.status === 'paid'
|
||||
? 'bg-emerald-600 hover:bg-emerald-700 text-white'
|
||||
: order?.status === 'shipped'
|
||||
? 'bg-blue-600 hover:bg-blue-700 text-white'
|
||||
: order?.status === 'completed'
|
||||
? 'bg-green-600 hover:bg-green-700 text-white'
|
||||
: ''
|
||||
}`}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{prevOrderId && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/dashboard/orders/${prevOrderId}`)}
|
||||
>
|
||||
{order?.status}
|
||||
</Badge>
|
||||
</div>
|
||||
Previous Order
|
||||
</Button>
|
||||
)}
|
||||
{nextOrderId && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/dashboard/orders/${nextOrderId}`)}
|
||||
>
|
||||
Next Order
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Order Navigation - Moved to top */}
|
||||
<div className="flex justify-between items-center border-b pb-4">
|
||||
<div className="w-[140px]">
|
||||
{nextOrderId && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
router.push(`/dashboard/orders/${nextOrderId}`);
|
||||
}}
|
||||
className="flex items-center"
|
||||
>
|
||||
<span className="mr-2">←</span>
|
||||
Older
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
Navigate Orders
|
||||
</div>
|
||||
<div className="w-[140px] flex justify-end">
|
||||
{prevOrderId && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
router.push(`/dashboard/orders/${prevOrderId}`);
|
||||
}}
|
||||
className="flex items-center"
|
||||
>
|
||||
Newer
|
||||
<span className="ml-2">→</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="grid grid-cols-3 gap-6">
|
||||
{/* Left Column - Order Details */}
|
||||
<div className="col-span-2 space-y-6">
|
||||
{/* Products Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>PGP Encrypted Address</CardTitle>
|
||||
<CardDescription>
|
||||
Securely encrypted delivery address
|
||||
</CardDescription>
|
||||
<CardTitle className="text-lg font-medium">Products</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Textarea
|
||||
value={order?.pgpAddress || ""}
|
||||
readOnly
|
||||
className="font-mono text-xs"
|
||||
rows={10}
|
||||
/>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Product</TableHead>
|
||||
<TableHead className="text-right">Quantity</TableHead>
|
||||
<TableHead className="text-right">Price/Unit</TableHead>
|
||||
<TableHead className="text-right">Total</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{order?.products.map((product) => (
|
||||
<TableRow key={product._id}>
|
||||
<TableCell className="font-medium">
|
||||
{productNames[product.productId] || "Loading..."}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">{product.quantity}</TableCell>
|
||||
<TableCell className="text-right">${product.pricePerUnit.toFixed(2)}</TableCell>
|
||||
<TableCell className="text-right">${product.totalItemPrice.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} />
|
||||
<TableCell className="text-right font-medium">Shipping ({order?.shippingMethod.type})</TableCell>
|
||||
<TableCell className="text-right">${order?.shippingMethod.price.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} />
|
||||
<TableCell className="text-right font-bold">Total</TableCell>
|
||||
<TableCell className="text-right font-bold">${order?.totalPrice.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => copyToClipboard(order?.pgpAddress || "")}
|
||||
>
|
||||
<Clipboard className="w-4 h-4 mr-2" />
|
||||
Copy to Clipboard
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
{/* Customer Details Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Shipping Information</CardTitle>
|
||||
<CardDescription>
|
||||
Shipping method and tracking details
|
||||
</CardDescription>
|
||||
<CardTitle className="text-lg font-medium">Customer Details</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Shipping Method</Label>
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300 font-medium">
|
||||
{order?.shippingMethod.type} - £
|
||||
{order?.shippingMethod.price.toFixed(2)}
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-zinc-500">PGP Address</Label>
|
||||
<div className="flex items-start gap-2 mt-1">
|
||||
<Textarea
|
||||
value={order?.pgpAddress || ""}
|
||||
readOnly
|
||||
className="font-mono text-sm bg-zinc-950 h-32 resize-none"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => copyToClipboard(order?.pgpAddress || "")}
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tracking">Tracking Number</Label>
|
||||
<Input
|
||||
id="tracking"
|
||||
value={trackingNumber}
|
||||
onChange={(e) => setTrackingNumber(e.target.value)}
|
||||
placeholder="Enter tracking number"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button
|
||||
onClick={handleAddTracking}
|
||||
disabled={isSending || !trackingNumber.trim()}
|
||||
>
|
||||
<Package className="w-4 h-4 mr-2" />
|
||||
{isSending ? "Updating..." : "Update Tracking"}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Order Items</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Item</TableHead>
|
||||
<TableHead>Quantity</TableHead>
|
||||
<TableHead>Price</TableHead>
|
||||
<TableHead>Total</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{order?.products.map((product) => (
|
||||
<TableRow key={product._id}>
|
||||
<TableCell>
|
||||
{productNames[product.productId] || "Unknown Product (Deleted)"}
|
||||
</TableCell>
|
||||
<TableCell>{product.quantity}</TableCell>
|
||||
<TableCell>£{product.pricePerUnit.toFixed(2)}</TableCell>
|
||||
<TableCell>£{product.totalItemPrice.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="font-bold text-right">
|
||||
Total:
|
||||
</TableCell>
|
||||
<TableCell className="font-bold">
|
||||
£{order?.totalPrice.toFixed(2)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Right Column - Actions and Status */}
|
||||
<div className="space-y-6">
|
||||
{/* Order Actions Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg font-medium">Order Actions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{order?.status === "unpaid" && (
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={handleMarkAsPaid}
|
||||
disabled={isPaid}
|
||||
>
|
||||
Mark as Paid
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Add Crypto Transaction Details */}
|
||||
<Collapsible>
|
||||
<div className="rounded-lg border bg-card text-card-foreground shadow-sm">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<div className="p-6 flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">Crypto Transactions</h3>
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200 [&[data-state=open]]:rotate-180" />
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="px-6 pb-6">
|
||||
{order?.txid && order.txid.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{order.txid.slice(order.txid.length > 2 ? 1 : 0).map((txid: string, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
className="p-3 bg-muted rounded-md"
|
||||
{order?.status === "paid" && (
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={handleMarkAsAcknowledged}
|
||||
disabled={isAcknowledging}
|
||||
>
|
||||
{isAcknowledging ? "Processing..." : "Acknowledge Order"}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{order?.status === "acknowledged" && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tracking">Tracking Number</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="tracking"
|
||||
value={trackingNumber}
|
||||
onChange={(e) => setTrackingNumber(e.target.value)}
|
||||
placeholder="Enter tracking number"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleAddTracking}
|
||||
disabled={!trackingNumber || isSending}
|
||||
>
|
||||
<code className="text-sm break-all">{txid}</code>
|
||||
</div>
|
||||
))}
|
||||
<Truck className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground">No crypto transactions associated with this order</p>
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</div>
|
||||
</Collapsible>
|
||||
|
||||
<div className="flex justify-between gap-4">
|
||||
<div>
|
||||
{order?.status !== "cancelled" && (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" size="lg" disabled={isDiscarding}>
|
||||
{isDiscarding ? "Deleting..." : "Delete Order"}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action will permanently delete the order and cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleDiscardOrder}>
|
||||
Delete Order
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
{(order?.status === "unpaid") && (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="lg"
|
||||
disabled={isCancelling}
|
||||
className="w-full"
|
||||
onClick={handleMarkAsShipped}
|
||||
disabled={isMarkingShipped || !trackingNumber}
|
||||
>
|
||||
{isCancelling ? "Cancelling..." : "Cancel Order"}
|
||||
{isMarkingShipped ? "Processing..." : "Mark as Shipped"}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Cancel this order?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will mark the order as cancelled. This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>No, keep it</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleCancelOrder}>
|
||||
Yes, cancel order
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(order?.status === "unpaid" || order?.status === "paid") && (
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={handleMarkAsAcknowledged}
|
||||
disabled={isAcknowledging}
|
||||
className="bg-purple-600 hover:bg-purple-700"
|
||||
>
|
||||
{isAcknowledging ? "Updating..." : "Mark as Acknowledged"}
|
||||
</Button>
|
||||
)}
|
||||
{order?.status !== "cancelled" && order?.status !== "completed" && (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" className="w-full">
|
||||
Cancel Order
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Cancel Order</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to cancel this order? This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleCancelOrder}
|
||||
className="bg-red-500 hover:bg-red-600"
|
||||
>
|
||||
Confirm Cancel
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
|
||||
{order?.status === "unpaid" && (
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={handleMarkAsPaid}
|
||||
disabled={isPaid}
|
||||
className="bg-emerald-600 hover:bg-emerald-700"
|
||||
>
|
||||
{isPaid ? "Order Marked as Paid" : "Mark Order as Paid"}
|
||||
</Button>
|
||||
)}
|
||||
{(order?.status === "completed" || order?.status === "cancelled") && (
|
||||
<div className="text-center py-6 text-sm text-zinc-500">
|
||||
No actions available - Order is {order.status.toLowerCase()}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{(order?.status === "paid" || order?.status === "acknowledged") && (
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={handleMarkAsShipped}
|
||||
disabled={isMarkingShipped}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
<Truck className="w-5 h-5 mr-2" />
|
||||
{isMarkingShipped ? "Updating..." : "Mark as Shipped"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{/* Transaction Details Card */}
|
||||
{order?.txid && order.txid.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg font-medium">Transaction Details</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{order.txid.map((tx, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<Input
|
||||
value={tx}
|
||||
readOnly
|
||||
className="font-mono text-sm bg-zinc-950"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => copyToClipboard(tx)}
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Tracking Information Card */}
|
||||
{order?.trackingNumber && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg font-medium">Tracking Information</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={order.trackingNumber}
|
||||
readOnly
|
||||
className="font-mono text-sm bg-zinc-950"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => copyToClipboard(order.trackingNumber || "")}
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user