@open-mercato/ui 0.6.6-develop.6330.1.a261878aa8 → 0.6.6-develop.6331.1.a33b8e99b7

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.
@@ -1,4 +1,7 @@
1
- import { extractOptimisticLockConflict } from "../utils/optimisticLock.js";
1
+ import {
2
+ extractOptimisticLockConflict,
3
+ extractRecordLockConflict
4
+ } from "../utils/optimisticLock.js";
2
5
  import { showRecordConflict } from "./store.js";
3
6
  import {
4
7
  showRecordConflict as showRecordConflict2,
@@ -7,24 +10,58 @@ import {
7
10
  getRecordConflictForTest
8
11
  } from "./store.js";
9
12
  import { RecordConflictBanner } from "./RecordConflictBanner.js";
13
+ let recordLockConflictHandler = null;
14
+ function registerRecordLockConflictHandler(handler) {
15
+ recordLockConflictHandler = handler;
16
+ return () => {
17
+ if (recordLockConflictHandler === handler) recordLockConflictHandler = null;
18
+ };
19
+ }
20
+ function hasRecordLockConflictHandlerForTest() {
21
+ return recordLockConflictHandler !== null;
22
+ }
23
+ function resetRecordLockConflictHandlerForTest() {
24
+ recordLockConflictHandler = null;
25
+ }
10
26
  function surfaceRecordConflict(error, t, options = {}) {
11
- const conflict = extractOptimisticLockConflict(error);
12
- if (!conflict) return false;
13
- showRecordConflict({
14
- message: t(
15
- "ui.forms.flash.recordModified",
16
- "This record was modified by someone else. Refresh and try again."
17
- ),
18
- title: options.title ?? null,
19
- currentUpdatedAt: conflict.currentUpdatedAt,
20
- onRefresh: options.onRefresh ?? null
21
- });
22
- return true;
27
+ const recordLockConflict = extractRecordLockConflict(error);
28
+ if (recordLockConflict && recordLockConflictHandler) {
29
+ if (recordLockConflictHandler(recordLockConflict, error)) return true;
30
+ }
31
+ const ossConflict = extractOptimisticLockConflict(error);
32
+ if (ossConflict) {
33
+ showRecordConflict({
34
+ message: t(
35
+ "ui.forms.flash.recordModified",
36
+ "This record was modified by someone else. Refresh and try again."
37
+ ),
38
+ title: options.title ?? null,
39
+ currentUpdatedAt: ossConflict.currentUpdatedAt,
40
+ onRefresh: options.onRefresh ?? null
41
+ });
42
+ return true;
43
+ }
44
+ if (recordLockConflict) {
45
+ showRecordConflict({
46
+ message: t(
47
+ "ui.forms.flash.recordModified",
48
+ "This record was modified by someone else. Refresh and try again."
49
+ ),
50
+ title: options.title ?? null,
51
+ currentUpdatedAt: null,
52
+ onRefresh: options.onRefresh ?? null
53
+ });
54
+ return true;
55
+ }
56
+ return false;
23
57
  }
24
58
  export {
25
59
  RecordConflictBanner,
26
60
  dismissRecordConflict,
27
61
  getRecordConflictForTest,
62
+ hasRecordLockConflictHandlerForTest,
63
+ registerRecordLockConflictHandler,
64
+ resetRecordLockConflictHandlerForTest,
28
65
  showRecordConflict2 as showRecordConflict,
29
66
  surfaceRecordConflict,
30
67
  useRecordConflict
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/conflicts/index.ts"],
4
- "sourcesContent": ["import { extractOptimisticLockConflict } from '../utils/optimisticLock'\nimport { showRecordConflict } from './store'\n\nexport {\n showRecordConflict,\n dismissRecordConflict,\n useRecordConflict,\n getRecordConflictForTest,\n type RecordConflictEntry,\n type ShowRecordConflictInput,\n} from './store'\nexport { RecordConflictBanner } from './RecordConflictBanner'\n\ntype Translate = (key: string, fallback?: string) => string\n\nexport type SurfaceRecordConflictOptions = {\n /** Custom refresh handler. Omit to let the banner reload the page. */\n onRefresh?: (() => void) | null\n /** Localized title override; the banner falls back to a generic title. */\n title?: string | null\n}\n\n/**\n * If `error` is an OSS optimistic-lock conflict (HTTP 409 with\n * `code: 'optimistic_lock_conflict'`), push the localized \"record modified\"\n * message onto the shared conflict bar and return `true`. Otherwise return\n * `false` so callers can fall back to their normal error handling.\n *\n * This is the single entry point every form uses, so the conflict surfaces the\n * same persistent, error-styled bar everywhere.\n */\nexport function surfaceRecordConflict(\n error: unknown,\n t: Translate,\n options: SurfaceRecordConflictOptions = {},\n): boolean {\n const conflict = extractOptimisticLockConflict(error)\n if (!conflict) return false\n showRecordConflict({\n message: t(\n 'ui.forms.flash.recordModified',\n 'This record was modified by someone else. Refresh and try again.',\n ),\n title: options.title ?? null,\n currentUpdatedAt: conflict.currentUpdatedAt,\n onRefresh: options.onRefresh ?? null,\n })\n return true\n}\n"],
5
- "mappings": "AAAA,SAAS,qCAAqC;AAC9C,SAAS,0BAA0B;AAEnC;AAAA,EACE,sBAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4BAA4B;AAoB9B,SAAS,sBACd,OACA,GACA,UAAwC,CAAC,GAChC;AACT,QAAM,WAAW,8BAA8B,KAAK;AACpD,MAAI,CAAC,SAAU,QAAO;AACtB,qBAAmB;AAAA,IACjB,SAAS;AAAA,MACP;AAAA,MACA;AAAA,IACF;AAAA,IACA,OAAO,QAAQ,SAAS;AAAA,IACxB,kBAAkB,SAAS;AAAA,IAC3B,WAAW,QAAQ,aAAa;AAAA,EAClC,CAAC;AACD,SAAO;AACT;",
4
+ "sourcesContent": ["import {\n extractOptimisticLockConflict,\n extractRecordLockConflict,\n type RecordLockConflictBody,\n} from '../utils/optimisticLock'\nimport { showRecordConflict } from './store'\n\nexport {\n showRecordConflict,\n dismissRecordConflict,\n useRecordConflict,\n getRecordConflictForTest,\n type RecordConflictEntry,\n type ShowRecordConflictInput,\n} from './store'\nexport { RecordConflictBanner } from './RecordConflictBanner'\n\ntype Translate = (key: string, fallback?: string) => string\n\nexport type SurfaceRecordConflictOptions = {\n /** Custom refresh handler. Omit to let the banner reload the page. */\n onRefresh?: (() => void) | null\n /** Localized title override; the banner falls back to a generic title. */\n title?: string | null\n}\n\n/**\n * Handler the enterprise `record_locks` merge-dialog widget registers (on mount)\n * so `surfaceRecordConflict` can defer the OSS conflict bar in favor of the\n * field-level merge dialog. Core/UI stays enterprise-free: enterprise calls\n * {@link registerRecordLockConflictHandler}; this module never imports it.\n *\n * MUST return `true` only when it actually owns/renders the surface for THIS\n * conflict (its widget is mounted for the conflicting record). Returning `false`\n * (different record / cannot render) makes `surfaceRecordConflict` fall through\n * to the OSS conflict bar, so a conflict is never swallowed and we never render\n * BOTH the merge dialog and the bar (single surface, S3).\n */\nexport type RecordLockConflictHandler = (conflict: RecordLockConflictBody, error: unknown) => boolean\n\nlet recordLockConflictHandler: RecordLockConflictHandler | null = null\n\n/**\n * Register the record-lock conflict handler (enterprise merge-dialog widget).\n * Returns an unregister function. When a handler is registered,\n * `surfaceRecordConflict` defers `record_lock_conflict` 409s to it instead of\n * rendering the OSS conflict bar (single surface, S3).\n *\n * SAFETY: when NO handler is registered (widget absent/removed),\n * `surfaceRecordConflict` still renders the OSS conflict bar for ANY 409 \u2014 a\n * conflict is never silently swallowed.\n */\nexport function registerRecordLockConflictHandler(handler: RecordLockConflictHandler): () => void {\n recordLockConflictHandler = handler\n return () => {\n if (recordLockConflictHandler === handler) recordLockConflictHandler = null\n }\n}\n\n/** Test-only: inspect whether a record-lock conflict handler is registered. */\nexport function hasRecordLockConflictHandlerForTest(): boolean {\n return recordLockConflictHandler !== null\n}\n\n/** Test-only: clear any registered record-lock conflict handler. */\nexport function resetRecordLockConflictHandlerForTest(): void {\n recordLockConflictHandler = null\n}\n\n/**\n * Single conflict surface (S3). Resolves a 409 to exactly one UI:\n *\n * - `record_lock_conflict` payload AND a merge-dialog handler registered \u2192\n * defer to the handler (no conflict bar) and return `true`.\n * - otherwise (OSS `optimistic_lock_conflict`, OR a `record_lock_conflict`\n * with NO handler registered) \u2192 render the OSS conflict bar and return\n * `true`. A conflict is ALWAYS surfaced; the worst case is plainer UX, never\n * a silently-swallowed conflict.\n * - not a recognized conflict \u2192 return `false` so callers fall back to their\n * normal error handling.\n */\nexport function surfaceRecordConflict(\n error: unknown,\n t: Translate,\n options: SurfaceRecordConflictOptions = {},\n): boolean {\n const recordLockConflict = extractRecordLockConflict(error)\n if (recordLockConflict && recordLockConflictHandler) {\n // The widget owns the surface ONLY when it handles this record's conflict\n // (returns true). If it declines (different record / not mounted for it),\n // fall through to the OSS bar so the conflict is never swallowed and we\n // never render both the merge dialog AND the bar.\n if (recordLockConflictHandler(recordLockConflict, error)) return true\n }\n\n const ossConflict = extractOptimisticLockConflict(error)\n if (ossConflict) {\n showRecordConflict({\n message: t(\n 'ui.forms.flash.recordModified',\n 'This record was modified by someone else. Refresh and try again.',\n ),\n title: options.title ?? null,\n currentUpdatedAt: ossConflict.currentUpdatedAt,\n onRefresh: options.onRefresh ?? null,\n })\n return true\n }\n\n // No handler is registered but the server returned a record-lock 409: still\n // render the OSS bar so the conflict is never swallowed.\n if (recordLockConflict) {\n showRecordConflict({\n message: t(\n 'ui.forms.flash.recordModified',\n 'This record was modified by someone else. Refresh and try again.',\n ),\n title: options.title ?? null,\n currentUpdatedAt: null,\n onRefresh: options.onRefresh ?? null,\n })\n return true\n }\n\n return false\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,0BAA0B;AAEnC;AAAA,EACE,sBAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4BAA4B;AAyBrC,IAAI,4BAA8D;AAY3D,SAAS,kCAAkC,SAAgD;AAChG,8BAA4B;AAC5B,SAAO,MAAM;AACX,QAAI,8BAA8B,QAAS,6BAA4B;AAAA,EACzE;AACF;AAGO,SAAS,sCAA+C;AAC7D,SAAO,8BAA8B;AACvC;AAGO,SAAS,wCAA8C;AAC5D,8BAA4B;AAC9B;AAcO,SAAS,sBACd,OACA,GACA,UAAwC,CAAC,GAChC;AACT,QAAM,qBAAqB,0BAA0B,KAAK;AAC1D,MAAI,sBAAsB,2BAA2B;AAKnD,QAAI,0BAA0B,oBAAoB,KAAK,EAAG,QAAO;AAAA,EACnE;AAEA,QAAM,cAAc,8BAA8B,KAAK;AACvD,MAAI,aAAa;AACf,uBAAmB;AAAA,MACjB,SAAS;AAAA,QACP;AAAA,QACA;AAAA,MACF;AAAA,MACA,OAAO,QAAQ,SAAS;AAAA,MACxB,kBAAkB,YAAY;AAAA,MAC9B,WAAW,QAAQ,aAAa;AAAA,IAClC,CAAC;AACD,WAAO;AAAA,EACT;AAIA,MAAI,oBAAoB;AACtB,uBAAmB;AAAA,MACjB,SAAS;AAAA,QACP;AAAA,QACA;AAAA,MACF;AAAA,MACA,OAAO,QAAQ,SAAS;AAAA,MACxB,kBAAkB;AAAA,MAClB,WAAW,QAAQ,aAAa;AAAA,IAClC,CAAC;AACD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;",
6
6
  "names": ["showRecordConflict"]
7
7
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
5
+ import { surfaceRecordConflict } from "../conflicts/index.js";
5
6
  import { LoadingMessage } from "@open-mercato/ui/backend/detail";
6
7
  import { createTranslatorWithFallback } from "@open-mercato/shared/lib/i18n/translate";
7
8
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -134,7 +135,8 @@ function AddressesSectionImpl({
134
135
  pushLoading();
135
136
  setIsSubmitting(true);
136
137
  try {
137
- await dataAdapter.update({ id, payload, context: dataContext });
138
+ const addressUpdatedAt = addresses.find((address) => address.id === id)?.updatedAt ?? null;
139
+ await dataAdapter.update({ id, payload, updatedAt: addressUpdatedAt, context: dataContext });
138
140
  setAddresses((prev) => {
139
141
  return prev.map((address) => {
140
142
  if (address.id !== id) {
@@ -159,6 +161,7 @@ function AddressesSectionImpl({
159
161
  });
160
162
  flash(label("success", "Address saved."), "success");
161
163
  } catch (err) {
164
+ surfaceRecordConflict(err, t);
162
165
  const message = err instanceof Error && err.message ? err.message : label("error", "Failed to save address.");
163
166
  const error = new Error(message);
164
167
  if (err && typeof err === "object" && err.details) {
@@ -170,7 +173,7 @@ function AddressesSectionImpl({
170
173
  popLoading();
171
174
  }
172
175
  },
173
- [dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading]
176
+ [addresses, dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading, t]
174
177
  );
175
178
  const handleDelete = React.useCallback(
176
179
  async (id) => {
@@ -180,19 +183,20 @@ function AddressesSectionImpl({
180
183
  pushLoading();
181
184
  setIsSubmitting(true);
182
185
  try {
183
- await dataAdapter.delete({ id, context: dataContext });
186
+ const addressUpdatedAt = addresses.find((address) => address.id === id)?.updatedAt ?? null;
187
+ await dataAdapter.delete({ id, updatedAt: addressUpdatedAt, context: dataContext });
184
188
  setAddresses((prev) => prev.filter((address) => address.id !== id));
185
189
  flash(label("deleted", "Address deleted."), "success");
186
190
  } catch (err) {
187
191
  const message = err instanceof Error && err.message ? err.message : label("error", "Failed to delete address.");
188
- flash(message, "error");
192
+ if (!surfaceRecordConflict(err, t)) flash(message, "error");
189
193
  throw err;
190
194
  } finally {
191
195
  setIsSubmitting(false);
192
196
  popLoading();
193
197
  }
194
198
  },
195
- [dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading]
199
+ [addresses, dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading, t]
196
200
  );
197
201
  const displayAddresses = React.useMemo(() => {
198
202
  return addresses.map((address) => ({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/detail/AddressesSection.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport AddressTiles, { type AddressInput, type AddressValue } from './AddressTiles'\nimport type { AddressTypesAdapter } from './AddressEditor'\nimport type { AddressFormatStrategy } from './addressFormat'\nimport { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '../injection/useRegisteredComponent'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type SectionAction = {\n label: React.ReactNode\n onClick: () => void\n disabled?: boolean\n icon?: React.ReactNode\n}\n\nexport type TabEmptyStateConfig = {\n title: string\n actionLabel: string\n description?: string\n}\n\nexport type AddressSummary = {\n id: string\n name?: string | null\n purpose?: string | null\n companyName?: string | null\n addressLine1: string\n addressLine2?: string | null\n buildingNumber?: string | null\n flatNumber?: string | null\n city?: string | null\n region?: string | null\n postalCode?: string | null\n country?: string | null\n isPrimary?: boolean\n}\n\nexport type AddressDataAdapter<C = unknown> = {\n list: (params: { entityId: string | null; context?: C }) => Promise<AddressSummary[]>\n create: (params: { entityId: string; payload: AddressInput; context?: C }) => Promise<{ id?: string } | void>\n update: (params: { id: string; payload: AddressInput; context?: C }) => Promise<void>\n delete: (params: { id: string; context?: C }) => Promise<void>\n}\n\nexport type AddressesSectionProps<C = unknown> = {\n entityId: string | null\n emptyLabel: string\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n translator?: Translator\n onLoadingChange?: (isLoading: boolean) => void\n dataAdapter: AddressDataAdapter<C>\n dataContext?: C\n addressTypesAdapter?: AddressTypesAdapter<C>\n addressTypesContext?: C\n loadFormat?: (context?: C) => Promise<AddressFormatStrategy>\n formatContext?: C\n labelPrefix?: string\n}\n\nfunction generateTempId() {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID()\n return `tmp_${Math.random().toString(36).slice(2)}`\n}\n\nfunction AddressesSectionImpl<C = unknown>({\n entityId,\n emptyLabel,\n addActionLabel,\n emptyState,\n onActionChange,\n translator,\n onLoadingChange,\n dataAdapter,\n dataContext,\n addressTypesAdapter,\n addressTypesContext,\n loadFormat,\n formatContext,\n labelPrefix = 'customers.people.detail.addresses',\n}: AddressesSectionProps<C>) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])\n const t = translator ?? fallbackTranslator\n\n const label = React.useCallback(\n (suffix: string, fallback?: string, params?: Record<string, string | number>) =>\n t(`${labelPrefix}.${suffix}`, fallback, params),\n [labelPrefix, t],\n )\n\n const normalizedEntityId = React.useMemo(() => {\n if (typeof entityId !== 'string') return null\n const trimmed = entityId.trim()\n return trimmed.length ? trimmed : null\n }, [entityId])\n const [addresses, setAddresses] = React.useState<AddressSummary[]>([])\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n const [isLoading, setIsLoading] = React.useState<boolean>(() => Boolean(normalizedEntityId))\n\n const loadingCounterRef = React.useRef(0)\n const pushLoading = React.useCallback(() => {\n loadingCounterRef.current += 1\n if (loadingCounterRef.current === 1) {\n onLoadingChange?.(true)\n }\n }, [onLoadingChange])\n const popLoading = React.useCallback(() => {\n loadingCounterRef.current = Math.max(0, loadingCounterRef.current - 1)\n if (loadingCounterRef.current === 0) {\n onLoadingChange?.(false)\n }\n }, [onLoadingChange])\n\n const loadAddresses = React.useCallback(async () => {\n if (!normalizedEntityId) {\n setAddresses([])\n setIsLoading(false)\n return\n }\n setIsLoading(true)\n pushLoading()\n try {\n const items = await dataAdapter.list({ entityId: normalizedEntityId, context: dataContext })\n setAddresses(items)\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to load addresses.')\n flash(message, 'error')\n setAddresses([])\n } finally {\n setIsLoading(false)\n popLoading()\n }\n }, [dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading])\n\n React.useEffect(() => {\n void loadAddresses()\n }, [loadAddresses])\n\n const handleCreate = React.useCallback(\n async (payload: AddressInput) => {\n if (!normalizedEntityId) {\n throw new Error(label('error', 'Failed to save address.'))\n }\n pushLoading()\n setIsSubmitting(true)\n try {\n const response = await dataAdapter.create({\n entityId: normalizedEntityId,\n payload,\n context: dataContext,\n })\n const newAddress: AddressSummary = {\n id: typeof response === 'object' && response && typeof response.id === 'string' ? response.id : generateTempId(),\n name: payload.name ?? null,\n purpose: payload.purpose ?? null,\n companyName: payload.companyName ?? null,\n addressLine1: payload.addressLine1,\n addressLine2: payload.addressLine2 ?? null,\n buildingNumber: payload.buildingNumber ?? null,\n flatNumber: payload.flatNumber ?? null,\n city: payload.city ?? null,\n region: payload.region ?? null,\n postalCode: payload.postalCode ?? null,\n country: payload.country ? payload.country.toUpperCase() : null,\n isPrimary: payload.isPrimary ?? false,\n }\n setAddresses((prev) => {\n const existing = payload.isPrimary ? prev.map((addr) => ({ ...addr, isPrimary: false })) : prev\n return [newAddress, ...existing]\n })\n flash(label('success', 'Address saved.'), 'success')\n } catch (err) {\n const message =\n err instanceof Error && err.message ? err.message : label('error', 'Failed to save address.')\n const error = new Error(message) as Error & { details?: unknown }\n if (err && typeof err === 'object' && (err as { details?: unknown }).details) {\n error.details = (err as { details?: unknown }).details\n }\n throw error\n } finally {\n setIsSubmitting(false)\n popLoading()\n }\n },\n [dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading],\n )\n\n const handleUpdate = React.useCallback(\n async (id: string, payload: AddressInput) => {\n if (!normalizedEntityId) {\n throw new Error(label('error', 'Failed to save address.'))\n }\n pushLoading()\n setIsSubmitting(true)\n try {\n await dataAdapter.update({ id, payload, context: dataContext })\n setAddresses((prev) => {\n return prev.map((address) => {\n if (address.id !== id) {\n return payload.isPrimary ? { ...address, isPrimary: false } : address\n }\n return {\n ...address,\n name: payload.name ?? null,\n purpose: payload.purpose ?? null,\n companyName: payload.companyName ?? null,\n addressLine1: payload.addressLine1,\n addressLine2: payload.addressLine2 ?? null,\n buildingNumber: payload.buildingNumber ?? null,\n flatNumber: payload.flatNumber ?? null,\n city: payload.city ?? null,\n region: payload.region ?? null,\n postalCode: payload.postalCode ?? null,\n country: payload.country ? payload.country.toUpperCase() : null,\n isPrimary: payload.isPrimary ?? false,\n }\n })\n })\n flash(label('success', 'Address saved.'), 'success')\n } catch (err) {\n const message =\n err instanceof Error && err.message ? err.message : label('error', 'Failed to save address.')\n const error = new Error(message) as Error & { details?: unknown }\n if (err && typeof err === 'object' && (err as { details?: unknown }).details) {\n error.details = (err as { details?: unknown }).details\n }\n throw error\n } finally {\n setIsSubmitting(false)\n popLoading()\n }\n },\n [dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading],\n )\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!normalizedEntityId) {\n throw new Error(label('error', 'Failed to delete address.'))\n }\n pushLoading()\n setIsSubmitting(true)\n try {\n await dataAdapter.delete({ id, context: dataContext })\n setAddresses((prev) => prev.filter((address) => address.id !== id))\n flash(label('deleted', 'Address deleted.'), 'success')\n } catch (err) {\n const message =\n err instanceof Error && err.message ? err.message : label('error', 'Failed to delete address.')\n flash(message, 'error')\n throw err\n } finally {\n setIsSubmitting(false)\n popLoading()\n }\n },\n [dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading],\n )\n\n const displayAddresses = React.useMemo<AddressValue[]>(() => {\n return addresses.map((address) => ({\n id: address.id,\n name: address.name ?? undefined,\n purpose: address.purpose ?? undefined,\n companyName: address.companyName ?? undefined,\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2 ?? undefined,\n buildingNumber: address.buildingNumber ?? undefined,\n flatNumber: address.flatNumber ?? undefined,\n city: address.city ?? undefined,\n region: address.region ?? undefined,\n postalCode: address.postalCode ?? undefined,\n country: address.country ?? undefined,\n isPrimary: address.isPrimary ?? false,\n }))\n }, [addresses])\n\n const [addAction, setAddAction] = React.useState<{\n openCreateForm: () => void\n addDisabled: boolean\n } | null>(null)\n\n const handleAddActionChange = React.useCallback(\n (action: { openCreateForm: () => void; addDisabled: boolean } | null) => {\n setAddAction(action)\n },\n [],\n )\n\n React.useEffect(() => {\n if (!onActionChange) return\n if (!addAction || addresses.length === 0) {\n onActionChange(null)\n return\n }\n onActionChange({\n label: addActionLabel,\n onClick: addAction.openCreateForm,\n disabled: addAction.addDisabled,\n })\n }, [addAction, addActionLabel, addresses.length, onActionChange])\n\n return (\n <div className=\"mt-4\">\n {isLoading ? (\n <div className=\"flex justify-center\">\n <LoadingMessage\n label={label('loading', 'Loading addresses\u2026')}\n className=\"min-h-[120px] w-full justify-center border-0 bg-transparent p-0\"\n />\n </div>\n ) : (\n <AddressTiles\n addresses={displayAddresses}\n onCreate={handleCreate}\n onUpdate={handleUpdate}\n onDelete={handleDelete}\n isSubmitting={isSubmitting}\n emptyLabel={emptyLabel}\n t={t}\n hideAddButton\n onAddActionChange={handleAddActionChange}\n emptyStateTitle={emptyState.title}\n emptyStateActionLabel={emptyState.actionLabel}\n labelPrefix={labelPrefix}\n addressTypesAdapter={addressTypesAdapter}\n addressTypesContext={addressTypesContext}\n loadFormat={loadFormat}\n formatContext={formatContext}\n />\n )}\n </div>\n )\n}\n\nexport function AddressesSection<C = unknown>(props: AddressesSectionProps<C>) {\n const handle = ComponentReplacementHandles.section('ui.detail', 'AddressesSection')\n const Resolved = useRegisteredComponent<AddressesSectionProps<C>>(\n handle,\n AddressesSectionImpl as React.ComponentType<AddressesSectionProps<C>>,\n )\n\n return (\n <div data-component-handle={handle}>\n <Resolved {...props} />\n </div>\n )\n}\n\nexport default AddressesSection\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { surfaceRecordConflict } from '../conflicts'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport AddressTiles, { type AddressInput, type AddressValue } from './AddressTiles'\nimport type { AddressTypesAdapter } from './AddressEditor'\nimport type { AddressFormatStrategy } from './addressFormat'\nimport { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '../injection/useRegisteredComponent'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type SectionAction = {\n label: React.ReactNode\n onClick: () => void\n disabled?: boolean\n icon?: React.ReactNode\n}\n\nexport type TabEmptyStateConfig = {\n title: string\n actionLabel: string\n description?: string\n}\n\nexport type AddressSummary = {\n id: string\n name?: string | null\n purpose?: string | null\n companyName?: string | null\n addressLine1: string\n addressLine2?: string | null\n buildingNumber?: string | null\n flatNumber?: string | null\n city?: string | null\n region?: string | null\n postalCode?: string | null\n country?: string | null\n isPrimary?: boolean\n updatedAt?: string | null\n}\n\nexport type AddressDataAdapter<C = unknown> = {\n list: (params: { entityId: string | null; context?: C }) => Promise<AddressSummary[]>\n create: (params: { entityId: string; payload: AddressInput; context?: C }) => Promise<{ id?: string } | void>\n update: (params: { id: string; payload: AddressInput; updatedAt?: string | null; context?: C }) => Promise<void>\n delete: (params: { id: string; updatedAt?: string | null; context?: C }) => Promise<void>\n}\n\nexport type AddressesSectionProps<C = unknown> = {\n entityId: string | null\n emptyLabel: string\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n translator?: Translator\n onLoadingChange?: (isLoading: boolean) => void\n dataAdapter: AddressDataAdapter<C>\n dataContext?: C\n addressTypesAdapter?: AddressTypesAdapter<C>\n addressTypesContext?: C\n loadFormat?: (context?: C) => Promise<AddressFormatStrategy>\n formatContext?: C\n labelPrefix?: string\n}\n\nfunction generateTempId() {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID()\n return `tmp_${Math.random().toString(36).slice(2)}`\n}\n\nfunction AddressesSectionImpl<C = unknown>({\n entityId,\n emptyLabel,\n addActionLabel,\n emptyState,\n onActionChange,\n translator,\n onLoadingChange,\n dataAdapter,\n dataContext,\n addressTypesAdapter,\n addressTypesContext,\n loadFormat,\n formatContext,\n labelPrefix = 'customers.people.detail.addresses',\n}: AddressesSectionProps<C>) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])\n const t = translator ?? fallbackTranslator\n\n const label = React.useCallback(\n (suffix: string, fallback?: string, params?: Record<string, string | number>) =>\n t(`${labelPrefix}.${suffix}`, fallback, params),\n [labelPrefix, t],\n )\n\n const normalizedEntityId = React.useMemo(() => {\n if (typeof entityId !== 'string') return null\n const trimmed = entityId.trim()\n return trimmed.length ? trimmed : null\n }, [entityId])\n const [addresses, setAddresses] = React.useState<AddressSummary[]>([])\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n const [isLoading, setIsLoading] = React.useState<boolean>(() => Boolean(normalizedEntityId))\n\n const loadingCounterRef = React.useRef(0)\n const pushLoading = React.useCallback(() => {\n loadingCounterRef.current += 1\n if (loadingCounterRef.current === 1) {\n onLoadingChange?.(true)\n }\n }, [onLoadingChange])\n const popLoading = React.useCallback(() => {\n loadingCounterRef.current = Math.max(0, loadingCounterRef.current - 1)\n if (loadingCounterRef.current === 0) {\n onLoadingChange?.(false)\n }\n }, [onLoadingChange])\n\n const loadAddresses = React.useCallback(async () => {\n if (!normalizedEntityId) {\n setAddresses([])\n setIsLoading(false)\n return\n }\n setIsLoading(true)\n pushLoading()\n try {\n const items = await dataAdapter.list({ entityId: normalizedEntityId, context: dataContext })\n setAddresses(items)\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to load addresses.')\n flash(message, 'error')\n setAddresses([])\n } finally {\n setIsLoading(false)\n popLoading()\n }\n }, [dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading])\n\n React.useEffect(() => {\n void loadAddresses()\n }, [loadAddresses])\n\n const handleCreate = React.useCallback(\n async (payload: AddressInput) => {\n if (!normalizedEntityId) {\n throw new Error(label('error', 'Failed to save address.'))\n }\n pushLoading()\n setIsSubmitting(true)\n try {\n const response = await dataAdapter.create({\n entityId: normalizedEntityId,\n payload,\n context: dataContext,\n })\n const newAddress: AddressSummary = {\n id: typeof response === 'object' && response && typeof response.id === 'string' ? response.id : generateTempId(),\n name: payload.name ?? null,\n purpose: payload.purpose ?? null,\n companyName: payload.companyName ?? null,\n addressLine1: payload.addressLine1,\n addressLine2: payload.addressLine2 ?? null,\n buildingNumber: payload.buildingNumber ?? null,\n flatNumber: payload.flatNumber ?? null,\n city: payload.city ?? null,\n region: payload.region ?? null,\n postalCode: payload.postalCode ?? null,\n country: payload.country ? payload.country.toUpperCase() : null,\n isPrimary: payload.isPrimary ?? false,\n }\n setAddresses((prev) => {\n const existing = payload.isPrimary ? prev.map((addr) => ({ ...addr, isPrimary: false })) : prev\n return [newAddress, ...existing]\n })\n flash(label('success', 'Address saved.'), 'success')\n } catch (err) {\n const message =\n err instanceof Error && err.message ? err.message : label('error', 'Failed to save address.')\n const error = new Error(message) as Error & { details?: unknown }\n if (err && typeof err === 'object' && (err as { details?: unknown }).details) {\n error.details = (err as { details?: unknown }).details\n }\n throw error\n } finally {\n setIsSubmitting(false)\n popLoading()\n }\n },\n [dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading],\n )\n\n const handleUpdate = React.useCallback(\n async (id: string, payload: AddressInput) => {\n if (!normalizedEntityId) {\n throw new Error(label('error', 'Failed to save address.'))\n }\n pushLoading()\n setIsSubmitting(true)\n try {\n const addressUpdatedAt = addresses.find((address) => address.id === id)?.updatedAt ?? null\n await dataAdapter.update({ id, payload, updatedAt: addressUpdatedAt, context: dataContext })\n setAddresses((prev) => {\n return prev.map((address) => {\n if (address.id !== id) {\n return payload.isPrimary ? { ...address, isPrimary: false } : address\n }\n return {\n ...address,\n name: payload.name ?? null,\n purpose: payload.purpose ?? null,\n companyName: payload.companyName ?? null,\n addressLine1: payload.addressLine1,\n addressLine2: payload.addressLine2 ?? null,\n buildingNumber: payload.buildingNumber ?? null,\n flatNumber: payload.flatNumber ?? null,\n city: payload.city ?? null,\n region: payload.region ?? null,\n postalCode: payload.postalCode ?? null,\n country: payload.country ? payload.country.toUpperCase() : null,\n isPrimary: payload.isPrimary ?? false,\n }\n })\n })\n flash(label('success', 'Address saved.'), 'success')\n } catch (err) {\n surfaceRecordConflict(err, t)\n const message =\n err instanceof Error && err.message ? err.message : label('error', 'Failed to save address.')\n const error = new Error(message) as Error & { details?: unknown }\n if (err && typeof err === 'object' && (err as { details?: unknown }).details) {\n error.details = (err as { details?: unknown }).details\n }\n throw error\n } finally {\n setIsSubmitting(false)\n popLoading()\n }\n },\n [addresses, dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading, t],\n )\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!normalizedEntityId) {\n throw new Error(label('error', 'Failed to delete address.'))\n }\n pushLoading()\n setIsSubmitting(true)\n try {\n const addressUpdatedAt = addresses.find((address) => address.id === id)?.updatedAt ?? null\n await dataAdapter.delete({ id, updatedAt: addressUpdatedAt, context: dataContext })\n setAddresses((prev) => prev.filter((address) => address.id !== id))\n flash(label('deleted', 'Address deleted.'), 'success')\n } catch (err) {\n const message =\n err instanceof Error && err.message ? err.message : label('error', 'Failed to delete address.')\n if (!surfaceRecordConflict(err, t)) flash(message, 'error')\n throw err\n } finally {\n setIsSubmitting(false)\n popLoading()\n }\n },\n [addresses, dataAdapter, dataContext, label, normalizedEntityId, popLoading, pushLoading, t],\n )\n\n const displayAddresses = React.useMemo<AddressValue[]>(() => {\n return addresses.map((address) => ({\n id: address.id,\n name: address.name ?? undefined,\n purpose: address.purpose ?? undefined,\n companyName: address.companyName ?? undefined,\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2 ?? undefined,\n buildingNumber: address.buildingNumber ?? undefined,\n flatNumber: address.flatNumber ?? undefined,\n city: address.city ?? undefined,\n region: address.region ?? undefined,\n postalCode: address.postalCode ?? undefined,\n country: address.country ?? undefined,\n isPrimary: address.isPrimary ?? false,\n }))\n }, [addresses])\n\n const [addAction, setAddAction] = React.useState<{\n openCreateForm: () => void\n addDisabled: boolean\n } | null>(null)\n\n const handleAddActionChange = React.useCallback(\n (action: { openCreateForm: () => void; addDisabled: boolean } | null) => {\n setAddAction(action)\n },\n [],\n )\n\n React.useEffect(() => {\n if (!onActionChange) return\n if (!addAction || addresses.length === 0) {\n onActionChange(null)\n return\n }\n onActionChange({\n label: addActionLabel,\n onClick: addAction.openCreateForm,\n disabled: addAction.addDisabled,\n })\n }, [addAction, addActionLabel, addresses.length, onActionChange])\n\n return (\n <div className=\"mt-4\">\n {isLoading ? (\n <div className=\"flex justify-center\">\n <LoadingMessage\n label={label('loading', 'Loading addresses\u2026')}\n className=\"min-h-[120px] w-full justify-center border-0 bg-transparent p-0\"\n />\n </div>\n ) : (\n <AddressTiles\n addresses={displayAddresses}\n onCreate={handleCreate}\n onUpdate={handleUpdate}\n onDelete={handleDelete}\n isSubmitting={isSubmitting}\n emptyLabel={emptyLabel}\n t={t}\n hideAddButton\n onAddActionChange={handleAddActionChange}\n emptyStateTitle={emptyState.title}\n emptyStateActionLabel={emptyState.actionLabel}\n labelPrefix={labelPrefix}\n addressTypesAdapter={addressTypesAdapter}\n addressTypesContext={addressTypesContext}\n loadFormat={loadFormat}\n formatContext={formatContext}\n />\n )}\n </div>\n )\n}\n\nexport function AddressesSection<C = unknown>(props: AddressesSectionProps<C>) {\n const handle = ComponentReplacementHandles.section('ui.detail', 'AddressesSection')\n const Resolved = useRegisteredComponent<AddressesSectionProps<C>>(\n handle,\n AddressesSectionImpl as React.ComponentType<AddressesSectionProps<C>>,\n )\n\n return (\n <div data-component-handle={handle}>\n <Resolved {...props} />\n </div>\n )\n}\n\nexport default AddressesSection\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -13,6 +13,7 @@ import {
13
13
  SelectValue
14
14
  } from "@open-mercato/ui/primitives/select";
15
15
  import { flash } from "../FlashMessages.js";
16
+ import { surfaceRecordConflict } from "../conflicts/index.js";
16
17
  import { SwitchableMarkdownInput } from "../inputs/SwitchableMarkdownInput.js";
17
18
  import { ErrorMessage } from "./ErrorMessage.js";
18
19
  import { LoadingMessage } from "./LoadingMessage.js";
@@ -86,6 +87,7 @@ function mapCommentSummary(input) {
86
87
  const id = typeof data.id === "string" ? data.id : generateTempId();
87
88
  const body = typeof data.body === "string" ? data.body : "";
88
89
  const createdAt = typeof data.createdAt === "string" ? data.createdAt : typeof data.created_at === "string" ? data.created_at : (/* @__PURE__ */ new Date()).toISOString();
90
+ const updatedAt = typeof data.updatedAt === "string" ? data.updatedAt : typeof data.updated_at === "string" ? data.updated_at : null;
89
91
  const authorUserId = typeof data.authorUserId === "string" ? data.authorUserId : typeof data.author_user_id === "string" ? data.author_user_id : null;
90
92
  const authorName = typeof data.authorName === "string" ? data.authorName : typeof data.author_name === "string" ? data.author_name : null;
91
93
  const authorEmail = typeof data.authorEmail === "string" ? data.authorEmail : typeof data.author_email === "string" ? data.author_email : null;
@@ -97,6 +99,7 @@ function mapCommentSummary(input) {
97
99
  id,
98
100
  body,
99
101
  createdAt,
102
+ updatedAt,
100
103
  authorUserId,
101
104
  authorName,
102
105
  authorEmail,
@@ -463,7 +466,7 @@ function NotesSectionImpl({
463
466
  return true;
464
467
  } catch (err) {
465
468
  const message = err instanceof Error ? err.message : label("error");
466
- flash(message, "error");
469
+ if (!surfaceRecordConflict(err, t)) flash(message, "error");
467
470
  return false;
468
471
  } finally {
469
472
  setIsSubmitting(false);
@@ -477,6 +480,7 @@ function NotesSectionImpl({
477
480
  const sanitizedBody = patch.body;
478
481
  const sanitizedIcon = patch.appearanceIcon !== void 0 && patch.appearanceIcon !== null && patch.appearanceIcon.trim().length ? patch.appearanceIcon.trim() : patch.appearanceIcon === null ? null : void 0;
479
482
  const sanitizedColor = patch.appearanceColor !== void 0 ? sanitizeHexColor(patch.appearanceColor ?? null) : void 0;
483
+ const noteUpdatedAt = notes.find((comment) => comment.id === noteId)?.updatedAt ?? null;
480
484
  try {
481
485
  await dataAdapter.update({
482
486
  id: noteId,
@@ -485,6 +489,7 @@ function NotesSectionImpl({
485
489
  appearanceIcon: sanitizedIcon,
486
490
  appearanceColor: sanitizedColor
487
491
  },
492
+ updatedAt: noteUpdatedAt,
488
493
  context: dataContext
489
494
  });
490
495
  setNotes((prev) => {
@@ -501,11 +506,11 @@ function NotesSectionImpl({
501
506
  flash(label("updateSuccess"), "success");
502
507
  } catch (error) {
503
508
  const message = error instanceof Error ? error.message : label("updateError");
504
- flash(message, "error");
509
+ if (!surfaceRecordConflict(error, t)) flash(message, "error");
505
510
  throw error instanceof Error ? error : new Error(message);
506
511
  }
507
512
  },
508
- [dataAdapter, dataContext, t]
513
+ [dataAdapter, dataContext, notes, t]
509
514
  );
510
515
  const handleDeleteNote = React.useCallback(
511
516
  async (note) => {
@@ -517,7 +522,7 @@ function NotesSectionImpl({
517
522
  setDeletingNoteId(note.id);
518
523
  pushLoading();
519
524
  try {
520
- await dataAdapter.delete({ id: note.id, context: dataContext });
525
+ await dataAdapter.delete({ id: note.id, updatedAt: note.updatedAt ?? null, context: dataContext });
521
526
  setNotes((prev) => prev.filter((existing) => existing.id !== note.id));
522
527
  if (pagedMode) {
523
528
  setVisibleCount((prev) => Math.max(0, prev - 1));
@@ -525,13 +530,13 @@ function NotesSectionImpl({
525
530
  flash(label("deleteSuccess", "Note deleted"), "success");
526
531
  } catch (err) {
527
532
  const message = err instanceof Error ? err.message : label("deleteError", "Failed to delete note");
528
- flash(message, "error");
533
+ if (!surfaceRecordConflict(err, t)) flash(message, "error");
529
534
  } finally {
530
535
  setDeletingNoteId(null);
531
536
  popLoading();
532
537
  }
533
538
  },
534
- [confirm, dataAdapter, dataContext, label, popLoading, pushLoading]
539
+ [confirm, dataAdapter, dataContext, label, popLoading, pushLoading, t]
535
540
  );
536
541
  const handleSubmit = React.useCallback(
537
542
  async (event) => {