786 lines
26 KiB
TypeScript
786 lines
26 KiB
TypeScript
"use client";
|
|
|
|
import { fetchData } from '@/lib/data-service';
|
|
import { useEffect, useState } from "react";
|
|
import { useParams } from "next/navigation";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Clipboard, Truck, Package, ArrowRight, ChevronDown } from "lucide-react";
|
|
import { useRouter } from "next/navigation";
|
|
import { toast } from "sonner";
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
AlertDialogTrigger,
|
|
} from "@/components/ui/alert-dialog";
|
|
import Layout from "@/components/layout/layout";
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from "@/components/ui/collapsible";
|
|
|
|
interface Order {
|
|
orderId: string;
|
|
status: string;
|
|
pgpAddress: string;
|
|
shippingMethod: { type: string; price: number };
|
|
txid: Array<string>;
|
|
products: Array<{
|
|
_id: string;
|
|
productId: string;
|
|
quantity: number;
|
|
pricePerUnit: number;
|
|
totalItemPrice: number;
|
|
}>;
|
|
totalPrice: number;
|
|
trackingNumber?: string;
|
|
}
|
|
|
|
interface OrderInList extends Order {
|
|
_id: string;
|
|
}
|
|
|
|
interface OrdersResponse {
|
|
orders: OrderInList[];
|
|
page: number;
|
|
totalPages: number;
|
|
totalOrders: number;
|
|
}
|
|
|
|
const getStatusVariant = (status: string) => {
|
|
switch (status) {
|
|
case 'acknowledged':
|
|
return 'secondary';
|
|
case 'paid':
|
|
return 'default';
|
|
case 'shipped':
|
|
return 'default';
|
|
case 'completed':
|
|
return 'default';
|
|
case 'cancelled':
|
|
return 'destructive';
|
|
case 'unpaid':
|
|
return 'secondary';
|
|
default:
|
|
return 'secondary';
|
|
}
|
|
};
|
|
|
|
export default function OrderDetailsPage() {
|
|
const [order, setOrder] = useState<Order | null>(null);
|
|
const [trackingNumber, setTrackingNumber] = useState("");
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState("");
|
|
const [productNames, setProductNames] = useState<Record<string, string>>({});
|
|
const [isPaid, setIsPaid] = useState(false);
|
|
const [isSending, setIsSending] = useState(false);
|
|
const [isMarkingShipped, setIsMarkingShipped] = useState(false);
|
|
const [isDiscarding, setIsDiscarding] = useState(false);
|
|
const [nextOrderId, setNextOrderId] = useState<string | null>(null);
|
|
const [prevOrderId, setPrevOrderId] = useState<string | null>(null);
|
|
const [totalOrders, setTotalOrders] = useState(0);
|
|
const [currentOrderNumber, setCurrentOrderNumber] = useState(0);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [isAcknowledging, setIsAcknowledging] = useState(false);
|
|
const [isCancelling, setIsCancelling] = useState(false);
|
|
|
|
const router = useRouter();
|
|
const params = useParams();
|
|
const orderId = params?.id;
|
|
|
|
const fetchProductNames = async (
|
|
productIds: string[],
|
|
authToken: string
|
|
): Promise<Record<string, string>> => {
|
|
const productNamesMap: Record<string, string> = {};
|
|
try {
|
|
const promises = productIds.map((id) =>
|
|
fetchData(`${process.env.NEXT_PUBLIC_API_URL}/products/${id}`, {
|
|
method: "GET",
|
|
headers: { Authorization: `Bearer ${authToken}` },
|
|
})
|
|
);
|
|
const responses = await Promise.all(promises);
|
|
const results = await Promise.all(responses.map((res) => res));
|
|
|
|
results.forEach((product, index) => {
|
|
productNamesMap[productIds[index]] = product.name || "Unknown Product";
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error("Failed to fetch product names:", err);
|
|
}
|
|
return productNamesMap;
|
|
};
|
|
|
|
const handleMarkAsPaid = async () => {
|
|
try {
|
|
// Add a loading state to give feedback
|
|
const loadingToast = toast.loading("Marking order as paid...");
|
|
|
|
const authToken = document.cookie
|
|
.split("; ")
|
|
.find((row) => row.startsWith("Authorization="))
|
|
?.split("=")[1];
|
|
|
|
if (!authToken) {
|
|
toast.dismiss(loadingToast);
|
|
toast.error("Authentication error - please log in again");
|
|
return;
|
|
}
|
|
|
|
// Log the request for debugging
|
|
console.log(`Sending request to ${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/status`);
|
|
console.log("Request payload:", { status: "paid" });
|
|
|
|
const response = await fetchData(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/status`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${authToken}`,
|
|
},
|
|
body: JSON.stringify({ status: "paid" }),
|
|
}
|
|
);
|
|
|
|
// Log the response
|
|
console.log("API response:", response);
|
|
|
|
toast.dismiss(loadingToast);
|
|
|
|
if (response && response.message === "Order status updated successfully") {
|
|
// Update both states
|
|
setIsPaid(true);
|
|
setOrder((prevOrder) => (prevOrder ? { ...prevOrder, status: "paid" } : null));
|
|
toast.success("Order marked as paid successfully");
|
|
} else {
|
|
// Handle unexpected response format
|
|
console.error("Unexpected response format:", response);
|
|
throw new Error(
|
|
response.error || response.message || "Failed to mark order as paid"
|
|
);
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Failed to mark order as paid:", error);
|
|
|
|
// More detailed error handling
|
|
let errorMessage = "Failed to mark order as paid";
|
|
|
|
if (error.message) {
|
|
errorMessage += `: ${error.message}`;
|
|
}
|
|
|
|
if (error.response) {
|
|
try {
|
|
const errorData = await error.response.json();
|
|
console.error("Error response data:", errorData);
|
|
if (errorData.message) {
|
|
errorMessage = errorData.message;
|
|
}
|
|
} catch (e) {
|
|
console.error("Could not parse error response:", e);
|
|
}
|
|
}
|
|
|
|
toast.error(errorMessage);
|
|
}
|
|
};
|
|
|
|
const handleMarkAsShipped = async () => {
|
|
try {
|
|
setIsMarkingShipped(true);
|
|
const authToken = document.cookie.split("Authorization=")[1];
|
|
const response = await fetchData(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/status`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${authToken}`,
|
|
},
|
|
body: JSON.stringify({ status: "shipped" }),
|
|
}
|
|
);
|
|
|
|
if (response && response.message === "Order status updated successfully") {
|
|
setOrder((prevOrder) => prevOrder ? { ...prevOrder, status: "shipped" } : null);
|
|
toast.success("Order marked as shipped successfully!");
|
|
} else {
|
|
throw new Error(response.error || "Failed to mark order as shipped");
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Failed to mark order as shipped:", error);
|
|
toast.error(error.message || "Failed to mark order as shipped");
|
|
} finally {
|
|
setIsMarkingShipped(false);
|
|
}
|
|
};
|
|
|
|
const handleMarkAsAcknowledged = async () => {
|
|
try {
|
|
setIsAcknowledging(true);
|
|
const authToken = document.cookie.split("Authorization=")[1];
|
|
const response = await fetchData(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/status`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${authToken}`,
|
|
},
|
|
body: JSON.stringify({ status: "acknowledged" }),
|
|
}
|
|
);
|
|
|
|
if (response && response.message === "Order status updated successfully") {
|
|
setOrder((prevOrder) => prevOrder ? { ...prevOrder, status: "acknowledged" } : null);
|
|
toast.success("Order marked as acknowledged!");
|
|
} else {
|
|
throw new Error(response.error || "Failed to mark order as acknowledged");
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Failed to mark order as acknowledged:", error);
|
|
toast.error(error.message || "Failed to mark order as acknowledged");
|
|
} finally {
|
|
setIsAcknowledging(false);
|
|
}
|
|
};
|
|
|
|
const handleDiscardOrder = async () => {
|
|
try {
|
|
setIsDiscarding(true);
|
|
const authToken = document.cookie.split("Authorization=")[1];
|
|
const response = await fetchData(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
|
|
{
|
|
method: "DELETE",
|
|
headers: {
|
|
Authorization: `Bearer ${authToken}`,
|
|
},
|
|
}
|
|
);
|
|
|
|
if (response && response.message === "Order deleted successfully") {
|
|
toast.success("Order deleted successfully!");
|
|
router.push('/dashboard/orders');
|
|
} else {
|
|
throw new Error(response.error || "Failed to delete order");
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Failed to delete order:", error);
|
|
toast.error(error.message || "Failed to delete order");
|
|
} finally {
|
|
setIsDiscarding(false);
|
|
}
|
|
};
|
|
|
|
const handleCancelOrder = async () => {
|
|
try {
|
|
setIsCancelling(true);
|
|
const authToken = document.cookie.split("Authorization=")[1];
|
|
|
|
// Update to match the correct API endpoint structure
|
|
const response = await fetchData(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/status`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${authToken}`,
|
|
},
|
|
body: JSON.stringify({ status: "cancelled" }),
|
|
}
|
|
);
|
|
|
|
if (response && response.message === "Order status updated successfully") {
|
|
setOrder((prevOrder) => prevOrder ? { ...prevOrder, status: "cancelled" } : null);
|
|
toast.success("Order cancelled successfully");
|
|
} else {
|
|
throw new Error(response.error || "Failed to cancel order");
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Failed to cancel order:", error);
|
|
toast.error(error.message || "Failed to cancel order");
|
|
} finally {
|
|
setIsCancelling(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const fetchOrderDetails = async () => {
|
|
try {
|
|
if (!orderId) return;
|
|
|
|
const authToken = document.cookie.split("Authorization=")[1];
|
|
|
|
const res = await fetchData(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
|
|
{
|
|
method: "GET",
|
|
headers: { Authorization: `Bearer ${authToken}` },
|
|
}
|
|
);
|
|
|
|
if (!res) throw new Error("Failed to fetch order details");
|
|
|
|
const data: Order = await res;
|
|
setOrder(data);
|
|
console.log(data);
|
|
|
|
const productIds = data.products.map((product) => product.productId);
|
|
const productNamesMap = await fetchProductNames(productIds, authToken);
|
|
setProductNames(productNamesMap);
|
|
|
|
if (data.status === "paid") {
|
|
setIsPaid(true);
|
|
}
|
|
} catch (err: any) {
|
|
router.push("/dashboard/orders");
|
|
setError(err.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchOrderDetails();
|
|
}, [orderId]);
|
|
|
|
useEffect(() => {
|
|
const fetchAdjacentOrders = async () => {
|
|
try {
|
|
const authToken = document.cookie.split("Authorization=")[1];
|
|
|
|
if (!order?.orderId) return;
|
|
|
|
// Get the current numerical orderId
|
|
const currentOrderId = parseInt(order.orderId);
|
|
console.log('Current orderId (numerical):', currentOrderId);
|
|
|
|
// Use the new optimized backend endpoint to get adjacent orders
|
|
const adjacentOrdersUrl = `${process.env.NEXT_PUBLIC_API_URL}/orders/adjacent/${currentOrderId}`;
|
|
console.log('Fetching adjacent orders:', adjacentOrdersUrl);
|
|
|
|
const adjacentOrdersRes = await fetchData(
|
|
adjacentOrdersUrl,
|
|
{
|
|
method: "GET",
|
|
headers: { Authorization: `Bearer ${authToken}` },
|
|
}
|
|
);
|
|
|
|
console.log('Adjacent orders response:', adjacentOrdersRes);
|
|
|
|
if (!adjacentOrdersRes) {
|
|
console.error("Invalid response from adjacent orders endpoint");
|
|
return;
|
|
}
|
|
|
|
// Set the next and previous order IDs
|
|
const { newer, older } = adjacentOrdersRes;
|
|
|
|
// Set IDs for navigation
|
|
setPrevOrderId(newer?._id || null);
|
|
setNextOrderId(older?._id || null);
|
|
|
|
if (newer) {
|
|
console.log(`Newer order: ${newer.orderId} (ID: ${newer._id})`);
|
|
} else {
|
|
console.log('No newer order found');
|
|
}
|
|
|
|
if (older) {
|
|
console.log(`Older order: ${older.orderId} (ID: ${older._id})`);
|
|
} else {
|
|
console.log('No older order found');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Failed to fetch adjacent orders:", error);
|
|
}
|
|
};
|
|
|
|
if (order) {
|
|
fetchAdjacentOrders();
|
|
}
|
|
}, [order]);
|
|
|
|
const handleAddTracking = async () => {
|
|
if (!trackingNumber) {
|
|
toast.error("Please enter a tracking number");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setIsSending(true);
|
|
const authToken = document.cookie.split("Authorization=")[1];
|
|
|
|
const response = await fetchData(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/tracking`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${authToken}`,
|
|
},
|
|
body: JSON.stringify({ trackingNumber }),
|
|
}
|
|
);
|
|
|
|
if (response.error) throw new Error(response.error);
|
|
|
|
// Update the local state
|
|
setOrder(prevOrder => prevOrder ? {
|
|
...prevOrder,
|
|
trackingNumber: trackingNumber
|
|
} : null);
|
|
|
|
toast.success("Tracking number updated successfully!");
|
|
setTrackingNumber(""); // Clear the input
|
|
} catch (err: any) {
|
|
console.error("Failed to update tracking:", err);
|
|
toast.error(err.message || "Failed to update tracking number");
|
|
} finally {
|
|
setIsSending(false);
|
|
}
|
|
};
|
|
|
|
const copyToClipboard = (text: string) => {
|
|
navigator.clipboard.writeText(text);
|
|
};
|
|
|
|
if (loading)
|
|
return (
|
|
<Layout>
|
|
<div className="text-center py-10">Loading order details...</div>
|
|
</Layout>
|
|
);
|
|
if (error)
|
|
return (
|
|
<Layout>
|
|
<div className="text-center text-red-500 py-10">Error: {error}</div>
|
|
</Layout>
|
|
);
|
|
|
|
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>
|
|
<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'
|
|
: ''
|
|
}`}
|
|
>
|
|
{order?.status}
|
|
</Badge>
|
|
</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">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>PGP Encrypted Address</CardTitle>
|
|
<CardDescription>
|
|
Securely encrypted delivery address
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Textarea
|
|
value={order?.pgpAddress || ""}
|
|
readOnly
|
|
className="font-mono text-xs"
|
|
rows={10}
|
|
/>
|
|
</CardContent>
|
|
<CardFooter>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => copyToClipboard(order?.pgpAddress || "")}
|
|
>
|
|
<Clipboard className="w-4 h-4 mr-2" />
|
|
Copy to Clipboard
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Shipping Information</CardTitle>
|
|
<CardDescription>
|
|
Shipping method and tracking details
|
|
</CardDescription>
|
|
</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>
|
|
</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] || "Loading..."}
|
|
</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>
|
|
|
|
{/* 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"
|
|
>
|
|
<code className="text-sm break-all">{txid}</code>
|
|
</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}
|
|
>
|
|
{isCancelling ? "Cancelling..." : "Cancel Order"}
|
|
</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>
|
|
)}
|
|
|
|
{(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 === "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 === "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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Layout>
|
|
);
|
|
} |