"use client"; import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { fetchClient } from "@/lib/api-client"; import { toast } from "sonner"; import { Package, User, Calendar, DollarSign, MapPin, Truck, CheckCircle, XCircle, Clock, Wallet, Copy, ExternalLink } from "lucide-react"; interface OrderDetails { _id: string; orderId: number; telegramBuyerId: string; telegramUsername?: string; totalPrice: number; cryptoTotal?: number; orderDate: string; paidAt?: string; status: string; products: Array<{ productId: string; name: string; quantity: number; pricePerUnit: number; totalItemPrice: number; }>; shippingMethod?: { type?: string; name?: string; // Legacy support price: number; }; pgpAddress?: string; trackingNumber?: string; discountAmount?: number; subtotalBeforeDiscount?: number; paymentAddress?: string; txid?: string | string[]; vendorId?: { username?: string; }; storeId?: string; } interface OrderDetailsModalProps { orderId: number | string; open: boolean; onOpenChange: (open: boolean) => void; } /** * Admin-only order details modal component * This component fetches order details from admin-only API endpoints * and should only be used in admin contexts */ // Helper function to format currency, removing unnecessary .00 const formatCurrency = (amount: number | undefined | null): string => { if (amount === undefined || amount === null || isNaN(amount) || amount === 0) { return ''; } // If it's a whole number, don't show decimals if (amount % 1 === 0) { return `£${amount.toFixed(0)}`; } return `£${amount.toFixed(2)}`; }; const getStatusConfig = (status: string) => { switch (status) { case 'acknowledged': return { label: 'Acknowledged', color: 'bg-purple-500/10 text-purple-500 border-purple-500/20', icon: CheckCircle }; case 'paid': return { label: 'Paid', color: 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20', icon: CheckCircle }; case 'shipped': return { label: 'Shipped', color: 'bg-blue-500/10 text-blue-500 border-blue-500/20', icon: Truck }; case 'completed': return { label: 'Completed', color: 'bg-green-500/10 text-green-500 border-green-500/20', icon: CheckCircle }; case 'cancelled': return { label: 'Cancelled', color: 'bg-red-500/10 text-red-500 border-red-500/20', icon: XCircle }; case 'unpaid': return { label: 'Unpaid', color: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20', icon: Clock }; case 'confirming': return { label: 'Confirming', color: 'bg-orange-500/10 text-orange-500 border-orange-500/20', icon: Clock }; default: return { label: status, color: 'bg-gray-500/10 text-gray-500 border-gray-500/20', icon: Package }; } }; export default function OrderDetailsModal({ orderId, open, onOpenChange }: OrderDetailsModalProps) { const [order, setOrder] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [copied, setCopied] = useState(null); const [updatingStatus, setUpdatingStatus] = useState(false); const copyToClipboard = async (text: string, label: string) => { try { await navigator.clipboard.writeText(text); setCopied(label); setTimeout(() => setCopied(null), 2000); } catch (err) { console.error('Failed to copy:', err); } }; const handleStatusChange = async (newStatus: string) => { if (!order || newStatus === order.status) return; setUpdatingStatus(true); try { const response = await fetchClient<{ message: string; order: { orderId: number; status: string; oldStatus: string } }>( `/admin/orders/${orderId}/status`, { method: 'PUT', body: { status: newStatus } } ); // Update local order state setOrder({ ...order, status: newStatus }); toast.success(`Order status updated to ${newStatus}`); // Optionally refresh order details to get latest data await fetchOrderDetails(); } catch (err) { console.error('Failed to update order status:', err); const errorMessage = err instanceof Error ? err.message : 'Failed to update order status'; toast.error(errorMessage); } finally { setUpdatingStatus(false); } }; useEffect(() => { if (open && orderId) { fetchOrderDetails(); } else { // Reset state when modal closes setOrder(null); setError(null); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, orderId]); const fetchOrderDetails = async () => { setLoading(true); setError(null); try { // Validate orderId before making request if (!orderId || orderId === 'undefined' || orderId === 'null') { throw new Error('Order ID is required'); } // Ensure orderId is a valid number or string const orderIdStr = String(orderId).trim(); if (!orderIdStr || orderIdStr === 'undefined' || orderIdStr === 'null') { throw new Error('Invalid order ID'); } // Fetch full order details from admin endpoint // Use /admin/orders/:orderId (fetchClient will add /api prefix and backend URL) console.log(`Fetching order details for order #${orderIdStr}`); const orderData = await fetchClient(`/admin/orders/${orderIdStr}`); console.log("Order data received:", orderData); if (!orderData) { throw new Error("No order data received from server"); } if (!orderData.orderId) { throw new Error("Invalid order data: missing orderId"); } setOrder(orderData); } catch (err) { console.error("Failed to fetch order details:", err); const errorMessage = err instanceof Error ? err.message : typeof err === 'string' ? err : "Failed to load order details. Please check your connection and try again."; setError(errorMessage); } finally { setLoading(false); } }; const statusConfig = order ? getStatusConfig(order.status) : null; const StatusIcon = statusConfig?.icon || Package; return ( Order Details #{orderId} Complete order information and status {loading && (
)} {error && (

{error}

Please check the browser console for more details.

)} {order && !loading && (
{/* Order Status */}
Order Status {statusConfig && ( {statusConfig.label} )}

Change Status:

{/* Order Information */} Order Information

Customer

{order.telegramBuyerId}

{order.telegramUsername && (

@{order.telegramUsername}

)}

Order Date

{new Date(order.orderDate).toLocaleString()}

{order.paidAt && (

Paid: {new Date(order.paidAt).toLocaleString()}

)}
{order.vendorId?.username && (

Vendor

{order.vendorId.username}

)}
{/* Products */} {order.products && order.products.length > 0 && ( Products
{order.products.map((product, index) => (

{product.name || `Product ${index + 1}`}

Quantity: {product.quantity} × {formatCurrency(product.pricePerUnit)}

{formatCurrency(product.totalItemPrice)}

))}
)} {/* Shipping & Pricing */} Pricing Details {order.subtotalBeforeDiscount !== undefined && order.subtotalBeforeDiscount !== null && typeof order.subtotalBeforeDiscount === 'number' && order.subtotalBeforeDiscount > 0 && formatCurrency(order.subtotalBeforeDiscount) ? (
Subtotal {formatCurrency(order.subtotalBeforeDiscount)}
) : null} {order.shippingMethod && order.shippingMethod.price !== undefined && order.shippingMethod.price !== null && typeof order.shippingMethod.price === 'number' && order.shippingMethod.price > 0 && formatCurrency(order.shippingMethod.price) ? (
Shipping {order.shippingMethod.type || order.shippingMethod.name ? `(${order.shippingMethod.type || order.shippingMethod.name})` : ''} {formatCurrency(order.shippingMethod.price)}
) : null} {order.discountAmount !== undefined && order.discountAmount !== null && typeof order.discountAmount === 'number' && order.discountAmount > 0 && formatCurrency(order.discountAmount) ? (
Discount -{formatCurrency(order.discountAmount)}
) : null}
Total {formatCurrency(order.totalPrice)}
{order.cryptoTotal && (
Crypto: {order.cryptoTotal} LTC
)}
{/* Shipping Address */} {order.pgpAddress && ( Shipping Address

{order.pgpAddress}

)} {/* Crypto Payment Address */} {order.paymentAddress && order.paymentAddress !== "NO_PAYMENT_REQUIRED" && ( Crypto Payment Address

{order.paymentAddress}

)} {/* Transaction IDs */} {order.txid && Array.isArray(order.txid) && order.txid.length > 0 && ( Transaction IDs
{order.txid.map((tx, index) => (

{tx}

))}
)} {/* Tracking */} {order.trackingNumber && ( Tracking Information

{order.trackingNumber}

)}
)}
); }