@open-mercato/ui 0.6.6-develop.6386.1.391a1afb9e → 0.6.6-develop.6387.1.f4896ad565
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.
- package/dist/backend/detail/AddressEditor.js +35 -1
- package/dist/backend/detail/AddressEditor.js.map +2 -2
- package/dist/backend/detail/AddressTiles.js +27 -2
- package/dist/backend/detail/AddressTiles.js.map +2 -2
- package/dist/backend/detail/AddressesSection.js +9 -1
- package/dist/backend/detail/AddressesSection.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/detail/AddressEditor.tsx +38 -0
- package/src/backend/detail/AddressTiles.tsx +37 -2
- package/src/backend/detail/AddressesSection.tsx +11 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
@@ -35,6 +35,7 @@ function AddressEditor({
|
|
|
35
35
|
errors = {},
|
|
36
36
|
hidePrimaryToggle = false,
|
|
37
37
|
showFormatHint = true,
|
|
38
|
+
showCoordinateFields = false,
|
|
38
39
|
addressTypesAdapter,
|
|
39
40
|
addressTypesContext
|
|
40
41
|
}) {
|
|
@@ -100,6 +101,7 @@ function AddressEditor({
|
|
|
100
101
|
region: value.region ?? "",
|
|
101
102
|
postalCode: value.postalCode ?? "",
|
|
102
103
|
country: value.country ?? "",
|
|
104
|
+
...showCoordinateFields ? { latitude: value.latitude ?? "", longitude: value.longitude ?? "" } : {},
|
|
103
105
|
isPrimary: value.isPrimary ?? false
|
|
104
106
|
};
|
|
105
107
|
const update = React.useCallback(
|
|
@@ -411,6 +413,38 @@ function AddressEditor({
|
|
|
411
413
|
] })
|
|
412
414
|
] })
|
|
413
415
|
] }),
|
|
416
|
+
showCoordinateFields ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
417
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [
|
|
418
|
+
/* @__PURE__ */ jsx(
|
|
419
|
+
Input,
|
|
420
|
+
{
|
|
421
|
+
className: inputClass("latitude"),
|
|
422
|
+
placeholder: label("fields.latitude", "Latitude"),
|
|
423
|
+
"aria-label": label("fields.latitude", "Latitude"),
|
|
424
|
+
inputMode: "decimal",
|
|
425
|
+
value: current.latitude ?? "",
|
|
426
|
+
onChange: (evt) => update("latitude", evt.target.value),
|
|
427
|
+
disabled,
|
|
428
|
+
"aria-invalid": errors.latitude ? "true" : void 0
|
|
429
|
+
}
|
|
430
|
+
),
|
|
431
|
+
/* @__PURE__ */ jsx(
|
|
432
|
+
Input,
|
|
433
|
+
{
|
|
434
|
+
className: inputClass("longitude"),
|
|
435
|
+
placeholder: label("fields.longitude", "Longitude"),
|
|
436
|
+
"aria-label": label("fields.longitude", "Longitude"),
|
|
437
|
+
inputMode: "decimal",
|
|
438
|
+
value: current.longitude ?? "",
|
|
439
|
+
onChange: (evt) => update("longitude", evt.target.value),
|
|
440
|
+
disabled,
|
|
441
|
+
"aria-invalid": errors.longitude ? "true" : void 0
|
|
442
|
+
}
|
|
443
|
+
)
|
|
444
|
+
] }),
|
|
445
|
+
errors.latitude ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: errors.latitude }) : null,
|
|
446
|
+
errors.longitude ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: errors.longitude }) : null
|
|
447
|
+
] }) : null,
|
|
414
448
|
showFormatHint ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: label("formatHint", "Format based on address settings") }) : null,
|
|
415
449
|
!hidePrimaryToggle ? /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
416
450
|
/* @__PURE__ */ jsx(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/detail/AddressEditor.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Plus, Settings } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { buildCountryOptions } from '@open-mercato/shared/lib/location/countries'\nimport { buildHrefWithReturnTo } from '@open-mercato/shared/lib/navigation/returnTo'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { AddressFormatStrategy } from './addressFormat'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type AddressTypeOption = {\n value: string\n label: string\n}\n\nexport type AddressTypesAdapter<C = unknown> = {\n list: (context?: C) => Promise<AddressTypeOption[]>\n create?: (value: string, context?: C) => Promise<AddressTypeOption | null>\n manageHref?: string\n}\n\nexport type AddressEditorDraft = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n isPrimary: boolean\n}\n\nexport type AddressEditorField =\n | 'name'\n | 'purpose'\n | 'companyName'\n | 'addressLine1'\n | 'addressLine2'\n | 'buildingNumber'\n | 'flatNumber'\n | 'city'\n | 'region'\n | 'postalCode'\n | 'country'\n | 'isPrimary'\n\ntype AddressEditorProps<C = unknown> = {\n value: AddressEditorDraft\n onChange: (next: AddressEditorDraft) => void\n format: AddressFormatStrategy\n t: Translator\n labelPrefix?: string\n disabled?: boolean\n errors?: Partial<Record<AddressEditorField, string>>\n hidePrimaryToggle?: boolean\n showFormatHint?: boolean\n addressTypesAdapter?: AddressTypesAdapter<C>\n addressTypesContext?: C\n}\n\nexport function AddressEditor<C = unknown>({\n value,\n onChange,\n format,\n t,\n labelPrefix = 'customers.people.detail.addresses',\n disabled = false,\n errors = {},\n hidePrimaryToggle = false,\n showFormatHint = true,\n addressTypesAdapter,\n addressTypesContext,\n}: AddressEditorProps<C>) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\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 [addressTypes, setAddressTypes] = React.useState<AddressTypeOption[]>([])\n const [addressTypesLoading, setAddressTypesLoading] = React.useState(false)\n const [addressTypeError, setAddressTypeError] = React.useState<string | null>(null)\n\n const [typeDialogOpen, setTypeDialogOpen] = React.useState(false)\n const [typeValue, setTypeValue] = React.useState('')\n const [typeFormError, setTypeFormError] = React.useState<string | null>(null)\n const [countryDialogOpen, setCountryDialogOpen] = React.useState(false)\n const [countryQuery, setCountryQuery] = React.useState('')\n\n const countryOptions = React.useMemo(\n () =>\n buildCountryOptions({\n transformLabel: (code, fallback) => t(`customers.countries.${code.toLowerCase()}`, fallback ?? code),\n }),\n [t],\n )\n\n React.useEffect(() => {\n let cancelled = false\n const load = async () => {\n if (!addressTypesAdapter) {\n setAddressTypes([])\n setAddressTypeError(null)\n return\n }\n setAddressTypesLoading(true)\n try {\n const result = await addressTypesAdapter.list(addressTypesContext)\n if (!cancelled) {\n setAddressTypes(Array.isArray(result) ? result : [])\n setAddressTypeError(null)\n }\n } catch (err) {\n if (!cancelled) {\n setAddressTypes([])\n setAddressTypeError(label('types.loadError', 'Failed to load address types'))\n }\n } finally {\n if (!cancelled) setAddressTypesLoading(false)\n }\n }\n load().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [addressTypesAdapter, addressTypesContext, label])\n\n const current: AddressEditorDraft = {\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n isPrimary: value.isPrimary ?? false,\n }\n\n const update = React.useCallback(\n (key: keyof AddressEditorDraft, nextValue: string | boolean) => {\n onChange({ ...current, [key]: nextValue })\n },\n [current, onChange],\n )\n\n const filteredCountryOptions = React.useMemo(() => {\n const query = countryQuery.trim().toLowerCase()\n if (!query.length) return countryOptions\n return countryOptions.filter(\n (option) => option.label.toLowerCase().includes(query) || option.code.toLowerCase().includes(query),\n )\n }, [countryOptions, countryQuery])\n\n const selectedCountry = React.useMemo(() => {\n const code = (current.country ?? '').toUpperCase()\n if (!code.length) return null\n return countryOptions.find((option) => option.code === code) ?? null\n }, [countryOptions, current.country])\n const returnTo = React.useMemo(() => {\n const query = searchParams?.toString() ?? ''\n if (!pathname) return null\n return query.length ? `${pathname}?${query}` : pathname\n }, [pathname, searchParams])\n const manageAddressTypesHref = React.useMemo(\n () => buildHrefWithReturnTo(addressTypesAdapter?.manageHref ?? '/backend/config/dictionaries', returnTo),\n [addressTypesAdapter?.manageHref, returnTo],\n )\n\n const handleTypeSubmit = React.useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n const trimmed = typeValue.trim()\n if (!trimmed.length) {\n setTypeFormError(label('types.emptyError', 'Please provide a value'))\n return\n }\n if (!addressTypesAdapter?.create) return\n setTypeFormError(null)\n const created = await addressTypesAdapter.create(trimmed, addressTypesContext)\n if (created) {\n setAddressTypes((prev) => {\n const map = new Map(prev.map((entry) => [entry.value, entry]))\n map.set(created.value, created)\n return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label))\n })\n }\n setTypeDialogOpen(false)\n setTypeValue('')\n },\n [addressTypesAdapter, addressTypesContext, label, typeValue],\n )\n\n const inputClass = (field: AddressEditorField) =>\n [\n 'w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n errors[field] ? 'border-red-500 aria-invalid:ring-destructive' : 'border-input bg-background',\n ].join(' ')\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('name')}\n placeholder={label('fields.label', 'Label')}\n value={current.name}\n onChange={(evt) => update('name', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.name ? 'true' : undefined}\n />\n <div className=\"flex gap-2\">\n <Select\n value={current.purpose || undefined}\n onValueChange={(next) => update('purpose', next ?? '')}\n disabled={disabled}\n >\n <SelectTrigger\n className={errors.purpose ? 'border-destructive' : undefined}\n aria-invalid={errors.purpose ? 'true' : undefined}\n >\n <SelectValue\n placeholder={\n addressTypesLoading\n ? label('types.loading', 'Loading\u2026')\n : label('types.placeholder', 'Address type')\n }\n />\n </SelectTrigger>\n <SelectContent>\n {addressTypes.map((entry) => (\n <SelectItem key={entry.value} value={entry.value}>\n {entry.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {addressTypesAdapter?.create ? (\n <Dialog open={typeDialogOpen} onOpenChange={setTypeDialogOpen}>\n <DialogTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"icon\" className=\"shrink-0\" disabled={disabled}>\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{label('types.add', 'Add address type')}</DialogTitle>\n <DialogDescription>\n {label('types.addHint', 'Create a new address type for reuse.')}\n </DialogDescription>\n </DialogHeader>\n <form className=\"space-y-3\" onSubmit={handleTypeSubmit}>\n <Input\n autoFocus\n value={typeValue}\n onChange={(evt) => {\n setTypeValue(evt.target.value)\n if (typeFormError) setTypeFormError(null)\n }}\n placeholder={label('types.placeholder', 'Address type')}\n disabled={disabled}\n aria-invalid={typeFormError ? 'true' : undefined}\n />\n {typeFormError ? <p className=\"text-sm text-destructive\">{typeFormError}</p> : null}\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setTypeDialogOpen(false)} disabled={disabled}>\n {label('types.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={disabled || !typeValue.trim()}>\n {label('types.save', 'Save')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n ) : null}\n <Button\n asChild\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0\"\n disabled={disabled}\n title={label('types.manage', 'Manage address types')}\n >\n <Link\n href={manageAddressTypesHref}\n aria-label={label('types.manage', 'Manage address types')}\n >\n <Settings className=\"h-4 w-4\" />\n </Link>\n </Button>\n </div>\n </div>\n {errors.purpose ? <p className=\"text-xs text-destructive\">{errors.purpose}</p> : null}\n {addressTypeError ? <p className=\"text-xs text-destructive\">{addressTypeError}</p> : null}\n <Input\n className={inputClass('companyName')}\n placeholder={label('fields.companyName', 'Company name')}\n value={current.companyName}\n onChange={(evt) => update('companyName', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.companyName ? 'true' : undefined}\n />\n\n {format === 'street_first' ? (\n <div className=\"grid gap-2 sm:grid-cols-[1.5fr,0.7fr,0.7fr]\">\n <Input\n className={inputClass('addressLine1')}\n placeholder={label('fields.street', 'Street')}\n value={current.addressLine1}\n onChange={(evt) => update('addressLine1', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine1 ? 'true' : undefined}\n />\n <Input\n className={inputClass('buildingNumber')}\n placeholder={label('fields.buildingNumber', 'Building number')}\n value={current.buildingNumber}\n onChange={(evt) => update('buildingNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.buildingNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('flatNumber')}\n placeholder={label('fields.flatNumber', 'Flat number')}\n value={current.flatNumber}\n onChange={(evt) => update('flatNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.flatNumber ? 'true' : undefined}\n />\n </div>\n ) : (\n <Input\n className={inputClass('addressLine1')}\n placeholder={label('fields.line1', 'Address line 1')}\n value={current.addressLine1}\n onChange={(evt) => update('addressLine1', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine1 ? 'true' : undefined}\n />\n )}\n\n <Input\n className={inputClass('addressLine2')}\n placeholder={label('fields.line2', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n\n {format !== 'street_first' ? (\n <div className=\"grid gap-2 sm:grid-cols-[1.5fr,0.7fr,0.7fr]\">\n <Input\n className={inputClass('buildingNumber')}\n placeholder={label('fields.buildingNumber', 'Building number')}\n value={current.buildingNumber}\n onChange={(evt) => update('buildingNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.buildingNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('flatNumber')}\n placeholder={label('fields.flatNumber', 'Flat number')}\n value={current.flatNumber}\n onChange={(evt) => update('flatNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.flatNumber ? 'true' : undefined}\n />\n </div>\n ) : null}\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('city')}\n placeholder={label('fields.city', 'City')}\n value={current.city}\n onChange={(evt) => update('city', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.city ? 'true' : undefined}\n />\n <Input\n className={inputClass('region')}\n placeholder={label('fields.region', 'Region')}\n value={current.region}\n onChange={(evt) => update('region', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.region ? 'true' : undefined}\n />\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('postalCode')}\n placeholder={label('fields.postalCode', 'Postal code')}\n value={current.postalCode}\n onChange={(evt) => update('postalCode', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.postalCode ? 'true' : undefined}\n />\n <Dialog open={countryDialogOpen} onOpenChange={setCountryDialogOpen}>\n <DialogTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" className={cn(inputClass('country'), 'cursor-pointer')} disabled={disabled}>\n {selectedCountry?.label ?? label('fields.country', 'Country')}\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{label('country.title', 'Choose a country')}</DialogTitle>\n <DialogDescription>{label('country.subtitle', 'Search for a country')}</DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <Input\n placeholder={label('country.search', 'Search countries')}\n value={countryQuery}\n onChange={(evt) => setCountryQuery(evt.target.value)}\n />\n <div className=\"max-h-64 overflow-auto rounded-md border border-border/70\">\n <ul className=\"divide-y divide-border/50\">\n {filteredCountryOptions.map((option) => (\n <li key={option.code}>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-between font-normal rounded-none\"\n onClick={() => {\n update('country', option.code)\n setCountryDialogOpen(false)\n }}\n >\n <span>{option.label}</span>\n <span className=\"text-xs text-muted-foreground\">{option.code}</span>\n </Button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </div>\n\n {showFormatHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {label('formatHint', 'Format based on address settings')}\n </p>\n ) : null}\n\n {!hidePrimaryToggle ? (\n <label className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <input\n type=\"checkbox\"\n checked={current.isPrimary}\n onChange={(evt) => update('isPrimary', evt.target.checked)}\n disabled={disabled}\n />\n {label('fields.primary', 'Primary address')}\n </label>\n ) : null}\n </div>\n )\n}\n\nexport default AddressEditor\n"],
|
|
5
|
-
"mappings": ";AAwOQ,cASE,YATF;AAtOR,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,aAAa,uBAAuB;AAC7C,SAAS,MAAM,gBAAgB;AAC/B,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AACpC,SAAS,6BAA6B;AACtC,SAAS,UAAU;AA2DZ,SAAS,cAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AAAA,EACX,SAAS,CAAC;AAAA,EACV,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB;AAAA,EACA;AACF,GAA0B;AACxB,QAAM,WAAW,YAAY;AAC7B,QAAM,eAAe,gBAAgB;AACrC,QAAM,QAAQ,MAAM;AAAA,IAClB,CAAC,QAAgB,UAAmB,WAClC,EAAE,GAAG,WAAW,IAAI,MAAM,IAAI,UAAU,MAAM;AAAA,IAChD,CAAC,aAAa,CAAC;AAAA,EACjB;AAEA,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAA8B,CAAC,CAAC;AAC9E,QAAM,CAAC,qBAAqB,sBAAsB,IAAI,MAAM,SAAS,KAAK;AAC1E,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAwB,IAAI;AAElF,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,KAAK;AAChE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,EAAE;AACnD,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,EAAE;AAEzD,QAAM,iBAAiB,MAAM;AAAA,IAC3B,MACE,oBAAoB;AAAA,MAClB,gBAAgB,CAAC,MAAM,aAAa,EAAE,uBAAuB,KAAK,YAAY,CAAC,IAAI,YAAY,IAAI;AAAA,IACrG,CAAC;AAAA,IACH,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,UAAM,OAAO,YAAY;AACvB,UAAI,CAAC,qBAAqB;AACxB,wBAAgB,CAAC,CAAC;AAClB,4BAAoB,IAAI;AACxB;AAAA,MACF;AACA,6BAAuB,IAAI;AAC3B,UAAI;AACF,cAAM,SAAS,MAAM,oBAAoB,KAAK,mBAAmB;AACjE,YAAI,CAAC,WAAW;AACd,0BAAgB,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,CAAC;AACnD,8BAAoB,IAAI;AAAA,QAC1B;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,CAAC,WAAW;AACd,0BAAgB,CAAC,CAAC;AAClB,8BAAoB,MAAM,mBAAmB,8BAA8B,CAAC;AAAA,QAC9E;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,wBAAuB,KAAK;AAAA,MAC9C;AAAA,IACF;AACA,SAAK,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACrB,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,qBAAqB,qBAAqB,KAAK,CAAC;AAEpD,QAAM,UAA8B;AAAA,IAClC,MAAM,MAAM,QAAQ;AAAA,IACpB,SAAS,MAAM,WAAW;AAAA,IAC1B,aAAa,MAAM,eAAe;AAAA,IAClC,cAAc,MAAM,gBAAgB;AAAA,IACpC,cAAc,MAAM,gBAAgB;AAAA,IACpC,gBAAgB,MAAM,kBAAkB;AAAA,IACxC,YAAY,MAAM,cAAc;AAAA,IAChC,MAAM,MAAM,QAAQ;AAAA,IACpB,QAAQ,MAAM,UAAU;AAAA,IACxB,YAAY,MAAM,cAAc;AAAA,IAChC,SAAS,MAAM,WAAW;AAAA,IAC1B,WAAW,MAAM,aAAa;AAAA,EAChC;AAEA,QAAM,SAAS,MAAM;AAAA,IACnB,CAAC,KAA+B,cAAgC;AAC9D,eAAS,EAAE,GAAG,SAAS,CAAC,GAAG,GAAG,UAAU,CAAC;AAAA,IAC3C;AAAA,IACA,CAAC,SAAS,QAAQ;AAAA,EACpB;AAEA,QAAM,yBAAyB,MAAM,QAAQ,MAAM;AACjD,UAAM,QAAQ,aAAa,KAAK,EAAE,YAAY;AAC9C,QAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,WAAO,eAAe;AAAA,MACpB,CAAC,WAAW,OAAO,MAAM,YAAY,EAAE,SAAS,KAAK,KAAK,OAAO,KAAK,YAAY,EAAE,SAAS,KAAK;AAAA,IACpG;AAAA,EACF,GAAG,CAAC,gBAAgB,YAAY,CAAC;AAEjC,QAAM,kBAAkB,MAAM,QAAQ,MAAM;AAC1C,UAAM,QAAQ,QAAQ,WAAW,IAAI,YAAY;AACjD,QAAI,CAAC,KAAK,OAAQ,QAAO;AACzB,WAAO,eAAe,KAAK,CAAC,WAAW,OAAO,SAAS,IAAI,KAAK;AAAA,EAClE,GAAG,CAAC,gBAAgB,QAAQ,OAAO,CAAC;AACpC,QAAM,WAAW,MAAM,QAAQ,MAAM;AACnC,UAAM,QAAQ,cAAc,SAAS,KAAK;AAC1C,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,MAAM,SAAS,GAAG,QAAQ,IAAI,KAAK,KAAK;AAAA,EACjD,GAAG,CAAC,UAAU,YAAY,CAAC;AAC3B,QAAM,yBAAyB,MAAM;AAAA,IACnC,MAAM,sBAAsB,qBAAqB,cAAc,gCAAgC,QAAQ;AAAA,IACvG,CAAC,qBAAqB,YAAY,QAAQ;AAAA,EAC5C;AAEA,QAAM,mBAAmB,MAAM;AAAA,IAC7B,OAAO,UAA4C;AACjD,YAAM,eAAe;AACrB,YAAM,UAAU,UAAU,KAAK;AAC/B,UAAI,CAAC,QAAQ,QAAQ;AACnB,yBAAiB,MAAM,oBAAoB,wBAAwB,CAAC;AACpE;AAAA,MACF;AACA,UAAI,CAAC,qBAAqB,OAAQ;AAClC,uBAAiB,IAAI;AACrB,YAAM,UAAU,MAAM,oBAAoB,OAAO,SAAS,mBAAmB;AAC7E,UAAI,SAAS;AACX,wBAAgB,CAAC,SAAS;AACxB,gBAAM,MAAM,IAAI,IAAI,KAAK,IAAI,CAAC,UAAU,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC;AAC7D,cAAI,IAAI,QAAQ,OAAO,OAAO;AAC9B,iBAAO,MAAM,KAAK,IAAI,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AAAA,QAC/E,CAAC;AAAA,MACH;AACA,wBAAkB,KAAK;AACvB,mBAAa,EAAE;AAAA,IACjB;AAAA,IACA,CAAC,qBAAqB,qBAAqB,OAAO,SAAS;AAAA,EAC7D;AAEA,QAAM,aAAa,CAAC,UAClB;AAAA,IACE;AAAA,IACA,OAAO,KAAK,IAAI,iDAAiD;AAAA,EACnE,EAAE,KAAK,GAAG;AAEZ,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,yBAAC,SAAI,WAAU,6BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,MAAM;AAAA,UAC5B,aAAa,MAAM,gBAAgB,OAAO;AAAA,UAC1C,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,QAAQ,IAAI,OAAO,KAAK;AAAA,UAClD;AAAA,UACA,gBAAc,OAAO,OAAO,SAAS;AAAA;AAAA,MACvC;AAAA,MACA,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,QAAQ,WAAW;AAAA,YAC1B,eAAe,CAAC,SAAS,OAAO,WAAW,QAAQ,EAAE;AAAA,YACrD;AAAA,YAEA;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW,OAAO,UAAU,uBAAuB;AAAA,kBACnD,gBAAc,OAAO,UAAU,SAAS;AAAA,kBAExC;AAAA,oBAAC;AAAA;AAAA,sBACC,aACE,sBACI,MAAM,iBAAiB,eAAU,IACjC,MAAM,qBAAqB,cAAc;AAAA;AAAA,kBAEjD;AAAA;AAAA,cACF;AAAA,cACA,oBAAC,iBACE,uBAAa,IAAI,CAAC,UACjB,oBAAC,cAA6B,OAAO,MAAM,OACxC,gBAAM,SADQ,MAAM,KAEvB,CACD,GACH;AAAA;AAAA;AAAA,QACF;AAAA,QACC,qBAAqB,SACpB,qBAAC,UAAO,MAAM,gBAAgB,cAAc,mBAC1C;AAAA,8BAAC,iBAAc,SAAO,MACpB,8BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,QAAO,WAAU,YAAW,UACvE,8BAAC,QAAK,WAAU,WAAU,GAC5B,GACF;AAAA,UACA,qBAAC,iBAAc,WAAU,eACvB;AAAA,iCAAC,gBACC;AAAA,kCAAC,eAAa,gBAAM,aAAa,kBAAkB,GAAE;AAAA,cACrD,oBAAC,qBACE,gBAAM,iBAAiB,sCAAsC,GAChE;AAAA,eACF;AAAA,YACA,qBAAC,UAAK,WAAU,aAAY,UAAU,kBACpC;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAS;AAAA,kBACT,OAAO;AAAA,kBACP,UAAU,CAAC,QAAQ;AACjB,iCAAa,IAAI,OAAO,KAAK;AAC7B,wBAAI,cAAe,kBAAiB,IAAI;AAAA,kBAC1C;AAAA,kBACA,aAAa,MAAM,qBAAqB,cAAc;AAAA,kBACtD;AAAA,kBACA,gBAAc,gBAAgB,SAAS;AAAA;AAAA,cACzC;AAAA,cACC,gBAAgB,oBAAC,OAAE,WAAU,4BAA4B,yBAAc,IAAO;AAAA,cAC/E,qBAAC,gBACC;AAAA,oCAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,kBAAkB,KAAK,GAAG,UAC9E,gBAAM,gBAAgB,QAAQ,GACjC;AAAA,gBACA,oBAAC,UAAO,MAAK,UAAS,UAAU,YAAY,CAAC,UAAU,KAAK,GACzD,gBAAM,cAAc,MAAM,GAC7B;AAAA,iBACF;AAAA,eACF;AAAA,aACF;AAAA,WACF,IACE;AAAA,QACJ;AAAA,UAAC;AAAA;AAAA,YACC,SAAO;AAAA,YACP,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YACV;AAAA,YACA,OAAO,MAAM,gBAAgB,sBAAsB;AAAA,YAEnD;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM;AAAA,gBACN,cAAY,MAAM,gBAAgB,sBAAsB;AAAA,gBAExD,8BAAC,YAAS,WAAU,WAAU;AAAA;AAAA,YAChC;AAAA;AAAA,QACF;AAAA,SACF;AAAA,OACF;AAAA,IACC,OAAO,UAAU,oBAAC,OAAE,WAAU,4BAA4B,iBAAO,SAAQ,IAAO;AAAA,IAChF,mBAAmB,oBAAC,OAAE,WAAU,4BAA4B,4BAAiB,IAAO;AAAA,IACrF;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,WAAW,aAAa;AAAA,QACnC,aAAa,MAAM,sBAAsB,cAAc;AAAA,QACvD,OAAO,QAAQ;AAAA,QACf,UAAU,CAAC,QAAQ,OAAO,eAAe,IAAI,OAAO,KAAK;AAAA,QACzD;AAAA,QACA,gBAAc,OAAO,cAAc,SAAS;AAAA;AAAA,IAC9C;AAAA,IAEC,WAAW,iBACV,qBAAC,SAAI,WAAU,+CACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,cAAc;AAAA,UACpC,aAAa,MAAM,iBAAiB,QAAQ;AAAA,UAC5C,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,gBAAgB,IAAI,OAAO,KAAK;AAAA,UAC1D;AAAA,UACA,gBAAc,OAAO,eAAe,SAAS;AAAA;AAAA,MAC/C;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,gBAAgB;AAAA,UACtC,aAAa,MAAM,yBAAyB,iBAAiB;AAAA,UAC7D,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,kBAAkB,IAAI,OAAO,KAAK;AAAA,UAC5D;AAAA,UACA,gBAAc,OAAO,iBAAiB,SAAS;AAAA;AAAA,MACjD;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,YAAY;AAAA,UAClC,aAAa,MAAM,qBAAqB,aAAa;AAAA,UACrD,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,cAAc,IAAI,OAAO,KAAK;AAAA,UACxD;AAAA,UACA,gBAAc,OAAO,aAAa,SAAS;AAAA;AAAA,MAC7C;AAAA,OACF,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,WAAW,cAAc;AAAA,QACpC,aAAa,MAAM,gBAAgB,gBAAgB;AAAA,QACnD,OAAO,QAAQ;AAAA,QACf,UAAU,CAAC,QAAQ,OAAO,gBAAgB,IAAI,OAAO,KAAK;AAAA,QAC1D;AAAA,QACA,gBAAc,OAAO,eAAe,SAAS;AAAA;AAAA,IAC/C;AAAA,IAGF;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,WAAW,cAAc;AAAA,QACpC,aAAa,MAAM,gBAAgB,gBAAgB;AAAA,QACnD,OAAO,QAAQ;AAAA,QACf,UAAU,CAAC,QAAQ,OAAO,gBAAgB,IAAI,OAAO,KAAK;AAAA,QAC1D;AAAA,QACA,gBAAc,OAAO,eAAe,SAAS;AAAA;AAAA,IAC/C;AAAA,IAEC,WAAW,iBACV,qBAAC,SAAI,WAAU,+CACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,gBAAgB;AAAA,UACtC,aAAa,MAAM,yBAAyB,iBAAiB;AAAA,UAC7D,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,kBAAkB,IAAI,OAAO,KAAK;AAAA,UAC5D;AAAA,UACA,gBAAc,OAAO,iBAAiB,SAAS;AAAA;AAAA,MACjD;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,YAAY;AAAA,UAClC,aAAa,MAAM,qBAAqB,aAAa;AAAA,UACrD,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,cAAc,IAAI,OAAO,KAAK;AAAA,UACxD;AAAA,UACA,gBAAc,OAAO,aAAa,SAAS;AAAA;AAAA,MAC7C;AAAA,OACF,IACE;AAAA,IAEJ,qBAAC,SAAI,WAAU,6BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,MAAM;AAAA,UAC5B,aAAa,MAAM,eAAe,MAAM;AAAA,UACxC,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,QAAQ,IAAI,OAAO,KAAK;AAAA,UAClD;AAAA,UACA,gBAAc,OAAO,OAAO,SAAS;AAAA;AAAA,MACvC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,QAAQ;AAAA,UAC9B,aAAa,MAAM,iBAAiB,QAAQ;AAAA,UAC5C,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,UAAU,IAAI,OAAO,KAAK;AAAA,UACpD;AAAA,UACA,gBAAc,OAAO,SAAS,SAAS;AAAA;AAAA,MACzC;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,6BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,WAAW,YAAY;AAAA,UAClC,aAAa,MAAM,qBAAqB,aAAa;AAAA,UACrD,OAAO,QAAQ;AAAA,UACf,UAAU,CAAC,QAAQ,OAAO,cAAc,IAAI,OAAO,KAAK;AAAA,UACxD;AAAA,UACA,gBAAc,OAAO,aAAa,SAAS;AAAA;AAAA,MAC7C;AAAA,MACA,qBAAC,UAAO,MAAM,mBAAmB,cAAc,sBAC7C;AAAA,4BAAC,iBAAc,SAAO,MACpB,8BAAC,UAAO,MAAK,UAAS,SAAQ,SAAQ,WAAW,GAAG,WAAW,SAAS,GAAG,gBAAgB,GAAG,UAC3F,2BAAiB,SAAS,MAAM,kBAAkB,SAAS,GAC9D,GACF;AAAA,QACA,qBAAC,iBAAc,WAAU,eACvB;AAAA,+BAAC,gBACC;AAAA,gCAAC,eAAa,gBAAM,iBAAiB,kBAAkB,GAAE;AAAA,YACzD,oBAAC,qBAAmB,gBAAM,oBAAoB,sBAAsB,GAAE;AAAA,aACxE;AAAA,UACA,qBAAC,SAAI,WAAU,aACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,aAAa,MAAM,kBAAkB,kBAAkB;AAAA,gBACvD,OAAO;AAAA,gBACP,UAAU,CAAC,QAAQ,gBAAgB,IAAI,OAAO,KAAK;AAAA;AAAA,YACrD;AAAA,YACA,oBAAC,SAAI,WAAU,6DACb,8BAAC,QAAG,WAAU,6BACX,iCAAuB,IAAI,CAAC,WAC3B,oBAAC,QACC;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS,MAAM;AACb,yBAAO,WAAW,OAAO,IAAI;AAC7B,uCAAqB,KAAK;AAAA,gBAC5B;AAAA,gBAEA;AAAA,sCAAC,UAAM,iBAAO,OAAM;AAAA,kBACpB,oBAAC,UAAK,WAAU,iCAAiC,iBAAO,MAAK;AAAA;AAAA;AAAA,YAC/D,KAZO,OAAO,IAahB,CACD,GACH,GACF;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,IAEC,iBACC,oBAAC,OAAE,WAAU,iCACV,gBAAM,cAAc,kCAAkC,GACzD,IACE;AAAA,IAEH,CAAC,oBACA,qBAAC,WAAM,WAAU,yDACf;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,QAAQ;AAAA,UACjB,UAAU,CAAC,QAAQ,OAAO,aAAa,IAAI,OAAO,OAAO;AAAA,UACzD;AAAA;AAAA,MACF;AAAA,MACC,MAAM,kBAAkB,iBAAiB;AAAA,OAC5C,IACE;AAAA,KACN;AAEJ;AAEA,IAAO,wBAAQ;",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Plus, Settings } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { buildCountryOptions } from '@open-mercato/shared/lib/location/countries'\nimport { buildHrefWithReturnTo } from '@open-mercato/shared/lib/navigation/returnTo'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { AddressFormatStrategy } from './addressFormat'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type AddressTypeOption = {\n value: string\n label: string\n}\n\nexport type AddressTypesAdapter<C = unknown> = {\n list: (context?: C) => Promise<AddressTypeOption[]>\n create?: (value: string, context?: C) => Promise<AddressTypeOption | null>\n manageHref?: string\n}\n\nexport type AddressEditorDraft = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n latitude?: string\n longitude?: string\n isPrimary: boolean\n}\n\nexport type AddressEditorField =\n | 'name'\n | 'purpose'\n | 'companyName'\n | 'addressLine1'\n | 'addressLine2'\n | 'buildingNumber'\n | 'flatNumber'\n | 'city'\n | 'region'\n | 'postalCode'\n | 'country'\n | 'latitude'\n | 'longitude'\n | 'isPrimary'\n\ntype AddressEditorProps<C = unknown> = {\n value: AddressEditorDraft\n onChange: (next: AddressEditorDraft) => void\n format: AddressFormatStrategy\n t: Translator\n labelPrefix?: string\n disabled?: boolean\n errors?: Partial<Record<AddressEditorField, string>>\n hidePrimaryToggle?: boolean\n showFormatHint?: boolean\n showCoordinateFields?: boolean\n addressTypesAdapter?: AddressTypesAdapter<C>\n addressTypesContext?: C\n}\n\nexport function AddressEditor<C = unknown>({\n value,\n onChange,\n format,\n t,\n labelPrefix = 'customers.people.detail.addresses',\n disabled = false,\n errors = {},\n hidePrimaryToggle = false,\n showFormatHint = true,\n showCoordinateFields = false,\n addressTypesAdapter,\n addressTypesContext,\n}: AddressEditorProps<C>) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\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 [addressTypes, setAddressTypes] = React.useState<AddressTypeOption[]>([])\n const [addressTypesLoading, setAddressTypesLoading] = React.useState(false)\n const [addressTypeError, setAddressTypeError] = React.useState<string | null>(null)\n\n const [typeDialogOpen, setTypeDialogOpen] = React.useState(false)\n const [typeValue, setTypeValue] = React.useState('')\n const [typeFormError, setTypeFormError] = React.useState<string | null>(null)\n const [countryDialogOpen, setCountryDialogOpen] = React.useState(false)\n const [countryQuery, setCountryQuery] = React.useState('')\n\n const countryOptions = React.useMemo(\n () =>\n buildCountryOptions({\n transformLabel: (code, fallback) => t(`customers.countries.${code.toLowerCase()}`, fallback ?? code),\n }),\n [t],\n )\n\n React.useEffect(() => {\n let cancelled = false\n const load = async () => {\n if (!addressTypesAdapter) {\n setAddressTypes([])\n setAddressTypeError(null)\n return\n }\n setAddressTypesLoading(true)\n try {\n const result = await addressTypesAdapter.list(addressTypesContext)\n if (!cancelled) {\n setAddressTypes(Array.isArray(result) ? result : [])\n setAddressTypeError(null)\n }\n } catch (err) {\n if (!cancelled) {\n setAddressTypes([])\n setAddressTypeError(label('types.loadError', 'Failed to load address types'))\n }\n } finally {\n if (!cancelled) setAddressTypesLoading(false)\n }\n }\n load().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [addressTypesAdapter, addressTypesContext, label])\n\n const current: AddressEditorDraft = {\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n ...(showCoordinateFields\n ? { latitude: value.latitude ?? '', longitude: value.longitude ?? '' }\n : {}),\n isPrimary: value.isPrimary ?? false,\n }\n\n const update = React.useCallback(\n (key: keyof AddressEditorDraft, nextValue: string | boolean) => {\n onChange({ ...current, [key]: nextValue })\n },\n [current, onChange],\n )\n\n const filteredCountryOptions = React.useMemo(() => {\n const query = countryQuery.trim().toLowerCase()\n if (!query.length) return countryOptions\n return countryOptions.filter(\n (option) => option.label.toLowerCase().includes(query) || option.code.toLowerCase().includes(query),\n )\n }, [countryOptions, countryQuery])\n\n const selectedCountry = React.useMemo(() => {\n const code = (current.country ?? '').toUpperCase()\n if (!code.length) return null\n return countryOptions.find((option) => option.code === code) ?? null\n }, [countryOptions, current.country])\n const returnTo = React.useMemo(() => {\n const query = searchParams?.toString() ?? ''\n if (!pathname) return null\n return query.length ? `${pathname}?${query}` : pathname\n }, [pathname, searchParams])\n const manageAddressTypesHref = React.useMemo(\n () => buildHrefWithReturnTo(addressTypesAdapter?.manageHref ?? '/backend/config/dictionaries', returnTo),\n [addressTypesAdapter?.manageHref, returnTo],\n )\n\n const handleTypeSubmit = React.useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n const trimmed = typeValue.trim()\n if (!trimmed.length) {\n setTypeFormError(label('types.emptyError', 'Please provide a value'))\n return\n }\n if (!addressTypesAdapter?.create) return\n setTypeFormError(null)\n const created = await addressTypesAdapter.create(trimmed, addressTypesContext)\n if (created) {\n setAddressTypes((prev) => {\n const map = new Map(prev.map((entry) => [entry.value, entry]))\n map.set(created.value, created)\n return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label))\n })\n }\n setTypeDialogOpen(false)\n setTypeValue('')\n },\n [addressTypesAdapter, addressTypesContext, label, typeValue],\n )\n\n const inputClass = (field: AddressEditorField) =>\n [\n 'w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n errors[field] ? 'border-red-500 aria-invalid:ring-destructive' : 'border-input bg-background',\n ].join(' ')\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('name')}\n placeholder={label('fields.label', 'Label')}\n value={current.name}\n onChange={(evt) => update('name', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.name ? 'true' : undefined}\n />\n <div className=\"flex gap-2\">\n <Select\n value={current.purpose || undefined}\n onValueChange={(next) => update('purpose', next ?? '')}\n disabled={disabled}\n >\n <SelectTrigger\n className={errors.purpose ? 'border-destructive' : undefined}\n aria-invalid={errors.purpose ? 'true' : undefined}\n >\n <SelectValue\n placeholder={\n addressTypesLoading\n ? label('types.loading', 'Loading\u2026')\n : label('types.placeholder', 'Address type')\n }\n />\n </SelectTrigger>\n <SelectContent>\n {addressTypes.map((entry) => (\n <SelectItem key={entry.value} value={entry.value}>\n {entry.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {addressTypesAdapter?.create ? (\n <Dialog open={typeDialogOpen} onOpenChange={setTypeDialogOpen}>\n <DialogTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"icon\" className=\"shrink-0\" disabled={disabled}>\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{label('types.add', 'Add address type')}</DialogTitle>\n <DialogDescription>\n {label('types.addHint', 'Create a new address type for reuse.')}\n </DialogDescription>\n </DialogHeader>\n <form className=\"space-y-3\" onSubmit={handleTypeSubmit}>\n <Input\n autoFocus\n value={typeValue}\n onChange={(evt) => {\n setTypeValue(evt.target.value)\n if (typeFormError) setTypeFormError(null)\n }}\n placeholder={label('types.placeholder', 'Address type')}\n disabled={disabled}\n aria-invalid={typeFormError ? 'true' : undefined}\n />\n {typeFormError ? <p className=\"text-sm text-destructive\">{typeFormError}</p> : null}\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setTypeDialogOpen(false)} disabled={disabled}>\n {label('types.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={disabled || !typeValue.trim()}>\n {label('types.save', 'Save')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n ) : null}\n <Button\n asChild\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0\"\n disabled={disabled}\n title={label('types.manage', 'Manage address types')}\n >\n <Link\n href={manageAddressTypesHref}\n aria-label={label('types.manage', 'Manage address types')}\n >\n <Settings className=\"h-4 w-4\" />\n </Link>\n </Button>\n </div>\n </div>\n {errors.purpose ? <p className=\"text-xs text-destructive\">{errors.purpose}</p> : null}\n {addressTypeError ? <p className=\"text-xs text-destructive\">{addressTypeError}</p> : null}\n <Input\n className={inputClass('companyName')}\n placeholder={label('fields.companyName', 'Company name')}\n value={current.companyName}\n onChange={(evt) => update('companyName', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.companyName ? 'true' : undefined}\n />\n\n {format === 'street_first' ? (\n <div className=\"grid gap-2 sm:grid-cols-[1.5fr,0.7fr,0.7fr]\">\n <Input\n className={inputClass('addressLine1')}\n placeholder={label('fields.street', 'Street')}\n value={current.addressLine1}\n onChange={(evt) => update('addressLine1', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine1 ? 'true' : undefined}\n />\n <Input\n className={inputClass('buildingNumber')}\n placeholder={label('fields.buildingNumber', 'Building number')}\n value={current.buildingNumber}\n onChange={(evt) => update('buildingNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.buildingNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('flatNumber')}\n placeholder={label('fields.flatNumber', 'Flat number')}\n value={current.flatNumber}\n onChange={(evt) => update('flatNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.flatNumber ? 'true' : undefined}\n />\n </div>\n ) : (\n <Input\n className={inputClass('addressLine1')}\n placeholder={label('fields.line1', 'Address line 1')}\n value={current.addressLine1}\n onChange={(evt) => update('addressLine1', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine1 ? 'true' : undefined}\n />\n )}\n\n <Input\n className={inputClass('addressLine2')}\n placeholder={label('fields.line2', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n\n {format !== 'street_first' ? (\n <div className=\"grid gap-2 sm:grid-cols-[1.5fr,0.7fr,0.7fr]\">\n <Input\n className={inputClass('buildingNumber')}\n placeholder={label('fields.buildingNumber', 'Building number')}\n value={current.buildingNumber}\n onChange={(evt) => update('buildingNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.buildingNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('flatNumber')}\n placeholder={label('fields.flatNumber', 'Flat number')}\n value={current.flatNumber}\n onChange={(evt) => update('flatNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.flatNumber ? 'true' : undefined}\n />\n </div>\n ) : null}\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('city')}\n placeholder={label('fields.city', 'City')}\n value={current.city}\n onChange={(evt) => update('city', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.city ? 'true' : undefined}\n />\n <Input\n className={inputClass('region')}\n placeholder={label('fields.region', 'Region')}\n value={current.region}\n onChange={(evt) => update('region', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.region ? 'true' : undefined}\n />\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('postalCode')}\n placeholder={label('fields.postalCode', 'Postal code')}\n value={current.postalCode}\n onChange={(evt) => update('postalCode', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.postalCode ? 'true' : undefined}\n />\n <Dialog open={countryDialogOpen} onOpenChange={setCountryDialogOpen}>\n <DialogTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" className={cn(inputClass('country'), 'cursor-pointer')} disabled={disabled}>\n {selectedCountry?.label ?? label('fields.country', 'Country')}\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{label('country.title', 'Choose a country')}</DialogTitle>\n <DialogDescription>{label('country.subtitle', 'Search for a country')}</DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <Input\n placeholder={label('country.search', 'Search countries')}\n value={countryQuery}\n onChange={(evt) => setCountryQuery(evt.target.value)}\n />\n <div className=\"max-h-64 overflow-auto rounded-md border border-border/70\">\n <ul className=\"divide-y divide-border/50\">\n {filteredCountryOptions.map((option) => (\n <li key={option.code}>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-between font-normal rounded-none\"\n onClick={() => {\n update('country', option.code)\n setCountryDialogOpen(false)\n }}\n >\n <span>{option.label}</span>\n <span className=\"text-xs text-muted-foreground\">{option.code}</span>\n </Button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </div>\n\n {showCoordinateFields ? (\n <>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('latitude')}\n placeholder={label('fields.latitude', 'Latitude')}\n aria-label={label('fields.latitude', 'Latitude')}\n inputMode=\"decimal\"\n value={current.latitude ?? ''}\n onChange={(evt) => update('latitude', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.latitude ? 'true' : undefined}\n />\n <Input\n className={inputClass('longitude')}\n placeholder={label('fields.longitude', 'Longitude')}\n aria-label={label('fields.longitude', 'Longitude')}\n inputMode=\"decimal\"\n value={current.longitude ?? ''}\n onChange={(evt) => update('longitude', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.longitude ? 'true' : undefined}\n />\n </div>\n {errors.latitude ? <p className=\"text-xs text-destructive\">{errors.latitude}</p> : null}\n {errors.longitude ? <p className=\"text-xs text-destructive\">{errors.longitude}</p> : null}\n </>\n ) : null}\n\n {showFormatHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {label('formatHint', 'Format based on address settings')}\n </p>\n ) : null}\n\n {!hidePrimaryToggle ? (\n <label className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <input\n type=\"checkbox\"\n checked={current.isPrimary}\n onChange={(evt) => update('isPrimary', evt.target.checked)}\n disabled={disabled}\n />\n {label('fields.primary', 'Primary address')}\n </label>\n ) : null}\n </div>\n )\n}\n\nexport default AddressEditor\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,6 +6,7 @@ import { Button } from "@open-mercato/ui/primitives/button";
|
|
|
6
6
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
7
7
|
import { TabEmptyState } from "@open-mercato/ui/backend/detail";
|
|
8
8
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
9
|
+
import { normalizeCoordinateInput, validateCoordinateInput } from "@open-mercato/shared/lib/location/coordinates";
|
|
9
10
|
import { AddressView, formatAddressString } from "./addressFormat.js";
|
|
10
11
|
import AddressEditor from "./AddressEditor.js";
|
|
11
12
|
const defaultDraft = {
|
|
@@ -20,6 +21,8 @@ const defaultDraft = {
|
|
|
20
21
|
region: "",
|
|
21
22
|
postalCode: "",
|
|
22
23
|
country: "",
|
|
24
|
+
latitude: "",
|
|
25
|
+
longitude: "",
|
|
23
26
|
isPrimary: false
|
|
24
27
|
};
|
|
25
28
|
const serverFieldMap = {
|
|
@@ -34,6 +37,8 @@ const serverFieldMap = {
|
|
|
34
37
|
region: "region",
|
|
35
38
|
postalCode: "postalCode",
|
|
36
39
|
country: "country",
|
|
40
|
+
latitude: "latitude",
|
|
41
|
+
longitude: "longitude",
|
|
37
42
|
isPrimary: "isPrimary"
|
|
38
43
|
};
|
|
39
44
|
function normalizeOptional(value) {
|
|
@@ -79,6 +84,7 @@ function AddressTiles({
|
|
|
79
84
|
emptyStateTitle,
|
|
80
85
|
emptyStateActionLabel,
|
|
81
86
|
labelPrefix = "customers.people.detail.addresses",
|
|
87
|
+
showCoordinateFields = false,
|
|
82
88
|
addressTypesAdapter,
|
|
83
89
|
addressTypesContext,
|
|
84
90
|
loadFormat,
|
|
@@ -112,6 +118,8 @@ function AddressTiles({
|
|
|
112
118
|
region: label("fields.region", "Region"),
|
|
113
119
|
postalCode: label("fields.postalCode", "Postal code"),
|
|
114
120
|
country: label("fields.country", "Country"),
|
|
121
|
+
latitude: label("fields.latitude", "Latitude"),
|
|
122
|
+
longitude: label("fields.longitude", "Longitude"),
|
|
115
123
|
isPrimary: label("fields.primary", "Primary address")
|
|
116
124
|
}),
|
|
117
125
|
[label]
|
|
@@ -175,6 +183,8 @@ function AddressTiles({
|
|
|
175
183
|
region: value.region ?? "",
|
|
176
184
|
postalCode: value.postalCode ?? "",
|
|
177
185
|
country: value.country ?? "",
|
|
186
|
+
latitude: value.latitude != null ? String(value.latitude) : "",
|
|
187
|
+
longitude: value.longitude != null ? String(value.longitude) : "",
|
|
178
188
|
isPrimary: value.isPrimary ?? false
|
|
179
189
|
});
|
|
180
190
|
setEditingId(value.id);
|
|
@@ -187,12 +197,20 @@ function AddressTiles({
|
|
|
187
197
|
if (!draft.addressLine1.trim()) {
|
|
188
198
|
errors.addressLine1 = label("validation.required", "{{field}} is required").replace("{{field}}", line1FieldLabel);
|
|
189
199
|
}
|
|
200
|
+
for (const key of ["latitude", "longitude"]) {
|
|
201
|
+
const result = validateCoordinateInput(key, draft[key]);
|
|
202
|
+
if (result.status === "invalid") {
|
|
203
|
+
errors[key] = label("validation.invalid", "Invalid value for {{field}}").replace("{{field}}", fieldLabels[key]);
|
|
204
|
+
} else if (result.status === "outOfRange") {
|
|
205
|
+
errors[key] = label("validation.coordinateRange", "{{field}} must be between {{min}} and {{max}}").replace("{{field}}", fieldLabels[key]).replace("{{min}}", `${result.min}`).replace("{{max}}", `${result.max}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
190
208
|
if (Object.keys(errors).length > 0) {
|
|
191
209
|
setFieldErrors(errors);
|
|
192
210
|
return false;
|
|
193
211
|
}
|
|
194
212
|
return true;
|
|
195
|
-
}, [draft.addressLine1, label, line1FieldLabel]);
|
|
213
|
+
}, [draft.addressLine1, draft.latitude, draft.longitude, fieldLabels, label, line1FieldLabel]);
|
|
196
214
|
const handleSave = React.useCallback(async () => {
|
|
197
215
|
if (!validate()) return;
|
|
198
216
|
setSaving(true);
|
|
@@ -210,6 +228,11 @@ function AddressTiles({
|
|
|
210
228
|
region: normalizeOptional(draft.region),
|
|
211
229
|
postalCode: normalizeOptional(draft.postalCode),
|
|
212
230
|
country: normalizeOptional(draft.country)?.toUpperCase(),
|
|
231
|
+
// On edit, an emptied coordinate must be sent as `null` to clear the stored value; omitting
|
|
232
|
+
// it (undefined) makes the partial-update handler keep the old coordinate. On create there is
|
|
233
|
+
// nothing to clear, so leave it undefined (omitted).
|
|
234
|
+
latitude: normalizeCoordinateInput(draft.latitude) ?? (editingId ? null : void 0),
|
|
235
|
+
longitude: normalizeCoordinateInput(draft.longitude) ?? (editingId ? null : void 0),
|
|
213
236
|
isPrimary: draft.isPrimary
|
|
214
237
|
};
|
|
215
238
|
if (editingId && onUpdate) {
|
|
@@ -302,7 +325,7 @@ function AddressTiles({
|
|
|
302
325
|
{
|
|
303
326
|
value: draft,
|
|
304
327
|
onChange: (next) => {
|
|
305
|
-
setDraft(next);
|
|
328
|
+
setDraft({ ...next, latitude: next.latitude ?? "", longitude: next.longitude ?? "" });
|
|
306
329
|
if (Object.keys(fieldErrors).length) {
|
|
307
330
|
const nextErrors = { ...fieldErrors };
|
|
308
331
|
Object.keys(nextErrors).forEach((key2) => {
|
|
@@ -319,6 +342,7 @@ function AddressTiles({
|
|
|
319
342
|
disabled: disableActions,
|
|
320
343
|
errors: fieldErrors,
|
|
321
344
|
showFormatHint: !formatLoading,
|
|
345
|
+
showCoordinateFields,
|
|
322
346
|
labelPrefix,
|
|
323
347
|
addressTypesAdapter,
|
|
324
348
|
addressTypesContext
|
|
@@ -352,6 +376,7 @@ function AddressTiles({
|
|
|
352
376
|
label,
|
|
353
377
|
labelPrefix,
|
|
354
378
|
saving,
|
|
379
|
+
showCoordinateFields,
|
|
355
380
|
t
|
|
356
381
|
]
|
|
357
382
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/detail/AddressTiles.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { AddressView, formatAddressString, type AddressFormatStrategy } from './addressFormat'\nimport AddressEditor, { type AddressTypesAdapter } from './AddressEditor'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\n\nexport type Translator = (\n key: string,\n fallback?: string,\n params?: Record<string, string | number>,\n) => string\n\nexport type AddressInput = {\n name?: string\n purpose?: string\n companyName?: string\n addressLine1: string\n addressLine2?: string\n buildingNumber?: string\n flatNumber?: string\n city?: string\n region?: string\n postalCode?: string\n country?: string\n isPrimary?: boolean\n}\n\nexport type AddressValue = AddressInput & {\n id: string\n purpose?: string | null\n companyName?: string | null\n}\n\ntype AddressTilesProps<C = unknown> = {\n addresses: AddressValue[]\n onCreate: (payload: AddressInput) => Promise<void> | void\n onUpdate?: (id: string, payload: AddressInput) => Promise<void> | void\n onDelete?: (id: string) => Promise<void> | void\n t: Translator\n emptyLabel: string\n isSubmitting?: boolean\n gridClassName?: string\n hideAddButton?: boolean\n onAddActionChange?: (action: { openCreateForm: () => void; addDisabled: boolean } | null) => void\n emptyStateTitle?: string\n emptyStateActionLabel?: string\n labelPrefix?: string\n addressTypesAdapter?: AddressTypesAdapter<C>\n addressTypesContext?: C\n loadFormat?: (context?: C) => Promise<AddressFormatStrategy>\n formatContext?: C\n}\n\ntype DraftAddressState = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n isPrimary: boolean\n}\n\ntype DraftFieldKey = keyof DraftAddressState\n\ntype AddressValidationDetail = {\n path?: Array<string | number>\n code?: string\n message?: string\n minimum?: number\n maximum?: number\n type?: string\n}\n\nconst defaultDraft: DraftAddressState = {\n name: '',\n purpose: '',\n companyName: '',\n addressLine1: '',\n addressLine2: '',\n buildingNumber: '',\n flatNumber: '',\n city: '',\n region: '',\n postalCode: '',\n country: '',\n isPrimary: false,\n}\n\nconst serverFieldMap: Record<string, DraftFieldKey> = {\n name: 'name',\n purpose: 'purpose',\n companyName: 'companyName',\n addressLine1: 'addressLine1',\n addressLine2: 'addressLine2',\n buildingNumber: 'buildingNumber',\n flatNumber: 'flatNumber',\n city: 'city',\n region: 'region',\n postalCode: 'postalCode',\n country: 'country',\n isPrimary: 'isPrimary',\n}\n\nfunction normalizeOptional(value: string): string | undefined {\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nfunction extractValidationDetails(error: unknown): AddressValidationDetail[] {\n if (!error || typeof error !== 'object') return []\n const candidate = (error as { details?: unknown }).details\n if (!Array.isArray(candidate)) return []\n return candidate\n .map((entry) => (entry && typeof entry === 'object' ? (entry as AddressValidationDetail) : null))\n .filter((entry): entry is AddressValidationDetail => entry !== null)\n}\n\nfunction resolveFieldMessage(detail: AddressValidationDetail, fieldLabel: string, t: Translator, prefix: string): string {\n const label = (suffix: string, fallback: string) => t(`${prefix}.${suffix}`, fallback)\n switch (detail.code) {\n case 'invalid_type':\n return label('validation.invalid', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n case 'too_small':\n if (detail.minimum === 1 && detail.type === 'string') {\n return label('validation.required', '{{field}} is required').replace('{{field}}', fieldLabel)\n }\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n case 'too_big':\n if (typeof detail.maximum === 'number') {\n return label('validation.tooLong', '{{field}} is too long').replace('{{field}}', fieldLabel)\n .replace('{{max}}', `${detail.maximum}`)\n }\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n default:\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n }\n}\n\nexport function AddressTiles<C = unknown>({\n addresses,\n onCreate,\n onUpdate,\n onDelete,\n t,\n emptyLabel,\n isSubmitting = false,\n gridClassName = 'grid grid-cols-1 gap-2 sm:gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',\n hideAddButton = false,\n onAddActionChange,\n emptyStateTitle,\n emptyStateActionLabel,\n labelPrefix = 'customers.people.detail.addresses',\n addressTypesAdapter,\n addressTypesContext,\n loadFormat,\n formatContext,\n}: AddressTilesProps<C>) {\n const scopeVersion = useOrganizationScopeVersion()\n const [isFormOpen, setIsFormOpen] = React.useState(false)\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draft, setDraft] = React.useState<DraftAddressState>(defaultDraft)\n const [saving, setSaving] = React.useState(false)\n const [deletingId, setDeletingId] = React.useState<string | null>(null)\n const [generalError, setGeneralError] = React.useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<DraftFieldKey, string>>>({})\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [formatLoading, setFormatLoading] = React.useState(false)\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 fieldLabels = React.useMemo(\n () => ({\n name: label('fields.label', 'Label'),\n purpose: label('fields.type', 'Address type'),\n companyName: label('fields.companyName', 'Company name'),\n addressLine1: label('fields.line1', 'Address line 1'),\n addressLine2: label('fields.line2', 'Address line 2'),\n street: label('fields.street', 'Street'),\n buildingNumber: label('fields.buildingNumber', 'Building number'),\n flatNumber: label('fields.flatNumber', 'Flat number'),\n city: label('fields.city', 'City'),\n region: label('fields.region', 'Region'),\n postalCode: label('fields.postalCode', 'Postal code'),\n country: label('fields.country', 'Country'),\n isPrimary: label('fields.primary', 'Primary address'),\n }),\n [label],\n )\n const line1FieldLabel = React.useMemo(\n () => (format === 'street_first' ? fieldLabels.street : fieldLabels.addressLine1),\n [fieldLabels.addressLine1, fieldLabels.street, format],\n )\n\n const resetForm = React.useCallback(() => {\n setDraft(defaultDraft)\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(null)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadFormatValue() {\n if (!loadFormat) {\n setFormat('line_first')\n setFormatLoading(false)\n return\n }\n setFormatLoading(true)\n try {\n const value = await loadFormat(formatContext)\n if (!cancelled && (value === 'street_first' || value === 'line_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setFormatLoading(false)\n }\n }\n void loadFormatValue()\n return () => {\n cancelled = true\n }\n }, [formatContext, label, loadFormat, scopeVersion])\n\n const openCreateForm = React.useCallback(() => {\n resetForm()\n setIsFormOpen(true)\n }, [resetForm])\n\n const handleCancel = React.useCallback(() => {\n resetForm()\n setIsFormOpen(false)\n }, [resetForm])\n\n const handleEdit = React.useCallback((value: AddressValue) => {\n setDraft({\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n isPrimary: value.isPrimary ?? false,\n })\n setEditingId(value.id)\n setIsFormOpen(true)\n setFieldErrors({})\n setGeneralError(null)\n }, [])\n\n const validate = React.useCallback((): boolean => {\n const errors: Partial<Record<DraftFieldKey, string>> = {}\n if (!draft.addressLine1.trim()) {\n errors.addressLine1 = label('validation.required', '{{field}} is required').replace('{{field}}', line1FieldLabel)\n }\n if (Object.keys(errors).length > 0) {\n setFieldErrors(errors)\n return false\n }\n return true\n }, [draft.addressLine1, label, line1FieldLabel])\n\n const handleSave = React.useCallback(async () => {\n if (!validate()) return\n setSaving(true)\n setGeneralError(null)\n try {\n const payload: AddressInput = {\n name: normalizeOptional(draft.name),\n purpose: normalizeOptional(draft.purpose),\n companyName: normalizeOptional(draft.companyName),\n addressLine1: draft.addressLine1.trim(),\n addressLine2: normalizeOptional(draft.addressLine2),\n buildingNumber: normalizeOptional(draft.buildingNumber),\n flatNumber: normalizeOptional(draft.flatNumber),\n city: normalizeOptional(draft.city),\n region: normalizeOptional(draft.region),\n postalCode: normalizeOptional(draft.postalCode),\n country: normalizeOptional(draft.country)?.toUpperCase(),\n isPrimary: draft.isPrimary,\n }\n if (editingId && onUpdate) {\n await onUpdate(editingId, payload)\n } else {\n await onCreate(payload)\n }\n resetForm()\n setIsFormOpen(false)\n } catch (err) {\n const details = extractValidationDetails(err)\n if (details.length) {\n const nextErrors: Partial<Record<DraftFieldKey, string>> = {}\n details.forEach((detail) => {\n const path = Array.isArray(detail.path) ? detail.path : []\n const key = typeof path[0] === 'string' ? path[0] : undefined\n if (!key) return\n const fieldKey = serverFieldMap[key]\n if (!fieldKey) return\n const fieldLabel = fieldKey === 'addressLine1' ? line1FieldLabel : (fieldLabels[fieldKey] ?? key)\n nextErrors[fieldKey] = resolveFieldMessage(detail, fieldLabel, t, labelPrefix)\n })\n setFieldErrors(nextErrors)\n setGeneralError(label('validation.summary', 'Please fix the highlighted fields.'))\n return\n }\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to save address')\n setGeneralError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [draft, editingId, fieldLabels, label, labelPrefix, onCreate, onUpdate, resetForm, t, validate])\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!onDelete) return\n setDeletingId(id)\n try {\n await onDelete(id)\n if (editingId === id) {\n resetForm()\n setIsFormOpen(false)\n }\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to delete address')\n flash(message, 'error')\n } finally {\n setDeletingId(null)\n }\n },\n [editingId, label, onDelete, resetForm]\n )\n\n const disableActions = saving || isSubmitting || deletingId !== null\n const isEditing = editingId !== null\n const addDisabled = disableActions || isEditing\n const hasAddresses = addresses.length > 0\n const emptyTitle = emptyStateTitle ?? emptyLabel\n const emptyActionLabel = emptyStateActionLabel ?? label('add', 'Add address')\n\n React.useEffect(() => {\n if (!onAddActionChange) return\n onAddActionChange({ openCreateForm, addDisabled })\n }, [onAddActionChange, openCreateForm, addDisabled])\n\n React.useEffect(\n () => () => {\n if (onAddActionChange) onAddActionChange(null)\n },\n [onAddActionChange]\n )\n\n const renderFormTile = React.useCallback(\n (key: string) => (\n <div\n key={key}\n className=\"rounded-lg border-2 border-dashed border-muted-foreground/50 bg-muted/30 p-4 text-sm\"\n onKeyDown={(event) => {\n if (!(event.metaKey || event.ctrlKey)) return\n if (event.key !== 'Enter') return\n event.preventDefault()\n if (disableActions) return\n void handleSave()\n }}\n >\n <div className=\"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <span>\n {editingId\n ? label('editTitle', 'Edit address')\n : label('addTitle', 'Add address')}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={handleCancel} disabled={disableActions}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n <div className=\"mt-3 space-y-3\">\n {formatLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {label('formatLoading', 'Loading address preferences\u2026')}\n </p>\n ) : null}\n <AddressEditor\n value={draft}\n onChange={(next) => {\n setDraft(next)\n if (Object.keys(fieldErrors).length) {\n const nextErrors = { ...fieldErrors }\n ;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {\n const candidate = (next as Record<string, unknown>)[key]\n if (candidate !== undefined && candidate !== null && `${candidate}`.length) {\n delete nextErrors[key]\n }\n })\n setFieldErrors(nextErrors)\n }\n }}\n format={format}\n t={t}\n disabled={disableActions}\n errors={fieldErrors}\n showFormatHint={!formatLoading}\n labelPrefix={labelPrefix}\n addressTypesAdapter={addressTypesAdapter}\n addressTypesContext={addressTypesContext}\n />\n {generalError ? <p className=\"text-xs text-red-600\">{generalError}</p> : null}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={disableActions}>\n {label('cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={disableActions}>\n {saving ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {editingId\n ? label('updating', 'Updating\u2026')\n : label('saving', 'Saving\u2026')}\n </>\n ) : editingId ? (\n label('update', 'Update address')\n ) : (\n label('save', 'Save address')\n )}\n </Button>\n </div>\n </div>\n </div>\n ),\n [\n addressTypesAdapter,\n addressTypesContext,\n disableActions,\n draft,\n editingId,\n fieldErrors,\n format,\n formatLoading,\n handleCancel,\n handleSave,\n generalError,\n label,\n labelPrefix,\n saving,\n t,\n ]\n )\n\n return (\n <div className=\"space-y-4\">\n {!hideAddButton && hasAddresses ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openCreateForm}\n disabled={addDisabled}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {label('add', 'Add address')}\n </Button>\n </div>\n ) : null}\n {hasAddresses ? (\n <div className={gridClassName}>\n {addresses.map((address) => {\n if (isFormOpen && editingId === address.id) {\n return renderFormTile(`form-${address.id}`)\n }\n const isDeleting = deletingId === address.id\n return (\n <div\n key={address.id}\n className=\"group rounded-lg border border-border/70 bg-card p-4 text-sm transition hover:border-border\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-sm font-semibold text-foreground\">\n {address.name ?? label('labelFallback', 'Address')}\n </p>\n {address.isPrimary ? (\n <span className=\"rounded-full border border-border bg-muted px-2 py-0.5 text-overline font-semibold uppercase\">\n {label('primaryBadge', 'Primary')}\n </span>\n ) : null}\n </div>\n {address.purpose ? (\n <p className=\"text-xs text-muted-foreground\">\n {address.purpose}\n </p>\n ) : null}\n <AddressView address={address} format={format} className=\"text-sm text-foreground\" />\n <p className=\"text-xs text-muted-foreground\">\n {formatAddressString(address, format)}\n </p>\n </div>\n <div className=\"flex items-center gap-1 opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100 focus-within:opacity-100\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleEdit(address)}\n disabled={disableActions}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleDelete(address.id)}\n disabled={disableActions}\n >\n {isDeleting ? (\n <span className=\"relative flex h-4 w-4 items-center justify-center text-destructive\">\n <span className=\"absolute h-4 w-4 animate-spin rounded-full border border-destructive border-t-transparent\" />\n </span>\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n </Button>\n </div>\n </div>\n </div>\n )\n })}\n {isFormOpen && !editingId ? renderFormTile('create') : null}\n </div>\n ) : isFormOpen ? (\n <div className={gridClassName}>\n {renderFormTile('create')}\n </div>\n ) : (\n <TabEmptyState\n title={emptyTitle}\n action={{\n label: emptyActionLabel,\n onClick: openCreateForm,\n disabled: addDisabled,\n }}\n />\n )}\n </div>\n )\n}\n\nexport default AddressTiles\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { normalizeCoordinateInput, validateCoordinateInput } from '@open-mercato/shared/lib/location/coordinates'\nimport { AddressView, formatAddressString, type AddressFormatStrategy } from './addressFormat'\nimport AddressEditor, { type AddressTypesAdapter } from './AddressEditor'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\n\nexport type Translator = (\n key: string,\n fallback?: string,\n params?: Record<string, string | number>,\n) => string\n\nexport type AddressInput = {\n name?: string\n purpose?: string\n companyName?: string\n addressLine1: string\n addressLine2?: string\n buildingNumber?: string\n flatNumber?: string\n city?: string\n region?: string\n postalCode?: string\n country?: string\n latitude?: number | null\n longitude?: number | null\n isPrimary?: boolean\n}\n\nexport type AddressValue = AddressInput & {\n id: string\n purpose?: string | null\n companyName?: string | null\n latitude?: number | null\n longitude?: number | null\n}\n\ntype AddressTilesProps<C = unknown> = {\n addresses: AddressValue[]\n onCreate: (payload: AddressInput) => Promise<void> | void\n onUpdate?: (id: string, payload: AddressInput) => Promise<void> | void\n onDelete?: (id: string) => Promise<void> | void\n t: Translator\n emptyLabel: string\n isSubmitting?: boolean\n gridClassName?: string\n hideAddButton?: boolean\n onAddActionChange?: (action: { openCreateForm: () => void; addDisabled: boolean } | null) => void\n emptyStateTitle?: string\n emptyStateActionLabel?: string\n labelPrefix?: string\n showCoordinateFields?: boolean\n addressTypesAdapter?: AddressTypesAdapter<C>\n addressTypesContext?: C\n loadFormat?: (context?: C) => Promise<AddressFormatStrategy>\n formatContext?: C\n}\n\ntype DraftAddressState = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n latitude: string\n longitude: string\n isPrimary: boolean\n}\n\ntype DraftFieldKey = keyof DraftAddressState\n\ntype AddressValidationDetail = {\n path?: Array<string | number>\n code?: string\n message?: string\n minimum?: number\n maximum?: number\n type?: string\n}\n\nconst defaultDraft: DraftAddressState = {\n name: '',\n purpose: '',\n companyName: '',\n addressLine1: '',\n addressLine2: '',\n buildingNumber: '',\n flatNumber: '',\n city: '',\n region: '',\n postalCode: '',\n country: '',\n latitude: '',\n longitude: '',\n isPrimary: false,\n}\n\nconst serverFieldMap: Record<string, DraftFieldKey> = {\n name: 'name',\n purpose: 'purpose',\n companyName: 'companyName',\n addressLine1: 'addressLine1',\n addressLine2: 'addressLine2',\n buildingNumber: 'buildingNumber',\n flatNumber: 'flatNumber',\n city: 'city',\n region: 'region',\n postalCode: 'postalCode',\n country: 'country',\n latitude: 'latitude',\n longitude: 'longitude',\n isPrimary: 'isPrimary',\n}\n\nfunction normalizeOptional(value: string): string | undefined {\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nfunction extractValidationDetails(error: unknown): AddressValidationDetail[] {\n if (!error || typeof error !== 'object') return []\n const candidate = (error as { details?: unknown }).details\n if (!Array.isArray(candidate)) return []\n return candidate\n .map((entry) => (entry && typeof entry === 'object' ? (entry as AddressValidationDetail) : null))\n .filter((entry): entry is AddressValidationDetail => entry !== null)\n}\n\nfunction resolveFieldMessage(detail: AddressValidationDetail, fieldLabel: string, t: Translator, prefix: string): string {\n const label = (suffix: string, fallback: string) => t(`${prefix}.${suffix}`, fallback)\n switch (detail.code) {\n case 'invalid_type':\n return label('validation.invalid', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n case 'too_small':\n if (detail.minimum === 1 && detail.type === 'string') {\n return label('validation.required', '{{field}} is required').replace('{{field}}', fieldLabel)\n }\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n case 'too_big':\n if (typeof detail.maximum === 'number') {\n return label('validation.tooLong', '{{field}} is too long').replace('{{field}}', fieldLabel)\n .replace('{{max}}', `${detail.maximum}`)\n }\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n default:\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n }\n}\n\nexport function AddressTiles<C = unknown>({\n addresses,\n onCreate,\n onUpdate,\n onDelete,\n t,\n emptyLabel,\n isSubmitting = false,\n gridClassName = 'grid grid-cols-1 gap-2 sm:gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',\n hideAddButton = false,\n onAddActionChange,\n emptyStateTitle,\n emptyStateActionLabel,\n labelPrefix = 'customers.people.detail.addresses',\n showCoordinateFields = false,\n addressTypesAdapter,\n addressTypesContext,\n loadFormat,\n formatContext,\n}: AddressTilesProps<C>) {\n const scopeVersion = useOrganizationScopeVersion()\n const [isFormOpen, setIsFormOpen] = React.useState(false)\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draft, setDraft] = React.useState<DraftAddressState>(defaultDraft)\n const [saving, setSaving] = React.useState(false)\n const [deletingId, setDeletingId] = React.useState<string | null>(null)\n const [generalError, setGeneralError] = React.useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<DraftFieldKey, string>>>({})\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [formatLoading, setFormatLoading] = React.useState(false)\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 fieldLabels = React.useMemo(\n () => ({\n name: label('fields.label', 'Label'),\n purpose: label('fields.type', 'Address type'),\n companyName: label('fields.companyName', 'Company name'),\n addressLine1: label('fields.line1', 'Address line 1'),\n addressLine2: label('fields.line2', 'Address line 2'),\n street: label('fields.street', 'Street'),\n buildingNumber: label('fields.buildingNumber', 'Building number'),\n flatNumber: label('fields.flatNumber', 'Flat number'),\n city: label('fields.city', 'City'),\n region: label('fields.region', 'Region'),\n postalCode: label('fields.postalCode', 'Postal code'),\n country: label('fields.country', 'Country'),\n latitude: label('fields.latitude', 'Latitude'),\n longitude: label('fields.longitude', 'Longitude'),\n isPrimary: label('fields.primary', 'Primary address'),\n }),\n [label],\n )\n const line1FieldLabel = React.useMemo(\n () => (format === 'street_first' ? fieldLabels.street : fieldLabels.addressLine1),\n [fieldLabels.addressLine1, fieldLabels.street, format],\n )\n\n const resetForm = React.useCallback(() => {\n setDraft(defaultDraft)\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(null)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadFormatValue() {\n if (!loadFormat) {\n setFormat('line_first')\n setFormatLoading(false)\n return\n }\n setFormatLoading(true)\n try {\n const value = await loadFormat(formatContext)\n if (!cancelled && (value === 'street_first' || value === 'line_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setFormatLoading(false)\n }\n }\n void loadFormatValue()\n return () => {\n cancelled = true\n }\n }, [formatContext, label, loadFormat, scopeVersion])\n\n const openCreateForm = React.useCallback(() => {\n resetForm()\n setIsFormOpen(true)\n }, [resetForm])\n\n const handleCancel = React.useCallback(() => {\n resetForm()\n setIsFormOpen(false)\n }, [resetForm])\n\n const handleEdit = React.useCallback((value: AddressValue) => {\n setDraft({\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n latitude: value.latitude != null ? String(value.latitude) : '',\n longitude: value.longitude != null ? String(value.longitude) : '',\n isPrimary: value.isPrimary ?? false,\n })\n setEditingId(value.id)\n setIsFormOpen(true)\n setFieldErrors({})\n setGeneralError(null)\n }, [])\n\n const validate = React.useCallback((): boolean => {\n const errors: Partial<Record<DraftFieldKey, string>> = {}\n if (!draft.addressLine1.trim()) {\n errors.addressLine1 = label('validation.required', '{{field}} is required').replace('{{field}}', line1FieldLabel)\n }\n for (const key of ['latitude', 'longitude'] as const) {\n const result = validateCoordinateInput(key, draft[key])\n if (result.status === 'invalid') {\n errors[key] = label('validation.invalid', 'Invalid value for {{field}}').replace('{{field}}', fieldLabels[key])\n } else if (result.status === 'outOfRange') {\n errors[key] = label('validation.coordinateRange', '{{field}} must be between {{min}} and {{max}}')\n .replace('{{field}}', fieldLabels[key])\n .replace('{{min}}', `${result.min}`)\n .replace('{{max}}', `${result.max}`)\n }\n }\n if (Object.keys(errors).length > 0) {\n setFieldErrors(errors)\n return false\n }\n return true\n }, [draft.addressLine1, draft.latitude, draft.longitude, fieldLabels, label, line1FieldLabel])\n\n const handleSave = React.useCallback(async () => {\n if (!validate()) return\n setSaving(true)\n setGeneralError(null)\n try {\n const payload: AddressInput = {\n name: normalizeOptional(draft.name),\n purpose: normalizeOptional(draft.purpose),\n companyName: normalizeOptional(draft.companyName),\n addressLine1: draft.addressLine1.trim(),\n addressLine2: normalizeOptional(draft.addressLine2),\n buildingNumber: normalizeOptional(draft.buildingNumber),\n flatNumber: normalizeOptional(draft.flatNumber),\n city: normalizeOptional(draft.city),\n region: normalizeOptional(draft.region),\n postalCode: normalizeOptional(draft.postalCode),\n country: normalizeOptional(draft.country)?.toUpperCase(),\n // On edit, an emptied coordinate must be sent as `null` to clear the stored value; omitting\n // it (undefined) makes the partial-update handler keep the old coordinate. On create there is\n // nothing to clear, so leave it undefined (omitted).\n latitude: normalizeCoordinateInput(draft.latitude) ?? (editingId ? null : undefined),\n longitude: normalizeCoordinateInput(draft.longitude) ?? (editingId ? null : undefined),\n isPrimary: draft.isPrimary,\n }\n if (editingId && onUpdate) {\n await onUpdate(editingId, payload)\n } else {\n await onCreate(payload)\n }\n resetForm()\n setIsFormOpen(false)\n } catch (err) {\n const details = extractValidationDetails(err)\n if (details.length) {\n const nextErrors: Partial<Record<DraftFieldKey, string>> = {}\n details.forEach((detail) => {\n const path = Array.isArray(detail.path) ? detail.path : []\n const key = typeof path[0] === 'string' ? path[0] : undefined\n if (!key) return\n const fieldKey = serverFieldMap[key]\n if (!fieldKey) return\n const fieldLabel = fieldKey === 'addressLine1' ? line1FieldLabel : (fieldLabels[fieldKey] ?? key)\n nextErrors[fieldKey] = resolveFieldMessage(detail, fieldLabel, t, labelPrefix)\n })\n setFieldErrors(nextErrors)\n setGeneralError(label('validation.summary', 'Please fix the highlighted fields.'))\n return\n }\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to save address')\n setGeneralError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [draft, editingId, fieldLabels, label, labelPrefix, onCreate, onUpdate, resetForm, t, validate])\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!onDelete) return\n setDeletingId(id)\n try {\n await onDelete(id)\n if (editingId === id) {\n resetForm()\n setIsFormOpen(false)\n }\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to delete address')\n flash(message, 'error')\n } finally {\n setDeletingId(null)\n }\n },\n [editingId, label, onDelete, resetForm]\n )\n\n const disableActions = saving || isSubmitting || deletingId !== null\n const isEditing = editingId !== null\n const addDisabled = disableActions || isEditing\n const hasAddresses = addresses.length > 0\n const emptyTitle = emptyStateTitle ?? emptyLabel\n const emptyActionLabel = emptyStateActionLabel ?? label('add', 'Add address')\n\n React.useEffect(() => {\n if (!onAddActionChange) return\n onAddActionChange({ openCreateForm, addDisabled })\n }, [onAddActionChange, openCreateForm, addDisabled])\n\n React.useEffect(\n () => () => {\n if (onAddActionChange) onAddActionChange(null)\n },\n [onAddActionChange]\n )\n\n const renderFormTile = React.useCallback(\n (key: string) => (\n <div\n key={key}\n className=\"rounded-lg border-2 border-dashed border-muted-foreground/50 bg-muted/30 p-4 text-sm\"\n onKeyDown={(event) => {\n if (!(event.metaKey || event.ctrlKey)) return\n if (event.key !== 'Enter') return\n event.preventDefault()\n if (disableActions) return\n void handleSave()\n }}\n >\n <div className=\"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <span>\n {editingId\n ? label('editTitle', 'Edit address')\n : label('addTitle', 'Add address')}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={handleCancel} disabled={disableActions}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n <div className=\"mt-3 space-y-3\">\n {formatLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {label('formatLoading', 'Loading address preferences\u2026')}\n </p>\n ) : null}\n <AddressEditor\n value={draft}\n onChange={(next) => {\n setDraft({ ...next, latitude: next.latitude ?? '', longitude: next.longitude ?? '' })\n if (Object.keys(fieldErrors).length) {\n const nextErrors = { ...fieldErrors }\n ;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {\n const candidate = (next as Record<string, unknown>)[key]\n if (candidate !== undefined && candidate !== null && `${candidate}`.length) {\n delete nextErrors[key]\n }\n })\n setFieldErrors(nextErrors)\n }\n }}\n format={format}\n t={t}\n disabled={disableActions}\n errors={fieldErrors}\n showFormatHint={!formatLoading}\n showCoordinateFields={showCoordinateFields}\n labelPrefix={labelPrefix}\n addressTypesAdapter={addressTypesAdapter}\n addressTypesContext={addressTypesContext}\n />\n {generalError ? <p className=\"text-xs text-red-600\">{generalError}</p> : null}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={disableActions}>\n {label('cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={disableActions}>\n {saving ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {editingId\n ? label('updating', 'Updating\u2026')\n : label('saving', 'Saving\u2026')}\n </>\n ) : editingId ? (\n label('update', 'Update address')\n ) : (\n label('save', 'Save address')\n )}\n </Button>\n </div>\n </div>\n </div>\n ),\n [\n addressTypesAdapter,\n addressTypesContext,\n disableActions,\n draft,\n editingId,\n fieldErrors,\n format,\n formatLoading,\n handleCancel,\n handleSave,\n generalError,\n label,\n labelPrefix,\n saving,\n showCoordinateFields,\n t,\n ]\n )\n\n return (\n <div className=\"space-y-4\">\n {!hideAddButton && hasAddresses ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openCreateForm}\n disabled={addDisabled}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {label('add', 'Add address')}\n </Button>\n </div>\n ) : null}\n {hasAddresses ? (\n <div className={gridClassName}>\n {addresses.map((address) => {\n if (isFormOpen && editingId === address.id) {\n return renderFormTile(`form-${address.id}`)\n }\n const isDeleting = deletingId === address.id\n return (\n <div\n key={address.id}\n className=\"group rounded-lg border border-border/70 bg-card p-4 text-sm transition hover:border-border\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-sm font-semibold text-foreground\">\n {address.name ?? label('labelFallback', 'Address')}\n </p>\n {address.isPrimary ? (\n <span className=\"rounded-full border border-border bg-muted px-2 py-0.5 text-overline font-semibold uppercase\">\n {label('primaryBadge', 'Primary')}\n </span>\n ) : null}\n </div>\n {address.purpose ? (\n <p className=\"text-xs text-muted-foreground\">\n {address.purpose}\n </p>\n ) : null}\n <AddressView address={address} format={format} className=\"text-sm text-foreground\" />\n <p className=\"text-xs text-muted-foreground\">\n {formatAddressString(address, format)}\n </p>\n </div>\n <div className=\"flex items-center gap-1 opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100 focus-within:opacity-100\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleEdit(address)}\n disabled={disableActions}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleDelete(address.id)}\n disabled={disableActions}\n >\n {isDeleting ? (\n <span className=\"relative flex h-4 w-4 items-center justify-center text-destructive\">\n <span className=\"absolute h-4 w-4 animate-spin rounded-full border border-destructive border-t-transparent\" />\n </span>\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n </Button>\n </div>\n </div>\n </div>\n )\n })}\n {isFormOpen && !editingId ? renderFormTile('create') : null}\n </div>\n ) : isFormOpen ? (\n <div className={gridClassName}>\n {renderFormTile('create')}\n </div>\n ) : (\n <TabEmptyState\n title={emptyTitle}\n action={{\n label: emptyActionLabel,\n onClick: openCreateForm,\n disabled: addDisabled,\n }}\n />\n )}\n </div>\n )\n}\n\nexport default AddressTiles\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["key"]
|
|
7
7
|
}
|
|
@@ -27,7 +27,8 @@ function AddressesSectionImpl({
|
|
|
27
27
|
addressTypesContext,
|
|
28
28
|
loadFormat,
|
|
29
29
|
formatContext,
|
|
30
|
-
labelPrefix = "customers.people.detail.addresses"
|
|
30
|
+
labelPrefix = "customers.people.detail.addresses",
|
|
31
|
+
showCoordinateFields = false
|
|
31
32
|
}) {
|
|
32
33
|
const tHook = useT();
|
|
33
34
|
const fallbackTranslator = React.useMemo(() => createTranslatorWithFallback(tHook), [tHook]);
|
|
@@ -106,6 +107,8 @@ function AddressesSectionImpl({
|
|
|
106
107
|
region: payload.region ?? null,
|
|
107
108
|
postalCode: payload.postalCode ?? null,
|
|
108
109
|
country: payload.country ? payload.country.toUpperCase() : null,
|
|
110
|
+
latitude: payload.latitude ?? null,
|
|
111
|
+
longitude: payload.longitude ?? null,
|
|
109
112
|
isPrimary: payload.isPrimary ?? false
|
|
110
113
|
};
|
|
111
114
|
setAddresses((prev) => {
|
|
@@ -155,6 +158,8 @@ function AddressesSectionImpl({
|
|
|
155
158
|
region: payload.region ?? null,
|
|
156
159
|
postalCode: payload.postalCode ?? null,
|
|
157
160
|
country: payload.country ? payload.country.toUpperCase() : null,
|
|
161
|
+
latitude: payload.latitude ?? null,
|
|
162
|
+
longitude: payload.longitude ?? null,
|
|
158
163
|
isPrimary: payload.isPrimary ?? false
|
|
159
164
|
};
|
|
160
165
|
});
|
|
@@ -212,6 +217,8 @@ function AddressesSectionImpl({
|
|
|
212
217
|
region: address.region ?? void 0,
|
|
213
218
|
postalCode: address.postalCode ?? void 0,
|
|
214
219
|
country: address.country ?? void 0,
|
|
220
|
+
latitude: address.latitude ?? void 0,
|
|
221
|
+
longitude: address.longitude ?? void 0,
|
|
215
222
|
isPrimary: address.isPrimary ?? false
|
|
216
223
|
}));
|
|
217
224
|
}, [addresses]);
|
|
@@ -255,6 +262,7 @@ function AddressesSectionImpl({
|
|
|
255
262
|
emptyStateTitle: emptyState.title,
|
|
256
263
|
emptyStateActionLabel: emptyState.actionLabel,
|
|
257
264
|
labelPrefix,
|
|
265
|
+
showCoordinateFields,
|
|
258
266
|
addressTypesAdapter,
|
|
259
267
|
addressTypesContext,
|
|
260
268
|
loadFormat,
|
|
@@ -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 { 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": ";
|
|
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 latitude?: number | null\n longitude?: number | 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 showCoordinateFields?: boolean\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 showCoordinateFields = false,\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 latitude: payload.latitude ?? null,\n longitude: payload.longitude ?? 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 latitude: payload.latitude ?? null,\n longitude: payload.longitude ?? 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 latitude: address.latitude ?? undefined,\n longitude: address.longitude ?? 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 showCoordinateFields={showCoordinateFields}\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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6387.1.f4896ad565",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -155,13 +155,13 @@
|
|
|
155
155
|
"remark-gfm": "^4.0.1"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
158
|
+
"@open-mercato/shared": "0.6.6-develop.6387.1.f4896ad565",
|
|
159
159
|
"react": ">=18.0.0",
|
|
160
160
|
"react-dom": ">=18.0.0",
|
|
161
161
|
"react-is": ">=18.0.0"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|
|
164
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
164
|
+
"@open-mercato/shared": "0.6.6-develop.6387.1.f4896ad565",
|
|
165
165
|
"@testing-library/dom": "^10.4.1",
|
|
166
166
|
"@testing-library/jest-dom": "^6.9.1",
|
|
167
167
|
"@testing-library/react": "^16.3.1",
|
|
@@ -52,6 +52,8 @@ export type AddressEditorDraft = {
|
|
|
52
52
|
region: string
|
|
53
53
|
postalCode: string
|
|
54
54
|
country: string
|
|
55
|
+
latitude?: string
|
|
56
|
+
longitude?: string
|
|
55
57
|
isPrimary: boolean
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -67,6 +69,8 @@ export type AddressEditorField =
|
|
|
67
69
|
| 'region'
|
|
68
70
|
| 'postalCode'
|
|
69
71
|
| 'country'
|
|
72
|
+
| 'latitude'
|
|
73
|
+
| 'longitude'
|
|
70
74
|
| 'isPrimary'
|
|
71
75
|
|
|
72
76
|
type AddressEditorProps<C = unknown> = {
|
|
@@ -79,6 +83,7 @@ type AddressEditorProps<C = unknown> = {
|
|
|
79
83
|
errors?: Partial<Record<AddressEditorField, string>>
|
|
80
84
|
hidePrimaryToggle?: boolean
|
|
81
85
|
showFormatHint?: boolean
|
|
86
|
+
showCoordinateFields?: boolean
|
|
82
87
|
addressTypesAdapter?: AddressTypesAdapter<C>
|
|
83
88
|
addressTypesContext?: C
|
|
84
89
|
}
|
|
@@ -93,6 +98,7 @@ export function AddressEditor<C = unknown>({
|
|
|
93
98
|
errors = {},
|
|
94
99
|
hidePrimaryToggle = false,
|
|
95
100
|
showFormatHint = true,
|
|
101
|
+
showCoordinateFields = false,
|
|
96
102
|
addressTypesAdapter,
|
|
97
103
|
addressTypesContext,
|
|
98
104
|
}: AddressEditorProps<C>) {
|
|
@@ -164,6 +170,9 @@ export function AddressEditor<C = unknown>({
|
|
|
164
170
|
region: value.region ?? '',
|
|
165
171
|
postalCode: value.postalCode ?? '',
|
|
166
172
|
country: value.country ?? '',
|
|
173
|
+
...(showCoordinateFields
|
|
174
|
+
? { latitude: value.latitude ?? '', longitude: value.longitude ?? '' }
|
|
175
|
+
: {}),
|
|
167
176
|
isPrimary: value.isPrimary ?? false,
|
|
168
177
|
}
|
|
169
178
|
|
|
@@ -469,6 +478,35 @@ export function AddressEditor<C = unknown>({
|
|
|
469
478
|
</Dialog>
|
|
470
479
|
</div>
|
|
471
480
|
|
|
481
|
+
{showCoordinateFields ? (
|
|
482
|
+
<>
|
|
483
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
484
|
+
<Input
|
|
485
|
+
className={inputClass('latitude')}
|
|
486
|
+
placeholder={label('fields.latitude', 'Latitude')}
|
|
487
|
+
aria-label={label('fields.latitude', 'Latitude')}
|
|
488
|
+
inputMode="decimal"
|
|
489
|
+
value={current.latitude ?? ''}
|
|
490
|
+
onChange={(evt) => update('latitude', evt.target.value)}
|
|
491
|
+
disabled={disabled}
|
|
492
|
+
aria-invalid={errors.latitude ? 'true' : undefined}
|
|
493
|
+
/>
|
|
494
|
+
<Input
|
|
495
|
+
className={inputClass('longitude')}
|
|
496
|
+
placeholder={label('fields.longitude', 'Longitude')}
|
|
497
|
+
aria-label={label('fields.longitude', 'Longitude')}
|
|
498
|
+
inputMode="decimal"
|
|
499
|
+
value={current.longitude ?? ''}
|
|
500
|
+
onChange={(evt) => update('longitude', evt.target.value)}
|
|
501
|
+
disabled={disabled}
|
|
502
|
+
aria-invalid={errors.longitude ? 'true' : undefined}
|
|
503
|
+
/>
|
|
504
|
+
</div>
|
|
505
|
+
{errors.latitude ? <p className="text-xs text-destructive">{errors.latitude}</p> : null}
|
|
506
|
+
{errors.longitude ? <p className="text-xs text-destructive">{errors.longitude}</p> : null}
|
|
507
|
+
</>
|
|
508
|
+
) : null}
|
|
509
|
+
|
|
472
510
|
{showFormatHint ? (
|
|
473
511
|
<p className="text-xs text-muted-foreground">
|
|
474
512
|
{label('formatHint', 'Format based on address settings')}
|
|
@@ -6,6 +6,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
|
|
|
6
6
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
7
7
|
import { TabEmptyState } from '@open-mercato/ui/backend/detail'
|
|
8
8
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
9
|
+
import { normalizeCoordinateInput, validateCoordinateInput } from '@open-mercato/shared/lib/location/coordinates'
|
|
9
10
|
import { AddressView, formatAddressString, type AddressFormatStrategy } from './addressFormat'
|
|
10
11
|
import AddressEditor, { type AddressTypesAdapter } from './AddressEditor'
|
|
11
12
|
import {
|
|
@@ -36,6 +37,8 @@ export type AddressInput = {
|
|
|
36
37
|
region?: string
|
|
37
38
|
postalCode?: string
|
|
38
39
|
country?: string
|
|
40
|
+
latitude?: number | null
|
|
41
|
+
longitude?: number | null
|
|
39
42
|
isPrimary?: boolean
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -43,6 +46,8 @@ export type AddressValue = AddressInput & {
|
|
|
43
46
|
id: string
|
|
44
47
|
purpose?: string | null
|
|
45
48
|
companyName?: string | null
|
|
49
|
+
latitude?: number | null
|
|
50
|
+
longitude?: number | null
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
type AddressTilesProps<C = unknown> = {
|
|
@@ -59,6 +64,7 @@ type AddressTilesProps<C = unknown> = {
|
|
|
59
64
|
emptyStateTitle?: string
|
|
60
65
|
emptyStateActionLabel?: string
|
|
61
66
|
labelPrefix?: string
|
|
67
|
+
showCoordinateFields?: boolean
|
|
62
68
|
addressTypesAdapter?: AddressTypesAdapter<C>
|
|
63
69
|
addressTypesContext?: C
|
|
64
70
|
loadFormat?: (context?: C) => Promise<AddressFormatStrategy>
|
|
@@ -77,6 +83,8 @@ type DraftAddressState = {
|
|
|
77
83
|
region: string
|
|
78
84
|
postalCode: string
|
|
79
85
|
country: string
|
|
86
|
+
latitude: string
|
|
87
|
+
longitude: string
|
|
80
88
|
isPrimary: boolean
|
|
81
89
|
}
|
|
82
90
|
|
|
@@ -103,6 +111,8 @@ const defaultDraft: DraftAddressState = {
|
|
|
103
111
|
region: '',
|
|
104
112
|
postalCode: '',
|
|
105
113
|
country: '',
|
|
114
|
+
latitude: '',
|
|
115
|
+
longitude: '',
|
|
106
116
|
isPrimary: false,
|
|
107
117
|
}
|
|
108
118
|
|
|
@@ -118,6 +128,8 @@ const serverFieldMap: Record<string, DraftFieldKey> = {
|
|
|
118
128
|
region: 'region',
|
|
119
129
|
postalCode: 'postalCode',
|
|
120
130
|
country: 'country',
|
|
131
|
+
latitude: 'latitude',
|
|
132
|
+
longitude: 'longitude',
|
|
121
133
|
isPrimary: 'isPrimary',
|
|
122
134
|
}
|
|
123
135
|
|
|
@@ -170,6 +182,7 @@ export function AddressTiles<C = unknown>({
|
|
|
170
182
|
emptyStateTitle,
|
|
171
183
|
emptyStateActionLabel,
|
|
172
184
|
labelPrefix = 'customers.people.detail.addresses',
|
|
185
|
+
showCoordinateFields = false,
|
|
173
186
|
addressTypesAdapter,
|
|
174
187
|
addressTypesContext,
|
|
175
188
|
loadFormat,
|
|
@@ -206,6 +219,8 @@ export function AddressTiles<C = unknown>({
|
|
|
206
219
|
region: label('fields.region', 'Region'),
|
|
207
220
|
postalCode: label('fields.postalCode', 'Postal code'),
|
|
208
221
|
country: label('fields.country', 'Country'),
|
|
222
|
+
latitude: label('fields.latitude', 'Latitude'),
|
|
223
|
+
longitude: label('fields.longitude', 'Longitude'),
|
|
209
224
|
isPrimary: label('fields.primary', 'Primary address'),
|
|
210
225
|
}),
|
|
211
226
|
[label],
|
|
@@ -277,6 +292,8 @@ export function AddressTiles<C = unknown>({
|
|
|
277
292
|
region: value.region ?? '',
|
|
278
293
|
postalCode: value.postalCode ?? '',
|
|
279
294
|
country: value.country ?? '',
|
|
295
|
+
latitude: value.latitude != null ? String(value.latitude) : '',
|
|
296
|
+
longitude: value.longitude != null ? String(value.longitude) : '',
|
|
280
297
|
isPrimary: value.isPrimary ?? false,
|
|
281
298
|
})
|
|
282
299
|
setEditingId(value.id)
|
|
@@ -290,12 +307,23 @@ export function AddressTiles<C = unknown>({
|
|
|
290
307
|
if (!draft.addressLine1.trim()) {
|
|
291
308
|
errors.addressLine1 = label('validation.required', '{{field}} is required').replace('{{field}}', line1FieldLabel)
|
|
292
309
|
}
|
|
310
|
+
for (const key of ['latitude', 'longitude'] as const) {
|
|
311
|
+
const result = validateCoordinateInput(key, draft[key])
|
|
312
|
+
if (result.status === 'invalid') {
|
|
313
|
+
errors[key] = label('validation.invalid', 'Invalid value for {{field}}').replace('{{field}}', fieldLabels[key])
|
|
314
|
+
} else if (result.status === 'outOfRange') {
|
|
315
|
+
errors[key] = label('validation.coordinateRange', '{{field}} must be between {{min}} and {{max}}')
|
|
316
|
+
.replace('{{field}}', fieldLabels[key])
|
|
317
|
+
.replace('{{min}}', `${result.min}`)
|
|
318
|
+
.replace('{{max}}', `${result.max}`)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
293
321
|
if (Object.keys(errors).length > 0) {
|
|
294
322
|
setFieldErrors(errors)
|
|
295
323
|
return false
|
|
296
324
|
}
|
|
297
325
|
return true
|
|
298
|
-
}, [draft.addressLine1, label, line1FieldLabel])
|
|
326
|
+
}, [draft.addressLine1, draft.latitude, draft.longitude, fieldLabels, label, line1FieldLabel])
|
|
299
327
|
|
|
300
328
|
const handleSave = React.useCallback(async () => {
|
|
301
329
|
if (!validate()) return
|
|
@@ -314,6 +342,11 @@ export function AddressTiles<C = unknown>({
|
|
|
314
342
|
region: normalizeOptional(draft.region),
|
|
315
343
|
postalCode: normalizeOptional(draft.postalCode),
|
|
316
344
|
country: normalizeOptional(draft.country)?.toUpperCase(),
|
|
345
|
+
// On edit, an emptied coordinate must be sent as `null` to clear the stored value; omitting
|
|
346
|
+
// it (undefined) makes the partial-update handler keep the old coordinate. On create there is
|
|
347
|
+
// nothing to clear, so leave it undefined (omitted).
|
|
348
|
+
latitude: normalizeCoordinateInput(draft.latitude) ?? (editingId ? null : undefined),
|
|
349
|
+
longitude: normalizeCoordinateInput(draft.longitude) ?? (editingId ? null : undefined),
|
|
317
350
|
isPrimary: draft.isPrimary,
|
|
318
351
|
}
|
|
319
352
|
if (editingId && onUpdate) {
|
|
@@ -425,7 +458,7 @@ export function AddressTiles<C = unknown>({
|
|
|
425
458
|
<AddressEditor
|
|
426
459
|
value={draft}
|
|
427
460
|
onChange={(next) => {
|
|
428
|
-
setDraft(next)
|
|
461
|
+
setDraft({ ...next, latitude: next.latitude ?? '', longitude: next.longitude ?? '' })
|
|
429
462
|
if (Object.keys(fieldErrors).length) {
|
|
430
463
|
const nextErrors = { ...fieldErrors }
|
|
431
464
|
;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {
|
|
@@ -442,6 +475,7 @@ export function AddressTiles<C = unknown>({
|
|
|
442
475
|
disabled={disableActions}
|
|
443
476
|
errors={fieldErrors}
|
|
444
477
|
showFormatHint={!formatLoading}
|
|
478
|
+
showCoordinateFields={showCoordinateFields}
|
|
445
479
|
labelPrefix={labelPrefix}
|
|
446
480
|
addressTypesAdapter={addressTypesAdapter}
|
|
447
481
|
addressTypesContext={addressTypesContext}
|
|
@@ -484,6 +518,7 @@ export function AddressTiles<C = unknown>({
|
|
|
484
518
|
label,
|
|
485
519
|
labelPrefix,
|
|
486
520
|
saving,
|
|
521
|
+
showCoordinateFields,
|
|
487
522
|
t,
|
|
488
523
|
]
|
|
489
524
|
)
|
|
@@ -40,6 +40,8 @@ export type AddressSummary = {
|
|
|
40
40
|
region?: string | null
|
|
41
41
|
postalCode?: string | null
|
|
42
42
|
country?: string | null
|
|
43
|
+
latitude?: number | null
|
|
44
|
+
longitude?: number | null
|
|
43
45
|
isPrimary?: boolean
|
|
44
46
|
updatedAt?: string | null
|
|
45
47
|
}
|
|
@@ -66,6 +68,7 @@ export type AddressesSectionProps<C = unknown> = {
|
|
|
66
68
|
loadFormat?: (context?: C) => Promise<AddressFormatStrategy>
|
|
67
69
|
formatContext?: C
|
|
68
70
|
labelPrefix?: string
|
|
71
|
+
showCoordinateFields?: boolean
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
function generateTempId() {
|
|
@@ -88,6 +91,7 @@ function AddressesSectionImpl<C = unknown>({
|
|
|
88
91
|
loadFormat,
|
|
89
92
|
formatContext,
|
|
90
93
|
labelPrefix = 'customers.people.detail.addresses',
|
|
94
|
+
showCoordinateFields = false,
|
|
91
95
|
}: AddressesSectionProps<C>) {
|
|
92
96
|
const tHook = useT()
|
|
93
97
|
const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])
|
|
@@ -176,6 +180,8 @@ function AddressesSectionImpl<C = unknown>({
|
|
|
176
180
|
region: payload.region ?? null,
|
|
177
181
|
postalCode: payload.postalCode ?? null,
|
|
178
182
|
country: payload.country ? payload.country.toUpperCase() : null,
|
|
183
|
+
latitude: payload.latitude ?? null,
|
|
184
|
+
longitude: payload.longitude ?? null,
|
|
179
185
|
isPrimary: payload.isPrimary ?? false,
|
|
180
186
|
}
|
|
181
187
|
setAddresses((prev) => {
|
|
@@ -227,6 +233,8 @@ function AddressesSectionImpl<C = unknown>({
|
|
|
227
233
|
region: payload.region ?? null,
|
|
228
234
|
postalCode: payload.postalCode ?? null,
|
|
229
235
|
country: payload.country ? payload.country.toUpperCase() : null,
|
|
236
|
+
latitude: payload.latitude ?? null,
|
|
237
|
+
longitude: payload.longitude ?? null,
|
|
230
238
|
isPrimary: payload.isPrimary ?? false,
|
|
231
239
|
}
|
|
232
240
|
})
|
|
@@ -288,6 +296,8 @@ function AddressesSectionImpl<C = unknown>({
|
|
|
288
296
|
region: address.region ?? undefined,
|
|
289
297
|
postalCode: address.postalCode ?? undefined,
|
|
290
298
|
country: address.country ?? undefined,
|
|
299
|
+
latitude: address.latitude ?? undefined,
|
|
300
|
+
longitude: address.longitude ?? undefined,
|
|
291
301
|
isPrimary: address.isPrimary ?? false,
|
|
292
302
|
}))
|
|
293
303
|
}, [addresses])
|
|
@@ -340,6 +350,7 @@ function AddressesSectionImpl<C = unknown>({
|
|
|
340
350
|
emptyStateTitle={emptyState.title}
|
|
341
351
|
emptyStateActionLabel={emptyState.actionLabel}
|
|
342
352
|
labelPrefix={labelPrefix}
|
|
353
|
+
showCoordinateFields={showCoordinateFields}
|
|
343
354
|
addressTypesAdapter={addressTypesAdapter}
|
|
344
355
|
addressTypesContext={addressTypesContext}
|
|
345
356
|
loadFormat={loadFormat}
|