@open-mercato/ui 0.6.7 → 0.6.8-develop.6874.1.982d6097d8

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.
Files changed (214) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +4 -2
  3. package/agentic/standalone-guide.md +1 -1
  4. package/build.mjs +2 -54
  5. package/dist/ai/AiChat.js +26 -7
  6. package/dist/ai/AiChat.js.map +2 -2
  7. package/dist/ai/LoopDisabledBanner.js +1 -1
  8. package/dist/ai/LoopDisabledBanner.js.map +2 -2
  9. package/dist/ai/parts/ConfirmationCard.js +4 -4
  10. package/dist/ai/parts/ConfirmationCard.js.map +2 -2
  11. package/dist/ai/parts/FieldDiffCard.js +2 -2
  12. package/dist/ai/parts/FieldDiffCard.js.map +2 -2
  13. package/dist/ai/parts/MutationResultCard.js +3 -3
  14. package/dist/ai/parts/MutationResultCard.js.map +2 -2
  15. package/dist/ai/ui-parts/pending-phase3-placeholder.js +1 -1
  16. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +2 -2
  17. package/dist/backend/AppShell.js +43 -4
  18. package/dist/backend/AppShell.js.map +2 -2
  19. package/dist/backend/AuthSessionGuard.js +14 -0
  20. package/dist/backend/AuthSessionGuard.js.map +2 -2
  21. package/dist/backend/BackendChromeProvider.js +6 -1
  22. package/dist/backend/BackendChromeProvider.js.map +2 -2
  23. package/dist/backend/CrudForm.js +6 -4
  24. package/dist/backend/CrudForm.js.map +2 -2
  25. package/dist/backend/DataTable.js +20 -18
  26. package/dist/backend/DataTable.js.map +2 -2
  27. package/dist/backend/JsonBuilder.js +3 -3
  28. package/dist/backend/JsonBuilder.js.map +2 -2
  29. package/dist/backend/NextStepCallout.js +4 -4
  30. package/dist/backend/NextStepCallout.js.map +1 -1
  31. package/dist/backend/PerspectiveSidebar.js +1 -1
  32. package/dist/backend/PerspectiveSidebar.js.map +2 -2
  33. package/dist/backend/ProfileDropdown.js +2 -1
  34. package/dist/backend/ProfileDropdown.js.map +2 -2
  35. package/dist/backend/RowActions.js +2 -2
  36. package/dist/backend/RowActions.js.map +2 -2
  37. package/dist/backend/confirm-dialog/ConfirmDialog.js +13 -1
  38. package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
  39. package/dist/backend/crud/useGroupOrder.js +14 -21
  40. package/dist/backend/crud/useGroupOrder.js.map +2 -2
  41. package/dist/backend/custom-fields/FieldDefinitionsManager.js +1 -1
  42. package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +2 -2
  43. package/dist/backend/dashboard/DashboardScreen.js +3 -3
  44. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  45. package/dist/backend/detail/AddressTiles.js +4 -5
  46. package/dist/backend/detail/AddressTiles.js.map +2 -2
  47. package/dist/backend/detail/AttachmentDeleteDialog.js +1 -1
  48. package/dist/backend/detail/AttachmentDeleteDialog.js.map +2 -2
  49. package/dist/backend/detail/AttachmentMetadataDialog.js.map +1 -1
  50. package/dist/backend/detail/AttachmentsSection.js +1 -1
  51. package/dist/backend/detail/AttachmentsSection.js.map +2 -2
  52. package/dist/backend/detail/NotesSection.js +1 -1
  53. package/dist/backend/detail/NotesSection.js.map +2 -2
  54. package/dist/backend/detail/TagsSection.js +1 -1
  55. package/dist/backend/detail/TagsSection.js.map +2 -2
  56. package/dist/backend/filters/AdvancedFilterPanel.js +1 -1
  57. package/dist/backend/filters/AdvancedFilterPanel.js.map +2 -2
  58. package/dist/backend/forms/FormHeader.js +1 -2
  59. package/dist/backend/forms/FormHeader.js.map +2 -2
  60. package/dist/backend/icons/lucideRegistry.generated.js +1 -25
  61. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  62. package/dist/backend/icons/lucideRegistry.js +3 -3
  63. package/dist/backend/icons/lucideRegistry.js.map +2 -2
  64. package/dist/backend/icons/lucideRegistryRuntime.js +28 -0
  65. package/dist/backend/icons/lucideRegistryRuntime.js.map +7 -0
  66. package/dist/backend/injection/spotIds.js +23 -18
  67. package/dist/backend/injection/spotIds.js.map +2 -2
  68. package/dist/backend/inputs/ComboboxInput.js.map +1 -1
  69. package/dist/backend/messages/EmailThreadsPanel.js +1 -1
  70. package/dist/backend/messages/EmailThreadsPanel.js.map +2 -2
  71. package/dist/backend/perspectiveState.js +34 -0
  72. package/dist/backend/perspectiveState.js.map +7 -0
  73. package/dist/backend/schedule/ScheduleAgenda.js +3 -3
  74. package/dist/backend/schedule/ScheduleAgenda.js.map +1 -1
  75. package/dist/backend/schedule/ScheduleGrid.js +3 -3
  76. package/dist/backend/schedule/ScheduleGrid.js.map +1 -1
  77. package/dist/backend/schedule/ScheduleToolbar.js +87 -70
  78. package/dist/backend/schedule/ScheduleToolbar.js.map +2 -2
  79. package/dist/backend/sidebar/SidebarCustomizationEditor.js +30 -2
  80. package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +2 -2
  81. package/dist/backend/sidebar/customization-helpers.js +30 -0
  82. package/dist/backend/sidebar/customization-helpers.js.map +2 -2
  83. package/dist/backend/utils/apiCall.js +21 -1
  84. package/dist/backend/utils/apiCall.js.map +2 -2
  85. package/dist/backend/utils/customFieldDefs.js.map +2 -2
  86. package/dist/backend/utils/customFieldForms.js +10 -9
  87. package/dist/backend/utils/customFieldForms.js.map +2 -2
  88. package/dist/backend/utils/fetchAllPages.js +57 -0
  89. package/dist/backend/utils/fetchAllPages.js.map +7 -0
  90. package/dist/backend/utils/nav.js +5 -2
  91. package/dist/backend/utils/nav.js.map +2 -2
  92. package/dist/backend/version-history/VersionHistoryPanel.js +1 -1
  93. package/dist/backend/version-history/VersionHistoryPanel.js.map +2 -2
  94. package/dist/frontend/LanguageSwitcher.js +2 -1
  95. package/dist/frontend/LanguageSwitcher.js.map +2 -2
  96. package/dist/portal/PortalContext.js +1 -1
  97. package/dist/portal/PortalContext.js.map +2 -2
  98. package/dist/portal/hooks/portalBridgeStatus.js +25 -0
  99. package/dist/portal/hooks/portalBridgeStatus.js.map +7 -0
  100. package/dist/portal/hooks/usePortalEventBridge.js +6 -0
  101. package/dist/portal/hooks/usePortalEventBridge.js.map +2 -2
  102. package/dist/portal/hooks/usePortalNotifications.js +42 -4
  103. package/dist/portal/hooks/usePortalNotifications.js.map +2 -2
  104. package/dist/portal/utils/nav.js +2 -3
  105. package/dist/portal/utils/nav.js.map +2 -2
  106. package/dist/primitives/ErrorNotice.js +1 -1
  107. package/dist/primitives/ErrorNotice.js.map +2 -2
  108. package/dist/primitives/Notice.js +1 -1
  109. package/dist/primitives/Notice.js.map +1 -1
  110. package/dist/primitives/button.js +9 -4
  111. package/dist/primitives/button.js.map +2 -2
  112. package/dist/theme/ThemeProvider.js +1 -1
  113. package/dist/theme/ThemeProvider.js.map +2 -2
  114. package/dist/theme/index.js +3 -0
  115. package/dist/theme/index.js.map +2 -2
  116. package/dist/theme/theme-init-script.js +17 -0
  117. package/dist/theme/theme-init-script.js.map +7 -0
  118. package/figma/alert.figma.tsx +35 -0
  119. package/figma/badge.figma.tsx +33 -0
  120. package/figma/button.figma.tsx +33 -0
  121. package/figma/checkbox.figma.tsx +28 -0
  122. package/figma/drawer.figma.tsx +40 -0
  123. package/figma/input-variants.figma.tsx +58 -0
  124. package/figma/radio.figma.tsx +16 -0
  125. package/figma/select.figma.tsx +24 -0
  126. package/figma/status-badge.figma.tsx +24 -0
  127. package/figma/switch.figma.tsx +28 -0
  128. package/figma/tabs.figma.tsx +22 -0
  129. package/figma/tag.figma.tsx +27 -0
  130. package/figma.config.json +6 -0
  131. package/jest.config.cjs +2 -1
  132. package/package.json +21 -18
  133. package/scripts/lucideRegistrySource.cjs +133 -0
  134. package/src/ai/AiChat.tsx +36 -7
  135. package/src/ai/LoopDisabledBanner.tsx +1 -1
  136. package/src/ai/__tests__/AiChat.test.tsx +31 -0
  137. package/src/ai/parts/ConfirmationCard.tsx +4 -4
  138. package/src/ai/parts/FieldDiffCard.tsx +2 -2
  139. package/src/ai/parts/MutationResultCard.tsx +3 -3
  140. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +2 -2
  141. package/src/backend/AppShell.tsx +55 -4
  142. package/src/backend/AuthSessionGuard.tsx +29 -0
  143. package/src/backend/BackendChromeProvider.tsx +19 -1
  144. package/src/backend/CrudForm.tsx +18 -4
  145. package/src/backend/DataTable.tsx +36 -19
  146. package/src/backend/JsonBuilder.tsx +3 -3
  147. package/src/backend/NextStepCallout.tsx +4 -4
  148. package/src/backend/PerspectiveSidebar.tsx +1 -1
  149. package/src/backend/ProfileDropdown.tsx +1 -0
  150. package/src/backend/RowActions.tsx +2 -2
  151. package/src/backend/__tests__/AppShell.test.tsx +65 -7
  152. package/src/backend/__tests__/AuthSessionGuard.test.tsx +52 -0
  153. package/src/backend/__tests__/BackendChromeProvider.test.tsx +98 -2
  154. package/src/backend/__tests__/CrudForm.fieldsetBinding.test.tsx +158 -0
  155. package/src/backend/__tests__/CrudForm.groupInjectionColumn.test.tsx +98 -0
  156. package/src/backend/__tests__/DataTable.metaHiddenLateColumns.test.tsx +105 -0
  157. package/src/backend/__tests__/DataTable.perspectiveStorage.test.ts +62 -1
  158. package/src/backend/__tests__/nav-utils.test.ts +139 -0
  159. package/src/backend/confirm-dialog/ConfirmDialog.tsx +16 -1
  160. package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +68 -1
  161. package/src/backend/crud/__tests__/useGroupOrder.test.ts +39 -1
  162. package/src/backend/crud/useGroupOrder.ts +30 -25
  163. package/src/backend/custom-fields/FieldDefinitionsManager.tsx +1 -1
  164. package/src/backend/dashboard/DashboardScreen.tsx +3 -3
  165. package/src/backend/detail/AddressTiles.tsx +3 -6
  166. package/src/backend/detail/AttachmentDeleteDialog.tsx +1 -1
  167. package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
  168. package/src/backend/detail/AttachmentsSection.tsx +1 -1
  169. package/src/backend/detail/NotesSection.tsx +1 -1
  170. package/src/backend/detail/TagsSection.tsx +1 -1
  171. package/src/backend/detail/__tests__/AttachmentAssignmentsEditor.test.tsx +21 -0
  172. package/src/backend/filters/AdvancedFilterPanel.tsx +1 -1
  173. package/src/backend/forms/FormHeader.tsx +1 -2
  174. package/src/backend/icons/__tests__/lucideRegistryGenerator.test.ts +177 -0
  175. package/src/backend/icons/lucideRegistry.generated.tsx +3 -37
  176. package/src/backend/icons/lucideRegistry.ts +2 -2
  177. package/src/backend/icons/lucideRegistryRuntime.tsx +36 -0
  178. package/src/backend/injection/__tests__/spotIds.extension-points.test.ts +38 -0
  179. package/src/backend/injection/spotIds.ts +23 -18
  180. package/src/backend/inputs/ComboboxInput.tsx +1 -1
  181. package/src/backend/messages/EmailThreadsPanel.tsx +1 -1
  182. package/src/backend/perspectiveState.ts +44 -0
  183. package/src/backend/schedule/ScheduleAgenda.tsx +3 -3
  184. package/src/backend/schedule/ScheduleGrid.tsx +3 -3
  185. package/src/backend/schedule/ScheduleToolbar.tsx +89 -68
  186. package/src/backend/sidebar/SidebarCustomizationEditor.tsx +42 -3
  187. package/src/backend/sidebar/__tests__/group-visibility.test.ts +131 -0
  188. package/src/backend/sidebar/customization-helpers.ts +47 -0
  189. package/src/backend/utils/__tests__/apiCall.test.ts +61 -0
  190. package/src/backend/utils/__tests__/customFieldForms.optionsPaging.test.ts +95 -0
  191. package/src/backend/utils/__tests__/fetchAllPages.test.ts +103 -0
  192. package/src/backend/utils/apiCall.ts +29 -1
  193. package/src/backend/utils/customFieldDefs.ts +1 -0
  194. package/src/backend/utils/customFieldForms.ts +15 -11
  195. package/src/backend/utils/fetchAllPages.ts +93 -0
  196. package/src/backend/utils/nav.ts +22 -2
  197. package/src/backend/version-history/VersionHistoryPanel.tsx +1 -1
  198. package/src/frontend/LanguageSwitcher.tsx +1 -0
  199. package/src/portal/PortalContext.tsx +1 -1
  200. package/src/portal/hooks/__tests__/usePortalEventBridge.test.tsx +66 -0
  201. package/src/portal/hooks/__tests__/usePortalNotifications.test.tsx +155 -0
  202. package/src/portal/hooks/portalBridgeStatus.ts +29 -0
  203. package/src/portal/hooks/usePortalEventBridge.ts +6 -0
  204. package/src/portal/hooks/usePortalNotifications.ts +55 -12
  205. package/src/portal/utils/__tests__/nav.test.ts +2 -2
  206. package/src/portal/utils/nav.ts +3 -4
  207. package/src/primitives/ErrorNotice.tsx +1 -1
  208. package/src/primitives/Notice.tsx +5 -5
  209. package/src/primitives/__tests__/button.test.tsx +70 -0
  210. package/src/primitives/button.tsx +10 -4
  211. package/src/theme/ThemeProvider.tsx +1 -2
  212. package/src/theme/__tests__/theme-init-script.test.ts +70 -0
  213. package/src/theme/index.ts +1 -0
  214. package/src/theme/theme-init-script.ts +23 -0
@@ -148,7 +148,7 @@ function JsonBuilder({
148
148
  disabled
149
149
  }
150
150
  ),
151
- parseError && /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600", children: "Invalid JSON format" })
151
+ parseError && /* @__PURE__ */ jsx("div", { className: "text-xs text-status-error-text", children: "Invalid JSON format" })
152
152
  ] }) : /* @__PURE__ */ jsx("div", { className: "min-h-[300px] text-sm overflow-x-auto", children: typeof value === "object" && value !== null ? /* @__PURE__ */ jsx(
153
153
  JsonNode,
154
154
  {
@@ -159,7 +159,7 @@ function JsonBuilder({
159
159
  isRoot: true
160
160
  }
161
161
  ) : /* @__PURE__ */ jsx("div", { className: "text-muted-foreground italic p-4 text-center", children: "Value is not an object or array. Switch to Raw to edit." }) }),
162
- error && /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600", children: error }),
162
+ error && /* @__PURE__ */ jsx("div", { className: "text-xs text-status-error-text", children: error }),
163
163
  ConfirmDialogElement
164
164
  ] });
165
165
  }
@@ -295,7 +295,7 @@ function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }
295
295
  type: "button",
296
296
  variant: "ghost",
297
297
  size: "xs",
298
- className: "text-muted-foreground hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity",
298
+ className: "text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity",
299
299
  title: "Remove item",
300
300
  onClick: onDelete,
301
301
  children: /* @__PURE__ */ jsx(Trash2, { className: "w-3.5 h-3.5" })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/JsonBuilder.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Input } from '../primitives/input'\nimport { InlineInput } from '../primitives/inline-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { Plus, Trash2, ChevronRight, ChevronDown, Code, LayoutList } from 'lucide-react'\nimport { useConfirmDialog } from './confirm-dialog'\nimport type { ConfirmDialogOptions } from './confirm-dialog'\n\nfunction cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(' ')\n}\n\nexport type JsonBuilderProps = {\n value: any\n onChange: (value: any) => void\n disabled?: boolean\n error?: string\n}\n\ntype JsonNodeType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'\n\ntype ConfirmFn = (options?: ConfirmDialogOptions) => Promise<boolean>\n\nfunction getJsonType(value: any): JsonNodeType {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n return typeof value as JsonNodeType\n}\n\nfunction defaultValueForType(type: JsonNodeType): any {\n switch (type) {\n case 'string': return \"\"\n case 'number': return 0\n case 'boolean': return false\n case 'object': return {}\n case 'array': return []\n case 'null': return null\n }\n}\n\nfunction toRawString(value: any): string {\n if (value === null || value === undefined) return '{}'\n if (typeof value === 'object') return JSON.stringify(value, null, 2)\n return String(value)\n}\n\nexport function JsonBuilder({\n value,\n onChange,\n disabled,\n error\n}: JsonBuilderProps) {\n const [mode, setMode] = React.useState<'raw' | 'builder'>('raw')\n const [rawString, setRawString] = React.useState(() => toRawString(value))\n const [rawDirty, setRawDirty] = React.useState(false)\n const [parseError, setParseError] = React.useState<string | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (value === null && !disabled) {\n onChange({})\n }\n }, [value, disabled, onChange])\n\n // Mirror external value changes (e.g. async record load, builder edits) into\n // the raw textarea only while the user has NOT started typing in it. Once the\n // textarea is dirty it becomes the source of truth, so re-deriving it from\n // `value` on every keystroke \u2014 the parent's onChange identity changes each\n // render \u2014 would clobber typing and make Raw JSON uneditable (issue #2817).\n React.useEffect(() => {\n if (rawDirty) return\n if (value !== null && typeof value === 'object') {\n setRawString(JSON.stringify(value, null, 2))\n setParseError(null)\n }\n }, [value, rawDirty])\n\n const handleRawChange = (str: string) => {\n setRawDirty(true)\n setRawString(str)\n try {\n if (str.trim() === '') {\n onChange({})\n setParseError(null)\n } else {\n const parsed = JSON.parse(str)\n onChange(parsed)\n setParseError(null)\n }\n } catch (e) {\n onChange(str)\n setParseError(\"Invalid JSON\")\n }\n }\n\n const switchToRaw = () => {\n // Re-sync the textarea from the current value and let it mirror external\n // changes again until the user edits it.\n setRawDirty(false)\n setRawString(toRawString(value))\n setParseError(null)\n setMode('raw')\n }\n\n const switchToBuilder = () => {\n try {\n if (typeof value === 'string') {\n JSON.parse(value)\n }\n setMode('builder')\n } catch (e) {\n alert(\"Cannot switch to Builder mode: Invalid JSON\")\n }\n }\n\n return (\n <div className=\"space-y-4 border rounded-md p-4 bg-card\">\n <div className=\"flex items-center space-x-2 border-b pb-2 mb-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'raw' && \"bg-muted text-foreground\")}\n onClick={switchToRaw}\n >\n <Code className=\"w-4 h-4\" />\n Raw JSON\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'builder' && \"bg-muted text-foreground\")}\n onClick={switchToBuilder}\n >\n <LayoutList className=\"w-4 h-4\" />\n Builder\n </Button>\n </div>\n\n {mode === 'raw' ? (\n <div className=\"space-y-2\">\n <textarea\n value={rawString}\n onChange={(e) => handleRawChange(e.target.value)}\n onBlur={() => {\n try {\n const parsed = JSON.parse(rawString)\n setRawString(JSON.stringify(parsed, null, 2))\n } catch { }\n }}\n placeholder='{\"key\": \"value\"}'\n className=\"w-full rounded border px-3 py-2 min-h-[300px] text-sm font-mono focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n disabled={disabled}\n />\n {parseError && (\n <div className=\"text-xs text-red-600\">Invalid JSON format</div>\n )}\n </div>\n ) : (\n <div className=\"min-h-[300px] text-sm overflow-x-auto\">\n {typeof value === 'object' && value !== null ? (\n <JsonNode\n data={value}\n onChange={onChange}\n readOnly={disabled}\n confirm={confirm}\n isRoot\n />\n ) : (\n <div className=\"text-muted-foreground italic p-4 text-center\">\n Value is not an object or array. Switch to Raw to edit.\n </div>\n )}\n </div>\n )}\n\n {error && <div className=\"text-xs text-red-600\">{error}</div>}\n {ConfirmDialogElement}\n </div>\n )\n}\n\ninterface JsonNodeProps {\n data: any\n onChange: (val: any) => void\n onDelete?: () => void\n readOnly?: boolean\n label?: string\n isRoot?: boolean\n confirm?: ConfirmFn\n}\n\nfunction JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }: JsonNodeProps) {\n const type = getJsonType(data)\n const isContainer = type === 'object' || type === 'array'\n const [collapsed, setCollapsed] = React.useState(false)\n\n const handleTypeChange = async (newType: JsonNodeType) => {\n if (newType === type) return\n // Switching away from a non-empty container discards its contents. Ask\n // for confirmation first so configured properties aren't lost silently\n // (issue #2817). The Select is controlled by the derived type, so when\n // the user cancels we simply leave the data untouched and the control\n // snaps back to its previous value.\n const itemCount = type === 'object'\n ? Object.keys(data).length\n : type === 'array'\n ? (data as any[]).length\n : 0\n if (itemCount > 0 && confirm) {\n const noun = type === 'object'\n ? `${itemCount} ${itemCount === 1 ? 'property' : 'properties'}`\n : `${itemCount} ${itemCount === 1 ? 'item' : 'items'}`\n const confirmed = await confirm({\n title: 'Change value type?',\n text: `Changing the type from ${type} to ${newType} will discard the ${noun} currently configured. This cannot be undone.`,\n confirmText: 'Discard and change',\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n onChange(defaultValueForType(newType))\n }\n\n const handleAddKey = () => {\n if (type === 'object') {\n const newKey = `newKey_${Object.keys(data).length}`\n onChange({ ...data, [newKey]: \"\" })\n } else if (type === 'array') {\n onChange([...data, \"\"])\n }\n }\n\n const handleChildChange = (key: string | number, newVal: any) => {\n if (type === 'object') {\n onChange({ ...data, [key]: newVal })\n } else if (type === 'array') {\n const arr = [...data]\n arr[Number(key)] = newVal\n onChange(arr)\n }\n }\n\n const handleKeyRename = (oldKey: string, newKey: string) => {\n if (oldKey === newKey) return\n const keys = Object.keys(data)\n const newData: any = {}\n keys.forEach(k => {\n if (k === oldKey) {\n newData[newKey] = data[k]\n } else {\n newData[k] = data[k]\n }\n })\n onChange(newData)\n }\n\n const handleChildDelete = (key: string | number) => {\n if (type === 'object') {\n const newData = { ...data }\n delete newData[key as string]\n onChange(newData)\n } else if (type === 'array') {\n onChange(data.filter((_: any, i: number) => i !== key))\n }\n }\n\n return (\n <div className={cn(\"pl-0\", !isRoot && \"pl-4 border-l border-border ml-1\")}>\n <div className=\"flex items-start gap-2 py-1 group\">\n\n {isContainer && (\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" className=\"mt-1 text-muted-foreground hover:text-foreground\" onClick={() => setCollapsed(!collapsed)}>\n {collapsed ? <ChevronRight className=\"w-3 h-3\" /> : <ChevronDown className=\"w-3 h-3\" />}\n </IconButton>\n )}\n {!isContainer && !isRoot && <div className=\"w-3\" />} {/* Spacer */}\n\n {label !== undefined && !isRoot && (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-muted-foreground font-mono\">\n {label}\n </span>\n <span className=\"text-muted-foreground text-xs\">:</span>\n </div>\n )}\n\n <div className=\"flex-1 flex gap-2 items-center flex-wrap\">\n\n {!readOnly && (\n <Select\n value={type}\n onValueChange={(next) => { void handleTypeChange(next as JsonNodeType) }}\n >\n <SelectTrigger size=\"sm\" className=\"w-auto min-w-[6rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"string\">String</SelectItem>\n <SelectItem value=\"number\">Number</SelectItem>\n <SelectItem value=\"boolean\">Boolean</SelectItem>\n <SelectItem value=\"object\">Object</SelectItem>\n <SelectItem value=\"array\">Array</SelectItem>\n <SelectItem value=\"null\">Null</SelectItem>\n </SelectContent>\n </Select>\n )}\n\n {type === 'string' && (\n <Input\n size=\"sm\"\n className=\"flex-1 min-w-0 sm:min-w-[120px]\"\n value={data}\n onChange={e => onChange(e.target.value)}\n disabled={readOnly}\n />\n )}\n {type === 'number' && (\n <Input\n type=\"number\"\n size=\"sm\"\n className=\"flex-1 w-full sm:w-[100px]\"\n value={data}\n onChange={e => onChange(parseFloat(e.target.value) || 0)}\n disabled={readOnly}\n />\n )}\n {type === 'boolean' && (\n <Select\n value={String(data)}\n onValueChange={(next) => onChange(next === 'true')}\n disabled={readOnly}\n >\n <SelectTrigger size=\"sm\" className=\"flex-1 w-full sm:w-[100px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">true</SelectItem>\n <SelectItem value=\"false\">false</SelectItem>\n </SelectContent>\n </Select>\n )}\n {type === 'null' && <span className=\"text-xs text-muted-foreground\">null</span>}\n {isContainer && (\n <span className=\"text-xs text-muted-foreground\">\n {type === 'object' ? `{ ${Object.keys(data).length} items }` : `[ ${data.length} items ]`}\n </span>\n )}\n\n {onDelete && !readOnly && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"text-muted-foreground hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity\"\n title=\"Remove item\"\n onClick={onDelete}\n >\n <Trash2 className=\"w-3.5 h-3.5\" />\n </IconButton>\n )}\n </div>\n </div>\n\n {isContainer && !collapsed && (\n <div className=\"flex flex-col gap-1 w-full pl-2\">\n {type === 'object' && Object.entries(data).map(([key, val], idx) => (\n <div key={idx} className=\"flex\">\n <div className=\"pt-2\">\n {/* Key Renamer */}\n <InlineInput\n className=\"w-full sm:w-[100px]\"\n inputClassName=\"text-right text-xs font-mono pr-1\"\n value={key}\n onChange={(e) => handleKeyRename(key, e.target.value)}\n disabled={readOnly}\n />\n </div>\n <div className=\"flex-1\">\n <JsonNode\n data={val}\n onChange={(v) => handleChildChange(key, v)}\n onDelete={() => handleChildDelete(key)}\n readOnly={readOnly}\n confirm={confirm}\n />\n </div>\n </div>\n ))}\n\n {type === 'array' && (data as any[]).map((val, idx) => (\n <JsonNode\n key={idx}\n label={String(idx)}\n data={val}\n onChange={(v) => handleChildChange(idx, v)}\n onDelete={() => handleChildDelete(idx)}\n readOnly={readOnly}\n confirm={confirm}\n />\n ))}\n\n {!readOnly && (\n <div className=\"pl-4 mt-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAddKey}\n className=\"h-6 text-xs\"\n >\n <Plus className=\"w-3 h-3 mr-1\" />\n Add {type === 'object' ? 'Property' : 'Item'}\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Input } from '../primitives/input'\nimport { InlineInput } from '../primitives/inline-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { Plus, Trash2, ChevronRight, ChevronDown, Code, LayoutList } from 'lucide-react'\nimport { useConfirmDialog } from './confirm-dialog'\nimport type { ConfirmDialogOptions } from './confirm-dialog'\n\nfunction cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(' ')\n}\n\nexport type JsonBuilderProps = {\n value: any\n onChange: (value: any) => void\n disabled?: boolean\n error?: string\n}\n\ntype JsonNodeType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'\n\ntype ConfirmFn = (options?: ConfirmDialogOptions) => Promise<boolean>\n\nfunction getJsonType(value: any): JsonNodeType {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n return typeof value as JsonNodeType\n}\n\nfunction defaultValueForType(type: JsonNodeType): any {\n switch (type) {\n case 'string': return \"\"\n case 'number': return 0\n case 'boolean': return false\n case 'object': return {}\n case 'array': return []\n case 'null': return null\n }\n}\n\nfunction toRawString(value: any): string {\n if (value === null || value === undefined) return '{}'\n if (typeof value === 'object') return JSON.stringify(value, null, 2)\n return String(value)\n}\n\nexport function JsonBuilder({\n value,\n onChange,\n disabled,\n error\n}: JsonBuilderProps) {\n const [mode, setMode] = React.useState<'raw' | 'builder'>('raw')\n const [rawString, setRawString] = React.useState(() => toRawString(value))\n const [rawDirty, setRawDirty] = React.useState(false)\n const [parseError, setParseError] = React.useState<string | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (value === null && !disabled) {\n onChange({})\n }\n }, [value, disabled, onChange])\n\n // Mirror external value changes (e.g. async record load, builder edits) into\n // the raw textarea only while the user has NOT started typing in it. Once the\n // textarea is dirty it becomes the source of truth, so re-deriving it from\n // `value` on every keystroke \u2014 the parent's onChange identity changes each\n // render \u2014 would clobber typing and make Raw JSON uneditable (issue #2817).\n React.useEffect(() => {\n if (rawDirty) return\n if (value !== null && typeof value === 'object') {\n setRawString(JSON.stringify(value, null, 2))\n setParseError(null)\n }\n }, [value, rawDirty])\n\n const handleRawChange = (str: string) => {\n setRawDirty(true)\n setRawString(str)\n try {\n if (str.trim() === '') {\n onChange({})\n setParseError(null)\n } else {\n const parsed = JSON.parse(str)\n onChange(parsed)\n setParseError(null)\n }\n } catch (e) {\n onChange(str)\n setParseError(\"Invalid JSON\")\n }\n }\n\n const switchToRaw = () => {\n // Re-sync the textarea from the current value and let it mirror external\n // changes again until the user edits it.\n setRawDirty(false)\n setRawString(toRawString(value))\n setParseError(null)\n setMode('raw')\n }\n\n const switchToBuilder = () => {\n try {\n if (typeof value === 'string') {\n JSON.parse(value)\n }\n setMode('builder')\n } catch (e) {\n alert(\"Cannot switch to Builder mode: Invalid JSON\")\n }\n }\n\n return (\n <div className=\"space-y-4 border rounded-md p-4 bg-card\">\n <div className=\"flex items-center space-x-2 border-b pb-2 mb-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'raw' && \"bg-muted text-foreground\")}\n onClick={switchToRaw}\n >\n <Code className=\"w-4 h-4\" />\n Raw JSON\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'builder' && \"bg-muted text-foreground\")}\n onClick={switchToBuilder}\n >\n <LayoutList className=\"w-4 h-4\" />\n Builder\n </Button>\n </div>\n\n {mode === 'raw' ? (\n <div className=\"space-y-2\">\n <textarea\n value={rawString}\n onChange={(e) => handleRawChange(e.target.value)}\n onBlur={() => {\n try {\n const parsed = JSON.parse(rawString)\n setRawString(JSON.stringify(parsed, null, 2))\n } catch { }\n }}\n placeholder='{\"key\": \"value\"}'\n className=\"w-full rounded border px-3 py-2 min-h-[300px] text-sm font-mono focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n disabled={disabled}\n />\n {parseError && (\n <div className=\"text-xs text-status-error-text\">Invalid JSON format</div>\n )}\n </div>\n ) : (\n <div className=\"min-h-[300px] text-sm overflow-x-auto\">\n {typeof value === 'object' && value !== null ? (\n <JsonNode\n data={value}\n onChange={onChange}\n readOnly={disabled}\n confirm={confirm}\n isRoot\n />\n ) : (\n <div className=\"text-muted-foreground italic p-4 text-center\">\n Value is not an object or array. Switch to Raw to edit.\n </div>\n )}\n </div>\n )}\n\n {error && <div className=\"text-xs text-status-error-text\">{error}</div>}\n {ConfirmDialogElement}\n </div>\n )\n}\n\ninterface JsonNodeProps {\n data: any\n onChange: (val: any) => void\n onDelete?: () => void\n readOnly?: boolean\n label?: string\n isRoot?: boolean\n confirm?: ConfirmFn\n}\n\nfunction JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }: JsonNodeProps) {\n const type = getJsonType(data)\n const isContainer = type === 'object' || type === 'array'\n const [collapsed, setCollapsed] = React.useState(false)\n\n const handleTypeChange = async (newType: JsonNodeType) => {\n if (newType === type) return\n // Switching away from a non-empty container discards its contents. Ask\n // for confirmation first so configured properties aren't lost silently\n // (issue #2817). The Select is controlled by the derived type, so when\n // the user cancels we simply leave the data untouched and the control\n // snaps back to its previous value.\n const itemCount = type === 'object'\n ? Object.keys(data).length\n : type === 'array'\n ? (data as any[]).length\n : 0\n if (itemCount > 0 && confirm) {\n const noun = type === 'object'\n ? `${itemCount} ${itemCount === 1 ? 'property' : 'properties'}`\n : `${itemCount} ${itemCount === 1 ? 'item' : 'items'}`\n const confirmed = await confirm({\n title: 'Change value type?',\n text: `Changing the type from ${type} to ${newType} will discard the ${noun} currently configured. This cannot be undone.`,\n confirmText: 'Discard and change',\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n onChange(defaultValueForType(newType))\n }\n\n const handleAddKey = () => {\n if (type === 'object') {\n const newKey = `newKey_${Object.keys(data).length}`\n onChange({ ...data, [newKey]: \"\" })\n } else if (type === 'array') {\n onChange([...data, \"\"])\n }\n }\n\n const handleChildChange = (key: string | number, newVal: any) => {\n if (type === 'object') {\n onChange({ ...data, [key]: newVal })\n } else if (type === 'array') {\n const arr = [...data]\n arr[Number(key)] = newVal\n onChange(arr)\n }\n }\n\n const handleKeyRename = (oldKey: string, newKey: string) => {\n if (oldKey === newKey) return\n const keys = Object.keys(data)\n const newData: any = {}\n keys.forEach(k => {\n if (k === oldKey) {\n newData[newKey] = data[k]\n } else {\n newData[k] = data[k]\n }\n })\n onChange(newData)\n }\n\n const handleChildDelete = (key: string | number) => {\n if (type === 'object') {\n const newData = { ...data }\n delete newData[key as string]\n onChange(newData)\n } else if (type === 'array') {\n onChange(data.filter((_: any, i: number) => i !== key))\n }\n }\n\n return (\n <div className={cn(\"pl-0\", !isRoot && \"pl-4 border-l border-border ml-1\")}>\n <div className=\"flex items-start gap-2 py-1 group\">\n\n {isContainer && (\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" className=\"mt-1 text-muted-foreground hover:text-foreground\" onClick={() => setCollapsed(!collapsed)}>\n {collapsed ? <ChevronRight className=\"w-3 h-3\" /> : <ChevronDown className=\"w-3 h-3\" />}\n </IconButton>\n )}\n {!isContainer && !isRoot && <div className=\"w-3\" />} {/* Spacer */}\n\n {label !== undefined && !isRoot && (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-muted-foreground font-mono\">\n {label}\n </span>\n <span className=\"text-muted-foreground text-xs\">:</span>\n </div>\n )}\n\n <div className=\"flex-1 flex gap-2 items-center flex-wrap\">\n\n {!readOnly && (\n <Select\n value={type}\n onValueChange={(next) => { void handleTypeChange(next as JsonNodeType) }}\n >\n <SelectTrigger size=\"sm\" className=\"w-auto min-w-[6rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"string\">String</SelectItem>\n <SelectItem value=\"number\">Number</SelectItem>\n <SelectItem value=\"boolean\">Boolean</SelectItem>\n <SelectItem value=\"object\">Object</SelectItem>\n <SelectItem value=\"array\">Array</SelectItem>\n <SelectItem value=\"null\">Null</SelectItem>\n </SelectContent>\n </Select>\n )}\n\n {type === 'string' && (\n <Input\n size=\"sm\"\n className=\"flex-1 min-w-0 sm:min-w-[120px]\"\n value={data}\n onChange={e => onChange(e.target.value)}\n disabled={readOnly}\n />\n )}\n {type === 'number' && (\n <Input\n type=\"number\"\n size=\"sm\"\n className=\"flex-1 w-full sm:w-[100px]\"\n value={data}\n onChange={e => onChange(parseFloat(e.target.value) || 0)}\n disabled={readOnly}\n />\n )}\n {type === 'boolean' && (\n <Select\n value={String(data)}\n onValueChange={(next) => onChange(next === 'true')}\n disabled={readOnly}\n >\n <SelectTrigger size=\"sm\" className=\"flex-1 w-full sm:w-[100px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">true</SelectItem>\n <SelectItem value=\"false\">false</SelectItem>\n </SelectContent>\n </Select>\n )}\n {type === 'null' && <span className=\"text-xs text-muted-foreground\">null</span>}\n {isContainer && (\n <span className=\"text-xs text-muted-foreground\">\n {type === 'object' ? `{ ${Object.keys(data).length} items }` : `[ ${data.length} items ]`}\n </span>\n )}\n\n {onDelete && !readOnly && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity\"\n title=\"Remove item\"\n onClick={onDelete}\n >\n <Trash2 className=\"w-3.5 h-3.5\" />\n </IconButton>\n )}\n </div>\n </div>\n\n {isContainer && !collapsed && (\n <div className=\"flex flex-col gap-1 w-full pl-2\">\n {type === 'object' && Object.entries(data).map(([key, val], idx) => (\n <div key={idx} className=\"flex\">\n <div className=\"pt-2\">\n {/* Key Renamer */}\n <InlineInput\n className=\"w-full sm:w-[100px]\"\n inputClassName=\"text-right text-xs font-mono pr-1\"\n value={key}\n onChange={(e) => handleKeyRename(key, e.target.value)}\n disabled={readOnly}\n />\n </div>\n <div className=\"flex-1\">\n <JsonNode\n data={val}\n onChange={(v) => handleChildChange(key, v)}\n onDelete={() => handleChildDelete(key)}\n readOnly={readOnly}\n confirm={confirm}\n />\n </div>\n </div>\n ))}\n\n {type === 'array' && (data as any[]).map((val, idx) => (\n <JsonNode\n key={idx}\n label={String(idx)}\n data={val}\n onChange={(v) => handleChildChange(idx, v)}\n onDelete={() => handleChildDelete(idx)}\n readOnly={readOnly}\n confirm={confirm}\n />\n ))}\n\n {!readOnly && (\n <div className=\"pl-4 mt-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAddKey}\n className=\"h-6 text-xs\"\n >\n <Plus className=\"w-3 h-3 mr-1\" />\n Add {type === 'object' ? 'Property' : 'Item'}\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,15 +7,15 @@ import { Progress } from "@open-mercato/ui/primitives/progress";
7
7
  const STEP_STYLES = {
8
8
  pending: "",
9
9
  active: "border-primary/40 bg-primary/10 text-primary",
10
- completed: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200"
10
+ completed: "border-status-success-border bg-status-success-bg text-status-success-text"
11
11
  };
12
12
  const STATUS_STYLES = {
13
13
  default: "border-border bg-background/80 text-foreground",
14
14
  info: "border-primary/20 bg-primary/5 text-primary",
15
- success: "border-emerald-500/20 bg-emerald-500/5 text-emerald-200",
16
- warning: "border-amber-500/20 bg-amber-500/5 text-amber-200",
15
+ success: "border-status-success-border bg-status-success-bg text-status-success-text",
16
+ warning: "border-status-warning-border bg-status-warning-bg text-status-warning-text",
17
17
  danger: "border-destructive/20 bg-destructive/5 text-destructive",
18
- muted: "border-zinc-500/20 bg-zinc-500/5 text-zinc-200"
18
+ muted: "border-status-neutral-border bg-status-neutral-bg text-status-neutral-text"
19
19
  };
20
20
  function NextStepCallout({
21
21
  icon,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/NextStepCallout.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\n\ntype NextStepCalloutStep = {\n id: string\n label: React.ReactNode\n state?: 'pending' | 'active' | 'completed'\n}\n\ntype NextStepCalloutStatusTone = 'default' | 'info' | 'success' | 'warning' | 'danger' | 'muted'\n\ntype NextStepCalloutStatus = {\n tone?: NextStepCalloutStatusTone\n icon?: React.ReactNode\n label: React.ReactNode\n badge?: React.ReactNode\n progressValue?: number | null\n progressDescription?: React.ReactNode\n errorMessage?: React.ReactNode\n}\n\ntype NextStepCalloutProps = {\n icon?: React.ReactNode\n title: React.ReactNode\n description?: React.ReactNode\n steps?: NextStepCalloutStep[]\n actionLabel: React.ReactNode\n actionIcon?: React.ReactNode\n onAction?: () => void\n disabled?: boolean\n disabledMessage?: React.ReactNode\n busy?: boolean\n status?: NextStepCalloutStatus | null\n className?: string\n}\n\nconst STEP_STYLES: Record<NonNullable<NextStepCalloutStep['state']>, string> = {\n pending: '',\n active: 'border-primary/40 bg-primary/10 text-primary',\n completed: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-200',\n}\n\nconst STATUS_STYLES: Record<NextStepCalloutStatusTone, string> = {\n default: 'border-border bg-background/80 text-foreground',\n info: 'border-primary/20 bg-primary/5 text-primary',\n success: 'border-emerald-500/20 bg-emerald-500/5 text-emerald-200',\n warning: 'border-amber-500/20 bg-amber-500/5 text-amber-200',\n danger: 'border-destructive/20 bg-destructive/5 text-destructive',\n muted: 'border-zinc-500/20 bg-zinc-500/5 text-zinc-200',\n}\n\nexport function NextStepCallout({\n icon,\n title,\n description,\n steps,\n actionLabel,\n actionIcon,\n onAction,\n disabled = false,\n disabledMessage,\n busy = false,\n status,\n className,\n}: NextStepCalloutProps) {\n return (\n <div className={cn('rounded-xl border border-dashed border-primary/30 bg-primary/5 px-6 py-8', className)}>\n <div className=\"mx-auto flex max-w-2xl flex-col items-center text-center\">\n {icon ? (\n <div className=\"mb-4 flex h-14 w-14 items-center justify-center rounded-full border border-primary/30 bg-background text-primary\">\n {icon}\n </div>\n ) : null}\n <h4 className=\"text-lg font-semibold\">{title}</h4>\n {description ? (\n <p className=\"mt-2 text-sm text-muted-foreground\">{description}</p>\n ) : null}\n {steps && steps.length > 0 ? (\n <div className=\"mt-4 flex flex-wrap items-center justify-center gap-2\">\n {steps.map((step) => (\n <Badge\n key={step.id}\n variant=\"outline\"\n className={cn(step.state ? STEP_STYLES[step.state] : STEP_STYLES.pending)}\n >\n {step.label}\n </Badge>\n ))}\n </div>\n ) : null}\n <Button\n type=\"button\"\n size=\"lg\"\n className=\"mt-6 min-w-72\"\n onClick={onAction}\n disabled={disabled || busy}\n >\n {actionIcon}\n {actionLabel}\n </Button>\n {disabledMessage ? (\n <p className=\"mt-3 text-xs text-muted-foreground\">{disabledMessage}</p>\n ) : null}\n {status ? (\n <div className={cn('mt-4 w-full rounded-lg border px-4 py-3 text-left', STATUS_STYLES[status.tone ?? 'default'])}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n {status.icon ? <span className=\"shrink-0\">{status.icon}</span> : null}\n <span>{status.label}</span>\n </div>\n {status.badge ? status.badge : null}\n </div>\n {typeof status.progressValue === 'number' ? (\n <Progress value={status.progressValue} className=\"mt-3 h-2\" />\n ) : busy ? (\n <div className=\"mt-3 h-2 rounded-full bg-secondary\" />\n ) : null}\n {status.progressDescription ? (\n <p className=\"mt-2 text-xs text-muted-foreground\">{status.progressDescription}</p>\n ) : null}\n {status.errorMessage ? (\n <p className=\"mt-2 text-xs text-destructive\">{status.errorMessage}</p>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\n\ntype NextStepCalloutStep = {\n id: string\n label: React.ReactNode\n state?: 'pending' | 'active' | 'completed'\n}\n\ntype NextStepCalloutStatusTone = 'default' | 'info' | 'success' | 'warning' | 'danger' | 'muted'\n\ntype NextStepCalloutStatus = {\n tone?: NextStepCalloutStatusTone\n icon?: React.ReactNode\n label: React.ReactNode\n badge?: React.ReactNode\n progressValue?: number | null\n progressDescription?: React.ReactNode\n errorMessage?: React.ReactNode\n}\n\ntype NextStepCalloutProps = {\n icon?: React.ReactNode\n title: React.ReactNode\n description?: React.ReactNode\n steps?: NextStepCalloutStep[]\n actionLabel: React.ReactNode\n actionIcon?: React.ReactNode\n onAction?: () => void\n disabled?: boolean\n disabledMessage?: React.ReactNode\n busy?: boolean\n status?: NextStepCalloutStatus | null\n className?: string\n}\n\nconst STEP_STYLES: Record<NonNullable<NextStepCalloutStep['state']>, string> = {\n pending: '',\n active: 'border-primary/40 bg-primary/10 text-primary',\n completed: 'border-status-success-border bg-status-success-bg text-status-success-text',\n}\n\nconst STATUS_STYLES: Record<NextStepCalloutStatusTone, string> = {\n default: 'border-border bg-background/80 text-foreground',\n info: 'border-primary/20 bg-primary/5 text-primary',\n success: 'border-status-success-border bg-status-success-bg text-status-success-text',\n warning: 'border-status-warning-border bg-status-warning-bg text-status-warning-text',\n danger: 'border-destructive/20 bg-destructive/5 text-destructive',\n muted: 'border-status-neutral-border bg-status-neutral-bg text-status-neutral-text',\n}\n\nexport function NextStepCallout({\n icon,\n title,\n description,\n steps,\n actionLabel,\n actionIcon,\n onAction,\n disabled = false,\n disabledMessage,\n busy = false,\n status,\n className,\n}: NextStepCalloutProps) {\n return (\n <div className={cn('rounded-xl border border-dashed border-primary/30 bg-primary/5 px-6 py-8', className)}>\n <div className=\"mx-auto flex max-w-2xl flex-col items-center text-center\">\n {icon ? (\n <div className=\"mb-4 flex h-14 w-14 items-center justify-center rounded-full border border-primary/30 bg-background text-primary\">\n {icon}\n </div>\n ) : null}\n <h4 className=\"text-lg font-semibold\">{title}</h4>\n {description ? (\n <p className=\"mt-2 text-sm text-muted-foreground\">{description}</p>\n ) : null}\n {steps && steps.length > 0 ? (\n <div className=\"mt-4 flex flex-wrap items-center justify-center gap-2\">\n {steps.map((step) => (\n <Badge\n key={step.id}\n variant=\"outline\"\n className={cn(step.state ? STEP_STYLES[step.state] : STEP_STYLES.pending)}\n >\n {step.label}\n </Badge>\n ))}\n </div>\n ) : null}\n <Button\n type=\"button\"\n size=\"lg\"\n className=\"mt-6 min-w-72\"\n onClick={onAction}\n disabled={disabled || busy}\n >\n {actionIcon}\n {actionLabel}\n </Button>\n {disabledMessage ? (\n <p className=\"mt-3 text-xs text-muted-foreground\">{disabledMessage}</p>\n ) : null}\n {status ? (\n <div className={cn('mt-4 w-full rounded-lg border px-4 py-3 text-left', STATUS_STYLES[status.tone ?? 'default'])}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n {status.icon ? <span className=\"shrink-0\">{status.icon}</span> : null}\n <span>{status.label}</span>\n </div>\n {status.badge ? status.badge : null}\n </div>\n {typeof status.progressValue === 'number' ? (\n <Progress value={status.progressValue} className=\"mt-3 h-2\" />\n ) : busy ? (\n <div className=\"mt-3 h-2 rounded-full bg-secondary\" />\n ) : null}\n {status.progressDescription ? (\n <p className=\"mt-2 text-xs text-muted-foreground\">{status.progressDescription}</p>\n ) : null}\n {status.errorMessage ? (\n <p className=\"mt-2 text-xs text-destructive\">{status.errorMessage}</p>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
5
  "mappings": ";AA0EU,cAqBF,YArBE;AAvEV,SAAS,UAAU;AACnB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AAmCzB,MAAM,cAAyE;AAAA,EAC7E,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AACb;AAEA,MAAM,gBAA2D;AAAA,EAC/D,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AACT;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AACF,GAAyB;AACvB,SACE,oBAAC,SAAI,WAAW,GAAG,4EAA4E,SAAS,GACtG,+BAAC,SAAI,WAAU,4DACZ;AAAA,WACC,oBAAC,SAAI,WAAU,oHACZ,gBACH,IACE;AAAA,IACJ,oBAAC,QAAG,WAAU,yBAAyB,iBAAM;AAAA,IAC5C,cACC,oBAAC,OAAE,WAAU,sCAAsC,uBAAY,IAC7D;AAAA,IACH,SAAS,MAAM,SAAS,IACvB,oBAAC,SAAI,WAAU,yDACZ,gBAAM,IAAI,CAAC,SACV;AAAA,MAAC;AAAA;AAAA,QAEC,SAAQ;AAAA,QACR,WAAW,GAAG,KAAK,QAAQ,YAAY,KAAK,KAAK,IAAI,YAAY,OAAO;AAAA,QAEvE,eAAK;AAAA;AAAA,MAJD,KAAK;AAAA,IAKZ,CACD,GACH,IACE;AAAA,IACJ;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,WAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,YAAY;AAAA,QAErB;AAAA;AAAA,UACA;AAAA;AAAA;AAAA,IACH;AAAA,IACC,kBACC,oBAAC,OAAE,WAAU,sCAAsC,2BAAgB,IACjE;AAAA,IACH,SACC,qBAAC,SAAI,WAAW,GAAG,qDAAqD,cAAc,OAAO,QAAQ,SAAS,CAAC,GAC7G;AAAA,2BAAC,SAAI,WAAU,qDACb;AAAA,6BAAC,SAAI,WAAU,+CACZ;AAAA,iBAAO,OAAO,oBAAC,UAAK,WAAU,YAAY,iBAAO,MAAK,IAAU;AAAA,UACjE,oBAAC,UAAM,iBAAO,OAAM;AAAA,WACtB;AAAA,QACC,OAAO,QAAQ,OAAO,QAAQ;AAAA,SACjC;AAAA,MACC,OAAO,OAAO,kBAAkB,WAC/B,oBAAC,YAAS,OAAO,OAAO,eAAe,WAAU,YAAW,IAC1D,OACF,oBAAC,SAAI,WAAU,sCAAqC,IAClD;AAAA,MACH,OAAO,sBACN,oBAAC,OAAE,WAAU,sCAAsC,iBAAO,qBAAoB,IAC5E;AAAA,MACH,OAAO,eACN,oBAAC,OAAE,WAAU,iCAAiC,iBAAO,cAAa,IAChE;AAAA,OACN,IACE;AAAA,KACN,GACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -423,7 +423,7 @@ function PerspectiveSidebar({
423
423
  onCancel: resetMode
424
424
  }
425
425
  ) : null,
426
- apiWarning ? /* @__PURE__ */ jsx(Alert, { variant: "warning", className: "text-xs", children: apiWarning }) : null,
426
+ apiWarning ? /* @__PURE__ */ jsx(Alert, { status: "warning", className: "text-xs", children: apiWarning }) : null,
427
427
  error ? /* @__PURE__ */ jsx("div", { className: "text-sm text-status-error-text", children: error }) : null
428
428
  ] }),
429
429
  /* @__PURE__ */ jsx("div", { className: "flex items-center px-4 mt-2 pt-2 pb-2", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm leading-none", children: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/PerspectiveSidebar.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ArrowLeft, Plus } from 'lucide-react'\nimport { Alert } from '../primitives/alert'\nimport { Button } from '../primitives/button'\nimport { Checkbox } from '../primitives/checkbox'\nimport { Spinner } from '../primitives/spinner'\nimport { useConfirmDialog } from './confirm-dialog'\nimport { flash } from './FlashMessages'\nimport { surfaceRecordConflict } from './conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n PerspectiveDto,\n PerspectiveSettings,\n RolePerspectiveDto,\n} from '@open-mercato/shared/modules/perspectives/types'\nimport { ViewChip } from './views/ViewChip'\nimport { NewViewForm } from './views/NewViewForm'\nimport { ShareForm } from './views/ShareForm'\nimport { type SidebarMode } from './views/types'\nimport { ColumnChooserSection, type ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type { ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type PerspectiveSidebarProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n loading: boolean\n perspectives: PerspectiveDto[]\n rolePerspectives: RolePerspectiveDto[]\n roles: Array<{ id: string; name: string; hasPerspective: boolean; hasDefault: boolean }>\n activePerspectiveId: string | null\n onActivatePerspective: (perspective: PerspectiveDto | RolePerspectiveDto, source: 'personal' | 'role') => void\n onDeletePerspective: (perspectiveId: string) => Promise<void>\n onClearRole: (perspective: RolePerspectiveDto) => Promise<void>\n onSave: (input: { name: string; isDefault: boolean; applyToRoles: string[]; setRoleDefault: boolean; perspectiveId?: string | null; settings?: PerspectiveSettings }) => Promise<void>\n canApplyToRoles: boolean\n availableColumns: ColumnChooserField[]\n visibleColumnKeys: string[]\n columnOrder: string[]\n onToggleColumn: (key: string) => void\n onReorderColumns: (orderedIds: string[]) => void\n saving: boolean\n deletingIds: string[]\n roleClearingIds: string[]\n apiWarning?: string | null\n}\n\nconst emptyArray: any[] = []\n\nexport function PerspectiveSidebar({\n open,\n onOpenChange,\n loading,\n perspectives,\n rolePerspectives,\n roles,\n activePerspectiveId,\n onActivatePerspective,\n onDeletePerspective,\n onClearRole,\n onSave,\n canApplyToRoles,\n availableColumns,\n visibleColumnKeys,\n columnOrder,\n onToggleColumn,\n onReorderColumns,\n saving,\n deletingIds,\n roleClearingIds,\n apiWarning,\n}: PerspectiveSidebarProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!open) return\n if (typeof document === 'undefined') return\n document.body.dataset.columnChooserOpen = 'true'\n return () => {\n delete document.body.dataset.columnChooserOpen\n }\n }, [open])\n\n function perspectiveLabel(p: PerspectiveDto | RolePerspectiveDto) {\n return p.name.trim().length ? p.name : t('ui.perspectives.untitled', 'Untitled view')\n }\n\n const [name, setName] = React.useState('')\n const [isDefault, setIsDefault] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mode, setMode] = React.useState<SidebarMode>({ type: 'idle' })\n const [renamingId, setRenamingId] = React.useState<string | null>(null)\n const [renameValue, setRenameValue] = React.useState('')\n const [shareRoles, setShareRoles] = React.useState<string[]>([])\n const [shareSetDefault, setShareSetDefault] = React.useState(false)\n const [sharedIds, setSharedIds] = React.useState<Set<string>>(new Set())\n const [pendingCloneBaselineIds, setPendingCloneBaselineIds] = React.useState<Set<string> | null>(null)\n\n const autosaveRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const isDefaultUserChangeRef = React.useRef(false)\n const isDefaultRef = React.useRef(isDefault)\n isDefaultRef.current = isDefault\n\n const onSaveRef = React.useRef(onSave)\n onSaveRef.current = onSave\n\n const perspectivesRef = React.useRef(perspectives)\n perspectivesRef.current = perspectives\n\n const flushAutosave = React.useCallback(() => {\n if (autosaveRef.current) {\n clearTimeout(autosaveRef.current)\n autosaveRef.current = null\n }\n }, [])\n\n const scheduleAutosave = React.useCallback(() => {\n if (!activePerspectiveId || mode.type === 'new') return\n const activePersonal = perspectivesRef.current.find((p) => p.id === activePerspectiveId)\n if (!activePersonal) return\n flushAutosave()\n const targetId = activePersonal.id\n const targetName = activePersonal.name\n autosaveRef.current = setTimeout(async () => {\n autosaveRef.current = null\n try {\n await onSaveRef.current({\n name: targetName,\n isDefault: isDefaultRef.current,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: targetId,\n })\n flash(t('ui.perspectives.autosave.success', 'View saved'), 'success')\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n flash(t('ui.perspectives.autosave.error', 'Failed to save view'), 'error')\n }\n }, 400)\n }, [activePerspectiveId, mode.type, flushAutosave, t])\n\n React.useEffect(() => {\n return () => { flushAutosave() }\n }, [flushAutosave])\n\n const resetMode = () => {\n setMode({ type: 'idle' })\n setName('')\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startNewMode = () => {\n setMode({ type: 'new' })\n setName('')\n setIsDefault(false)\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startShareMode = (p: PerspectiveDto) => {\n setMode({ type: 'share', perspectiveId: p.id, perspectiveName: p.name, perspectiveIsDefault: p.isDefault })\n setName('')\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n React.useEffect(() => {\n if (!open) {\n flushAutosave()\n setError(null)\n resetMode()\n setRenamingId(null)\n setSharedIds(new Set())\n setPendingCloneBaselineIds(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n const active = perspectives.find((p) => p.id === activePerspectiveId)\n ?? rolePerspectives.find((p) => p.id === activePerspectiveId)\n isDefaultUserChangeRef.current = false\n if (active) {\n setIsDefault(active.isDefault)\n } else {\n setIsDefault(false)\n }\n requestAnimationFrame(() => { isDefaultUserChangeRef.current = true })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, activePerspectiveId])\n\n const handleSaveNew = async () => {\n setError(null)\n try {\n await onSave({ name: name.trim(), isDefault, applyToRoles: [], setRoleDefault: false, perspectiveId: null })\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to save view')\n }\n }\n\n const handleToggleColumnWithAutosave = React.useCallback((key: string) => {\n onToggleColumn(key)\n scheduleAutosave()\n }, [onToggleColumn, scheduleAutosave])\n\n const handleReorderColumnsWithAutosave = React.useCallback((orderedIds: string[]) => {\n onReorderColumns(orderedIds)\n scheduleAutosave()\n }, [onReorderColumns, scheduleAutosave])\n\n const handleRename = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const trimmed = renameValue.trim()\n if (!trimmed) {\n setRenamingId(null)\n return\n }\n const conflict = perspectivesRef.current.some(\n (item) => item.id !== p.id && item.name.trim() === trimmed,\n )\n if (conflict) {\n flash(t('ui.perspectives.error.nameExists', 'View with this name already exists'), 'error')\n return\n }\n setError(null)\n try {\n await onSave({\n name: trimmed,\n isDefault: p.isDefault,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: p.id,\n })\n setRenamingId(null)\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to rename view')\n }\n }\n\n const handleClone = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const originalName = perspectiveLabel(p)\n setError(null)\n const baseline = new Set(perspectivesRef.current.map((item) => item.id))\n const originalSettingsKey = JSON.stringify(p.settings ?? {})\n const sharedRoleIds = Array.from(\n new Set(\n rolePerspectives\n .filter((rp) => JSON.stringify(rp.settings ?? {}) === originalSettingsKey)\n .map((rp) => rp.roleId),\n ),\n )\n const buildCloneName = (n: number) =>\n n <= 1 ? `${originalName} (copy)` : `${originalName} (copy ${n})`\n const existingNames = new Set(\n perspectivesRef.current.map((item) => item.name.trim()),\n )\n let counter = 1\n while (existingNames.has(buildCloneName(counter))) {\n counter += 1\n }\n const clonedSettings = structuredClone(p.settings)\n const MAX_ATTEMPTS = 50\n let lastErr: unknown = null\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n try {\n await onSave({\n name: buildCloneName(counter),\n isDefault: false,\n applyToRoles: sharedRoleIds,\n setRoleDefault: false,\n perspectiveId: null,\n settings: clonedSettings,\n })\n setPendingCloneBaselineIds(baseline)\n return\n } catch (err: unknown) {\n const errMessage = err instanceof Error ? err.message : ''\n const msg = errMessage.toLowerCase()\n const isDuplicate = msg.includes('duplicate key') || msg.includes('unique constraint')\n if (!isDuplicate) {\n if (surfaceRecordConflict(err, t)) return\n setError(errMessage || 'Failed to clone view')\n return\n }\n lastErr = err\n counter += 1\n }\n }\n if (surfaceRecordConflict(lastErr, t)) return\n setError(lastErr instanceof Error ? lastErr.message : 'Failed to clone view')\n }\n\n React.useEffect(() => {\n if (!pendingCloneBaselineIds) return\n const created = perspectives.find((item) => !pendingCloneBaselineIds.has(item.id))\n if (!created) return\n setRenamingId(created.id)\n setRenameValue(created.name)\n setPendingCloneBaselineIds(null)\n }, [perspectives, pendingCloneBaselineIds])\n\n const handleDelete = async (p: PerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.delete.text', 'This view will be removed for you and all shared roles. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onDeletePerspective(p.id)\n }\n }\n\n const handleDeleteRole = async (p: RolePerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.deleteRole.text', 'This shared view will be removed for the role. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onClearRole(p)\n }\n }\n\n const handleShareApply = async () => {\n if (mode.type !== 'share') return\n setError(null)\n try {\n await onSave({ name: mode.perspectiveName, isDefault: mode.perspectiveIsDefault, applyToRoles: shareRoles, setRoleDefault: shareSetDefault, perspectiveId: mode.perspectiveId })\n setSharedIds((prev) => new Set([...prev, mode.perspectiveId]))\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to share view')\n }\n }\n\n const toggleShareRole = (roleId: string) => {\n setShareRoles((prev) => {\n const next = new Set(prev)\n if (next.has(roleId)) next.delete(roleId)\n else next.add(roleId)\n return Array.from(next)\n })\n }\n\n if (!open) return null\n\n const isNew = mode.type === 'new'\n const isShare = mode.type === 'share'\n\n return (\n <div className=\"fixed inset-0 z-modal\">\n <div className=\"absolute inset-0 bg-black/20\" onClick={() => onOpenChange(false)} role=\"presentation\" />\n <div className=\"fixed right-0 top-0 h-full w-full sm:w-80 bg-background shadow-xl border-l flex flex-col\">\n <div className=\"flex items-center p-4 border-b\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n className=\"h-auto -ml-2 gap-2 px-2 text-lg font-semibold hover:bg-transparent\"\n aria-label={t('ui.perspectives.close', 'Close')}\n >\n <ArrowLeft className=\"size-5\" />\n {t('ui.perspectives.title', 'Views')}\n </Button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n {/* Saved views as chips */}\n <section className=\"p-4 space-y-3\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">{t('ui.perspectives.savedViews.title', 'Saved views')}</h3>\n {loading ? <Spinner size=\"sm\" /> : null}\n <div className=\"flex flex-wrap gap-1.5\">\n {/* + New chip */}\n <Button\n type=\"button\"\n variant={isNew ? 'secondary' : 'outline'}\n size=\"sm\"\n className={`h-8 px-3 py-1.5 text-sm ${!isNew ? 'border-dashed' : ''}`}\n onClick={startNewMode}\n >\n <Plus className=\"size-3 mr-1\" />\n {t('ui.perspectives.savedViews.new', 'New')}\n </Button>\n {(perspectives ?? emptyArray).map((p) => {\n const isActive = activePerspectiveId === p.id\n const deleting = deletingIds.includes(p.id)\n const isShared = sharedIds.has(p.id)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"personal\"\n isActive={isActive}\n disabled={deleting}\n isShared={isShared}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={deleting}\n onActivate={() => { onActivatePerspective(p, 'personal'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onRenameStart={() => { setRenamingId(p.id); setRenameValue(p.name) }}\n onClone={() => void handleClone(p)}\n onShareStart={() => startShareMode(p)}\n onDelete={() => void handleDelete(p)}\n />\n )\n })}\n {rolePerspectives\n .filter((rp) => {\n const rpName = rp.name.trim()\n return !perspectives.some((pp) => pp.name.trim() === rpName)\n })\n .map((p) => {\n const isActive = activePerspectiveId === p.id\n const clearing = roleClearingIds.includes(p.roleId)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"role\"\n isActive={isActive}\n disabled={clearing}\n isShared={false}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={clearing}\n onActivate={() => { onActivatePerspective(p, 'role'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onClone={() => void handleClone(p)}\n onDelete={() => void handleDeleteRole(p)}\n />\n )\n })}\n </div>\n\n {/* Inline form slot \u2014 shared between New and Share modes */}\n {isNew ? (\n <NewViewForm\n name={name}\n onNameChange={setName}\n onSubmit={() => void handleSaveNew()}\n onCancel={resetMode}\n saving={saving}\n />\n ) : null}\n\n {isShare ? (\n <ShareForm\n roles={roles}\n shareRoles={shareRoles}\n shareSetDefault={shareSetDefault}\n onToggleRole={toggleShareRole}\n onToggleSetDefault={setShareSetDefault}\n onApply={() => void handleShareApply()}\n onCancel={resetMode}\n />\n ) : null}\n\n {apiWarning ? (\n <Alert variant=\"warning\" className=\"text-xs\">{apiWarning}</Alert>\n ) : null}\n {error ? <div className=\"text-sm text-status-error-text\">{error}</div> : null}\n </section>\n\n {/* Set as default \u2014 separated from chips (border-t) and from columns (ColumnList owns border-t) */}\n <div className=\"flex items-center px-4 mt-2 pt-2 pb-2\">\n <label className=\"inline-flex items-center gap-2 text-sm leading-none\">\n <Checkbox\n checked={isDefault}\n onCheckedChange={(checked) => {\n setIsDefault(checked === true)\n if (isDefaultUserChangeRef.current) {\n scheduleAutosave()\n }\n }}\n />\n {t('ui.perspectives.form.makeDefault', 'Set as default')}\n </label>\n </div>\n\n <ColumnChooserSection\n availableColumns={availableColumns}\n visibleColumnKeys={visibleColumnKeys}\n columnOrder={columnOrder}\n onToggleColumn={handleToggleColumnWithAutosave}\n onReorderColumns={handleReorderColumnsWithAutosave}\n dndContextId=\"perspective-columns\"\n />\n </div>\n </div>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ArrowLeft, Plus } from 'lucide-react'\nimport { Alert } from '../primitives/alert'\nimport { Button } from '../primitives/button'\nimport { Checkbox } from '../primitives/checkbox'\nimport { Spinner } from '../primitives/spinner'\nimport { useConfirmDialog } from './confirm-dialog'\nimport { flash } from './FlashMessages'\nimport { surfaceRecordConflict } from './conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n PerspectiveDto,\n PerspectiveSettings,\n RolePerspectiveDto,\n} from '@open-mercato/shared/modules/perspectives/types'\nimport { ViewChip } from './views/ViewChip'\nimport { NewViewForm } from './views/NewViewForm'\nimport { ShareForm } from './views/ShareForm'\nimport { type SidebarMode } from './views/types'\nimport { ColumnChooserSection, type ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type { ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type PerspectiveSidebarProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n loading: boolean\n perspectives: PerspectiveDto[]\n rolePerspectives: RolePerspectiveDto[]\n roles: Array<{ id: string; name: string; hasPerspective: boolean; hasDefault: boolean }>\n activePerspectiveId: string | null\n onActivatePerspective: (perspective: PerspectiveDto | RolePerspectiveDto, source: 'personal' | 'role') => void\n onDeletePerspective: (perspectiveId: string) => Promise<void>\n onClearRole: (perspective: RolePerspectiveDto) => Promise<void>\n onSave: (input: { name: string; isDefault: boolean; applyToRoles: string[]; setRoleDefault: boolean; perspectiveId?: string | null; settings?: PerspectiveSettings }) => Promise<void>\n canApplyToRoles: boolean\n availableColumns: ColumnChooserField[]\n visibleColumnKeys: string[]\n columnOrder: string[]\n onToggleColumn: (key: string) => void\n onReorderColumns: (orderedIds: string[]) => void\n saving: boolean\n deletingIds: string[]\n roleClearingIds: string[]\n apiWarning?: string | null\n}\n\nconst emptyArray: any[] = []\n\nexport function PerspectiveSidebar({\n open,\n onOpenChange,\n loading,\n perspectives,\n rolePerspectives,\n roles,\n activePerspectiveId,\n onActivatePerspective,\n onDeletePerspective,\n onClearRole,\n onSave,\n canApplyToRoles,\n availableColumns,\n visibleColumnKeys,\n columnOrder,\n onToggleColumn,\n onReorderColumns,\n saving,\n deletingIds,\n roleClearingIds,\n apiWarning,\n}: PerspectiveSidebarProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!open) return\n if (typeof document === 'undefined') return\n document.body.dataset.columnChooserOpen = 'true'\n return () => {\n delete document.body.dataset.columnChooserOpen\n }\n }, [open])\n\n function perspectiveLabel(p: PerspectiveDto | RolePerspectiveDto) {\n return p.name.trim().length ? p.name : t('ui.perspectives.untitled', 'Untitled view')\n }\n\n const [name, setName] = React.useState('')\n const [isDefault, setIsDefault] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mode, setMode] = React.useState<SidebarMode>({ type: 'idle' })\n const [renamingId, setRenamingId] = React.useState<string | null>(null)\n const [renameValue, setRenameValue] = React.useState('')\n const [shareRoles, setShareRoles] = React.useState<string[]>([])\n const [shareSetDefault, setShareSetDefault] = React.useState(false)\n const [sharedIds, setSharedIds] = React.useState<Set<string>>(new Set())\n const [pendingCloneBaselineIds, setPendingCloneBaselineIds] = React.useState<Set<string> | null>(null)\n\n const autosaveRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const isDefaultUserChangeRef = React.useRef(false)\n const isDefaultRef = React.useRef(isDefault)\n isDefaultRef.current = isDefault\n\n const onSaveRef = React.useRef(onSave)\n onSaveRef.current = onSave\n\n const perspectivesRef = React.useRef(perspectives)\n perspectivesRef.current = perspectives\n\n const flushAutosave = React.useCallback(() => {\n if (autosaveRef.current) {\n clearTimeout(autosaveRef.current)\n autosaveRef.current = null\n }\n }, [])\n\n const scheduleAutosave = React.useCallback(() => {\n if (!activePerspectiveId || mode.type === 'new') return\n const activePersonal = perspectivesRef.current.find((p) => p.id === activePerspectiveId)\n if (!activePersonal) return\n flushAutosave()\n const targetId = activePersonal.id\n const targetName = activePersonal.name\n autosaveRef.current = setTimeout(async () => {\n autosaveRef.current = null\n try {\n await onSaveRef.current({\n name: targetName,\n isDefault: isDefaultRef.current,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: targetId,\n })\n flash(t('ui.perspectives.autosave.success', 'View saved'), 'success')\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n flash(t('ui.perspectives.autosave.error', 'Failed to save view'), 'error')\n }\n }, 400)\n }, [activePerspectiveId, mode.type, flushAutosave, t])\n\n React.useEffect(() => {\n return () => { flushAutosave() }\n }, [flushAutosave])\n\n const resetMode = () => {\n setMode({ type: 'idle' })\n setName('')\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startNewMode = () => {\n setMode({ type: 'new' })\n setName('')\n setIsDefault(false)\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startShareMode = (p: PerspectiveDto) => {\n setMode({ type: 'share', perspectiveId: p.id, perspectiveName: p.name, perspectiveIsDefault: p.isDefault })\n setName('')\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n React.useEffect(() => {\n if (!open) {\n flushAutosave()\n setError(null)\n resetMode()\n setRenamingId(null)\n setSharedIds(new Set())\n setPendingCloneBaselineIds(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n const active = perspectives.find((p) => p.id === activePerspectiveId)\n ?? rolePerspectives.find((p) => p.id === activePerspectiveId)\n isDefaultUserChangeRef.current = false\n if (active) {\n setIsDefault(active.isDefault)\n } else {\n setIsDefault(false)\n }\n requestAnimationFrame(() => { isDefaultUserChangeRef.current = true })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, activePerspectiveId])\n\n const handleSaveNew = async () => {\n setError(null)\n try {\n await onSave({ name: name.trim(), isDefault, applyToRoles: [], setRoleDefault: false, perspectiveId: null })\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to save view')\n }\n }\n\n const handleToggleColumnWithAutosave = React.useCallback((key: string) => {\n onToggleColumn(key)\n scheduleAutosave()\n }, [onToggleColumn, scheduleAutosave])\n\n const handleReorderColumnsWithAutosave = React.useCallback((orderedIds: string[]) => {\n onReorderColumns(orderedIds)\n scheduleAutosave()\n }, [onReorderColumns, scheduleAutosave])\n\n const handleRename = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const trimmed = renameValue.trim()\n if (!trimmed) {\n setRenamingId(null)\n return\n }\n const conflict = perspectivesRef.current.some(\n (item) => item.id !== p.id && item.name.trim() === trimmed,\n )\n if (conflict) {\n flash(t('ui.perspectives.error.nameExists', 'View with this name already exists'), 'error')\n return\n }\n setError(null)\n try {\n await onSave({\n name: trimmed,\n isDefault: p.isDefault,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: p.id,\n })\n setRenamingId(null)\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to rename view')\n }\n }\n\n const handleClone = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const originalName = perspectiveLabel(p)\n setError(null)\n const baseline = new Set(perspectivesRef.current.map((item) => item.id))\n const originalSettingsKey = JSON.stringify(p.settings ?? {})\n const sharedRoleIds = Array.from(\n new Set(\n rolePerspectives\n .filter((rp) => JSON.stringify(rp.settings ?? {}) === originalSettingsKey)\n .map((rp) => rp.roleId),\n ),\n )\n const buildCloneName = (n: number) =>\n n <= 1 ? `${originalName} (copy)` : `${originalName} (copy ${n})`\n const existingNames = new Set(\n perspectivesRef.current.map((item) => item.name.trim()),\n )\n let counter = 1\n while (existingNames.has(buildCloneName(counter))) {\n counter += 1\n }\n const clonedSettings = structuredClone(p.settings)\n const MAX_ATTEMPTS = 50\n let lastErr: unknown = null\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n try {\n await onSave({\n name: buildCloneName(counter),\n isDefault: false,\n applyToRoles: sharedRoleIds,\n setRoleDefault: false,\n perspectiveId: null,\n settings: clonedSettings,\n })\n setPendingCloneBaselineIds(baseline)\n return\n } catch (err: unknown) {\n const errMessage = err instanceof Error ? err.message : ''\n const msg = errMessage.toLowerCase()\n const isDuplicate = msg.includes('duplicate key') || msg.includes('unique constraint')\n if (!isDuplicate) {\n if (surfaceRecordConflict(err, t)) return\n setError(errMessage || 'Failed to clone view')\n return\n }\n lastErr = err\n counter += 1\n }\n }\n if (surfaceRecordConflict(lastErr, t)) return\n setError(lastErr instanceof Error ? lastErr.message : 'Failed to clone view')\n }\n\n React.useEffect(() => {\n if (!pendingCloneBaselineIds) return\n const created = perspectives.find((item) => !pendingCloneBaselineIds.has(item.id))\n if (!created) return\n setRenamingId(created.id)\n setRenameValue(created.name)\n setPendingCloneBaselineIds(null)\n }, [perspectives, pendingCloneBaselineIds])\n\n const handleDelete = async (p: PerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.delete.text', 'This view will be removed for you and all shared roles. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onDeletePerspective(p.id)\n }\n }\n\n const handleDeleteRole = async (p: RolePerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.deleteRole.text', 'This shared view will be removed for the role. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onClearRole(p)\n }\n }\n\n const handleShareApply = async () => {\n if (mode.type !== 'share') return\n setError(null)\n try {\n await onSave({ name: mode.perspectiveName, isDefault: mode.perspectiveIsDefault, applyToRoles: shareRoles, setRoleDefault: shareSetDefault, perspectiveId: mode.perspectiveId })\n setSharedIds((prev) => new Set([...prev, mode.perspectiveId]))\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to share view')\n }\n }\n\n const toggleShareRole = (roleId: string) => {\n setShareRoles((prev) => {\n const next = new Set(prev)\n if (next.has(roleId)) next.delete(roleId)\n else next.add(roleId)\n return Array.from(next)\n })\n }\n\n if (!open) return null\n\n const isNew = mode.type === 'new'\n const isShare = mode.type === 'share'\n\n return (\n <div className=\"fixed inset-0 z-modal\">\n <div className=\"absolute inset-0 bg-black/20\" onClick={() => onOpenChange(false)} role=\"presentation\" />\n <div className=\"fixed right-0 top-0 h-full w-full sm:w-80 bg-background shadow-xl border-l flex flex-col\">\n <div className=\"flex items-center p-4 border-b\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n className=\"h-auto -ml-2 gap-2 px-2 text-lg font-semibold hover:bg-transparent\"\n aria-label={t('ui.perspectives.close', 'Close')}\n >\n <ArrowLeft className=\"size-5\" />\n {t('ui.perspectives.title', 'Views')}\n </Button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n {/* Saved views as chips */}\n <section className=\"p-4 space-y-3\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">{t('ui.perspectives.savedViews.title', 'Saved views')}</h3>\n {loading ? <Spinner size=\"sm\" /> : null}\n <div className=\"flex flex-wrap gap-1.5\">\n {/* + New chip */}\n <Button\n type=\"button\"\n variant={isNew ? 'secondary' : 'outline'}\n size=\"sm\"\n className={`h-8 px-3 py-1.5 text-sm ${!isNew ? 'border-dashed' : ''}`}\n onClick={startNewMode}\n >\n <Plus className=\"size-3 mr-1\" />\n {t('ui.perspectives.savedViews.new', 'New')}\n </Button>\n {(perspectives ?? emptyArray).map((p) => {\n const isActive = activePerspectiveId === p.id\n const deleting = deletingIds.includes(p.id)\n const isShared = sharedIds.has(p.id)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"personal\"\n isActive={isActive}\n disabled={deleting}\n isShared={isShared}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={deleting}\n onActivate={() => { onActivatePerspective(p, 'personal'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onRenameStart={() => { setRenamingId(p.id); setRenameValue(p.name) }}\n onClone={() => void handleClone(p)}\n onShareStart={() => startShareMode(p)}\n onDelete={() => void handleDelete(p)}\n />\n )\n })}\n {rolePerspectives\n .filter((rp) => {\n const rpName = rp.name.trim()\n return !perspectives.some((pp) => pp.name.trim() === rpName)\n })\n .map((p) => {\n const isActive = activePerspectiveId === p.id\n const clearing = roleClearingIds.includes(p.roleId)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"role\"\n isActive={isActive}\n disabled={clearing}\n isShared={false}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={clearing}\n onActivate={() => { onActivatePerspective(p, 'role'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onClone={() => void handleClone(p)}\n onDelete={() => void handleDeleteRole(p)}\n />\n )\n })}\n </div>\n\n {/* Inline form slot \u2014 shared between New and Share modes */}\n {isNew ? (\n <NewViewForm\n name={name}\n onNameChange={setName}\n onSubmit={() => void handleSaveNew()}\n onCancel={resetMode}\n saving={saving}\n />\n ) : null}\n\n {isShare ? (\n <ShareForm\n roles={roles}\n shareRoles={shareRoles}\n shareSetDefault={shareSetDefault}\n onToggleRole={toggleShareRole}\n onToggleSetDefault={setShareSetDefault}\n onApply={() => void handleShareApply()}\n onCancel={resetMode}\n />\n ) : null}\n\n {apiWarning ? (\n <Alert status=\"warning\" className=\"text-xs\">{apiWarning}</Alert>\n ) : null}\n {error ? <div className=\"text-sm text-status-error-text\">{error}</div> : null}\n </section>\n\n {/* Set as default \u2014 separated from chips (border-t) and from columns (ColumnList owns border-t) */}\n <div className=\"flex items-center px-4 mt-2 pt-2 pb-2\">\n <label className=\"inline-flex items-center gap-2 text-sm leading-none\">\n <Checkbox\n checked={isDefault}\n onCheckedChange={(checked) => {\n setIsDefault(checked === true)\n if (isDefaultUserChangeRef.current) {\n scheduleAutosave()\n }\n }}\n />\n {t('ui.perspectives.form.makeDefault', 'Set as default')}\n </label>\n </div>\n\n <ColumnChooserSection\n availableColumns={availableColumns}\n visibleColumnKeys={visibleColumnKeys}\n columnOrder={columnOrder}\n onToggleColumn={handleToggleColumnWithAutosave}\n onReorderColumns={handleReorderColumnsWithAutosave}\n dndContextId=\"perspective-columns\"\n />\n </div>\n </div>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -20,7 +20,8 @@ const localeLabels = {
20
20
  en: "English",
21
21
  de: "Deutsch",
22
22
  es: "Espa\xF1ol",
23
- pl: "Polski"
23
+ pl: "Polski",
24
+ ko: "\uD55C\uAD6D\uC5B4"
24
25
  };
25
26
  function ProfileDropdown({
26
27
  email,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/ProfileDropdown.tsx"],
4
- "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport Link from 'next/link'\nimport { User, LogOut, Bell, Moon, Sun, Globe, Key, Check, ChevronRight } from 'lucide-react'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport { useTheme } from '@open-mercato/ui/theme'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { IconButton } from '../primitives/icon-button'\nimport { Switch } from '../primitives/switch'\nimport { useInjectedMenuItems } from './injection/useInjectedMenuItems'\nimport { mergeMenuItems, type MergedMenuItem } from './injection/mergeMenuItems'\nimport { resolveInjectedIcon } from './injection/resolveInjectedIcon'\nimport { InjectionSpot } from './injection/InjectionSpot'\nimport { BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID } from './injection/spotIds'\n\nexport type ProfileDropdownProps = {\n email?: string\n displayName?: string\n changePasswordHref?: string\n notificationsHref?: string\n}\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\nconst localeLabels: Record<Locale, string> = {\n en: 'English',\n de: 'Deutsch',\n es: 'Espa\u00F1ol',\n pl: 'Polski',\n}\n\nexport function ProfileDropdown({\n email,\n displayName,\n changePasswordHref = '/backend/profile/change-password',\n notificationsHref,\n}: ProfileDropdownProps) {\n const t = useT()\n const currentLocale = useLocale()\n const { resolvedTheme, setTheme } = useTheme()\n const [open, setOpen] = React.useState(false)\n const [languageOpen, setLanguageOpen] = React.useState(false)\n const [mounted, setMounted] = React.useState(false)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const menuRef = React.useRef<HTMLDivElement>(null)\n const [menuPosition, setMenuPosition] = React.useState<{ top: number; right: number } | null>(null)\n const { items: injectedItems } = useInjectedMenuItems('menu:topbar:profile-dropdown')\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n // Position the portaled menu under the trigger. Rendering the menu in a body\n // portal keeps it out of the sticky header's backdrop-blur stacking context so\n // it always paints above page content such as the DataTable sticky Actions column.\n useIsomorphicLayoutEffect(() => {\n if (!open) {\n setMenuPosition(null)\n return\n }\n const updatePosition = () => {\n const trigger = buttonRef.current\n if (!trigger) return\n const rect = trigger.getBoundingClientRect()\n setMenuPosition({\n top: rect.bottom + 8,\n right: Math.max(0, window.innerWidth - rect.right),\n })\n }\n updatePosition()\n window.addEventListener('resize', updatePosition)\n window.addEventListener('scroll', updatePosition, true)\n return () => {\n window.removeEventListener('resize', updatePosition)\n window.removeEventListener('scroll', updatePosition, true)\n }\n }, [open])\n\n const isDark = resolvedTheme === 'dark'\n\n // Close on click outside\n React.useEffect(() => {\n if (!open) return\n function handleClick(event: MouseEvent) {\n if (\n menuRef.current &&\n !menuRef.current.contains(event.target as Node) &&\n buttonRef.current &&\n !buttonRef.current.contains(event.target as Node)\n ) {\n setOpen(false)\n setLanguageOpen(false)\n }\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [open])\n\n // Close on Escape\n React.useEffect(() => {\n if (!open) return\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n if (languageOpen) {\n setLanguageOpen(false)\n } else {\n setOpen(false)\n buttonRef.current?.focus()\n }\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open, languageOpen])\n\n const handleThemeToggle = () => {\n setTheme(isDark ? 'light' : 'dark')\n }\n\n const handleLocaleChange = async (locale: Locale) => {\n try {\n await fetch('/api/auth/locale', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ locale }),\n })\n window.location.reload()\n } catch {}\n }\n\n // Unified row class \u2014 every menu item uses this for perfectly aligned layout.\n // h-9 keeps all rows the same height regardless of whether they have a trailing element.\n const menuItemClass = cn(\n 'group flex h-9 w-full items-center gap-3 rounded-md px-2.5 text-sm text-foreground',\n 'transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60',\n 'cursor-pointer',\n )\n const menuIconClass = 'size-4 shrink-0 text-muted-foreground group-hover:text-foreground'\n\n const resolveMenuLabel = React.useCallback(\n (item: Pick<MergedMenuItem, 'id' | 'label' | 'labelKey'>): string => {\n if (item.labelKey && item.label) return t(item.labelKey, item.label)\n if (item.labelKey) return t(item.labelKey, item.id)\n if (item.label && item.label.includes('.')) return t(item.label, item.id)\n return item.label ?? item.id\n },\n [t],\n )\n\n const builtInMenuItems = React.useMemo(\n () => {\n const items: Array<{ id: string; separator?: boolean }> = [{ id: 'change-password' }]\n if (notificationsHref) items.push({ id: 'notifications' })\n items.push({ id: 'theme-toggle', separator: true }, { id: 'language' }, { id: 'sign-out', separator: true })\n return items\n },\n [notificationsHref],\n )\n\n const mergedMenuItems = React.useMemo(\n () => mergeMenuItems(builtInMenuItems, injectedItems),\n [builtInMenuItems, injectedItems],\n )\n const injectionContext = React.useMemo(\n () => ({\n email,\n displayName,\n locale: currentLocale,\n }),\n [currentLocale, displayName, email],\n )\n\n const renderInjectedItem = React.useCallback(\n (item: MergedMenuItem) => {\n const label = resolveMenuLabel(item)\n const icon = resolveInjectedIcon(item.icon)\n const inner = (\n <>\n <span className={menuIconClass}>{icon}</span>\n <span className=\"flex-1 truncate\">{label}</span>\n </>\n )\n if (item.href) {\n return (\n <Link\n key={item.id}\n href={item.href}\n className={menuItemClass}\n role=\"menuitem\"\n data-menu-item-id={item.id}\n onClick={() => setOpen(false)}\n >\n {inner}\n </Link>\n )\n }\n return (\n <button\n key={item.id}\n type=\"button\"\n className={menuItemClass}\n role=\"menuitem\"\n data-menu-item-id={item.id}\n onClick={() => {\n item.onClick?.()\n setOpen(false)\n }}\n >\n {inner}\n </button>\n )\n },\n [menuItemClass, menuIconClass, resolveMenuLabel],\n )\n\n const renderBuiltInItem = React.useCallback(\n (id: string) => {\n if (id === 'change-password') {\n return (\n <Link\n key={id}\n href={changePasswordHref}\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setOpen(false)}\n >\n <Key className={menuIconClass} />\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.changePassword', 'Change Password')}</span>\n </Link>\n )\n }\n\n if (id === 'notifications' && notificationsHref) {\n return (\n <Link\n key={id}\n href={notificationsHref}\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setOpen(false)}\n >\n <Bell className={menuIconClass} />\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.notifications', 'Notification Preferences')}</span>\n </Link>\n )\n }\n\n if (id === 'theme-toggle') {\n if (!mounted) return null\n return (\n <div\n key={id}\n className={cn(menuItemClass, 'justify-between')}\n role=\"menuitem\"\n onClick={handleThemeToggle}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleThemeToggle()\n }\n }}\n tabIndex={0}\n >\n <span className=\"flex flex-1 items-center gap-3\">\n {isDark ? <Moon className={menuIconClass} /> : <Sun className={menuIconClass} />}\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.theme', 'Theme')}</span>\n </span>\n <Switch\n checked={isDark}\n onCheckedChange={handleThemeToggle}\n aria-label={t('ui.profileMenu.theme', 'Theme')}\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n )\n }\n\n if (id === 'language') {\n return (\n <div key={id} className=\"contents\">\n <button\n type=\"button\"\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setLanguageOpen(!languageOpen)}\n aria-expanded={languageOpen}\n >\n <Globe className={menuIconClass} />\n <span className=\"truncate\">{t('ui.profileMenu.language', 'Language')}</span>\n <span className=\"text-xs text-muted-foreground\">{localeLabels[currentLocale]}</span>\n <ChevronRight\n className={cn(\n 'ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform',\n languageOpen && 'rotate-90',\n )}\n aria-hidden=\"true\"\n />\n </button>\n {languageOpen && (\n <div className=\"ml-7 mr-1 flex flex-col gap-0.5 border-l pl-2 py-1\">\n {locales.map((locale) => (\n <button\n key={locale}\n type=\"button\"\n className={cn(\n 'flex h-8 w-full items-center justify-between gap-2 rounded-md px-2 text-sm transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60',\n locale === currentLocale ? 'font-medium text-foreground' : 'text-muted-foreground',\n )}\n onClick={() => handleLocaleChange(locale)}\n >\n <span className=\"truncate\">{localeLabels[locale]}</span>\n {locale === currentLocale && <Check className=\"size-3.5 shrink-0 text-accent-indigo\" aria-hidden=\"true\" />}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n }\n\n if (id === 'sign-out') {\n return (\n <form key={id} action=\"/api/auth/logout\" method=\"POST\">\n <button\n type=\"submit\"\n className={menuItemClass}\n role=\"menuitem\"\n >\n <LogOut className={menuIconClass} />\n <span className=\"flex-1 truncate text-left\">{t('ui.userMenu.logout', 'Sign Out')}</span>\n </button>\n </form>\n )\n }\n\n return null\n },\n [\n changePasswordHref,\n currentLocale,\n handleThemeToggle,\n isDark,\n languageOpen,\n menuItemClass,\n menuIconClass,\n mounted,\n notificationsHref,\n t,\n ],\n )\n\n return (\n <div className=\"relative\">\n <IconButton\n ref={buttonRef}\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n aria-haspopup=\"menu\"\n data-testid=\"profile-dropdown-trigger\"\n title={email || t('ui.userMenu.userFallback', 'User')}\n >\n <User className=\"size-4\" />\n </IconButton>\n\n {open && menuPosition && typeof document !== 'undefined' && createPortal(\n <div\n ref={menuRef}\n className=\"fixed z-popover w-64 overflow-hidden rounded-lg border bg-popover p-0 shadow-lg\"\n style={{ top: menuPosition.top, right: menuPosition.right }}\n role=\"menu\"\n data-testid=\"profile-dropdown\"\n >\n {/* User info header */}\n {(displayName || email) && (\n <div className=\"flex items-center gap-3 border-b bg-muted/30 px-3 py-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-full bg-accent-indigo/10 text-accent-indigo\">\n <User className=\"size-4\" aria-hidden=\"true\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {displayName ? (\n <span className=\"truncate text-sm font-medium leading-5 text-foreground\">{displayName}</span>\n ) : null}\n {email ? (\n <span className={cn(\n 'truncate text-xs leading-4',\n displayName ? 'text-muted-foreground' : 'text-foreground font-medium',\n )}>\n {email}\n </span>\n ) : null}\n {!displayName && email ? (\n <span className=\"truncate text-overline uppercase tracking-wider text-muted-foreground/80\">\n {t('ui.userMenu.loggedInAs', 'Logged in')}\n </span>\n ) : null}\n </div>\n </div>\n )}\n\n <div className=\"flex flex-col p-1.5\">\n {mergedMenuItems.map((item) => (\n <React.Fragment key={item.id}>\n {item.separator ? <div className=\"my-1 h-px bg-border\" aria-hidden=\"true\" /> : null}\n {item.source === 'injected'\n ? (item.href || item.onClick || item.label || item.labelKey ? renderInjectedItem(item) : null)\n : renderBuiltInItem(item.id)}\n </React.Fragment>\n ))}\n <InjectionSpot\n spotId={BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n </div>\n </div>,\n document.body,\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport Link from 'next/link'\nimport { User, LogOut, Bell, Moon, Sun, Globe, Key, Check, ChevronRight } from 'lucide-react'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport { useTheme } from '@open-mercato/ui/theme'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { IconButton } from '../primitives/icon-button'\nimport { Switch } from '../primitives/switch'\nimport { useInjectedMenuItems } from './injection/useInjectedMenuItems'\nimport { mergeMenuItems, type MergedMenuItem } from './injection/mergeMenuItems'\nimport { resolveInjectedIcon } from './injection/resolveInjectedIcon'\nimport { InjectionSpot } from './injection/InjectionSpot'\nimport { BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID } from './injection/spotIds'\n\nexport type ProfileDropdownProps = {\n email?: string\n displayName?: string\n changePasswordHref?: string\n notificationsHref?: string\n}\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\nconst localeLabels: Record<Locale, string> = {\n en: 'English',\n de: 'Deutsch',\n es: 'Espa\u00F1ol',\n pl: 'Polski',\n ko: '\uD55C\uAD6D\uC5B4',\n}\n\nexport function ProfileDropdown({\n email,\n displayName,\n changePasswordHref = '/backend/profile/change-password',\n notificationsHref,\n}: ProfileDropdownProps) {\n const t = useT()\n const currentLocale = useLocale()\n const { resolvedTheme, setTheme } = useTheme()\n const [open, setOpen] = React.useState(false)\n const [languageOpen, setLanguageOpen] = React.useState(false)\n const [mounted, setMounted] = React.useState(false)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const menuRef = React.useRef<HTMLDivElement>(null)\n const [menuPosition, setMenuPosition] = React.useState<{ top: number; right: number } | null>(null)\n const { items: injectedItems } = useInjectedMenuItems('menu:topbar:profile-dropdown')\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n // Position the portaled menu under the trigger. Rendering the menu in a body\n // portal keeps it out of the sticky header's backdrop-blur stacking context so\n // it always paints above page content such as the DataTable sticky Actions column.\n useIsomorphicLayoutEffect(() => {\n if (!open) {\n setMenuPosition(null)\n return\n }\n const updatePosition = () => {\n const trigger = buttonRef.current\n if (!trigger) return\n const rect = trigger.getBoundingClientRect()\n setMenuPosition({\n top: rect.bottom + 8,\n right: Math.max(0, window.innerWidth - rect.right),\n })\n }\n updatePosition()\n window.addEventListener('resize', updatePosition)\n window.addEventListener('scroll', updatePosition, true)\n return () => {\n window.removeEventListener('resize', updatePosition)\n window.removeEventListener('scroll', updatePosition, true)\n }\n }, [open])\n\n const isDark = resolvedTheme === 'dark'\n\n // Close on click outside\n React.useEffect(() => {\n if (!open) return\n function handleClick(event: MouseEvent) {\n if (\n menuRef.current &&\n !menuRef.current.contains(event.target as Node) &&\n buttonRef.current &&\n !buttonRef.current.contains(event.target as Node)\n ) {\n setOpen(false)\n setLanguageOpen(false)\n }\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [open])\n\n // Close on Escape\n React.useEffect(() => {\n if (!open) return\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n if (languageOpen) {\n setLanguageOpen(false)\n } else {\n setOpen(false)\n buttonRef.current?.focus()\n }\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open, languageOpen])\n\n const handleThemeToggle = () => {\n setTheme(isDark ? 'light' : 'dark')\n }\n\n const handleLocaleChange = async (locale: Locale) => {\n try {\n await fetch('/api/auth/locale', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ locale }),\n })\n window.location.reload()\n } catch {}\n }\n\n // Unified row class \u2014 every menu item uses this for perfectly aligned layout.\n // h-9 keeps all rows the same height regardless of whether they have a trailing element.\n const menuItemClass = cn(\n 'group flex h-9 w-full items-center gap-3 rounded-md px-2.5 text-sm text-foreground',\n 'transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60',\n 'cursor-pointer',\n )\n const menuIconClass = 'size-4 shrink-0 text-muted-foreground group-hover:text-foreground'\n\n const resolveMenuLabel = React.useCallback(\n (item: Pick<MergedMenuItem, 'id' | 'label' | 'labelKey'>): string => {\n if (item.labelKey && item.label) return t(item.labelKey, item.label)\n if (item.labelKey) return t(item.labelKey, item.id)\n if (item.label && item.label.includes('.')) return t(item.label, item.id)\n return item.label ?? item.id\n },\n [t],\n )\n\n const builtInMenuItems = React.useMemo(\n () => {\n const items: Array<{ id: string; separator?: boolean }> = [{ id: 'change-password' }]\n if (notificationsHref) items.push({ id: 'notifications' })\n items.push({ id: 'theme-toggle', separator: true }, { id: 'language' }, { id: 'sign-out', separator: true })\n return items\n },\n [notificationsHref],\n )\n\n const mergedMenuItems = React.useMemo(\n () => mergeMenuItems(builtInMenuItems, injectedItems),\n [builtInMenuItems, injectedItems],\n )\n const injectionContext = React.useMemo(\n () => ({\n email,\n displayName,\n locale: currentLocale,\n }),\n [currentLocale, displayName, email],\n )\n\n const renderInjectedItem = React.useCallback(\n (item: MergedMenuItem) => {\n const label = resolveMenuLabel(item)\n const icon = resolveInjectedIcon(item.icon)\n const inner = (\n <>\n <span className={menuIconClass}>{icon}</span>\n <span className=\"flex-1 truncate\">{label}</span>\n </>\n )\n if (item.href) {\n return (\n <Link\n key={item.id}\n href={item.href}\n className={menuItemClass}\n role=\"menuitem\"\n data-menu-item-id={item.id}\n onClick={() => setOpen(false)}\n >\n {inner}\n </Link>\n )\n }\n return (\n <button\n key={item.id}\n type=\"button\"\n className={menuItemClass}\n role=\"menuitem\"\n data-menu-item-id={item.id}\n onClick={() => {\n item.onClick?.()\n setOpen(false)\n }}\n >\n {inner}\n </button>\n )\n },\n [menuItemClass, menuIconClass, resolveMenuLabel],\n )\n\n const renderBuiltInItem = React.useCallback(\n (id: string) => {\n if (id === 'change-password') {\n return (\n <Link\n key={id}\n href={changePasswordHref}\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setOpen(false)}\n >\n <Key className={menuIconClass} />\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.changePassword', 'Change Password')}</span>\n </Link>\n )\n }\n\n if (id === 'notifications' && notificationsHref) {\n return (\n <Link\n key={id}\n href={notificationsHref}\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setOpen(false)}\n >\n <Bell className={menuIconClass} />\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.notifications', 'Notification Preferences')}</span>\n </Link>\n )\n }\n\n if (id === 'theme-toggle') {\n if (!mounted) return null\n return (\n <div\n key={id}\n className={cn(menuItemClass, 'justify-between')}\n role=\"menuitem\"\n onClick={handleThemeToggle}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleThemeToggle()\n }\n }}\n tabIndex={0}\n >\n <span className=\"flex flex-1 items-center gap-3\">\n {isDark ? <Moon className={menuIconClass} /> : <Sun className={menuIconClass} />}\n <span className=\"flex-1 truncate\">{t('ui.profileMenu.theme', 'Theme')}</span>\n </span>\n <Switch\n checked={isDark}\n onCheckedChange={handleThemeToggle}\n aria-label={t('ui.profileMenu.theme', 'Theme')}\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n )\n }\n\n if (id === 'language') {\n return (\n <div key={id} className=\"contents\">\n <button\n type=\"button\"\n className={menuItemClass}\n role=\"menuitem\"\n onClick={() => setLanguageOpen(!languageOpen)}\n aria-expanded={languageOpen}\n >\n <Globe className={menuIconClass} />\n <span className=\"truncate\">{t('ui.profileMenu.language', 'Language')}</span>\n <span className=\"text-xs text-muted-foreground\">{localeLabels[currentLocale]}</span>\n <ChevronRight\n className={cn(\n 'ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform',\n languageOpen && 'rotate-90',\n )}\n aria-hidden=\"true\"\n />\n </button>\n {languageOpen && (\n <div className=\"ml-7 mr-1 flex flex-col gap-0.5 border-l pl-2 py-1\">\n {locales.map((locale) => (\n <button\n key={locale}\n type=\"button\"\n className={cn(\n 'flex h-8 w-full items-center justify-between gap-2 rounded-md px-2 text-sm transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60',\n locale === currentLocale ? 'font-medium text-foreground' : 'text-muted-foreground',\n )}\n onClick={() => handleLocaleChange(locale)}\n >\n <span className=\"truncate\">{localeLabels[locale]}</span>\n {locale === currentLocale && <Check className=\"size-3.5 shrink-0 text-accent-indigo\" aria-hidden=\"true\" />}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n }\n\n if (id === 'sign-out') {\n return (\n <form key={id} action=\"/api/auth/logout\" method=\"POST\">\n <button\n type=\"submit\"\n className={menuItemClass}\n role=\"menuitem\"\n >\n <LogOut className={menuIconClass} />\n <span className=\"flex-1 truncate text-left\">{t('ui.userMenu.logout', 'Sign Out')}</span>\n </button>\n </form>\n )\n }\n\n return null\n },\n [\n changePasswordHref,\n currentLocale,\n handleThemeToggle,\n isDark,\n languageOpen,\n menuItemClass,\n menuIconClass,\n mounted,\n notificationsHref,\n t,\n ],\n )\n\n return (\n <div className=\"relative\">\n <IconButton\n ref={buttonRef}\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n aria-haspopup=\"menu\"\n data-testid=\"profile-dropdown-trigger\"\n title={email || t('ui.userMenu.userFallback', 'User')}\n >\n <User className=\"size-4\" />\n </IconButton>\n\n {open && menuPosition && typeof document !== 'undefined' && createPortal(\n <div\n ref={menuRef}\n className=\"fixed z-popover w-64 overflow-hidden rounded-lg border bg-popover p-0 shadow-lg\"\n style={{ top: menuPosition.top, right: menuPosition.right }}\n role=\"menu\"\n data-testid=\"profile-dropdown\"\n >\n {/* User info header */}\n {(displayName || email) && (\n <div className=\"flex items-center gap-3 border-b bg-muted/30 px-3 py-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-full bg-accent-indigo/10 text-accent-indigo\">\n <User className=\"size-4\" aria-hidden=\"true\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {displayName ? (\n <span className=\"truncate text-sm font-medium leading-5 text-foreground\">{displayName}</span>\n ) : null}\n {email ? (\n <span className={cn(\n 'truncate text-xs leading-4',\n displayName ? 'text-muted-foreground' : 'text-foreground font-medium',\n )}>\n {email}\n </span>\n ) : null}\n {!displayName && email ? (\n <span className=\"truncate text-overline uppercase tracking-wider text-muted-foreground/80\">\n {t('ui.userMenu.loggedInAs', 'Logged in')}\n </span>\n ) : null}\n </div>\n </div>\n )}\n\n <div className=\"flex flex-col p-1.5\">\n {mergedMenuItems.map((item) => (\n <React.Fragment key={item.id}>\n {item.separator ? <div className=\"my-1 h-px bg-border\" aria-hidden=\"true\" /> : null}\n {item.source === 'injected'\n ? (item.href || item.onClick || item.label || item.labelKey ? renderInjectedItem(item) : null)\n : renderBuiltInItem(item.id)}\n </React.Fragment>\n ))}\n <InjectionSpot\n spotId={BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n </div>\n </div>,\n document.body,\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -115,7 +115,7 @@ function RowActions({ items = [] }) {
115
115
  "a",
116
116
  {
117
117
  href: it.href,
118
- className: `block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? "text-red-600" : ""}`,
118
+ className: `block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? "text-destructive" : ""}`,
119
119
  role: "menuitem",
120
120
  onClick: (event) => {
121
121
  event.stopPropagation();
@@ -130,7 +130,7 @@ function RowActions({ items = [] }) {
130
130
  type: "button",
131
131
  variant: "ghost",
132
132
  size: "sm",
133
- className: `w-full justify-start rounded-none font-normal ${it.destructive ? "text-red-600" : ""}`,
133
+ className: `w-full justify-start rounded-none font-normal ${it.destructive ? "text-destructive" : ""}`,
134
134
  role: "menuitem",
135
135
  onClick: (event) => {
136
136
  event.stopPropagation();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/RowActions.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { IconButton } from '../primitives/icon-button'\nimport { Button } from '../primitives/button'\n\nexport type RowActionItem = {\n id?: string\n label: string\n onSelect?: () => void\n href?: string\n destructive?: boolean\n}\n\nexport function RowActions({ items = [] }: { items?: RowActionItem[] }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const btnRef = React.useRef<HTMLButtonElement>(null)\n const menuRef = React.useRef<HTMLDivElement>(null)\n const hoverTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)\n const [anchorRect, setAnchorRect] = React.useState<DOMRect | null>(null)\n const [direction, setDirection] = React.useState<'down' | 'up'>('down')\n\n const updatePosition = React.useCallback(() => {\n if (!btnRef.current) return\n const rect = btnRef.current.getBoundingClientRect()\n setAnchorRect(rect)\n // Decide whether to open up or down based on available viewport space\n const spaceBelow = window.innerHeight - rect.bottom\n const spaceAbove = rect.top\n setDirection(spaceBelow < 180 && spaceAbove > spaceBelow ? 'up' : 'down')\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n updatePosition()\n function onDocClick(e: MouseEvent) {\n const t = e.target as Node\n if (menuRef.current && !menuRef.current.contains(t) && btnRef.current && !btnRef.current.contains(t)) {\n setOpen(false)\n }\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n setOpen(false)\n btnRef.current?.focus()\n }\n }\n function onScrollOrResize() {\n updatePosition()\n }\n document.addEventListener('mousedown', onDocClick)\n document.addEventListener('keydown', onKey)\n window.addEventListener('scroll', onScrollOrResize, true)\n window.addEventListener('resize', onScrollOrResize)\n return () => {\n document.removeEventListener('mousedown', onDocClick)\n document.removeEventListener('keydown', onKey)\n window.removeEventListener('scroll', onScrollOrResize, true)\n window.removeEventListener('resize', onScrollOrResize)\n }\n }, [open, updatePosition])\n\n // Cleanup timeout on unmount\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n if (items.length === 0) return null\n\n const handlePointerEnter = (event: React.PointerEvent) => {\n if (event.pointerType === 'touch') return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n setOpen(true)\n }\n\n const handlePointerLeave = (event: React.PointerEvent) => {\n if (event.pointerType === 'touch') return\n hoverTimeoutRef.current = setTimeout(() => {\n setOpen(false)\n }, 150)\n }\n\n return (\n <div\n className=\"relative inline-block text-left\"\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n <IconButton\n ref={btnRef}\n type=\"button\"\n variant=\"ghost\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={(e) => { e.stopPropagation(); setOpen(true); requestAnimationFrame(updatePosition) }}\n >\n <span aria-hidden=\"true\">\u22EF</span>\n <span className=\"sr-only\">{t('ui.rowActions.openActions', 'Open actions')}</span>\n </IconButton>\n {open && anchorRect && createPortal(\n <div\n ref={menuRef}\n role=\"menu\"\n className=\"fixed w-44 max-w-[calc(100vw-1rem)] rounded-md border bg-background p-1 shadow focus-visible:outline-none z-dropdown\"\n style={{\n top: direction === 'down' ? anchorRect.bottom + 8 : anchorRect.top - 8,\n left: Math.min(anchorRect.right, window.innerWidth - 8),\n transform: `translate(-100%, ${direction === 'down' ? '0' : '-100%'})`,\n }}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n {items.map((it, idx) => (\n it.href ? (\n <a\n key={idx}\n href={it.href}\n className={`block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? 'text-red-600' : ''}`}\n role=\"menuitem\"\n onClick={(event) => {\n event.stopPropagation()\n setOpen(false)\n }}\n >\n {it.label}\n </a>\n ) : (\n <Button\n key={idx}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={`w-full justify-start rounded-none font-normal ${it.destructive ? 'text-red-600' : ''}`}\n role=\"menuitem\"\n onClick={(event) => {\n event.stopPropagation()\n setOpen(false)\n it.onSelect?.()\n }}\n >\n {it.label}\n </Button>\n )\n ))}\n </div>,\n document.body\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { IconButton } from '../primitives/icon-button'\nimport { Button } from '../primitives/button'\n\nexport type RowActionItem = {\n id?: string\n label: string\n onSelect?: () => void\n href?: string\n destructive?: boolean\n}\n\nexport function RowActions({ items = [] }: { items?: RowActionItem[] }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const btnRef = React.useRef<HTMLButtonElement>(null)\n const menuRef = React.useRef<HTMLDivElement>(null)\n const hoverTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)\n const [anchorRect, setAnchorRect] = React.useState<DOMRect | null>(null)\n const [direction, setDirection] = React.useState<'down' | 'up'>('down')\n\n const updatePosition = React.useCallback(() => {\n if (!btnRef.current) return\n const rect = btnRef.current.getBoundingClientRect()\n setAnchorRect(rect)\n // Decide whether to open up or down based on available viewport space\n const spaceBelow = window.innerHeight - rect.bottom\n const spaceAbove = rect.top\n setDirection(spaceBelow < 180 && spaceAbove > spaceBelow ? 'up' : 'down')\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n updatePosition()\n function onDocClick(e: MouseEvent) {\n const t = e.target as Node\n if (menuRef.current && !menuRef.current.contains(t) && btnRef.current && !btnRef.current.contains(t)) {\n setOpen(false)\n }\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n setOpen(false)\n btnRef.current?.focus()\n }\n }\n function onScrollOrResize() {\n updatePosition()\n }\n document.addEventListener('mousedown', onDocClick)\n document.addEventListener('keydown', onKey)\n window.addEventListener('scroll', onScrollOrResize, true)\n window.addEventListener('resize', onScrollOrResize)\n return () => {\n document.removeEventListener('mousedown', onDocClick)\n document.removeEventListener('keydown', onKey)\n window.removeEventListener('scroll', onScrollOrResize, true)\n window.removeEventListener('resize', onScrollOrResize)\n }\n }, [open, updatePosition])\n\n // Cleanup timeout on unmount\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n if (items.length === 0) return null\n\n const handlePointerEnter = (event: React.PointerEvent) => {\n if (event.pointerType === 'touch') return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n setOpen(true)\n }\n\n const handlePointerLeave = (event: React.PointerEvent) => {\n if (event.pointerType === 'touch') return\n hoverTimeoutRef.current = setTimeout(() => {\n setOpen(false)\n }, 150)\n }\n\n return (\n <div\n className=\"relative inline-block text-left\"\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n <IconButton\n ref={btnRef}\n type=\"button\"\n variant=\"ghost\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={(e) => { e.stopPropagation(); setOpen(true); requestAnimationFrame(updatePosition) }}\n >\n <span aria-hidden=\"true\">\u22EF</span>\n <span className=\"sr-only\">{t('ui.rowActions.openActions', 'Open actions')}</span>\n </IconButton>\n {open && anchorRect && createPortal(\n <div\n ref={menuRef}\n role=\"menu\"\n className=\"fixed w-44 max-w-[calc(100vw-1rem)] rounded-md border bg-background p-1 shadow focus-visible:outline-none z-dropdown\"\n style={{\n top: direction === 'down' ? anchorRect.bottom + 8 : anchorRect.top - 8,\n left: Math.min(anchorRect.right, window.innerWidth - 8),\n transform: `translate(-100%, ${direction === 'down' ? '0' : '-100%'})`,\n }}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n {items.map((it, idx) => (\n it.href ? (\n <a\n key={idx}\n href={it.href}\n className={`block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? 'text-destructive' : ''}`}\n role=\"menuitem\"\n onClick={(event) => {\n event.stopPropagation()\n setOpen(false)\n }}\n >\n {it.label}\n </a>\n ) : (\n <Button\n key={idx}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={`w-full justify-start rounded-none font-normal ${it.destructive ? 'text-destructive' : ''}`}\n role=\"menuitem\"\n onClick={(event) => {\n event.stopPropagation()\n setOpen(false)\n it.onSelect?.()\n }}\n >\n {it.label}\n </Button>\n )\n ))}\n </div>,\n document.body\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["t"]
7
7
  }