diff --git a/apps/edr-freight-web/portal/src/pages/MyPortalPage/components/UpcomingWindowsSection.tsx b/apps/edr-freight-web/portal/src/pages/MyPortalPage/components/UpcomingWindowsSection.tsx
index bb5b629de..300ff643f 100644
--- a/apps/edr-freight-web/portal/src/pages/MyPortalPage/components/UpcomingWindowsSection.tsx
+++ b/apps/edr-freight-web/portal/src/pages/MyPortalPage/components/UpcomingWindowsSection.tsx
@@ -45,22 +45,50 @@ function windowLabel(w: MyBookingWindow): string {
}
/**
- * The deadline + label for whichever phase the window is currently in. Phases
- * run: window open (closes at windowClosesAt) → document review (docReviewEndsAt)
- * → payment (paymentPhaseEndsAt). Returns null when no phase is timing down.
+ * The countdown for whichever phase the window is currently in. Phases run:
+ * pre-window (opens at windowOpensAt) → open (closes at windowClosesAt) →
+ * document review (docReviewEndsAt) → payment (paymentPhaseEndsAt).
+ *
+ * `label` describes the deadline being counted down to; `expiredText` names the
+ * NEXT step so that when a deadline lapses between the 60s refetches the row
+ * announces what comes next ("Booking opening now…", "Review starting…") rather
+ * than the bare word "Expired". Returns null when no phase is timing down.
*/
function phaseCountdown(
w: MyBookingWindow,
-): { label: string; deadline: string } | null {
+): { label: string; deadline: string; expiredText: string } | null {
switch (w.windowPhase) {
+ case "PRE_WINDOW":
+ if (w.windowOpensAt)
+ return {
+ label: "Booking opens in",
+ deadline: w.windowOpensAt,
+ expiredText: "Booking opening now…",
+ };
+ return null;
case "OPEN":
- if (w.windowClosesAt) return { label: "Window closes in", deadline: w.windowClosesAt };
+ if (w.windowClosesAt)
+ return {
+ label: "Window closes in",
+ deadline: w.windowClosesAt,
+ expiredText: "Document review starting…",
+ };
return null;
case "DOC_REVIEW":
- if (w.docReviewEndsAt) return { label: "Document review ends in", deadline: w.docReviewEndsAt };
+ if (w.docReviewEndsAt)
+ return {
+ label: "Document review ends in",
+ deadline: w.docReviewEndsAt,
+ expiredText: "Payment starting…",
+ };
return null;
case "PAYMENT":
- if (w.paymentPhaseEndsAt) return { label: "Payment due in", deadline: w.paymentPhaseEndsAt };
+ if (w.paymentPhaseEndsAt)
+ return {
+ label: "Payment due in",
+ deadline: w.paymentPhaseEndsAt,
+ expiredText: "Payment window closing…",
+ };
return null;
default:
return null;
@@ -141,9 +169,11 @@ interface UpcomingWindowsSectionProps {
}
/**
- * The customer's upcoming/open booking windows on their active-contract
- * lanes. Import trains open a window on one booking day; export trains open
- * 24h before departure. Hidden entirely when there is nothing to show.
+ * All announced upcoming/open booking windows, shown to every customer
+ * regardless of whether they hold a contract on the lane. Import trains open a
+ * window on one booking day; export trains open 24h before departure. Rows on a
+ * lane the customer has an active contract for carry a "Book now" action;
+ * others route to the contract list. Hidden entirely when nothing is announced.
*/
export const UpcomingWindowsSection = memo(function UpcomingWindowsSection({
windows,
@@ -162,7 +192,7 @@ export const UpcomingWindowsSection = memo(function UpcomingWindowsSection({
Booking Windows
- Upcoming and open booking windows on your contract lanes
+ Upcoming and open booking windows across all lanes
@@ -211,6 +241,7 @@ export const UpcomingWindowsSection = memo(function UpcomingWindowsSection({
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 79676cab1..c2729bebb 100644
--- a/apps/edr-freight-web/portal/src/services/bookings.service.ts
+++ b/apps/edr-freight-web/portal/src/services/bookings.service.ts
@@ -47,13 +47,16 @@ export interface PriceLineItem {
}
/**
- * An upcoming/open booking window on one of the signed-in customer's
- * active-contract lanes. Import trains open a window on one booking day;
+ * An announced upcoming/open booking window, shown to every signed-in customer
+ * regardless of contract. Import trains open a window on one booking day;
* export trains open 24h before departure (first come, first served).
*/
export interface MyBookingWindow {
scheduleId: string;
- /** Contract whose route this window belongs to, when the row carries it. */
+ /**
+ * The customer's active contract on this lane, when they hold one — enables
+ * "Book now" to target it. Null for lanes they have no contract on.
+ */
contractId: string | null;
/** ONE_TIME contracts can't draw down against a window — button is hidden. */
contractKind: "ONE_TIME" | "GENERAL" | null;