Update frontend
This commit is contained in:
@@ -24,18 +24,63 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Clipboard, Package, } from "lucide-react";
|
||||
import { Clipboard, Truck, Package } from "lucide-react";
|
||||
|
||||
interface Order {
|
||||
orderId: string;
|
||||
status: string;
|
||||
pgpAddress: string;
|
||||
shippingMethod: { type: string; price: number };
|
||||
products: Array<{
|
||||
_id: string;
|
||||
productId: string;
|
||||
quantity: number;
|
||||
pricePerUnit: number;
|
||||
totalItemPrice: number;
|
||||
}>;
|
||||
totalPrice: number;
|
||||
}
|
||||
|
||||
export default function OrderDetailsPage() {
|
||||
const [order, setOrder] = useState<any>(null);
|
||||
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>>({}); // Map of productId to productName
|
||||
|
||||
const [productNames, setProductNames] = useState<Record<string, string>>({});
|
||||
const [isPaid, setIsPaid] = useState(false);
|
||||
|
||||
const params = useParams();
|
||||
const orderId = params.id;
|
||||
const orderId = params?.id;
|
||||
|
||||
const handleMarkAsPaid = async () => {
|
||||
try {
|
||||
const authToken = document.cookie.split("Authorization=")[1];
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
body: JSON.stringify({ status: "paid" }),
|
||||
}
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
setIsPaid(true); // Update isPaid state
|
||||
setOrder((prevOrder) => (prevOrder ? { ...prevOrder, status: "paid" } : null)); // Update order status
|
||||
console.log("Order marked as paid successfully.");
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
console.error("Failed to mark order as paid:", errorData.message);
|
||||
alert(`Error: ${errorData.message}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("An error occurred while marking the order as paid:", error.message);
|
||||
alert("An unexpected error occurred. Please try again.");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrderDetails = async () => {
|
||||
@@ -44,20 +89,26 @@ export default function OrderDetailsPage() {
|
||||
|
||||
const authToken = document.cookie.split("Authorization=")[1];
|
||||
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`, {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) throw new Error("Failed to fetch order details");
|
||||
|
||||
const data = await res.json();
|
||||
const data: Order = await res.json();
|
||||
setOrder(data);
|
||||
|
||||
// Fetch product names for the order
|
||||
const productIds = data.products.map((product: any) => product.productId);
|
||||
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) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
@@ -65,7 +116,10 @@ export default function OrderDetailsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchProductNames = async (productIds: string[], authToken: string) => {
|
||||
const fetchProductNames = async (
|
||||
productIds: string[],
|
||||
authToken: string
|
||||
): Promise<Record<string, string>> => {
|
||||
const productNamesMap: Record<string, string> = {};
|
||||
try {
|
||||
const promises = productIds.map((id) =>
|
||||
@@ -89,21 +143,23 @@ export default function OrderDetailsPage() {
|
||||
fetchOrderDetails();
|
||||
}, [orderId]);
|
||||
|
||||
|
||||
const handleAddTracking = async () => {
|
||||
if (!trackingNumber) return;
|
||||
|
||||
try {
|
||||
const authToken = document.cookie.split("Authorization=")[1];
|
||||
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/tracking`, {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/tracking`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
body: JSON.stringify({ trackingNumber }),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) throw new Error("Failed to update tracking number");
|
||||
|
||||
@@ -117,16 +173,18 @@ export default function OrderDetailsPage() {
|
||||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
if (loading) return <div className="text-center py-10">Loading order details...</div>;
|
||||
if (error) return <div className="text-center text-red-500 py-10">Error: {error}</div>;
|
||||
if (loading)
|
||||
return <div className="text-center py-10">Loading order details...</div>;
|
||||
if (error)
|
||||
return <div className="text-center text-red-500 py-10">Error: {error}</div>;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold">Order Details: {order.orderId}</h1>
|
||||
<Badge variant={order.status === "paid" ? "default" : "secondary"}>
|
||||
{order.status}
|
||||
<h1 className="text-3xl font-bold">Order Details: {order?.orderId}</h1>
|
||||
<Badge variant={order?.status === "paid" ? "paid" : "unpaid"}>
|
||||
{order?.status}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
@@ -134,13 +192,23 @@ export default function OrderDetailsPage() {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>PGP Encrypted Address</CardTitle>
|
||||
<CardDescription>Securely encrypted delivery address</CardDescription>
|
||||
<CardDescription>
|
||||
Securely encrypted delivery address
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Textarea value={order.pgpAddress} readOnly className="font-mono text-xs" rows={10} />
|
||||
<Textarea
|
||||
value={order?.pgpAddress || ""}
|
||||
readOnly
|
||||
className="font-mono text-xs"
|
||||
rows={10}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button variant="outline" onClick={() => copyToClipboard(order.pgpAddress)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => copyToClipboard(order?.pgpAddress || "")}
|
||||
>
|
||||
<Clipboard className="w-4 h-4 mr-2" />
|
||||
Copy to Clipboard
|
||||
</Button>
|
||||
@@ -158,7 +226,8 @@ export default function OrderDetailsPage() {
|
||||
<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)}
|
||||
{order?.shippingMethod.type} - £
|
||||
{order?.shippingMethod.price.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -195,9 +264,11 @@ export default function OrderDetailsPage() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{order.products.map((product: any) => (
|
||||
{order?.products.map((product) => (
|
||||
<TableRow key={product._id}>
|
||||
<TableCell>{productNames[product.productId] || "Loading..."}</TableCell>
|
||||
<TableCell>
|
||||
{productNames[product.productId] || "Loading..."}
|
||||
</TableCell>
|
||||
<TableCell>{product.quantity}</TableCell>
|
||||
<TableCell>£{product.pricePerUnit.toFixed(2)}</TableCell>
|
||||
<TableCell>£{product.totalItemPrice.toFixed(2)}</TableCell>
|
||||
@@ -208,13 +279,18 @@ export default function OrderDetailsPage() {
|
||||
Total:
|
||||
</TableCell>
|
||||
<TableCell className="font-bold">
|
||||
£{order.totalPrice.toFixed(2)}
|
||||
£{order?.totalPrice.toFixed(2)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex justify-end space-x-4">
|
||||
<Button size="lg" onClick={handleMarkAsPaid} disabled={isPaid}>
|
||||
{isPaid ? "Order Marked as Paid" : "Mark Order as Paid"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
paid: "border-transparent bg-green-500 text-white hover:bg-green-600",
|
||||
unpaid: "border-transparent bg-red-500 text-white hover:bg-red-600",
|
||||
pending: "border-transparent bg-yellow-500 text-white hover:bg-yellow-600",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
@@ -30,7 +30,7 @@ export interface BadgeProps
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
export { Badge, badgeVariants };
|
||||
@@ -1,4 +1,3 @@
|
||||
// In models/products.ts
|
||||
export interface Product {
|
||||
_id?: string;
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user