@freighttech/shipment-tracking 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,11 +24,79 @@ import { Button as ButtonV2 } from "@freighttech/ui/primitives-v2";
24
24
  import { Input } from "@freighttech/ui/primitives/input";
25
25
  import { Label } from "@freighttech/ui/primitives/label";
26
26
  import { Checkbox } from "@freighttech/ui/primitives/checkbox";
27
- import { Textarea } from "@freighttech/ui/primitives/textarea";
28
27
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@freighttech/ui/primitives/card";
29
28
  import { Badge } from "@freighttech/ui/primitives/badge";
30
29
  import { Loader2, CheckCircle2, XCircle, Shield, Plus } from "lucide-react";
31
30
  import { ShipsGoConfigSection } from "../../components/ShipsGoConfigSection.js";
31
+ const CLIENT_ID = { key: "client_id", label: "Client ID" };
32
+ const CLIENT_SECRET = { key: "client_secret", label: "Client secret", secret: true };
33
+ const CARRIER_SPECS = [
34
+ {
35
+ value: "maersk",
36
+ label: "Maersk",
37
+ defaultEndpoint: "https://api.maersk.com/track-and-trace-private/events",
38
+ credentials: [CLIENT_ID, CLIENT_SECRET]
39
+ },
40
+ {
41
+ value: "msc",
42
+ label: "MSC",
43
+ defaultEndpoint: "https://api.tech.msc.com/msc/trackandtrace/v2.2/events",
44
+ // The MSC OAuth account identity (client_id/scope/token_url/aud) is supplied
45
+ // per config, not baked into the adapter — so the form collects it here
46
+ // alongside the certificate. `aud` defaults to `token_url` when left blank.
47
+ credentials: [
48
+ CLIENT_ID,
49
+ { key: "scope", label: "Scope" },
50
+ { key: "token_url", label: "Token URL" },
51
+ { key: "aud", label: "Audience", optional: true, placeholder: "defaults to Token URL" },
52
+ { key: "certificate_base64", label: "Certificate (base64 PFX)", secret: true }
53
+ ]
54
+ },
55
+ {
56
+ value: "cma-cgm",
57
+ label: "CMA CGM",
58
+ defaultEndpoint: "https://apis.cma-cgm.net/operation/trackandtrace/v1/events",
59
+ credentials: [{ key: "api_key", label: "API key", secret: true }]
60
+ },
61
+ {
62
+ value: "hapag-lloyd",
63
+ label: "Hapag-Lloyd",
64
+ defaultEndpoint: "https://api.hlag.com/hlag/external/v2/events",
65
+ credentials: [CLIENT_ID, CLIENT_SECRET]
66
+ },
67
+ {
68
+ value: "zim",
69
+ label: "ZIM",
70
+ defaultEndpoint: "https://apigw.zim.com/trackAndTrace/v1",
71
+ credentials: [CLIENT_ID, CLIENT_SECRET, { key: "subscription_key", label: "Subscription key", secret: true }]
72
+ },
73
+ {
74
+ value: "cosco",
75
+ label: "COSCO",
76
+ defaultEndpoint: "https://apis.cargosmart.com/openapi/cs2/ctvc/COSU",
77
+ credentials: [
78
+ { key: "app_key", label: "App key", secret: true },
79
+ { key: "customer_id", label: "Customer ID" },
80
+ { key: "scac_code", label: "SCAC code", optional: true, placeholder: "COSU" }
81
+ ]
82
+ },
83
+ {
84
+ value: "evergreen",
85
+ label: "Evergreen",
86
+ defaultEndpoint: "",
87
+ endpointRequired: true,
88
+ credentials: [CLIENT_ID, CLIENT_SECRET, { key: "token_url", label: "Token URL" }]
89
+ }
90
+ ];
91
+ function specFor(carrierCode) {
92
+ return CARRIER_SPECS.find((c) => c.value === carrierCode);
93
+ }
94
+ function credentialFieldsFor(carrierCode, authConfig) {
95
+ const spec = specFor(carrierCode);
96
+ if (spec) return spec.credentials;
97
+ const keys = authConfig ? Object.keys(authConfig) : [];
98
+ return keys.map((k) => ({ key: k, label: k, secret: /secret|password|key|token|cert/i.test(k) }));
99
+ }
32
100
  function mapItem(item) {
33
101
  const id = typeof item.id === "string" ? item.id : null;
34
102
  if (!id) return null;
@@ -43,19 +111,26 @@ function mapItem(item) {
43
111
  createdAt: item.createdAt ?? null
44
112
  };
45
113
  }
46
- const emptyForm = {
47
- carrierCode: "",
48
- apiEndpoint: "",
49
- authConfig: "",
50
- rateLimitRequests: 60,
51
- rateLimitWindowSeconds: 60,
52
- isActive: true
53
- };
114
+ function formForCarrier(carrierCode) {
115
+ const spec = specFor(carrierCode);
116
+ return {
117
+ carrierCode,
118
+ apiEndpoint: spec?.defaultEndpoint ?? "",
119
+ credentials: {},
120
+ rateLimitRequests: 60,
121
+ rateLimitWindowSeconds: 60,
122
+ isActive: true
123
+ };
124
+ }
125
+ const emptyForm = formForCarrier(CARRIER_SPECS[0].value);
54
126
  function rowToForm(row) {
127
+ const credentials = {};
128
+ const auth = row.authConfig ?? {};
129
+ for (const [k, v] of Object.entries(auth)) credentials[k] = v == null ? "" : String(v);
55
130
  return {
56
131
  carrierCode: row.carrierCode,
57
- apiEndpoint: row.apiEndpoint ?? "",
58
- authConfig: row.authConfig ? JSON.stringify(row.authConfig, null, 2) : "",
132
+ apiEndpoint: row.apiEndpoint ?? specFor(row.carrierCode)?.defaultEndpoint ?? "",
133
+ credentials,
59
134
  rateLimitRequests: row.rateLimitRequests,
60
135
  rateLimitWindowSeconds: row.rateLimitWindowSeconds,
61
136
  isActive: row.isActive
@@ -297,14 +372,25 @@ function TrackingAuthConfigPage() {
297
372
  const [form, setForm] = useState(emptyForm);
298
373
  const [submitting, setSubmitting] = useState(false);
299
374
  const isEdit = editingId !== null;
375
+ const credFields = credentialFieldsFor(form.carrierCode, form.credentials);
376
+ const spec = specFor(form.carrierCode);
377
+ const setField = useCallback(
378
+ (key, value) => setForm((p) => ({ ...p, [key]: value })),
379
+ []
380
+ );
381
+ const setCredential = useCallback(
382
+ (key, value) => setForm((p) => ({ ...p, credentials: { ...p.credentials, [key]: value } })),
383
+ []
384
+ );
385
+ const changeCarrier = useCallback((carrierCode) => setForm(formForCarrier(carrierCode)), []);
300
386
  const columns = useMemo(
301
387
  () => [
302
388
  {
303
389
  data: "carrierCode",
304
- title: t("shipment_tracking.carrier_configs.fields.carrierCode", "Carrier Code"),
390
+ title: t("shipment_tracking.carrier_configs.fields.carrierCode", "Carrier"),
305
391
  width: 150,
306
392
  readOnly: true,
307
- renderer: (value) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: String(value ?? "") })
393
+ renderer: (value) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: specFor(String(value))?.label ?? String(value ?? "") })
308
394
  },
309
395
  {
310
396
  data: "apiEndpoint",
@@ -379,33 +465,44 @@ function TrackingAuthConfigPage() {
379
465
  const handleSubmit = useCallback(async () => {
380
466
  if (submitting) return;
381
467
  if (!isEdit && !form.carrierCode.trim()) {
382
- flash(t("shipment_tracking.carrier_configs.validation.carrierCodeRequired", "Carrier code is required"), "error");
468
+ flash(t("shipment_tracking.carrier_configs.validation.carrierCodeRequired", "Carrier is required"), "error");
383
469
  return;
384
470
  }
385
- if (form.authConfig.trim()) {
471
+ const currentSpec = specFor(form.carrierCode);
472
+ const endpoint = form.apiEndpoint.trim();
473
+ if (currentSpec?.endpointRequired && !endpoint) {
474
+ flash(t("shipment_tracking.carrier_configs.validation.endpointRequired", "API endpoint is required for this carrier"), "error");
475
+ return;
476
+ }
477
+ if (endpoint) {
386
478
  try {
387
- JSON.parse(form.authConfig);
479
+ new URL(endpoint);
388
480
  } catch {
389
- flash(t("shipment_tracking.carrier_configs.validation.authConfigInvalidJson", "Auth config must be valid JSON"), "error");
481
+ flash(t("shipment_tracking.carrier_configs.validation.apiEndpointInvalidUrl", "API endpoint must be a valid URL"), "error");
390
482
  return;
391
483
  }
392
484
  }
393
- if (form.apiEndpoint.trim()) {
394
- try {
395
- new URL(form.apiEndpoint.trim());
396
- } catch {
397
- flash(t("shipment_tracking.carrier_configs.validation.apiEndpointInvalidUrl", "API endpoint must be a valid URL"), "error");
485
+ const fields = credentialFieldsFor(form.carrierCode, form.credentials);
486
+ const authConfig = {};
487
+ for (const f of fields) {
488
+ const val = (form.credentials[f.key] ?? "").trim();
489
+ if (!val) {
490
+ if (f.optional) continue;
491
+ flash(
492
+ t("shipment_tracking.carrier_configs.validation.credentialsRequired", "Fill in all credential fields"),
493
+ "error"
494
+ );
398
495
  return;
399
496
  }
497
+ authConfig[f.key] = val;
400
498
  }
401
499
  setSubmitting(true);
402
500
  try {
403
- const authConfig = form.authConfig.trim() ? JSON.parse(form.authConfig) : void 0;
501
+ const hasAuth = Object.keys(authConfig).length > 0;
404
502
  if (isEdit) {
405
503
  const body = { id: editingId };
406
- if (form.apiEndpoint.trim()) body.apiEndpoint = form.apiEndpoint.trim();
407
- else body.apiEndpoint = null;
408
- body.authConfig = authConfig ?? null;
504
+ body.apiEndpoint = endpoint || null;
505
+ body.authConfig = hasAuth ? authConfig : null;
409
506
  body.rateLimitRequests = form.rateLimitRequests;
410
507
  body.rateLimitWindowSeconds = form.rateLimitWindowSeconds;
411
508
  body.isActive = form.isActive;
@@ -421,8 +518,8 @@ function TrackingAuthConfigPage() {
421
518
  rateLimitWindowSeconds: form.rateLimitWindowSeconds,
422
519
  isActive: form.isActive
423
520
  };
424
- if (form.apiEndpoint.trim()) body.apiEndpoint = form.apiEndpoint.trim();
425
- if (authConfig) body.authConfig = authConfig;
521
+ if (endpoint) body.apiEndpoint = endpoint;
522
+ if (hasAuth) body.authConfig = authConfig;
426
523
  await apiCallOrThrow("/api/shipment_tracking/carrier-configs", {
427
524
  method: "POST",
428
525
  body: JSON.stringify(body)
@@ -527,16 +624,15 @@ function TrackingAuthConfigPage() {
527
624
  /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: isEdit ? t("shipment_tracking.carrier_configs.edit", "Edit Carrier Config") : t("shipment_tracking.carrier_configs.create", "Add Carrier Config") }) }),
528
625
  /* @__PURE__ */ jsxs("div", { className: "grid gap-4 py-2", children: [
529
626
  /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
530
- /* @__PURE__ */ jsx(Label, { htmlFor: "carrierCode", children: t("shipment_tracking.carrier_configs.fields.carrierCode", "Carrier Code") }),
531
- /* @__PURE__ */ jsx(
532
- Input,
627
+ /* @__PURE__ */ jsx(Label, { htmlFor: "carrierCode", children: t("shipment_tracking.carrier_configs.fields.carrierCode", "Carrier") }),
628
+ isEdit ? /* @__PURE__ */ jsx(Input, { id: "carrierCode", value: spec?.label ?? form.carrierCode, disabled: true }) : /* @__PURE__ */ jsx(
629
+ "select",
533
630
  {
534
631
  id: "carrierCode",
632
+ className: "border rounded h-9 px-2 text-sm",
535
633
  value: form.carrierCode,
536
- onChange: (event) => setForm((prev) => ({ ...prev, carrierCode: event.target.value })),
537
- placeholder: "e.g. maersk, msc, cma-cgm",
538
- disabled: isEdit,
539
- autoFocus: !isEdit
634
+ onChange: (event) => changeCarrier(event.target.value),
635
+ children: CARRIER_SPECS.map((c) => /* @__PURE__ */ jsx("option", { value: c.value, children: c.label }, c.value))
540
636
  }
541
637
  )
542
638
  ] }),
@@ -547,25 +643,29 @@ function TrackingAuthConfigPage() {
547
643
  {
548
644
  id: "apiEndpoint",
549
645
  value: form.apiEndpoint,
550
- onChange: (event) => setForm((prev) => ({ ...prev, apiEndpoint: event.target.value })),
646
+ onChange: (event) => setField("apiEndpoint", event.target.value),
551
647
  placeholder: "https://api.example.com/tracking"
552
648
  }
553
- )
649
+ ),
650
+ spec?.endpointRequired && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("shipment_tracking.carrier_configs.hints.endpointRequired", "This carrier requires an explicit API endpoint.") })
554
651
  ] }),
555
- /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
556
- /* @__PURE__ */ jsx(Label, { htmlFor: "authConfig", children: t("shipment_tracking.carrier_configs.fields.authConfig", "Auth Config (JSON)") }),
652
+ credFields.map((f) => /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
653
+ /* @__PURE__ */ jsxs(Label, { htmlFor: `cred-${f.key}`, children: [
654
+ t(`shipment_tracking.carrier_configs.cred.${f.key}`, f.label),
655
+ f.optional ? ` (${t("common.optional", "optional")})` : ""
656
+ ] }),
557
657
  /* @__PURE__ */ jsx(
558
- Textarea,
658
+ Input,
559
659
  {
560
- id: "authConfig",
561
- value: form.authConfig,
562
- onChange: (event) => setForm((prev) => ({ ...prev, authConfig: event.target.value })),
563
- placeholder: '{"apiKey": "..."}',
564
- rows: 3,
565
- className: "font-mono text-xs"
660
+ id: `cred-${f.key}`,
661
+ type: f.secret ? "password" : "text",
662
+ autoComplete: "off",
663
+ value: form.credentials[f.key] ?? "",
664
+ onChange: (event) => setCredential(f.key, event.target.value),
665
+ placeholder: f.placeholder
566
666
  }
567
667
  )
568
- ] }),
668
+ ] }, f.key)),
569
669
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
570
670
  /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
571
671
  /* @__PURE__ */ jsx(Label, { htmlFor: "rateLimitRequests", children: t("shipment_tracking.carrier_configs.fields.rateLimitRequests", "Rate Limit (requests)") }),
@@ -577,7 +677,7 @@ function TrackingAuthConfigPage() {
577
677
  min: 1,
578
678
  max: 1e4,
579
679
  value: form.rateLimitRequests,
580
- onChange: (event) => setForm((prev) => ({ ...prev, rateLimitRequests: Number(event.target.value) || 60 }))
680
+ onChange: (event) => setField("rateLimitRequests", Number(event.target.value) || 60)
581
681
  }
582
682
  )
583
683
  ] }),
@@ -591,7 +691,7 @@ function TrackingAuthConfigPage() {
591
691
  min: 1,
592
692
  max: 86400,
593
693
  value: form.rateLimitWindowSeconds,
594
- onChange: (event) => setForm((prev) => ({ ...prev, rateLimitWindowSeconds: Number(event.target.value) || 60 }))
694
+ onChange: (event) => setField("rateLimitWindowSeconds", Number(event.target.value) || 60)
595
695
  }
596
696
  )
597
697
  ] })
@@ -602,7 +702,7 @@ function TrackingAuthConfigPage() {
602
702
  {
603
703
  id: "isActive",
604
704
  checked: form.isActive,
605
- onCheckedChange: (checked) => setForm((prev) => ({ ...prev, isActive: checked === true }))
705
+ onCheckedChange: (checked) => setField("isActive", checked === true)
606
706
  }
607
707
  ),
608
708
  /* @__PURE__ */ jsx(Label, { htmlFor: "isActive", className: "cursor-pointer", children: t("shipment_tracking.carrier_configs.fields.isActive", "Active") })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/shipment_tracking/backend/carrier-configs/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useCallback, useMemo, useState } from 'react'\nimport { Page, PageBody } from '@freighttech/ui/backend/Page'\nimport { BooleanIcon } from '@freighttech/ui/backend/ValueIcons'\nimport {\n DynamicTable,\n TableSkeleton,\n useDynamicTablePage,\n} from '@freighttech/ui/backend/dynamic-table'\nimport type { ColumnDef, ContextMenuAction } from '@freighttech/ui/backend/dynamic-table'\nimport { apiCallOrThrow } from '@freighttech/ui/backend/utils/apiCall'\nimport { flash } from '@freighttech/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n} from '@freighttech/ui/primitives/dialog'\nimport { Button } from '@freighttech/ui/primitives/button'\nimport { Button as ButtonV2 } from '@freighttech/ui/primitives-v2'\nimport { Input } from '@freighttech/ui/primitives/input'\nimport { Label } from '@freighttech/ui/primitives/label'\nimport { Checkbox } from '@freighttech/ui/primitives/checkbox'\nimport { Textarea } from '@freighttech/ui/primitives/textarea'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@freighttech/ui/primitives/card'\nimport { Badge } from '@freighttech/ui/primitives/badge'\nimport { Loader2, CheckCircle2, XCircle, Shield, Plus } from 'lucide-react'\nimport { ShipsGoConfigSection } from '../../components/ShipsGoConfigSection'\n\n// \u2500\u2500\u2500 Carrier Config Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype CarrierConfigRow = {\n id: string\n carrierCode: string\n apiEndpoint: string | null\n authConfig: Record<string, unknown> | null\n rateLimitRequests: number\n rateLimitWindowSeconds: number\n isActive: boolean\n createdAt: string | null\n}\n\nfunction mapItem(item: Record<string, unknown>): CarrierConfigRow | null {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n\n return {\n id,\n carrierCode: (item.carrierCode as string) ?? '',\n apiEndpoint: (item.apiEndpoint as string) ?? null,\n authConfig: (item.authConfig as Record<string, unknown>) ?? null,\n rateLimitRequests: typeof item.rateLimitRequests === 'number' ? item.rateLimitRequests : 60,\n rateLimitWindowSeconds: typeof item.rateLimitWindowSeconds === 'number' ? item.rateLimitWindowSeconds : 60,\n isActive: item.isActive === true,\n createdAt: (item.createdAt as string) ?? null,\n }\n}\n\ntype FormState = {\n carrierCode: string\n apiEndpoint: string\n authConfig: string\n rateLimitRequests: number\n rateLimitWindowSeconds: number\n isActive: boolean\n}\n\nconst emptyForm: FormState = {\n carrierCode: '',\n apiEndpoint: '',\n authConfig: '',\n rateLimitRequests: 60,\n rateLimitWindowSeconds: 60,\n isActive: true,\n}\n\nfunction rowToForm(row: CarrierConfigRow): FormState {\n return {\n carrierCode: row.carrierCode,\n apiEndpoint: row.apiEndpoint ?? '',\n authConfig: row.authConfig ? JSON.stringify(row.authConfig, null, 2) : '',\n rateLimitRequests: row.rateLimitRequests,\n rateLimitWindowSeconds: row.rateLimitWindowSeconds,\n isActive: row.isActive,\n }\n}\n\n// \u2500\u2500\u2500 BIC Config Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype BicConfigData = {\n id: string\n organizationId: string\n tenantId: string\n isEnabled: boolean\n username: string\n baseUrl: string\n createdAt: string\n updatedAt: string\n} | null\n\ntype BicFormState = {\n isEnabled: boolean\n username: string\n password: string\n baseUrl: string\n}\n\nconst emptyBicForm: BicFormState = {\n isEnabled: false,\n username: '',\n password: '',\n baseUrl: 'https://api.bic-code.org',\n}\n\n// \u2500\u2500\u2500 BIC Config Section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction BicConfigSection() {\n const t = useT()\n const [config, setConfig] = React.useState<BicConfigData>(null)\n const [form, setForm] = React.useState<BicFormState>(emptyBicForm)\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [testing, setTesting] = React.useState(false)\n const [testResult, setTestResult] = React.useState<{ success: boolean; message: string } | null>(null)\n\n const fetchConfig = React.useCallback(async () => {\n setLoading(true)\n try {\n const { result } = await apiCallOrThrow<{ config: BicConfigData }>('/api/shipment_tracking/bic-configs')\n setConfig(result?.config ?? null)\n if (result?.config) {\n setForm({\n isEnabled: result.config.isEnabled,\n username: result.config.username,\n password: '', // Password is never returned from API\n baseUrl: result.config.baseUrl,\n })\n } else {\n setForm(emptyBicForm)\n }\n } catch {\n // Config doesn't exist yet, that's fine\n setConfig(null)\n setForm(emptyBicForm)\n } finally {\n setLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchConfig()\n }, [fetchConfig])\n\n const handleTest = React.useCallback(async () => {\n if (!form.username || !form.password) {\n flash(t('shipment_tracking.bic_config.validation.usernameRequired', 'Username is required'), 'error')\n return\n }\n\n setTesting(true)\n setTestResult(null)\n\n try {\n const { result } = await apiCallOrThrow<{ success: boolean; message: string }>(\n '/api/shipment_tracking/bic-configs/test',\n {\n method: 'POST',\n body: JSON.stringify({\n username: form.username,\n password: form.password,\n baseUrl: form.baseUrl,\n }),\n }\n )\n\n setTestResult(result ?? { success: false, message: 'Unknown error' })\n\n if (result?.success) {\n flash(t('shipment_tracking.bic_config.flash.testSuccess', 'BIC API connection successful'), 'success')\n } else {\n flash(result?.message ?? t('shipment_tracking.bic_config.flash.testFailed', 'BIC API connection failed'), 'error')\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Connection test failed'\n setTestResult({ success: false, message })\n flash(message, 'error')\n } finally {\n setTesting(false)\n }\n }, [form, t])\n\n const handleSave = React.useCallback(async () => {\n if (!form.username) {\n flash(t('shipment_tracking.bic_config.validation.usernameRequired', 'Username is required'), 'error')\n return\n }\n\n // Password is required for new config or when enabling\n if (!config && !form.password) {\n flash(t('shipment_tracking.bic_config.validation.passwordRequired', 'Password is required'), 'error')\n return\n }\n\n setSaving(true)\n try {\n const body: Record<string, unknown> = {\n isEnabled: form.isEnabled,\n username: form.username,\n baseUrl: form.baseUrl || 'https://api.bic-code.org',\n }\n\n // Only include password if it was changed\n if (form.password) {\n body.password = form.password\n } else if (config) {\n // If editing and no new password, we still need to send something\n // The API should handle this gracefully\n body.password = '__UNCHANGED__'\n }\n\n await apiCallOrThrow('/api/shipment_tracking/bic-configs', {\n method: 'PUT',\n body: JSON.stringify(body),\n })\n\n flash(t('shipment_tracking.bic_config.flash.saved', 'BIC configuration saved'), 'success')\n fetchConfig()\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to save'\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [form, config, t, fetchConfig])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSave()\n }\n },\n [handleSave]\n )\n\n if (loading) {\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Shield className=\"h-5 w-5\" />\n {t('shipment_tracking.bic_config.title', 'BIC Facility API')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </CardContent>\n </Card>\n )\n }\n\n return (\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Shield className=\"h-5 w-5\" />\n <CardTitle>{t('shipment_tracking.bic_config.title', 'BIC Facility API')}</CardTitle>\n </div>\n <div className=\"flex items-center gap-2\">\n {config ? (\n <>\n <Badge variant=\"outline\">\n {t('shipment_tracking.bic_config.status.configured', 'Configured')}\n </Badge>\n <Badge variant={config.isEnabled ? 'default' : 'secondary'}>\n {config.isEnabled\n ? t('shipment_tracking.bic_config.status.enabled', 'Enabled')\n : t('shipment_tracking.bic_config.status.disabled', 'Disabled')}\n </Badge>\n </>\n ) : (\n <Badge variant=\"secondary\">\n {t('shipment_tracking.bic_config.status.notConfigured', 'Not Configured')}\n </Badge>\n )}\n </div>\n </div>\n <CardDescription>\n {t(\n 'shipment_tracking.bic_config.description',\n 'Configure BIC Facility Code API credentials to enrich shipment tracking locations with coordinates, addresses, and terminal operator information.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent onKeyDown={handleKeyDown}>\n <div className=\"grid gap-4\">\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"bicEnabled\"\n checked={form.isEnabled}\n onCheckedChange={(checked) =>\n setForm((prev) => ({ ...prev, isEnabled: checked === true }))\n }\n />\n <Label htmlFor=\"bicEnabled\" className=\"cursor-pointer font-medium\">\n {t('shipment_tracking.bic_config.fields.isEnabled', 'Enable BIC Enrichment')}\n </Label>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"bicUsername\">\n {t('shipment_tracking.bic_config.fields.username', 'Username')}\n </Label>\n <Input\n id=\"bicUsername\"\n value={form.username}\n onChange={(e) => setForm((prev) => ({ ...prev, username: e.target.value }))}\n placeholder=\"your-bic-username\"\n />\n </div>\n\n <div className=\"grid gap-2\">\n <Label htmlFor=\"bicPassword\">\n {t('shipment_tracking.bic_config.fields.password', 'Password')}\n </Label>\n <Input\n id=\"bicPassword\"\n type=\"password\"\n value={form.password}\n onChange={(e) => setForm((prev) => ({ ...prev, password: e.target.value }))}\n placeholder={config ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022' : 'your-bic-password'}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'shipment_tracking.bic_config.hints.password',\n 'Your password will be encrypted and stored securely.'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"grid gap-2\">\n <Label htmlFor=\"bicBaseUrl\">\n {t('shipment_tracking.bic_config.fields.baseUrl', 'API Base URL')}\n </Label>\n <Input\n id=\"bicBaseUrl\"\n value={form.baseUrl}\n onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))}\n placeholder=\"https://api.bic-code.org\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('shipment_tracking.bic_config.hints.baseUrl', 'Default: https://api.bic-code.org')}\n </p>\n </div>\n\n {testResult && (\n <div\n className={`flex items-center gap-2 rounded-md p-3 text-sm ${\n testResult.success\n ? 'bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400'\n : 'bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400'\n }`}\n >\n {testResult.success ? (\n <CheckCircle2 className=\"h-4 w-4\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n <span>{testResult.message}</span>\n </div>\n )}\n\n <div className=\"flex items-center gap-2 pt-2\">\n <Button\n variant=\"outline\"\n onClick={handleTest}\n disabled={testing || !form.username || !form.password}\n >\n {testing && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {t('shipment_tracking.bic_config.actions.testConnection', 'Test Connection')}\n </Button>\n <Button onClick={handleSave} disabled={saving}>\n {saving && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {t('shipment_tracking.bic_config.actions.save', 'Save Configuration')}\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n\n// \u2500\u2500\u2500 Main Page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport default function TrackingAuthConfigPage() {\n const t = useT()\n\n const [dialogOpen, setDialogOpen] = useState(false)\n const [editingId, setEditingId] = useState<string | null>(null)\n const [form, setForm] = useState<FormState>(emptyForm)\n const [submitting, setSubmitting] = useState(false)\n\n const isEdit = editingId !== null\n\n const columns = useMemo<ColumnDef[]>(\n () => [\n {\n data: 'carrierCode',\n title: t('shipment_tracking.carrier_configs.fields.carrierCode', 'Carrier Code'),\n width: 150,\n readOnly: true,\n renderer: (value: unknown) => (\n <span className=\"font-medium\">{String(value ?? '')}</span>\n ),\n },\n {\n data: 'apiEndpoint',\n title: t('shipment_tracking.carrier_configs.fields.apiEndpoint', 'API Endpoint'),\n width: 280,\n readOnly: true,\n renderer: (value: unknown) => (\n <span className=\"text-sm truncate max-w-[260px] block\">{String(value || '-')}</span>\n ),\n },\n {\n data: 'rateLimit',\n title: t('shipment_tracking.carrier_configs.fields.rateLimit', 'Rate Limit'),\n width: 120,\n readOnly: true,\n renderer: (_value: unknown, rowData: Record<string, unknown>) => (\n <span className=\"text-sm\">\n {String(rowData.rateLimitRequests ?? 60)} / {String(rowData.rateLimitWindowSeconds ?? 60)}s\n </span>\n ),\n },\n {\n data: 'isActive',\n title: t('shipment_tracking.carrier_configs.fields.isActive', 'Active'),\n width: 80,\n readOnly: true,\n renderer: (value: unknown) => <BooleanIcon value={value === true || value === 'true'} />,\n },\n ],\n [t],\n )\n\n const openCreateDialog = useCallback(() => {\n setEditingId(null)\n setForm(emptyForm)\n setDialogOpen(true)\n }, [])\n\n const addButton = useMemo(\n () => (\n <ButtonV2 variant=\"primary\" size=\"sm\" icon={<Plus className=\"h-4 w-4\" />} onClick={openCreateDialog}>\n {t('shipment_tracking.carrier_configs.create', 'Add Carrier Config')}\n </ButtonV2>\n ),\n [openCreateDialog, t],\n )\n\n const table = useDynamicTablePage<CarrierConfigRow>({\n source: '/api/shipment_tracking/carrier-configs',\n // Saved views. Without this the perspective tab strip auto-hides\n // (hidePerspectiveTabs ?? !hasPerspectives), so users cannot save or\n // switch a view on this list at all.\n perspectives: 'shipment_tracking_carrier_configs',\n columns,\n tableName: t('shipment_tracking.carrier_configs.title', 'Carrier Configs'),\n defaultPageSize: 20,\n mapApiItem: mapItem,\n cellEdit: false,\n tableProps: {\n height: '100%',\n uiConfig: {\n readOnlyStyle: 'normal',\n hideAddRowButton: true,\n searchBarEnd: addButton,\n },\n },\n })\n\n const openEditDialog = useCallback(\n (id: string) => {\n const items = (table.query.data as { items?: Record<string, unknown>[] })?.items ?? []\n const mapped = items.map(mapItem).filter((x): x is CarrierConfigRow => x !== null)\n const row = mapped.find((r) => r.id === id)\n if (!row) return\n setEditingId(id)\n setForm(rowToForm(row))\n setDialogOpen(true)\n },\n [table.query.data],\n )\n\n const handleSubmit = useCallback(async () => {\n if (submitting) return\n\n if (!isEdit && !form.carrierCode.trim()) {\n flash(t('shipment_tracking.carrier_configs.validation.carrierCodeRequired', 'Carrier code is required'), 'error')\n return\n }\n\n if (form.authConfig.trim()) {\n try {\n JSON.parse(form.authConfig)\n } catch {\n flash(t('shipment_tracking.carrier_configs.validation.authConfigInvalidJson', 'Auth config must be valid JSON'), 'error')\n return\n }\n }\n\n if (form.apiEndpoint.trim()) {\n try {\n new URL(form.apiEndpoint.trim())\n } catch {\n flash(t('shipment_tracking.carrier_configs.validation.apiEndpointInvalidUrl', 'API endpoint must be a valid URL'), 'error')\n return\n }\n }\n\n setSubmitting(true)\n try {\n const authConfig = form.authConfig.trim() ? JSON.parse(form.authConfig) : undefined\n\n if (isEdit) {\n const body: Record<string, unknown> = { id: editingId }\n if (form.apiEndpoint.trim()) body.apiEndpoint = form.apiEndpoint.trim()\n else body.apiEndpoint = null\n body.authConfig = authConfig ?? null\n body.rateLimitRequests = form.rateLimitRequests\n body.rateLimitWindowSeconds = form.rateLimitWindowSeconds\n body.isActive = form.isActive\n\n await apiCallOrThrow('/api/shipment_tracking/carrier-configs', {\n method: 'PUT',\n body: JSON.stringify(body),\n })\n flash(t('shipment_tracking.carrier_configs.flash.updated', 'Carrier config updated'), 'success')\n } else {\n const body: Record<string, unknown> = {\n carrierCode: form.carrierCode.trim(),\n rateLimitRequests: form.rateLimitRequests,\n rateLimitWindowSeconds: form.rateLimitWindowSeconds,\n isActive: form.isActive,\n }\n if (form.apiEndpoint.trim()) body.apiEndpoint = form.apiEndpoint.trim()\n if (authConfig) body.authConfig = authConfig\n\n await apiCallOrThrow('/api/shipment_tracking/carrier-configs', {\n method: 'POST',\n body: JSON.stringify(body),\n })\n flash(t('shipment_tracking.carrier_configs.flash.created', 'Carrier config created'), 'success')\n }\n\n setDialogOpen(false)\n table.refresh()\n } catch {\n flash(isEdit ? t('shipment_tracking.carrier_configs.flash.updateFailed', 'Failed to update carrier config') : t('shipment_tracking.carrier_configs.flash.createFailed', 'Failed to create carrier config'), 'error')\n } finally {\n setSubmitting(false)\n }\n }, [form, isEdit, editingId, submitting, table.refresh, t])\n\n const handleDialogKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n const handleDelete = useCallback(\n async (id: string) => {\n if (!confirm(t('shipment_tracking.carrier_configs.confirmDelete', 'Delete this carrier config?'))) return\n\n try {\n await apiCallOrThrow('/api/shipment_tracking/carrier-configs', {\n method: 'DELETE',\n body: JSON.stringify({ id }),\n })\n flash(t('shipment_tracking.carrier_configs.flash.deleted', 'Carrier config deleted'), 'success')\n table.refresh()\n } catch {\n flash(t('shipment_tracking.carrier_configs.flash.deleteFailed', 'Failed to delete carrier config'), 'error')\n }\n },\n [table.refresh, t],\n )\n\n const rowActions = useCallback(\n (rowData: any): ContextMenuAction[] => {\n if (!rowData?.id) return []\n return [\n { id: 'edit', label: t('common.edit', 'Edit') },\n { id: 'delete', label: t('common.delete', 'Delete') },\n ]\n },\n [t],\n )\n\n const handleRowAction = useCallback(\n (actionId: string, rowData: any) => {\n const id = rowData?.id as string | undefined\n if (!id) return\n if (actionId === 'edit') openEditDialog(id)\n else if (actionId === 'delete') handleDelete(id)\n },\n [openEditDialog, handleDelete],\n )\n\n const tableHeight = useMemo(() => {\n const rowH = 40\n const headerH = 40\n const toolbarH = 50\n const minHeight = 200\n const maxHeight = 400\n const rowCount = (table.query.data as { total?: number })?.total ?? 0\n const contentHeight = toolbarH + headerH + rowCount * rowH + 20\n return Math.min(Math.max(contentHeight, minHeight), maxHeight)\n }, [table.query.data])\n\n if (table.isLoading) {\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <BicConfigSection />\n <ShipsGoConfigSection />\n <Card>\n <CardHeader>\n <CardTitle>{t('shipment_tracking.carrier_configs.title', 'Carrier Configs')}</CardTitle>\n </CardHeader>\n <CardContent>\n <TableSkeleton rows={5} columns={4} />\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n {/* BIC Config Section */}\n <BicConfigSection />\n\n {/* ShipsGo Aggregator Config Section */}\n <ShipsGoConfigSection />\n\n {/* Carrier Configs Section */}\n <Card>\n <CardHeader>\n <CardTitle>{t('shipment_tracking.carrier_configs.title', 'Carrier Configs')}</CardTitle>\n <CardDescription>\n {t(\n 'shipment_tracking.auth_config.description',\n 'Configure authentication for carrier tracking APIs and facility enrichment services.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div style={{ height: tableHeight }}>\n <DynamicTable\n {...table.props}\n striped\n density=\"md\"\n rowActions={rowActions}\n onRowAction={handleRowAction}\n />\n </div>\n </CardContent>\n </Card>\n\n <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n <DialogContent className=\"sm:max-w-lg\" onKeyDown={handleDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>{isEdit ? t('shipment_tracking.carrier_configs.edit', 'Edit Carrier Config') : t('shipment_tracking.carrier_configs.create', 'Add Carrier Config')}</DialogTitle>\n </DialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"carrierCode\">{t('shipment_tracking.carrier_configs.fields.carrierCode', 'Carrier Code')}</Label>\n <Input\n id=\"carrierCode\"\n value={form.carrierCode}\n onChange={(event) => setForm((prev) => ({ ...prev, carrierCode: event.target.value }))}\n placeholder=\"e.g. maersk, msc, cma-cgm\"\n disabled={isEdit}\n autoFocus={!isEdit}\n />\n </div>\n\n <div className=\"grid gap-2\">\n <Label htmlFor=\"apiEndpoint\">{t('shipment_tracking.carrier_configs.fields.apiEndpoint', 'API Endpoint')}</Label>\n <Input\n id=\"apiEndpoint\"\n value={form.apiEndpoint}\n onChange={(event) => setForm((prev) => ({ ...prev, apiEndpoint: event.target.value }))}\n placeholder=\"https://api.example.com/tracking\"\n />\n </div>\n\n <div className=\"grid gap-2\">\n <Label htmlFor=\"authConfig\">{t('shipment_tracking.carrier_configs.fields.authConfig', 'Auth Config (JSON)')}</Label>\n <Textarea\n id=\"authConfig\"\n value={form.authConfig}\n onChange={(event) => setForm((prev) => ({ ...prev, authConfig: event.target.value }))}\n placeholder='{\"apiKey\": \"...\"}'\n rows={3}\n className=\"font-mono text-xs\"\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"rateLimitRequests\">{t('shipment_tracking.carrier_configs.fields.rateLimitRequests', 'Rate Limit (requests)')}</Label>\n <Input\n id=\"rateLimitRequests\"\n type=\"number\"\n min={1}\n max={10000}\n value={form.rateLimitRequests}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, rateLimitRequests: Number(event.target.value) || 60 }))\n }\n />\n </div>\n <div className=\"grid gap-2\">\n <Label htmlFor=\"rateLimitWindowSeconds\">{t('shipment_tracking.carrier_configs.fields.rateLimitWindowSeconds', 'Window (seconds)')}</Label>\n <Input\n id=\"rateLimitWindowSeconds\"\n type=\"number\"\n min={1}\n max={86400}\n value={form.rateLimitWindowSeconds}\n onChange={(event) =>\n setForm((prev) => ({ ...prev, rateLimitWindowSeconds: Number(event.target.value) || 60 }))\n }\n />\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"isActive\"\n checked={form.isActive}\n onCheckedChange={(checked) =>\n setForm((prev) => ({ ...prev, isActive: checked === true }))\n }\n />\n <Label htmlFor=\"isActive\" className=\"cursor-pointer\">\n {t('shipment_tracking.carrier_configs.fields.isActive', 'Active')}\n </Label>\n </div>\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setDialogOpen(false)} disabled={submitting}>\n {t('common.cancel', 'Cancel')}\n </Button>\n <Button onClick={handleSubmit} disabled={submitting}>\n {submitting ? t('common.saving', 'Saving...') : isEdit ? t('common.save', 'Save') : t('common.create', 'Create')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useCallback, useMemo, useState } from 'react'\nimport { Page, PageBody } from '@freighttech/ui/backend/Page'\nimport { BooleanIcon } from '@freighttech/ui/backend/ValueIcons'\nimport {\n DynamicTable,\n TableSkeleton,\n useDynamicTablePage,\n} from '@freighttech/ui/backend/dynamic-table'\nimport type { ColumnDef, ContextMenuAction } from '@freighttech/ui/backend/dynamic-table'\nimport { apiCallOrThrow } from '@freighttech/ui/backend/utils/apiCall'\nimport { flash } from '@freighttech/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n} from '@freighttech/ui/primitives/dialog'\nimport { Button } from '@freighttech/ui/primitives/button'\nimport { Button as ButtonV2 } from '@freighttech/ui/primitives-v2'\nimport { Input } from '@freighttech/ui/primitives/input'\nimport { Label } from '@freighttech/ui/primitives/label'\nimport { Checkbox } from '@freighttech/ui/primitives/checkbox'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@freighttech/ui/primitives/card'\nimport { Badge } from '@freighttech/ui/primitives/badge'\nimport { Loader2, CheckCircle2, XCircle, Shield, Plus } from 'lucide-react'\nimport { ShipsGoConfigSection } from '../../components/ShipsGoConfigSection'\n\n// \u2500\u2500\u2500 Carrier specs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// One entry per registered carrier adapter (see\n// lib/adapters/index.ts / services/carrierRegistry.ts). Each carrier maps to a\n// fixed set of auth_config keys and a default events endpoint (the value the\n// adapter falls back to when apiEndpoint is null). ShipsGo is intentionally\n// absent \u2014 it has its own dedicated config section, not a carrier-configs row.\ntype CredField = { key: string; label: string; secret?: boolean; placeholder?: string; optional?: boolean }\n\ntype CarrierSpec = {\n value: string\n label: string\n /** Adapter's default events endpoint; '' when the adapter has none. */\n defaultEndpoint: string\n /** Evergreen has no default endpoint \u2014 apiEndpoint must be supplied. */\n endpointRequired?: boolean\n credentials: CredField[]\n}\n\nconst CLIENT_ID: CredField = { key: 'client_id', label: 'Client ID' }\nconst CLIENT_SECRET: CredField = { key: 'client_secret', label: 'Client secret', secret: true }\n\nconst CARRIER_SPECS: CarrierSpec[] = [\n {\n value: 'maersk',\n label: 'Maersk',\n defaultEndpoint: 'https://api.maersk.com/track-and-trace-private/events',\n credentials: [CLIENT_ID, CLIENT_SECRET],\n },\n {\n value: 'msc',\n label: 'MSC',\n defaultEndpoint: 'https://api.tech.msc.com/msc/trackandtrace/v2.2/events',\n // The MSC OAuth account identity (client_id/scope/token_url/aud) is supplied\n // per config, not baked into the adapter \u2014 so the form collects it here\n // alongside the certificate. `aud` defaults to `token_url` when left blank.\n credentials: [\n CLIENT_ID,\n { key: 'scope', label: 'Scope' },\n { key: 'token_url', label: 'Token URL' },\n { key: 'aud', label: 'Audience', optional: true, placeholder: 'defaults to Token URL' },\n { key: 'certificate_base64', label: 'Certificate (base64 PFX)', secret: true },\n ],\n },\n {\n value: 'cma-cgm',\n label: 'CMA CGM',\n defaultEndpoint: 'https://apis.cma-cgm.net/operation/trackandtrace/v1/events',\n credentials: [{ key: 'api_key', label: 'API key', secret: true }],\n },\n {\n value: 'hapag-lloyd',\n label: 'Hapag-Lloyd',\n defaultEndpoint: 'https://api.hlag.com/hlag/external/v2/events',\n credentials: [CLIENT_ID, CLIENT_SECRET],\n },\n {\n value: 'zim',\n label: 'ZIM',\n defaultEndpoint: 'https://apigw.zim.com/trackAndTrace/v1',\n credentials: [CLIENT_ID, CLIENT_SECRET, { key: 'subscription_key', label: 'Subscription key', secret: true }],\n },\n {\n value: 'cosco',\n label: 'COSCO',\n defaultEndpoint: 'https://apis.cargosmart.com/openapi/cs2/ctvc/COSU',\n credentials: [\n { key: 'app_key', label: 'App key', secret: true },\n { key: 'customer_id', label: 'Customer ID' },\n { key: 'scac_code', label: 'SCAC code', optional: true, placeholder: 'COSU' },\n ],\n },\n {\n value: 'evergreen',\n label: 'Evergreen',\n defaultEndpoint: '',\n endpointRequired: true,\n credentials: [CLIENT_ID, CLIENT_SECRET, { key: 'token_url', label: 'Token URL' }],\n },\n]\n\nfunction specFor(carrierCode: string): CarrierSpec | undefined {\n return CARRIER_SPECS.find((c) => c.value === carrierCode)\n}\n\n/**\n * Credential fields for a carrier. For a known carrier this is its fixed set;\n * for an unrecognised legacy row (edit) we derive the fields from the stored\n * auth_config keys so the form never falls back to raw JSON.\n */\nfunction credentialFieldsFor(carrierCode: string, authConfig?: Record<string, unknown> | null): CredField[] {\n const spec = specFor(carrierCode)\n if (spec) return spec.credentials\n const keys = authConfig ? Object.keys(authConfig) : []\n return keys.map((k) => ({ key: k, label: k, secret: /secret|password|key|token|cert/i.test(k) }))\n}\n\n// \u2500\u2500\u2500 Carrier Config Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype CarrierConfigRow = {\n id: string\n carrierCode: string\n apiEndpoint: string | null\n authConfig: Record<string, unknown> | null\n rateLimitRequests: number\n rateLimitWindowSeconds: number\n isActive: boolean\n createdAt: string | null\n}\n\nfunction mapItem(item: Record<string, unknown>): CarrierConfigRow | null {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n\n return {\n id,\n carrierCode: (item.carrierCode as string) ?? '',\n apiEndpoint: (item.apiEndpoint as string) ?? null,\n authConfig: (item.authConfig as Record<string, unknown>) ?? null,\n rateLimitRequests: typeof item.rateLimitRequests === 'number' ? item.rateLimitRequests : 60,\n rateLimitWindowSeconds: typeof item.rateLimitWindowSeconds === 'number' ? item.rateLimitWindowSeconds : 60,\n isActive: item.isActive === true,\n createdAt: (item.createdAt as string) ?? null,\n }\n}\n\ntype FormState = {\n carrierCode: string\n apiEndpoint: string\n credentials: Record<string, string>\n rateLimitRequests: number\n rateLimitWindowSeconds: number\n isActive: boolean\n}\n\nfunction formForCarrier(carrierCode: string): FormState {\n const spec = specFor(carrierCode)\n return {\n carrierCode,\n apiEndpoint: spec?.defaultEndpoint ?? '',\n credentials: {},\n rateLimitRequests: 60,\n rateLimitWindowSeconds: 60,\n isActive: true,\n }\n}\n\nconst emptyForm: FormState = formForCarrier(CARRIER_SPECS[0].value)\n\nfunction rowToForm(row: CarrierConfigRow): FormState {\n // auth_config is returned by the API (plaintext jsonb); prefill the fields.\n const credentials: Record<string, string> = {}\n const auth = row.authConfig ?? {}\n for (const [k, v] of Object.entries(auth)) credentials[k] = v == null ? '' : String(v)\n return {\n carrierCode: row.carrierCode,\n apiEndpoint: row.apiEndpoint ?? specFor(row.carrierCode)?.defaultEndpoint ?? '',\n credentials,\n rateLimitRequests: row.rateLimitRequests,\n rateLimitWindowSeconds: row.rateLimitWindowSeconds,\n isActive: row.isActive,\n }\n}\n\n// \u2500\u2500\u2500 BIC Config Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype BicConfigData = {\n id: string\n organizationId: string\n tenantId: string\n isEnabled: boolean\n username: string\n baseUrl: string\n createdAt: string\n updatedAt: string\n} | null\n\ntype BicFormState = {\n isEnabled: boolean\n username: string\n password: string\n baseUrl: string\n}\n\nconst emptyBicForm: BicFormState = {\n isEnabled: false,\n username: '',\n password: '',\n baseUrl: 'https://api.bic-code.org',\n}\n\n// \u2500\u2500\u2500 BIC Config Section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction BicConfigSection() {\n const t = useT()\n const [config, setConfig] = React.useState<BicConfigData>(null)\n const [form, setForm] = React.useState<BicFormState>(emptyBicForm)\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [testing, setTesting] = React.useState(false)\n const [testResult, setTestResult] = React.useState<{ success: boolean; message: string } | null>(null)\n\n const fetchConfig = React.useCallback(async () => {\n setLoading(true)\n try {\n const { result } = await apiCallOrThrow<{ config: BicConfigData }>('/api/shipment_tracking/bic-configs')\n setConfig(result?.config ?? null)\n if (result?.config) {\n setForm({\n isEnabled: result.config.isEnabled,\n username: result.config.username,\n password: '', // Password is never returned from API\n baseUrl: result.config.baseUrl,\n })\n } else {\n setForm(emptyBicForm)\n }\n } catch {\n // Config doesn't exist yet, that's fine\n setConfig(null)\n setForm(emptyBicForm)\n } finally {\n setLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchConfig()\n }, [fetchConfig])\n\n const handleTest = React.useCallback(async () => {\n if (!form.username || !form.password) {\n flash(t('shipment_tracking.bic_config.validation.usernameRequired', 'Username is required'), 'error')\n return\n }\n\n setTesting(true)\n setTestResult(null)\n\n try {\n const { result } = await apiCallOrThrow<{ success: boolean; message: string }>(\n '/api/shipment_tracking/bic-configs/test',\n {\n method: 'POST',\n body: JSON.stringify({\n username: form.username,\n password: form.password,\n baseUrl: form.baseUrl,\n }),\n }\n )\n\n setTestResult(result ?? { success: false, message: 'Unknown error' })\n\n if (result?.success) {\n flash(t('shipment_tracking.bic_config.flash.testSuccess', 'BIC API connection successful'), 'success')\n } else {\n flash(result?.message ?? t('shipment_tracking.bic_config.flash.testFailed', 'BIC API connection failed'), 'error')\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Connection test failed'\n setTestResult({ success: false, message })\n flash(message, 'error')\n } finally {\n setTesting(false)\n }\n }, [form, t])\n\n const handleSave = React.useCallback(async () => {\n if (!form.username) {\n flash(t('shipment_tracking.bic_config.validation.usernameRequired', 'Username is required'), 'error')\n return\n }\n\n // Password is required for new config or when enabling\n if (!config && !form.password) {\n flash(t('shipment_tracking.bic_config.validation.passwordRequired', 'Password is required'), 'error')\n return\n }\n\n setSaving(true)\n try {\n const body: Record<string, unknown> = {\n isEnabled: form.isEnabled,\n username: form.username,\n baseUrl: form.baseUrl || 'https://api.bic-code.org',\n }\n\n // Only include password if it was changed\n if (form.password) {\n body.password = form.password\n } else if (config) {\n // If editing and no new password, we still need to send something\n // The API should handle this gracefully\n body.password = '__UNCHANGED__'\n }\n\n await apiCallOrThrow('/api/shipment_tracking/bic-configs', {\n method: 'PUT',\n body: JSON.stringify(body),\n })\n\n flash(t('shipment_tracking.bic_config.flash.saved', 'BIC configuration saved'), 'success')\n fetchConfig()\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to save'\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [form, config, t, fetchConfig])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSave()\n }\n },\n [handleSave]\n )\n\n if (loading) {\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Shield className=\"h-5 w-5\" />\n {t('shipment_tracking.bic_config.title', 'BIC Facility API')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </CardContent>\n </Card>\n )\n }\n\n return (\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Shield className=\"h-5 w-5\" />\n <CardTitle>{t('shipment_tracking.bic_config.title', 'BIC Facility API')}</CardTitle>\n </div>\n <div className=\"flex items-center gap-2\">\n {config ? (\n <>\n <Badge variant=\"outline\">\n {t('shipment_tracking.bic_config.status.configured', 'Configured')}\n </Badge>\n <Badge variant={config.isEnabled ? 'default' : 'secondary'}>\n {config.isEnabled\n ? t('shipment_tracking.bic_config.status.enabled', 'Enabled')\n : t('shipment_tracking.bic_config.status.disabled', 'Disabled')}\n </Badge>\n </>\n ) : (\n <Badge variant=\"secondary\">\n {t('shipment_tracking.bic_config.status.notConfigured', 'Not Configured')}\n </Badge>\n )}\n </div>\n </div>\n <CardDescription>\n {t(\n 'shipment_tracking.bic_config.description',\n 'Configure BIC Facility Code API credentials to enrich shipment tracking locations with coordinates, addresses, and terminal operator information.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent onKeyDown={handleKeyDown}>\n <div className=\"grid gap-4\">\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"bicEnabled\"\n checked={form.isEnabled}\n onCheckedChange={(checked) =>\n setForm((prev) => ({ ...prev, isEnabled: checked === true }))\n }\n />\n <Label htmlFor=\"bicEnabled\" className=\"cursor-pointer font-medium\">\n {t('shipment_tracking.bic_config.fields.isEnabled', 'Enable BIC Enrichment')}\n </Label>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"bicUsername\">\n {t('shipment_tracking.bic_config.fields.username', 'Username')}\n </Label>\n <Input\n id=\"bicUsername\"\n value={form.username}\n onChange={(e) => setForm((prev) => ({ ...prev, username: e.target.value }))}\n placeholder=\"your-bic-username\"\n />\n </div>\n\n <div className=\"grid gap-2\">\n <Label htmlFor=\"bicPassword\">\n {t('shipment_tracking.bic_config.fields.password', 'Password')}\n </Label>\n <Input\n id=\"bicPassword\"\n type=\"password\"\n value={form.password}\n onChange={(e) => setForm((prev) => ({ ...prev, password: e.target.value }))}\n placeholder={config ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022' : 'your-bic-password'}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'shipment_tracking.bic_config.hints.password',\n 'Your password will be encrypted and stored securely.'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"grid gap-2\">\n <Label htmlFor=\"bicBaseUrl\">\n {t('shipment_tracking.bic_config.fields.baseUrl', 'API Base URL')}\n </Label>\n <Input\n id=\"bicBaseUrl\"\n value={form.baseUrl}\n onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))}\n placeholder=\"https://api.bic-code.org\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('shipment_tracking.bic_config.hints.baseUrl', 'Default: https://api.bic-code.org')}\n </p>\n </div>\n\n {testResult && (\n <div\n className={`flex items-center gap-2 rounded-md p-3 text-sm ${\n testResult.success\n ? 'bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400'\n : 'bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400'\n }`}\n >\n {testResult.success ? (\n <CheckCircle2 className=\"h-4 w-4\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n <span>{testResult.message}</span>\n </div>\n )}\n\n <div className=\"flex items-center gap-2 pt-2\">\n <Button\n variant=\"outline\"\n onClick={handleTest}\n disabled={testing || !form.username || !form.password}\n >\n {testing && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {t('shipment_tracking.bic_config.actions.testConnection', 'Test Connection')}\n </Button>\n <Button onClick={handleSave} disabled={saving}>\n {saving && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {t('shipment_tracking.bic_config.actions.save', 'Save Configuration')}\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n\n// \u2500\u2500\u2500 Main Page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport default function TrackingAuthConfigPage() {\n const t = useT()\n\n const [dialogOpen, setDialogOpen] = useState(false)\n const [editingId, setEditingId] = useState<string | null>(null)\n const [form, setForm] = useState<FormState>(emptyForm)\n const [submitting, setSubmitting] = useState(false)\n\n const isEdit = editingId !== null\n const credFields = credentialFieldsFor(form.carrierCode, form.credentials)\n const spec = specFor(form.carrierCode)\n\n const setField = useCallback(\n <K extends keyof FormState>(key: K, value: FormState[K]) => setForm((p) => ({ ...p, [key]: value })),\n [],\n )\n const setCredential = useCallback(\n (key: string, value: string) => setForm((p) => ({ ...p, credentials: { ...p.credentials, [key]: value } })),\n [],\n )\n const changeCarrier = useCallback((carrierCode: string) => setForm(formForCarrier(carrierCode)), [])\n\n const columns = useMemo<ColumnDef[]>(\n () => [\n {\n data: 'carrierCode',\n title: t('shipment_tracking.carrier_configs.fields.carrierCode', 'Carrier'),\n width: 150,\n readOnly: true,\n renderer: (value: unknown) => (\n <span className=\"font-medium\">{specFor(String(value))?.label ?? String(value ?? '')}</span>\n ),\n },\n {\n data: 'apiEndpoint',\n title: t('shipment_tracking.carrier_configs.fields.apiEndpoint', 'API Endpoint'),\n width: 280,\n readOnly: true,\n renderer: (value: unknown) => (\n <span className=\"text-sm truncate max-w-[260px] block\">{String(value || '-')}</span>\n ),\n },\n {\n data: 'rateLimit',\n title: t('shipment_tracking.carrier_configs.fields.rateLimit', 'Rate Limit'),\n width: 120,\n readOnly: true,\n renderer: (_value: unknown, rowData: Record<string, unknown>) => (\n <span className=\"text-sm\">\n {String(rowData.rateLimitRequests ?? 60)} / {String(rowData.rateLimitWindowSeconds ?? 60)}s\n </span>\n ),\n },\n {\n data: 'isActive',\n title: t('shipment_tracking.carrier_configs.fields.isActive', 'Active'),\n width: 80,\n readOnly: true,\n renderer: (value: unknown) => <BooleanIcon value={value === true || value === 'true'} />,\n },\n ],\n [t],\n )\n\n const openCreateDialog = useCallback(() => {\n setEditingId(null)\n setForm(emptyForm)\n setDialogOpen(true)\n }, [])\n\n const addButton = useMemo(\n () => (\n <ButtonV2 variant=\"primary\" size=\"sm\" icon={<Plus className=\"h-4 w-4\" />} onClick={openCreateDialog}>\n {t('shipment_tracking.carrier_configs.create', 'Add Carrier Config')}\n </ButtonV2>\n ),\n [openCreateDialog, t],\n )\n\n const table = useDynamicTablePage<CarrierConfigRow>({\n source: '/api/shipment_tracking/carrier-configs',\n // Saved views. Without this the perspective tab strip auto-hides\n // (hidePerspectiveTabs ?? !hasPerspectives), so users cannot save or\n // switch a view on this list at all.\n perspectives: 'shipment_tracking_carrier_configs',\n columns,\n tableName: t('shipment_tracking.carrier_configs.title', 'Carrier Configs'),\n defaultPageSize: 20,\n mapApiItem: mapItem,\n cellEdit: false,\n tableProps: {\n height: '100%',\n uiConfig: {\n readOnlyStyle: 'normal',\n hideAddRowButton: true,\n searchBarEnd: addButton,\n },\n },\n })\n\n const openEditDialog = useCallback(\n (id: string) => {\n const items = (table.query.data as { items?: Record<string, unknown>[] })?.items ?? []\n const mapped = items.map(mapItem).filter((x): x is CarrierConfigRow => x !== null)\n const row = mapped.find((r) => r.id === id)\n if (!row) return\n setEditingId(id)\n setForm(rowToForm(row))\n setDialogOpen(true)\n },\n [table.query.data],\n )\n\n const handleSubmit = useCallback(async () => {\n if (submitting) return\n\n if (!isEdit && !form.carrierCode.trim()) {\n flash(t('shipment_tracking.carrier_configs.validation.carrierCodeRequired', 'Carrier is required'), 'error')\n return\n }\n\n const currentSpec = specFor(form.carrierCode)\n const endpoint = form.apiEndpoint.trim()\n if (currentSpec?.endpointRequired && !endpoint) {\n flash(t('shipment_tracking.carrier_configs.validation.endpointRequired', 'API endpoint is required for this carrier'), 'error')\n return\n }\n if (endpoint) {\n try {\n new URL(endpoint)\n } catch {\n flash(t('shipment_tracking.carrier_configs.validation.apiEndpointInvalidUrl', 'API endpoint must be a valid URL'), 'error')\n return\n }\n }\n\n // Assemble auth_config from the structured fields; require all non-optional.\n const fields = credentialFieldsFor(form.carrierCode, form.credentials)\n const authConfig: Record<string, string> = {}\n for (const f of fields) {\n const val = (form.credentials[f.key] ?? '').trim()\n if (!val) {\n if (f.optional) continue\n flash(\n t('shipment_tracking.carrier_configs.validation.credentialsRequired', 'Fill in all credential fields'),\n 'error',\n )\n return\n }\n authConfig[f.key] = val\n }\n\n setSubmitting(true)\n try {\n const hasAuth = Object.keys(authConfig).length > 0\n if (isEdit) {\n const body: Record<string, unknown> = { id: editingId }\n body.apiEndpoint = endpoint || null\n body.authConfig = hasAuth ? authConfig : null\n body.rateLimitRequests = form.rateLimitRequests\n body.rateLimitWindowSeconds = form.rateLimitWindowSeconds\n body.isActive = form.isActive\n\n await apiCallOrThrow('/api/shipment_tracking/carrier-configs', {\n method: 'PUT',\n body: JSON.stringify(body),\n })\n flash(t('shipment_tracking.carrier_configs.flash.updated', 'Carrier config updated'), 'success')\n } else {\n const body: Record<string, unknown> = {\n carrierCode: form.carrierCode.trim(),\n rateLimitRequests: form.rateLimitRequests,\n rateLimitWindowSeconds: form.rateLimitWindowSeconds,\n isActive: form.isActive,\n }\n if (endpoint) body.apiEndpoint = endpoint\n if (hasAuth) body.authConfig = authConfig\n\n await apiCallOrThrow('/api/shipment_tracking/carrier-configs', {\n method: 'POST',\n body: JSON.stringify(body),\n })\n flash(t('shipment_tracking.carrier_configs.flash.created', 'Carrier config created'), 'success')\n }\n\n setDialogOpen(false)\n table.refresh()\n } catch {\n flash(isEdit ? t('shipment_tracking.carrier_configs.flash.updateFailed', 'Failed to update carrier config') : t('shipment_tracking.carrier_configs.flash.createFailed', 'Failed to create carrier config'), 'error')\n } finally {\n setSubmitting(false)\n }\n }, [form, isEdit, editingId, submitting, table.refresh, t])\n\n const handleDialogKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n const handleDelete = useCallback(\n async (id: string) => {\n if (!confirm(t('shipment_tracking.carrier_configs.confirmDelete', 'Delete this carrier config?'))) return\n\n try {\n await apiCallOrThrow('/api/shipment_tracking/carrier-configs', {\n method: 'DELETE',\n body: JSON.stringify({ id }),\n })\n flash(t('shipment_tracking.carrier_configs.flash.deleted', 'Carrier config deleted'), 'success')\n table.refresh()\n } catch {\n flash(t('shipment_tracking.carrier_configs.flash.deleteFailed', 'Failed to delete carrier config'), 'error')\n }\n },\n [table.refresh, t],\n )\n\n const rowActions = useCallback(\n (rowData: any): ContextMenuAction[] => {\n if (!rowData?.id) return []\n return [\n { id: 'edit', label: t('common.edit', 'Edit') },\n { id: 'delete', label: t('common.delete', 'Delete') },\n ]\n },\n [t],\n )\n\n const handleRowAction = useCallback(\n (actionId: string, rowData: any) => {\n const id = rowData?.id as string | undefined\n if (!id) return\n if (actionId === 'edit') openEditDialog(id)\n else if (actionId === 'delete') handleDelete(id)\n },\n [openEditDialog, handleDelete],\n )\n\n const tableHeight = useMemo(() => {\n const rowH = 40\n const headerH = 40\n const toolbarH = 50\n const minHeight = 200\n const maxHeight = 400\n const rowCount = (table.query.data as { total?: number })?.total ?? 0\n const contentHeight = toolbarH + headerH + rowCount * rowH + 20\n return Math.min(Math.max(contentHeight, minHeight), maxHeight)\n }, [table.query.data])\n\n if (table.isLoading) {\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <BicConfigSection />\n <ShipsGoConfigSection />\n <Card>\n <CardHeader>\n <CardTitle>{t('shipment_tracking.carrier_configs.title', 'Carrier Configs')}</CardTitle>\n </CardHeader>\n <CardContent>\n <TableSkeleton rows={5} columns={4} />\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n {/* BIC Config Section */}\n <BicConfigSection />\n\n {/* ShipsGo Aggregator Config Section */}\n <ShipsGoConfigSection />\n\n {/* Carrier Configs Section */}\n <Card>\n <CardHeader>\n <CardTitle>{t('shipment_tracking.carrier_configs.title', 'Carrier Configs')}</CardTitle>\n <CardDescription>\n {t(\n 'shipment_tracking.auth_config.description',\n 'Configure authentication for carrier tracking APIs and facility enrichment services.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div style={{ height: tableHeight }}>\n <DynamicTable\n {...table.props}\n striped\n density=\"md\"\n rowActions={rowActions}\n onRowAction={handleRowAction}\n />\n </div>\n </CardContent>\n </Card>\n\n <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n <DialogContent className=\"sm:max-w-lg\" onKeyDown={handleDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>{isEdit ? t('shipment_tracking.carrier_configs.edit', 'Edit Carrier Config') : t('shipment_tracking.carrier_configs.create', 'Add Carrier Config')}</DialogTitle>\n </DialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"carrierCode\">{t('shipment_tracking.carrier_configs.fields.carrierCode', 'Carrier')}</Label>\n {isEdit ? (\n <Input id=\"carrierCode\" value={spec?.label ?? form.carrierCode} disabled />\n ) : (\n <select\n id=\"carrierCode\"\n className=\"border rounded h-9 px-2 text-sm\"\n value={form.carrierCode}\n onChange={(event) => changeCarrier(event.target.value)}\n >\n {CARRIER_SPECS.map((c) => (\n <option key={c.value} value={c.value}>{c.label}</option>\n ))}\n </select>\n )}\n </div>\n\n <div className=\"grid gap-2\">\n <Label htmlFor=\"apiEndpoint\">{t('shipment_tracking.carrier_configs.fields.apiEndpoint', 'API Endpoint')}</Label>\n <Input\n id=\"apiEndpoint\"\n value={form.apiEndpoint}\n onChange={(event) => setField('apiEndpoint', event.target.value)}\n placeholder=\"https://api.example.com/tracking\"\n />\n {spec?.endpointRequired && (\n <p className=\"text-xs text-muted-foreground\">\n {t('shipment_tracking.carrier_configs.hints.endpointRequired', 'This carrier requires an explicit API endpoint.')}\n </p>\n )}\n </div>\n\n {/* Structured credentials (per carrier) */}\n {credFields.map((f) => (\n <div className=\"grid gap-2\" key={f.key}>\n <Label htmlFor={`cred-${f.key}`}>\n {t(`shipment_tracking.carrier_configs.cred.${f.key}`, f.label)}\n {f.optional ? ` (${t('common.optional', 'optional')})` : ''}\n </Label>\n <Input\n id={`cred-${f.key}`}\n type={f.secret ? 'password' : 'text'}\n autoComplete=\"off\"\n value={form.credentials[f.key] ?? ''}\n onChange={(event) => setCredential(f.key, event.target.value)}\n placeholder={f.placeholder}\n />\n </div>\n ))}\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"rateLimitRequests\">{t('shipment_tracking.carrier_configs.fields.rateLimitRequests', 'Rate Limit (requests)')}</Label>\n <Input\n id=\"rateLimitRequests\"\n type=\"number\"\n min={1}\n max={10000}\n value={form.rateLimitRequests}\n onChange={(event) =>\n setField('rateLimitRequests', Number(event.target.value) || 60)\n }\n />\n </div>\n <div className=\"grid gap-2\">\n <Label htmlFor=\"rateLimitWindowSeconds\">{t('shipment_tracking.carrier_configs.fields.rateLimitWindowSeconds', 'Window (seconds)')}</Label>\n <Input\n id=\"rateLimitWindowSeconds\"\n type=\"number\"\n min={1}\n max={86400}\n value={form.rateLimitWindowSeconds}\n onChange={(event) =>\n setField('rateLimitWindowSeconds', Number(event.target.value) || 60)\n }\n />\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"isActive\"\n checked={form.isActive}\n onCheckedChange={(checked) => setField('isActive', checked === true)}\n />\n <Label htmlFor=\"isActive\" className=\"cursor-pointer\">\n {t('shipment_tracking.carrier_configs.fields.isActive', 'Active')}\n </Label>\n </div>\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setDialogOpen(false)} disabled={submitting}>\n {t('common.cancel', 'Cancel')}\n </Button>\n <Button onClick={handleSubmit} disabled={submitting}>\n {submitting ? t('common.saving', 'Saving...') : isEdit ? t('common.save', 'Save') : t('common.create', 'Create')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -68,9 +68,21 @@
68
68
  "shipment_tracking.carrier_configs.flash.updateFailed": "Nie udało się zaktualizować konfiguracji przewoźnika",
69
69
  "shipment_tracking.carrier_configs.flash.updated": "Konfiguracja przewoźnika została zaktualizowana",
70
70
  "shipment_tracking.carrier_configs.title": "Konfiguracja przewoźników",
71
+ "shipment_tracking.carrier_configs.cred.api_key": "Klucz API",
72
+ "shipment_tracking.carrier_configs.cred.app_key": "App key",
73
+ "shipment_tracking.carrier_configs.cred.certificate_base64": "Certyfikat (base64 PFX)",
74
+ "shipment_tracking.carrier_configs.cred.client_id": "Client ID",
75
+ "shipment_tracking.carrier_configs.cred.client_secret": "Client secret",
76
+ "shipment_tracking.carrier_configs.cred.customer_id": "Customer ID",
77
+ "shipment_tracking.carrier_configs.cred.scac_code": "Kod SCAC",
78
+ "shipment_tracking.carrier_configs.cred.subscription_key": "Subscription key",
79
+ "shipment_tracking.carrier_configs.cred.token_url": "Adres URL tokenu",
80
+ "shipment_tracking.carrier_configs.hints.endpointRequired": "Ten przewoźnik wymaga jawnego adresu endpointu API.",
71
81
  "shipment_tracking.carrier_configs.validation.apiEndpointInvalidUrl": "Endpoint API musi być poprawnym adresem URL",
72
82
  "shipment_tracking.carrier_configs.validation.authConfigInvalidJson": "Konfiguracja autoryzacji musi być poprawnym JSON",
73
- "shipment_tracking.carrier_configs.validation.carrierCodeRequired": "Kod przewoźnika jest wymagany",
83
+ "shipment_tracking.carrier_configs.validation.carrierCodeRequired": "Przewoźnik jest wymagany",
84
+ "shipment_tracking.carrier_configs.validation.credentialsRequired": "Uzupełnij wszystkie pola danych uwierzytelniających",
85
+ "shipment_tracking.carrier_configs.validation.endpointRequired": "Dla tego przewoźnika adres endpointu API jest wymagany",
74
86
  "shipment_tracking.details.bookingDetails": "Szczegóły rezerwacji",
75
87
  "shipment_tracking.details.coordinates": "Współrzędne",
76
88
  "shipment_tracking.details.currentPosition": "Aktualna pozycja",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freighttech/shipment-tracking",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -25,12 +25,107 @@ import { Button as ButtonV2 } from '@freighttech/ui/primitives-v2'
25
25
  import { Input } from '@freighttech/ui/primitives/input'
26
26
  import { Label } from '@freighttech/ui/primitives/label'
27
27
  import { Checkbox } from '@freighttech/ui/primitives/checkbox'
28
- import { Textarea } from '@freighttech/ui/primitives/textarea'
29
28
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@freighttech/ui/primitives/card'
30
29
  import { Badge } from '@freighttech/ui/primitives/badge'
31
30
  import { Loader2, CheckCircle2, XCircle, Shield, Plus } from 'lucide-react'
32
31
  import { ShipsGoConfigSection } from '../../components/ShipsGoConfigSection'
33
32
 
33
+ // ─── Carrier specs ───────────────────────────────────────────
34
+ // One entry per registered carrier adapter (see
35
+ // lib/adapters/index.ts / services/carrierRegistry.ts). Each carrier maps to a
36
+ // fixed set of auth_config keys and a default events endpoint (the value the
37
+ // adapter falls back to when apiEndpoint is null). ShipsGo is intentionally
38
+ // absent — it has its own dedicated config section, not a carrier-configs row.
39
+ type CredField = { key: string; label: string; secret?: boolean; placeholder?: string; optional?: boolean }
40
+
41
+ type CarrierSpec = {
42
+ value: string
43
+ label: string
44
+ /** Adapter's default events endpoint; '' when the adapter has none. */
45
+ defaultEndpoint: string
46
+ /** Evergreen has no default endpoint — apiEndpoint must be supplied. */
47
+ endpointRequired?: boolean
48
+ credentials: CredField[]
49
+ }
50
+
51
+ const CLIENT_ID: CredField = { key: 'client_id', label: 'Client ID' }
52
+ const CLIENT_SECRET: CredField = { key: 'client_secret', label: 'Client secret', secret: true }
53
+
54
+ const CARRIER_SPECS: CarrierSpec[] = [
55
+ {
56
+ value: 'maersk',
57
+ label: 'Maersk',
58
+ defaultEndpoint: 'https://api.maersk.com/track-and-trace-private/events',
59
+ credentials: [CLIENT_ID, CLIENT_SECRET],
60
+ },
61
+ {
62
+ value: 'msc',
63
+ label: 'MSC',
64
+ defaultEndpoint: 'https://api.tech.msc.com/msc/trackandtrace/v2.2/events',
65
+ // The MSC OAuth account identity (client_id/scope/token_url/aud) is supplied
66
+ // per config, not baked into the adapter — so the form collects it here
67
+ // alongside the certificate. `aud` defaults to `token_url` when left blank.
68
+ credentials: [
69
+ CLIENT_ID,
70
+ { key: 'scope', label: 'Scope' },
71
+ { key: 'token_url', label: 'Token URL' },
72
+ { key: 'aud', label: 'Audience', optional: true, placeholder: 'defaults to Token URL' },
73
+ { key: 'certificate_base64', label: 'Certificate (base64 PFX)', secret: true },
74
+ ],
75
+ },
76
+ {
77
+ value: 'cma-cgm',
78
+ label: 'CMA CGM',
79
+ defaultEndpoint: 'https://apis.cma-cgm.net/operation/trackandtrace/v1/events',
80
+ credentials: [{ key: 'api_key', label: 'API key', secret: true }],
81
+ },
82
+ {
83
+ value: 'hapag-lloyd',
84
+ label: 'Hapag-Lloyd',
85
+ defaultEndpoint: 'https://api.hlag.com/hlag/external/v2/events',
86
+ credentials: [CLIENT_ID, CLIENT_SECRET],
87
+ },
88
+ {
89
+ value: 'zim',
90
+ label: 'ZIM',
91
+ defaultEndpoint: 'https://apigw.zim.com/trackAndTrace/v1',
92
+ credentials: [CLIENT_ID, CLIENT_SECRET, { key: 'subscription_key', label: 'Subscription key', secret: true }],
93
+ },
94
+ {
95
+ value: 'cosco',
96
+ label: 'COSCO',
97
+ defaultEndpoint: 'https://apis.cargosmart.com/openapi/cs2/ctvc/COSU',
98
+ credentials: [
99
+ { key: 'app_key', label: 'App key', secret: true },
100
+ { key: 'customer_id', label: 'Customer ID' },
101
+ { key: 'scac_code', label: 'SCAC code', optional: true, placeholder: 'COSU' },
102
+ ],
103
+ },
104
+ {
105
+ value: 'evergreen',
106
+ label: 'Evergreen',
107
+ defaultEndpoint: '',
108
+ endpointRequired: true,
109
+ credentials: [CLIENT_ID, CLIENT_SECRET, { key: 'token_url', label: 'Token URL' }],
110
+ },
111
+ ]
112
+
113
+ function specFor(carrierCode: string): CarrierSpec | undefined {
114
+ return CARRIER_SPECS.find((c) => c.value === carrierCode)
115
+ }
116
+
117
+ /**
118
+ * Credential fields for a carrier. For a known carrier this is its fixed set;
119
+ * for an unrecognised legacy row (edit) we derive the fields from the stored
120
+ * auth_config keys so the form never falls back to raw JSON.
121
+ */
122
+ function credentialFieldsFor(carrierCode: string, authConfig?: Record<string, unknown> | null): CredField[] {
123
+ const spec = specFor(carrierCode)
124
+ if (spec) return spec.credentials
125
+ const keys = authConfig ? Object.keys(authConfig) : []
126
+ return keys.map((k) => ({ key: k, label: k, secret: /secret|password|key|token|cert/i.test(k) }))
127
+ }
128
+
34
129
  // ─── Carrier Config Types ────────────────────────────────────
35
130
 
36
131
  type CarrierConfigRow = {
@@ -63,26 +158,35 @@ function mapItem(item: Record<string, unknown>): CarrierConfigRow | null {
63
158
  type FormState = {
64
159
  carrierCode: string
65
160
  apiEndpoint: string
66
- authConfig: string
161
+ credentials: Record<string, string>
67
162
  rateLimitRequests: number
68
163
  rateLimitWindowSeconds: number
69
164
  isActive: boolean
70
165
  }
71
166
 
72
- const emptyForm: FormState = {
73
- carrierCode: '',
74
- apiEndpoint: '',
75
- authConfig: '',
76
- rateLimitRequests: 60,
77
- rateLimitWindowSeconds: 60,
78
- isActive: true,
167
+ function formForCarrier(carrierCode: string): FormState {
168
+ const spec = specFor(carrierCode)
169
+ return {
170
+ carrierCode,
171
+ apiEndpoint: spec?.defaultEndpoint ?? '',
172
+ credentials: {},
173
+ rateLimitRequests: 60,
174
+ rateLimitWindowSeconds: 60,
175
+ isActive: true,
176
+ }
79
177
  }
80
178
 
179
+ const emptyForm: FormState = formForCarrier(CARRIER_SPECS[0].value)
180
+
81
181
  function rowToForm(row: CarrierConfigRow): FormState {
182
+ // auth_config is returned by the API (plaintext jsonb); prefill the fields.
183
+ const credentials: Record<string, string> = {}
184
+ const auth = row.authConfig ?? {}
185
+ for (const [k, v] of Object.entries(auth)) credentials[k] = v == null ? '' : String(v)
82
186
  return {
83
187
  carrierCode: row.carrierCode,
84
- apiEndpoint: row.apiEndpoint ?? '',
85
- authConfig: row.authConfig ? JSON.stringify(row.authConfig, null, 2) : '',
188
+ apiEndpoint: row.apiEndpoint ?? specFor(row.carrierCode)?.defaultEndpoint ?? '',
189
+ credentials,
86
190
  rateLimitRequests: row.rateLimitRequests,
87
191
  rateLimitWindowSeconds: row.rateLimitWindowSeconds,
88
192
  isActive: row.isActive,
@@ -408,16 +512,28 @@ export default function TrackingAuthConfigPage() {
408
512
  const [submitting, setSubmitting] = useState(false)
409
513
 
410
514
  const isEdit = editingId !== null
515
+ const credFields = credentialFieldsFor(form.carrierCode, form.credentials)
516
+ const spec = specFor(form.carrierCode)
517
+
518
+ const setField = useCallback(
519
+ <K extends keyof FormState>(key: K, value: FormState[K]) => setForm((p) => ({ ...p, [key]: value })),
520
+ [],
521
+ )
522
+ const setCredential = useCallback(
523
+ (key: string, value: string) => setForm((p) => ({ ...p, credentials: { ...p.credentials, [key]: value } })),
524
+ [],
525
+ )
526
+ const changeCarrier = useCallback((carrierCode: string) => setForm(formForCarrier(carrierCode)), [])
411
527
 
412
528
  const columns = useMemo<ColumnDef[]>(
413
529
  () => [
414
530
  {
415
531
  data: 'carrierCode',
416
- title: t('shipment_tracking.carrier_configs.fields.carrierCode', 'Carrier Code'),
532
+ title: t('shipment_tracking.carrier_configs.fields.carrierCode', 'Carrier'),
417
533
  width: 150,
418
534
  readOnly: true,
419
535
  renderer: (value: unknown) => (
420
- <span className="font-medium">{String(value ?? '')}</span>
536
+ <span className="font-medium">{specFor(String(value))?.label ?? String(value ?? '')}</span>
421
537
  ),
422
538
  },
423
539
  {
@@ -504,37 +620,48 @@ export default function TrackingAuthConfigPage() {
504
620
  if (submitting) return
505
621
 
506
622
  if (!isEdit && !form.carrierCode.trim()) {
507
- flash(t('shipment_tracking.carrier_configs.validation.carrierCodeRequired', 'Carrier code is required'), 'error')
623
+ flash(t('shipment_tracking.carrier_configs.validation.carrierCodeRequired', 'Carrier is required'), 'error')
508
624
  return
509
625
  }
510
626
 
511
- if (form.authConfig.trim()) {
627
+ const currentSpec = specFor(form.carrierCode)
628
+ const endpoint = form.apiEndpoint.trim()
629
+ if (currentSpec?.endpointRequired && !endpoint) {
630
+ flash(t('shipment_tracking.carrier_configs.validation.endpointRequired', 'API endpoint is required for this carrier'), 'error')
631
+ return
632
+ }
633
+ if (endpoint) {
512
634
  try {
513
- JSON.parse(form.authConfig)
635
+ new URL(endpoint)
514
636
  } catch {
515
- flash(t('shipment_tracking.carrier_configs.validation.authConfigInvalidJson', 'Auth config must be valid JSON'), 'error')
637
+ flash(t('shipment_tracking.carrier_configs.validation.apiEndpointInvalidUrl', 'API endpoint must be a valid URL'), 'error')
516
638
  return
517
639
  }
518
640
  }
519
641
 
520
- if (form.apiEndpoint.trim()) {
521
- try {
522
- new URL(form.apiEndpoint.trim())
523
- } catch {
524
- flash(t('shipment_tracking.carrier_configs.validation.apiEndpointInvalidUrl', 'API endpoint must be a valid URL'), 'error')
642
+ // Assemble auth_config from the structured fields; require all non-optional.
643
+ const fields = credentialFieldsFor(form.carrierCode, form.credentials)
644
+ const authConfig: Record<string, string> = {}
645
+ for (const f of fields) {
646
+ const val = (form.credentials[f.key] ?? '').trim()
647
+ if (!val) {
648
+ if (f.optional) continue
649
+ flash(
650
+ t('shipment_tracking.carrier_configs.validation.credentialsRequired', 'Fill in all credential fields'),
651
+ 'error',
652
+ )
525
653
  return
526
654
  }
655
+ authConfig[f.key] = val
527
656
  }
528
657
 
529
658
  setSubmitting(true)
530
659
  try {
531
- const authConfig = form.authConfig.trim() ? JSON.parse(form.authConfig) : undefined
532
-
660
+ const hasAuth = Object.keys(authConfig).length > 0
533
661
  if (isEdit) {
534
662
  const body: Record<string, unknown> = { id: editingId }
535
- if (form.apiEndpoint.trim()) body.apiEndpoint = form.apiEndpoint.trim()
536
- else body.apiEndpoint = null
537
- body.authConfig = authConfig ?? null
663
+ body.apiEndpoint = endpoint || null
664
+ body.authConfig = hasAuth ? authConfig : null
538
665
  body.rateLimitRequests = form.rateLimitRequests
539
666
  body.rateLimitWindowSeconds = form.rateLimitWindowSeconds
540
667
  body.isActive = form.isActive
@@ -551,8 +678,8 @@ export default function TrackingAuthConfigPage() {
551
678
  rateLimitWindowSeconds: form.rateLimitWindowSeconds,
552
679
  isActive: form.isActive,
553
680
  }
554
- if (form.apiEndpoint.trim()) body.apiEndpoint = form.apiEndpoint.trim()
555
- if (authConfig) body.authConfig = authConfig
681
+ if (endpoint) body.apiEndpoint = endpoint
682
+ if (hasAuth) body.authConfig = authConfig
556
683
 
557
684
  await apiCallOrThrow('/api/shipment_tracking/carrier-configs', {
558
685
  method: 'POST',
@@ -690,15 +817,21 @@ export default function TrackingAuthConfigPage() {
690
817
 
691
818
  <div className="grid gap-4 py-2">
692
819
  <div className="grid gap-2">
693
- <Label htmlFor="carrierCode">{t('shipment_tracking.carrier_configs.fields.carrierCode', 'Carrier Code')}</Label>
694
- <Input
695
- id="carrierCode"
696
- value={form.carrierCode}
697
- onChange={(event) => setForm((prev) => ({ ...prev, carrierCode: event.target.value }))}
698
- placeholder="e.g. maersk, msc, cma-cgm"
699
- disabled={isEdit}
700
- autoFocus={!isEdit}
701
- />
820
+ <Label htmlFor="carrierCode">{t('shipment_tracking.carrier_configs.fields.carrierCode', 'Carrier')}</Label>
821
+ {isEdit ? (
822
+ <Input id="carrierCode" value={spec?.label ?? form.carrierCode} disabled />
823
+ ) : (
824
+ <select
825
+ id="carrierCode"
826
+ className="border rounded h-9 px-2 text-sm"
827
+ value={form.carrierCode}
828
+ onChange={(event) => changeCarrier(event.target.value)}
829
+ >
830
+ {CARRIER_SPECS.map((c) => (
831
+ <option key={c.value} value={c.value}>{c.label}</option>
832
+ ))}
833
+ </select>
834
+ )}
702
835
  </div>
703
836
 
704
837
  <div className="grid gap-2">
@@ -706,22 +839,33 @@ export default function TrackingAuthConfigPage() {
706
839
  <Input
707
840
  id="apiEndpoint"
708
841
  value={form.apiEndpoint}
709
- onChange={(event) => setForm((prev) => ({ ...prev, apiEndpoint: event.target.value }))}
842
+ onChange={(event) => setField('apiEndpoint', event.target.value)}
710
843
  placeholder="https://api.example.com/tracking"
711
844
  />
845
+ {spec?.endpointRequired && (
846
+ <p className="text-xs text-muted-foreground">
847
+ {t('shipment_tracking.carrier_configs.hints.endpointRequired', 'This carrier requires an explicit API endpoint.')}
848
+ </p>
849
+ )}
712
850
  </div>
713
851
 
714
- <div className="grid gap-2">
715
- <Label htmlFor="authConfig">{t('shipment_tracking.carrier_configs.fields.authConfig', 'Auth Config (JSON)')}</Label>
716
- <Textarea
717
- id="authConfig"
718
- value={form.authConfig}
719
- onChange={(event) => setForm((prev) => ({ ...prev, authConfig: event.target.value }))}
720
- placeholder='{"apiKey": "..."}'
721
- rows={3}
722
- className="font-mono text-xs"
723
- />
724
- </div>
852
+ {/* Structured credentials (per carrier) */}
853
+ {credFields.map((f) => (
854
+ <div className="grid gap-2" key={f.key}>
855
+ <Label htmlFor={`cred-${f.key}`}>
856
+ {t(`shipment_tracking.carrier_configs.cred.${f.key}`, f.label)}
857
+ {f.optional ? ` (${t('common.optional', 'optional')})` : ''}
858
+ </Label>
859
+ <Input
860
+ id={`cred-${f.key}`}
861
+ type={f.secret ? 'password' : 'text'}
862
+ autoComplete="off"
863
+ value={form.credentials[f.key] ?? ''}
864
+ onChange={(event) => setCredential(f.key, event.target.value)}
865
+ placeholder={f.placeholder}
866
+ />
867
+ </div>
868
+ ))}
725
869
 
726
870
  <div className="grid grid-cols-2 gap-4">
727
871
  <div className="grid gap-2">
@@ -733,7 +877,7 @@ export default function TrackingAuthConfigPage() {
733
877
  max={10000}
734
878
  value={form.rateLimitRequests}
735
879
  onChange={(event) =>
736
- setForm((prev) => ({ ...prev, rateLimitRequests: Number(event.target.value) || 60 }))
880
+ setField('rateLimitRequests', Number(event.target.value) || 60)
737
881
  }
738
882
  />
739
883
  </div>
@@ -746,7 +890,7 @@ export default function TrackingAuthConfigPage() {
746
890
  max={86400}
747
891
  value={form.rateLimitWindowSeconds}
748
892
  onChange={(event) =>
749
- setForm((prev) => ({ ...prev, rateLimitWindowSeconds: Number(event.target.value) || 60 }))
893
+ setField('rateLimitWindowSeconds', Number(event.target.value) || 60)
750
894
  }
751
895
  />
752
896
  </div>
@@ -756,9 +900,7 @@ export default function TrackingAuthConfigPage() {
756
900
  <Checkbox
757
901
  id="isActive"
758
902
  checked={form.isActive}
759
- onCheckedChange={(checked) =>
760
- setForm((prev) => ({ ...prev, isActive: checked === true }))
761
- }
903
+ onCheckedChange={(checked) => setField('isActive', checked === true)}
762
904
  />
763
905
  <Label htmlFor="isActive" className="cursor-pointer">
764
906
  {t('shipment_tracking.carrier_configs.fields.isActive', 'Active')}
@@ -68,9 +68,21 @@
68
68
  "shipment_tracking.carrier_configs.flash.updateFailed": "Nie udało się zaktualizować konfiguracji przewoźnika",
69
69
  "shipment_tracking.carrier_configs.flash.updated": "Konfiguracja przewoźnika została zaktualizowana",
70
70
  "shipment_tracking.carrier_configs.title": "Konfiguracja przewoźników",
71
+ "shipment_tracking.carrier_configs.cred.api_key": "Klucz API",
72
+ "shipment_tracking.carrier_configs.cred.app_key": "App key",
73
+ "shipment_tracking.carrier_configs.cred.certificate_base64": "Certyfikat (base64 PFX)",
74
+ "shipment_tracking.carrier_configs.cred.client_id": "Client ID",
75
+ "shipment_tracking.carrier_configs.cred.client_secret": "Client secret",
76
+ "shipment_tracking.carrier_configs.cred.customer_id": "Customer ID",
77
+ "shipment_tracking.carrier_configs.cred.scac_code": "Kod SCAC",
78
+ "shipment_tracking.carrier_configs.cred.subscription_key": "Subscription key",
79
+ "shipment_tracking.carrier_configs.cred.token_url": "Adres URL tokenu",
80
+ "shipment_tracking.carrier_configs.hints.endpointRequired": "Ten przewoźnik wymaga jawnego adresu endpointu API.",
71
81
  "shipment_tracking.carrier_configs.validation.apiEndpointInvalidUrl": "Endpoint API musi być poprawnym adresem URL",
72
82
  "shipment_tracking.carrier_configs.validation.authConfigInvalidJson": "Konfiguracja autoryzacji musi być poprawnym JSON",
73
- "shipment_tracking.carrier_configs.validation.carrierCodeRequired": "Kod przewoźnika jest wymagany",
83
+ "shipment_tracking.carrier_configs.validation.carrierCodeRequired": "Przewoźnik jest wymagany",
84
+ "shipment_tracking.carrier_configs.validation.credentialsRequired": "Uzupełnij wszystkie pola danych uwierzytelniających",
85
+ "shipment_tracking.carrier_configs.validation.endpointRequired": "Dla tego przewoźnika adres endpointu API jest wymagany",
74
86
  "shipment_tracking.details.bookingDetails": "Szczegóły rezerwacji",
75
87
  "shipment_tracking.details.coordinates": "Współrzędne",
76
88
  "shipment_tracking.details.currentPosition": "Aktualna pozycja",