Files
emaui/apps/backoffice/src/app/features/certificate-designer/config/designer.ts
mihretue baea9f2b57 chore(dev): serve the portal on 3000 for the Fayda redirect
The Fayda redirect URI registered for local testing is
http://localhost:3000/callback, matched exactly by the provider, so the portal
has to be the thing listening there. Its dev and preview servers move from 4200
to 3000 and the API moves to 3001.

Every hardcoded fallback to http://localhost:3000/api follows — six copies of
the same default across libs/api, libs/auth, the portal and the backoffice —
otherwise a developer without a .env would have had the app calling itself.

e2e is unaffected: it binds its own ports (3011/4302/4303) explicitly.
2026-08-25 19:04:27 +00:00

72 lines
2.8 KiB
TypeScript

import type { Bilingual, LicenseCategory, LicenseTemplate, LicenseType } from '@ema-platform/api';
/** Same resolution — and same fallback — the shared RTK Query baseQuery uses. */
export const API_BASE_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3001/api';
export const STATUS_COLOR: Record<LicenseTemplate['status'], string> = {
DRAFT: 'gray',
PUBLISHED: 'teal',
ARCHIVED: 'dark',
};
/** Page options sent with every save and preview — A4, background printed. */
export function pageOptionsFor(landscape: boolean) {
return { format: 'A4' as const, landscape, printBackground: true };
}
/**
* Order the category groups appear in, and their fallback labels.
*
* Seafarer certification sits first: it holds over 50 of the 80-odd licence
* types, and it is what the designer is opened for most often. Any category
* not listed here still renders — it falls to the end under its own key —
* so a new category on the server does not silently hide its types.
*/
const CATEGORY_ORDER: { key: LicenseCategory; fallback: string }[] = [
{ key: 'MARITIME_PERSONNEL', fallback: 'Seafarer certification (CoC / CoP)' },
{ key: 'CARGO_FREIGHT', fallback: 'Cargo & freight' },
{ key: 'SHIPPING_AGENCY', fallback: 'Shipping agency' },
{ key: 'INVESTMENT', fallback: 'Investment & joint ventures' },
{ key: 'VESSEL_SERVICES', fallback: 'Vessel services' },
{ key: 'WAIVER_SERVICES', fallback: 'Waiver services' },
];
/**
* The licence-type picker's options, grouped by category.
*
* Grouping is what makes the picker usable at all: certificates of competency
* and proficiency are a different kind of thing from an operator's logistics
* licence, and a flat alphabetical list interleaves the two.
*/
export function groupedTypeOptions(
licenseTypes: LicenseType[],
localized: (value?: Bilingual) => string,
t: (key: string, fallback: string) => string,
) {
const byCategory = new Map<string, { value: string; label: string }[]>();
for (const type of licenseTypes) {
const option = { value: type.id, label: localized(type.name) || type.key };
const bucket = byCategory.get(type.category);
if (bucket) bucket.push(option);
else byCategory.set(type.category, [option]);
}
const known = CATEGORY_ORDER.map(({ key, fallback }) => ({
group: t(`designer.category.${key}`, fallback),
items: (byCategory.get(key) ?? []).sort((a, b) => a.label.localeCompare(b.label)),
}));
// Categories the client does not know about yet, so their types stay reachable.
const unknown = [...byCategory.entries()]
.filter(([key]) => !CATEGORY_ORDER.some((c) => c.key === key))
.map(([key, items]) => ({
group: key,
items: items.sort((a, b) => a.label.localeCompare(b.label)),
}));
return [...known, ...unknown].filter((group) => group.items.length > 0);
}