diff --git a/apps/portal/src/app/components/AmharicDatePicker.tsx b/apps/portal/src/app/components/AmharicDatePicker.tsx
index 5ef8c4f37..717d97b6a 100644
--- a/apps/portal/src/app/components/AmharicDatePicker.tsx
+++ b/apps/portal/src/app/components/AmharicDatePicker.tsx
@@ -59,22 +59,23 @@ const ETH_FORMATTERS = {
formatMonthDropdown: (month: Date) => ethMonthName(month),
};
-// Local-date parse/format for the "YYYY-MM-DD" wire format (matches native
-// semantics). Deliberately NOT `new Date(iso)` (parses as
-// UTC midnight, off-by-one in negative-offset zones) and NOT
-// `.toISOString()` (shifts by the local offset when formatting) — same class
-// of bug the UTC-noon workaround above already had to fix once in this file.
+// Wire format is a full ISO-8601 instant string (e.g.
+// "2026-07-31T00:00:00.000Z"), what the backend expects for date fields.
+// The picker only selects a calendar day, so the time is pinned to UTC
+// midnight — reading back with getUTC*() (not local get*()) keeps the
+// calendar day stable regardless of the runtime's timezone, avoiding the
+// same off-by-one class of bug the UTC-noon workaround above exists for.
function parseISO(value?: string | null): Date | null {
if (!value) return null;
- const [y, m, d] = value.split('-').map(Number);
- return y && m && d ? new Date(y, m - 1, d) : null;
+ const d = new Date(value);
+ if (isNaN(d.getTime())) return null;
+ return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
}
function formatISO(date: Date): string {
- const y = date.getFullYear();
- const m = String(date.getMonth() + 1).padStart(2, '0');
- const d = String(date.getDate()).padStart(2, '0');
- return `${y}-${m}-${d}`;
+ return new Date(
+ Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
+ ).toISOString();
}
export function toEthiopicDateLabel(date: Date | string): string {
@@ -88,6 +89,12 @@ export function toEthiopicDateLabel(date: Date | string): string {
}
}
+export function toGregorianDateLabel(date: Date | string): string {
+ const d = typeof date === 'string' ? parseISO(date) : date;
+ if (!d) return '';
+ return d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
+}
+
const YEAR_DROPDOWN_END = new Date(new Date().getFullYear() + 50, 11, 31);
export interface AmharicDatePickerProps {
diff --git a/apps/portal/src/app/features/seafarer/pages/SeafarerRegistrationPage.tsx b/apps/portal/src/app/features/seafarer/pages/SeafarerRegistrationPage.tsx
index e849275af..e32e7678c 100644
--- a/apps/portal/src/app/features/seafarer/pages/SeafarerRegistrationPage.tsx
+++ b/apps/portal/src/app/features/seafarer/pages/SeafarerRegistrationPage.tsx
@@ -35,7 +35,7 @@ import {
import { useNavigate } from 'react-router-dom';
import { notify, BilingualInput, useErrorHandler } from '@ema-platform/ui';
import type { BilingualValue } from '@ema-platform/ui';
-import { AmharicDatePicker, toEthiopicDateLabel } from '../../../components/AmharicDatePicker';
+import { AmharicDatePicker, toEthiopicDateLabel, toGregorianDateLabel } from '../../../components/AmharicDatePicker';
import { LocationPicker } from '../../location/components/LocationPicker';
// ---------------------------------------------------------------------------
@@ -458,13 +458,13 @@ export function SeafarerRegistrationPage() {
-
+
-
+
diff --git a/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx b/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx
index afde671f6..8ec9b81cd 100644
--- a/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx
+++ b/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx
@@ -1,5 +1,5 @@
import { useRef, useState } from "react";
-import { AmharicDatePicker } from "../../../components/AmharicDatePicker";
+import { AmharicDatePicker, toGregorianDateLabel } from "../../../components/AmharicDatePicker";
import {
Alert,
Badge,
@@ -1228,8 +1228,8 @@ export function SeamanBookApplicationPage() {
-
-
+
+
@@ -1254,7 +1254,7 @@ export function SeamanBookApplicationPage() {
: paymentRef
}
/>
-
+
-
-
+
+
diff --git a/apps/portal/src/app/features/waiver/pages/WaiverApplicationPage.tsx b/apps/portal/src/app/features/waiver/pages/WaiverApplicationPage.tsx
index 396a10a87..2dbfe1ab9 100644
--- a/apps/portal/src/app/features/waiver/pages/WaiverApplicationPage.tsx
+++ b/apps/portal/src/app/features/waiver/pages/WaiverApplicationPage.tsx
@@ -1,7 +1,7 @@
import { useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useApiMutation } from '@ema-platform/api';
-import { AmharicDatePicker } from '../../../components/AmharicDatePicker';
+import { AmharicDatePicker, toGregorianDateLabel } from '../../../components/AmharicDatePicker';
import {
Alert,
Badge,
@@ -454,8 +454,8 @@ export function WaiverApplicationPage() {
- {isPreWaiver && }
- {isPostWaiver && }
+ {isPreWaiver && }
+ {isPostWaiver && }