diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx
index 6c979eaa0..c398eccb4 100644
--- a/apps/edr-freight-web/portal/src/App.tsx
+++ b/apps/edr-freight-web/portal/src/App.tsx
@@ -64,7 +64,6 @@ const App = () => {
if (user && location.pathname === "/") navigate("/portal");
else if (!customer && !!isInProtectedRoutes) navigate("/onboarding");
- else if (customer && !isInProtectedRoutes) return navigate("/portal");
}, [user, location, customer]);
if (isPending) {
@@ -109,7 +108,10 @@ const App = () => {
} />
} />
} />
- } />
+ }
+ />
} />
} />
} />
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx
index 2dcecfaac..92643ccef 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx
@@ -436,20 +436,6 @@ function DraftBookingView({
)}
- {pricingQuery.isError && (
-
-
-
-
Pricing failed
-
- {pricingQuery.error instanceof Error
- ? pricingQuery.error.message
- : "An unexpected error occurred."}
-
-
-
- )}
-
{uploadMutation.isError && (
@@ -776,11 +762,28 @@ function DraftBookingView({
function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) {
const navigate = useNavigate();
+ const queryClient = useQueryClient();
+
+ const payMutation = useMutation({
+ mutationFn: () => api.bookings.pay.call({ id: booking.id }),
+ onSuccess: (data) => {
+ if (data.redirectUrl) {
+ window.location.href = data.redirectUrl;
+ }
+ },
+ });
const normalizedStatus = booking.status as keyof typeof STATUS_MAP;
const statusConfig = STATUS_MAP[normalizedStatus] || STATUS_MAP.DRAFT;
const currentStageIndex = statusConfig.stage;
+ const pricing = booking.pricingBreakdown;
+
+ const uploadedCodes = useMemo(
+ () => new Set(booking.files?.map((f) => f.code) ?? []),
+ [booking.files],
+ );
+
return (
@@ -797,7 +800,7 @@ function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) {
-
+
{booking.reference}
@@ -814,11 +817,100 @@ function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) {
+ {normalizedStatus === "FULLY_EXECUTED" && booking.paymentStatus !== "PAID" && (
+
+ )}
- {renderContractCard(booking, navigate)}
+ {renderContractCard(booking, navigate, payMutation)}
+
+ {pricing && (
+
+
+
+
+ Pricing Breakdown
+
+
+
+
+
+
+
+ | Description |
+ Amount |
+
+
+
+ {pricing.lineItems.map((item, i) => (
+
+ | {item.description} |
+
+ {item.amount.toLocaleString()} {item.currency}
+ |
+
+ ))}
+
+ | Total Estimated Cost |
+
+ {pricing.totalAmount.toLocaleString()} {pricing.currency}
+ |
+
+
+
+
+
+
+ )}
+
+ {booking.files && booking.files.length > 0 && (
+
+
+
+
+ Uploaded Documents ({booking.files.length})
+
+
+
+
+
+
+ )}
@@ -1174,6 +1266,7 @@ function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) {
function renderContractCard(
booking: Freight.IBooking,
navigate: ReturnType,
+ payMutation: { mutate: () => void; isPending: boolean },
) {
const s = booking.status;
if (
diff --git a/apps/edr-freight-web/portal/src/services/api.ts b/apps/edr-freight-web/portal/src/services/api.ts
index c49ebad0f..ced534965 100644
--- a/apps/edr-freight-web/portal/src/services/api.ts
+++ b/apps/edr-freight-web/portal/src/services/api.ts
@@ -173,6 +173,12 @@ export const api = {
>("bookings", "uploadDocuments", ({ id, files }) =>
bookingsService.uploadDocuments(id, files),
),
+
+ pay: endpoint<{ id: string }, { redirectUrl: string }>(
+ "bookings",
+ "pay",
+ ({ id }) => bookingsService.pay(id),
+ ),
},
consignments: {
diff --git a/apps/edr-freight-web/portal/src/services/bookings.service.ts b/apps/edr-freight-web/portal/src/services/bookings.service.ts
index d9481d59e..44ccf1e05 100644
--- a/apps/edr-freight-web/portal/src/services/bookings.service.ts
+++ b/apps/edr-freight-web/portal/src/services/bookings.service.ts
@@ -134,6 +134,11 @@ export const bookingsService = {
return data;
},
+ pay: async (id: string): Promise<{ redirectUrl: string }> => {
+ const { data } = await client.post(`/api/bookings/${id}/payment/pay`);
+ return data.data ?? data;
+ },
+
signContract: async (
id: string,
payload: SignContractPayload,