From 74b7aa4877163453683751c4690472182822cd09 Mon Sep 17 00:00:00 2001 From: NotII <46204250+NotII@users.noreply.github.com> Date: Mon, 22 Sep 2025 00:45:29 +0100 Subject: [PATCH] Add shipping dialog with tracking number to order page Introduces a shipping dialog to the order details page, allowing users to optionally enter a tracking number when marking an order as shipped. Updates API client logic to better handle HTTP-only authentication cookies. Improves broadcast dialog validation and message handling. --- app/dashboard/orders/[id]/page.tsx | 89 +++++++++++++++++++++++++- components/modals/broadcast-dialog.tsx | 9 ++- components/tables/order-table.tsx | 1 + docs/README.md | 1 + lib/api-client.ts | 31 +++++++-- public/git-info.json | 4 +- 6 files changed, 121 insertions(+), 14 deletions(-) diff --git a/app/dashboard/orders/[id]/page.tsx b/app/dashboard/orders/[id]/page.tsx index fcadd39..f72aacd 100644 --- a/app/dashboard/orders/[id]/page.tsx +++ b/app/dashboard/orders/[id]/page.tsx @@ -158,6 +158,8 @@ export default function OrderDetailsPage() { const [isAcknowledging, setIsAcknowledging] = useState(false); const [isCancelling, setIsCancelling] = useState(false); const [refreshTrigger, setRefreshTrigger] = useState(0); + const [showShippingDialog, setShowShippingDialog] = useState(false); + const [shippingTrackingNumber, setShippingTrackingNumber] = useState(""); const router = useRouter(); const params = useParams(); @@ -262,7 +264,7 @@ export default function OrderDetailsPage() { } }; - const handleMarkAsShipped = async () => { + const handleMarkAsShipped = async (trackingNumber?: string) => { try { setIsMarkingShipped(true); @@ -274,6 +276,12 @@ export default function OrderDetailsPage() { if (response && response.message === "Order status updated successfully") { setOrder((prevOrder) => prevOrder ? { ...prevOrder, status: "shipped" } : null); + + // If tracking number is provided, add it + if (trackingNumber && trackingNumber.trim()) { + await handleAddTrackingNumber(trackingNumber.trim()); + } + toast.success("Order marked as shipped successfully!"); } else { throw new Error(response.error || "Failed to mark order as shipped"); @@ -286,6 +294,48 @@ export default function OrderDetailsPage() { } }; + const handleAddTrackingNumber = async (trackingNumber: string) => { + try { + 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 added successfully!"); + } catch (err: any) { + console.error("Failed to add tracking number:", err); + toast.error(err.message || "Failed to add tracking number"); + } + }; + + const handleShippingDialogConfirm = async () => { + await handleMarkAsShipped(shippingTrackingNumber); + setShowShippingDialog(false); + setShippingTrackingNumber(""); + }; + + const handleShippingDialogCancel = () => { + setShowShippingDialog(false); + setShippingTrackingNumber(""); + }; + const handleMarkAsAcknowledged = async () => { try { setIsAcknowledging(true); @@ -922,7 +972,7 @@ export default function OrderDetailsPage() { {order?.status === "acknowledged" && ( setShowShippingDialog(true)} disabled={isMarkingShipped} > {isMarkingShipped ? "Processing..." : "Mark as Shipped"} @@ -1082,6 +1132,41 @@ export default function OrderDetailsPage() { )} + + {/* Shipping Dialog */} + + + + Mark Order as Shipped + + Mark this order as shipped. You can optionally add a tracking number. + + + + + Tracking Number (Optional) + setShippingTrackingNumber(e.target.value)} + placeholder="Enter tracking number" + className="mt-1" + /> + + + + + Cancel + + + {isMarkingShipped ? "Processing..." : "Mark as Shipped"} + + + + ); diff --git a/components/modals/broadcast-dialog.tsx b/components/modals/broadcast-dialog.tsx index bbc9d0f..50a19ae 100644 --- a/components/modals/broadcast-dialog.tsx +++ b/components/modals/broadcast-dialog.tsx @@ -83,7 +83,7 @@ export default function BroadcastDialog({ open, setOpen }: BroadcastDialogProps) }; const sendBroadcast = async () => { - if (!broadcastMessage.trim() && !selectedImage) { + if ((!broadcastMessage || !broadcastMessage.trim()) && !selectedImage) { toast.warning("Please provide a message or image to broadcast."); return; } @@ -110,9 +110,8 @@ export default function BroadcastDialog({ open, setOpen }: BroadcastDialogProps) if (selectedImage) { const formData = new FormData(); formData.append('file', selectedImage); - if (broadcastMessage.trim()) { - formData.append('message', broadcastMessage); - } + // Always append message, even if empty (backend will validate) + formData.append('message', broadcastMessage || ''); if (selectedProducts.length > 0) { formData.append('productIds', JSON.stringify(selectedProducts)); } @@ -336,7 +335,7 @@ __italic text__ 4096) || (!broadcastMessage.trim() && !selectedImage)} + disabled={isSending || (broadcastMessage.length > 4096) || ((!broadcastMessage || !broadcastMessage.trim()) && !selectedImage)} className="bg-emerald-600 hover:bg-emerald-700" > {isSending ? "Sending..." : "Send Broadcast"} diff --git a/components/tables/order-table.tsx b/components/tables/order-table.tsx index 56d1e41..c6ce634 100644 --- a/components/tables/order-table.tsx +++ b/components/tables/order-table.tsx @@ -600,6 +600,7 @@ export default function OrderTable() { + ); diff --git a/docs/README.md b/docs/README.md index 7c89b95..c9abc51 100644 --- a/docs/README.md +++ b/docs/README.md @@ -49,3 +49,4 @@ When adding new documentation: - **🔧 Configuration** - Environment variables, API setup - **🐛 Troubleshooting** - Common issues and fixes + diff --git a/lib/api-client.ts b/lib/api-client.ts index 93a837f..6e34363 100644 --- a/lib/api-client.ts +++ b/lib/api-client.ts @@ -151,14 +151,32 @@ function normalizeApiUrl(url: string): string { /** * Get the authentication token from cookies or localStorage + * Note: HTTP-only cookies cannot be read by JavaScript, so we return null + * and rely on the browser to automatically include them in requests */ export function getAuthToken(): string | null { if (typeof document === 'undefined') return null; // Guard for SSR - return document.cookie + // Try localStorage first (for non-HTTP-only tokens) + const localToken = localStorage.getItem('Authorization'); + if (localToken) { + return localToken; + } + + // For HTTP-only cookies, we can't read them from JavaScript + // The browser will automatically include them in requests + // Check if the cookie exists (we can't read its value) + const hasAuthCookie = document.cookie .split('; ') - .find(row => row.startsWith('Authorization=')) - ?.split('=')[1] || localStorage.getItem('Authorization'); + .some(row => row.startsWith('Authorization=')); + + if (hasAuthCookie) { + // Return a special marker to indicate the cookie exists + // The actual token will be sent automatically by the browser + return 'HTTP_ONLY_COOKIE'; + } + + return null; } /** @@ -188,9 +206,11 @@ function createApiHeaders(token?: string | null, customHeaders: Record( ...(headers as Record), }; - if (authToken) { + if (authToken && authToken !== 'HTTP_ONLY_COOKIE') { // Backend expects "Bearer TOKEN" format requestHeaders['Authorization'] = `Bearer ${authToken}`; } + // For HTTP_ONLY_COOKIE, the browser will automatically include the cookie const fetchOptions: RequestInit = { method, diff --git a/public/git-info.json b/public/git-info.json index 0fab168..ba3d780 100644 --- a/public/git-info.json +++ b/public/git-info.json @@ -1,4 +1,4 @@ { - "commitHash": "a10b9e0", - "buildTime": "2025-08-31T17:56:33.446Z" + "commitHash": "8554481", + "buildTime": "2025-09-17T17:02:11.044Z" } \ No newline at end of file