@open-mercato/ui 0.6.7-develop.6828.1.ab1620a63e → 0.6.7-develop.6842.1.8cd8a8883c

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 (46) hide show
  1. package/dist/backend/JsonBuilder.js +3 -3
  2. package/dist/backend/JsonBuilder.js.map +2 -2
  3. package/dist/backend/ProfileDropdown.js +2 -1
  4. package/dist/backend/ProfileDropdown.js.map +2 -2
  5. package/dist/backend/RowActions.js +2 -2
  6. package/dist/backend/RowActions.js.map +2 -2
  7. package/dist/backend/confirm-dialog/ConfirmDialog.js +1 -1
  8. package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
  9. package/dist/backend/detail/AttachmentDeleteDialog.js +1 -1
  10. package/dist/backend/detail/AttachmentDeleteDialog.js.map +2 -2
  11. package/dist/backend/detail/AttachmentsSection.js +1 -1
  12. package/dist/backend/detail/AttachmentsSection.js.map +2 -2
  13. package/dist/backend/detail/NotesSection.js +1 -1
  14. package/dist/backend/detail/NotesSection.js.map +2 -2
  15. package/dist/backend/detail/TagsSection.js +1 -1
  16. package/dist/backend/detail/TagsSection.js.map +2 -2
  17. package/dist/backend/forms/FormHeader.js +1 -2
  18. package/dist/backend/forms/FormHeader.js.map +2 -2
  19. package/dist/backend/schedule/ScheduleAgenda.js +3 -3
  20. package/dist/backend/schedule/ScheduleAgenda.js.map +1 -1
  21. package/dist/backend/schedule/ScheduleGrid.js +3 -3
  22. package/dist/backend/schedule/ScheduleGrid.js.map +1 -1
  23. package/dist/backend/utils/apiCall.js +21 -1
  24. package/dist/backend/utils/apiCall.js.map +2 -2
  25. package/dist/frontend/LanguageSwitcher.js +2 -1
  26. package/dist/frontend/LanguageSwitcher.js.map +2 -2
  27. package/dist/primitives/button.js +9 -4
  28. package/dist/primitives/button.js.map +2 -2
  29. package/package.json +4 -4
  30. package/src/backend/JsonBuilder.tsx +3 -3
  31. package/src/backend/ProfileDropdown.tsx +1 -0
  32. package/src/backend/RowActions.tsx +2 -2
  33. package/src/backend/confirm-dialog/ConfirmDialog.tsx +1 -1
  34. package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +35 -0
  35. package/src/backend/detail/AttachmentDeleteDialog.tsx +1 -1
  36. package/src/backend/detail/AttachmentsSection.tsx +1 -1
  37. package/src/backend/detail/NotesSection.tsx +1 -1
  38. package/src/backend/detail/TagsSection.tsx +1 -1
  39. package/src/backend/forms/FormHeader.tsx +1 -2
  40. package/src/backend/schedule/ScheduleAgenda.tsx +3 -3
  41. package/src/backend/schedule/ScheduleGrid.tsx +3 -3
  42. package/src/backend/utils/__tests__/apiCall.test.ts +61 -0
  43. package/src/backend/utils/apiCall.ts +29 -1
  44. package/src/frontend/LanguageSwitcher.tsx +1 -0
  45. package/src/primitives/__tests__/button.test.tsx +70 -0
  46. package/src/primitives/button.tsx +10 -4
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/detail/TagsSection.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Pencil, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '../injection/useRegisteredComponent'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui')\n\nexport type TagOption = {\n id: string\n label: string\n color?: string | null\n}\n\nexport type TagsSectionLabels = {\n loading: string\n placeholder: string\n empty: string\n loadError: string\n createError: string\n updateError: string\n labelRequired: string\n saveShortcut: string\n cancelShortcut: string\n edit?: string\n cancel?: string\n success?: string\n saving?: string\n autoSaveHint?: string\n}\n\nexport type TagsSectionController = {\n flush: () => Promise<void>\n}\n\nexport type TagsSectionProps = {\n title: string\n tags: TagOption[]\n onChange?: (next: TagOption[]) => void\n isSubmitting?: boolean\n canEdit?: boolean\n autoSave?: boolean\n controllerRef?: React.MutableRefObject<TagsSectionController | null>\n loadOptions: (query?: string) => Promise<TagOption[]>\n createTag: (label: string) => Promise<TagOption>\n onSave: (params: {\n next: TagOption[]\n added: TagOption[]\n removed: TagOption[]\n }) => Promise<void>\n labels: TagsSectionLabels\n}\n\nfunction normalizeTagLabels(labels: string[]): string[] {\n return Array.from(\n new Set(\n labels\n .map((label) => label.trim().toLowerCase())\n .filter((label) => label.length > 0),\n ),\n ).sort((a, b) => a.localeCompare(b))\n}\n\nfunction buildTagLabelKey(labels: string[]): string {\n return normalizeTagLabels(labels).join('\\0')\n}\n\nfunction TagsSectionImpl({\n title,\n tags,\n onChange,\n isSubmitting = false,\n canEdit = true,\n autoSave = false,\n loadOptions,\n createTag,\n onSave,\n labels,\n controllerRef,\n}: TagsSectionProps) {\n const [editing, setEditing] = React.useState(autoSave)\n const [draft, setDraft] = React.useState<string[]>([])\n const [saving, setSaving] = React.useState(false)\n const [, setOptions] = React.useState<Map<string, TagOption>>(() => new Map())\n const [loadingOptions, setLoadingOptions] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const draftRef = React.useRef<string[]>([])\n const savedTagsRef = React.useRef<TagOption[]>(tags)\n const optionsRef = React.useRef<Map<string, TagOption>>(new Map())\n const saveTaskRef = React.useRef<Promise<void> | null>(null)\n React.useEffect(() => {\n savedTagsRef.current = tags\n setOptions((prev) => {\n const next = new Map(prev)\n for (const tag of tags) {\n next.set(tag.label.toLowerCase(), tag)\n }\n optionsRef.current = next\n return next\n })\n }, [tags])\n\n React.useEffect(() => {\n draftRef.current = draft\n }, [draft])\n\n const syncFetchedOptions = React.useCallback((fetched: TagOption[]) => {\n if (!fetched.length) return\n setOptions((prev) => {\n const next = new Map(prev)\n for (const tag of fetched) {\n next.set(tag.label.toLowerCase(), tag)\n }\n optionsRef.current = next\n return next\n })\n }, [])\n\n const hasPendingDraftChanges = React.useCallback(() => {\n if (!autoSave || !autoSaveUserEditedRef.current) return false\n const draftLabels = normalizeTagLabels(draftRef.current)\n const savedLabels = normalizeTagLabels(savedTagsRef.current.map((tag) => tag.label))\n if (draftLabels.length !== savedLabels.length) return true\n return draftLabels.some((label, index) => label !== savedLabels[index])\n }, [autoSave])\n\n const loadSuggestions = React.useCallback(\n async (query?: string) => {\n try {\n const fetched = await loadOptions(query)\n syncFetchedOptions(fetched)\n return fetched.map((tag) => tag.label)\n } catch (err) {\n logger.error('Failed to load tag suggestions', { err })\n return []\n }\n },\n [loadOptions, syncFetchedOptions],\n )\n\n const startEditing = React.useCallback(async () => {\n if (editing || isSubmitting || !canEdit) return\n setError(null)\n setDraft(tags.map((tag) => tag.label))\n setEditing(true)\n setLoadingOptions(true)\n try {\n const fetched = await loadOptions()\n syncFetchedOptions(fetched)\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.loadError\n setError(message)\n flash(message, 'error')\n } finally {\n setLoadingOptions(false)\n }\n }, [canEdit, editing, isSubmitting, labels.loadError, loadOptions, syncFetchedOptions, tags])\n\n const cancelEditing = React.useCallback(() => {\n setEditing(false)\n setDraft([])\n setError(null)\n }, [])\n\n const ensureTagOption = React.useCallback(\n async (label: string): Promise<TagOption> => {\n const normalized = label.trim()\n if (!normalized.length) {\n throw new Error(labels.labelRequired)\n }\n const existing = optionsRef.current.get(normalized.toLowerCase())\n if (existing) return existing\n try {\n const created = await createTag(normalized)\n setOptions((prev) => {\n const next = new Map(prev)\n next.set(created.label.toLowerCase(), created)\n optionsRef.current = next\n return next\n })\n return created\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.createError\n throw new Error(message)\n }\n },\n [createTag, labels.createError, labels.labelRequired],\n )\n\n const handleSave = React.useCallback(async () => {\n if (saveTaskRef.current) {\n await saveTaskRef.current\n if (!autoSave || !hasPendingDraftChanges()) return\n }\n if (autoSave && !hasPendingDraftChanges()) {\n autoSaveUserEditedRef.current = false\n return\n }\n\n const trimmed = draftRef.current.map((label) => label.trim()).filter((label) => label.length > 0)\n const uniqueLabels = Array.from(new Set(trimmed.map((label) => label.toLowerCase())))\n\n let shouldContinueAutoSave = false\n\n const task = (async () => {\n const currentTags = savedTagsRef.current\n const currentIds = new Set(currentTags.map((tag) => tag.id))\n const submittedDraftKey = buildTagLabelKey(trimmed)\n\n setSaving(true)\n setError(null)\n try {\n const finalTagOptions = await Promise.all(\n uniqueLabels.map((normalized) => {\n const existing = optionsRef.current.get(normalized)\n if (existing) return existing\n const matchingLabel = trimmed.find((label) => label.toLowerCase() === normalized) ?? normalized\n return ensureTagOption(matchingLabel)\n }),\n )\n\n const finalIds = new Set(finalTagOptions.map((tag) => tag.id))\n const added = finalTagOptions.filter((tag) => !currentIds.has(tag.id))\n const removed = currentTags.filter((tag) => !finalIds.has(tag.id))\n\n if (added.length > 0 || removed.length > 0) {\n await onSave({ next: finalTagOptions, added, removed })\n }\n\n savedTagsRef.current = finalTagOptions\n onChange?.(finalTagOptions)\n if (autoSave) {\n const latestDraftKey = buildTagLabelKey(draftRef.current)\n if (latestDraftKey === submittedDraftKey) {\n autoSaveUserEditedRef.current = false\n setDraft(finalTagOptions.map((tag) => tag.label))\n } else {\n shouldContinueAutoSave = true\n }\n } else {\n setEditing(false)\n setDraft([])\n }\n if (labels.success && (added.length > 0 || removed.length > 0)) flash(labels.success, 'success')\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.updateError\n setError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n })()\n\n saveTaskRef.current = task\n try {\n await task\n } finally {\n saveTaskRef.current = null\n }\n if (shouldContinueAutoSave) {\n void handleSave()\n }\n }, [autoSave, ensureTagOption, hasPendingDraftChanges, labels.success, labels.updateError, onChange, onSave])\n\n const activeTags = editing ? draft : tags.map((tag) => tag.label)\n\n const handleEditingKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (!editing) return\n if (event.key === 'Escape') {\n event.preventDefault()\n cancelEditing()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (saving || isSubmitting) return\n void handleSave()\n }\n },\n [cancelEditing, editing, handleSave, isSubmitting, saving],\n )\n\n const autoSaveInitializedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (!autoSave || autoSaveInitializedRef.current) return\n autoSaveInitializedRef.current = true\n setEditing(true)\n setDraft(tags.map((tag) => tag.label))\n let cancelled = false\n loadOptions().then((fetched) => {\n if (cancelled) return\n syncFetchedOptions(fetched)\n }).catch(() => {})\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [autoSave])\n\n const autoSaveUserEditedRef = React.useRef(false)\n const originalOnChange = React.useCallback(\n (values: string[]) => {\n autoSaveUserEditedRef.current = true\n setDraft(values)\n },\n [],\n )\n\n React.useEffect(() => {\n if (!autoSave || !editing || !autoSaveUserEditedRef.current) return\n if (!hasPendingDraftChanges()) return\n void handleSave()\n }, [autoSave, draft, editing, handleSave, hasPendingDraftChanges])\n\n const flush = React.useCallback(async () => {\n if (saveTaskRef.current) {\n await saveTaskRef.current\n if (!autoSave || !editing || !hasPendingDraftChanges()) return\n }\n if (!autoSave || !editing || !hasPendingDraftChanges()) return\n await handleSave()\n }, [autoSave, editing, handleSave, hasPendingDraftChanges])\n\n React.useEffect(() => {\n if (!controllerRef) return\n controllerRef.current = { flush }\n return () => {\n if (controllerRef.current?.flush === flush) {\n controllerRef.current = null\n }\n }\n }, [controllerRef, flush])\n\n const disableInteraction = isSubmitting || !canEdit\n const autoSaveStatusLabel = saving\n ? (labels.saving ?? 'Saving\u2026')\n : (labels.autoSaveHint ?? null)\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between group\">\n <h2 className=\"text-sm font-semibold\">\n {title}\n </h2>\n {autoSave ? (\n autoSaveStatusLabel ? <p className=\"text-xs text-muted-foreground\">{autoSaveStatusLabel}</p> : null\n ) : (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={editing ? cancelEditing : startEditing}\n disabled={disableInteraction || saving}\n className={\n editing\n ? 'opacity-100 transition-opacity duration-150'\n : 'opacity-100 md:opacity-0 transition-opacity duration-150 md:group-hover:opacity-100 focus-visible:opacity-100'\n }\n >\n {editing ? <X className=\"h-4 w-4\" /> : <Pencil className=\"h-4 w-4\" />}\n <span className=\"sr-only\">\n {editing ? labels.cancel ?? 'Cancel' : labels.edit ?? 'Edit'}\n </span>\n </Button>\n )}\n </div>\n\n {editing ? (\n <div className=\"rounded-lg border bg-card p-4 space-y-3\">\n <DataLoader\n isLoading={loadingOptions}\n loadingMessage={labels.loading}\n spinnerSize=\"sm\"\n >\n <div className=\"space-y-3\" onKeyDown={handleEditingKeyDown}>\n <TagsInput\n value={activeTags}\n onChange={autoSave ? originalOnChange : (values) => setDraft(values)}\n placeholder={labels.placeholder}\n loadSuggestions={loadSuggestions}\n autoFocus={!autoSave}\n />\n {error ? <p className=\"text-xs text-red-600\">{error}</p> : null}\n {autoSave ? null : (\n <div className=\"flex items-center gap-2 mt-3 mb-2\">\n <Button type=\"button\" size=\"sm\" onClick={handleSave} disabled={saving || isSubmitting}>\n {saving ? (\n <span className=\"mr-2 h-4 w-4 animate-spin rounded-full border border-background border-t-primary\" />\n ) : null}\n {labels.saveShortcut}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={cancelEditing}\n disabled={saving || isSubmitting}\n >\n {labels.cancelShortcut}\n </Button>\n </div>\n )}\n </div>\n </DataLoader>\n </div>\n ) : (\n <div\n className=\"group/tags relative rounded-lg border bg-muted/30 p-4 transition-colors hover:border-primary/40 focus-visible:border-primary focus-visible:outline-none\"\n role={disableInteraction ? undefined : 'button'}\n tabIndex={disableInteraction ? -1 : 0}\n onClick={disableInteraction ? undefined : startEditing}\n onKeyDown={(event) => {\n if (disableInteraction) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n void startEditing()\n }\n }}\n >\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute right-3 top-3 text-muted-foreground opacity-0 transition-opacity duration-150 group-hover/tags:opacity-100 group-focus-within/tags:opacity-100\"\n >\n <Pencil className=\"h-4 w-4\" />\n </span>\n {tags.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {labels.empty}\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {tags.map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium\"\n style={tag.color ? { borderColor: tag.color, color: tag.color } : undefined}\n >\n {tag.label}\n </span>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n\nexport function TagsSection(props: TagsSectionProps) {\n const handle = ComponentReplacementHandles.section('ui.detail', 'TagsSection')\n const Resolved = useRegisteredComponent<TagsSectionProps>(\n handle,\n TagsSectionImpl as React.ComponentType<TagsSectionProps>,\n )\n\n return (\n <div data-component-handle={handle}>\n <Resolved {...props} />\n </div>\n )\n}\n\nexport default TagsSection\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Pencil, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '../injection/useRegisteredComponent'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui')\n\nexport type TagOption = {\n id: string\n label: string\n color?: string | null\n}\n\nexport type TagsSectionLabels = {\n loading: string\n placeholder: string\n empty: string\n loadError: string\n createError: string\n updateError: string\n labelRequired: string\n saveShortcut: string\n cancelShortcut: string\n edit?: string\n cancel?: string\n success?: string\n saving?: string\n autoSaveHint?: string\n}\n\nexport type TagsSectionController = {\n flush: () => Promise<void>\n}\n\nexport type TagsSectionProps = {\n title: string\n tags: TagOption[]\n onChange?: (next: TagOption[]) => void\n isSubmitting?: boolean\n canEdit?: boolean\n autoSave?: boolean\n controllerRef?: React.MutableRefObject<TagsSectionController | null>\n loadOptions: (query?: string) => Promise<TagOption[]>\n createTag: (label: string) => Promise<TagOption>\n onSave: (params: {\n next: TagOption[]\n added: TagOption[]\n removed: TagOption[]\n }) => Promise<void>\n labels: TagsSectionLabels\n}\n\nfunction normalizeTagLabels(labels: string[]): string[] {\n return Array.from(\n new Set(\n labels\n .map((label) => label.trim().toLowerCase())\n .filter((label) => label.length > 0),\n ),\n ).sort((a, b) => a.localeCompare(b))\n}\n\nfunction buildTagLabelKey(labels: string[]): string {\n return normalizeTagLabels(labels).join('\\0')\n}\n\nfunction TagsSectionImpl({\n title,\n tags,\n onChange,\n isSubmitting = false,\n canEdit = true,\n autoSave = false,\n loadOptions,\n createTag,\n onSave,\n labels,\n controllerRef,\n}: TagsSectionProps) {\n const [editing, setEditing] = React.useState(autoSave)\n const [draft, setDraft] = React.useState<string[]>([])\n const [saving, setSaving] = React.useState(false)\n const [, setOptions] = React.useState<Map<string, TagOption>>(() => new Map())\n const [loadingOptions, setLoadingOptions] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const draftRef = React.useRef<string[]>([])\n const savedTagsRef = React.useRef<TagOption[]>(tags)\n const optionsRef = React.useRef<Map<string, TagOption>>(new Map())\n const saveTaskRef = React.useRef<Promise<void> | null>(null)\n React.useEffect(() => {\n savedTagsRef.current = tags\n setOptions((prev) => {\n const next = new Map(prev)\n for (const tag of tags) {\n next.set(tag.label.toLowerCase(), tag)\n }\n optionsRef.current = next\n return next\n })\n }, [tags])\n\n React.useEffect(() => {\n draftRef.current = draft\n }, [draft])\n\n const syncFetchedOptions = React.useCallback((fetched: TagOption[]) => {\n if (!fetched.length) return\n setOptions((prev) => {\n const next = new Map(prev)\n for (const tag of fetched) {\n next.set(tag.label.toLowerCase(), tag)\n }\n optionsRef.current = next\n return next\n })\n }, [])\n\n const hasPendingDraftChanges = React.useCallback(() => {\n if (!autoSave || !autoSaveUserEditedRef.current) return false\n const draftLabels = normalizeTagLabels(draftRef.current)\n const savedLabels = normalizeTagLabels(savedTagsRef.current.map((tag) => tag.label))\n if (draftLabels.length !== savedLabels.length) return true\n return draftLabels.some((label, index) => label !== savedLabels[index])\n }, [autoSave])\n\n const loadSuggestions = React.useCallback(\n async (query?: string) => {\n try {\n const fetched = await loadOptions(query)\n syncFetchedOptions(fetched)\n return fetched.map((tag) => tag.label)\n } catch (err) {\n logger.error('Failed to load tag suggestions', { err })\n return []\n }\n },\n [loadOptions, syncFetchedOptions],\n )\n\n const startEditing = React.useCallback(async () => {\n if (editing || isSubmitting || !canEdit) return\n setError(null)\n setDraft(tags.map((tag) => tag.label))\n setEditing(true)\n setLoadingOptions(true)\n try {\n const fetched = await loadOptions()\n syncFetchedOptions(fetched)\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.loadError\n setError(message)\n flash(message, 'error')\n } finally {\n setLoadingOptions(false)\n }\n }, [canEdit, editing, isSubmitting, labels.loadError, loadOptions, syncFetchedOptions, tags])\n\n const cancelEditing = React.useCallback(() => {\n setEditing(false)\n setDraft([])\n setError(null)\n }, [])\n\n const ensureTagOption = React.useCallback(\n async (label: string): Promise<TagOption> => {\n const normalized = label.trim()\n if (!normalized.length) {\n throw new Error(labels.labelRequired)\n }\n const existing = optionsRef.current.get(normalized.toLowerCase())\n if (existing) return existing\n try {\n const created = await createTag(normalized)\n setOptions((prev) => {\n const next = new Map(prev)\n next.set(created.label.toLowerCase(), created)\n optionsRef.current = next\n return next\n })\n return created\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.createError\n throw new Error(message)\n }\n },\n [createTag, labels.createError, labels.labelRequired],\n )\n\n const handleSave = React.useCallback(async () => {\n if (saveTaskRef.current) {\n await saveTaskRef.current\n if (!autoSave || !hasPendingDraftChanges()) return\n }\n if (autoSave && !hasPendingDraftChanges()) {\n autoSaveUserEditedRef.current = false\n return\n }\n\n const trimmed = draftRef.current.map((label) => label.trim()).filter((label) => label.length > 0)\n const uniqueLabels = Array.from(new Set(trimmed.map((label) => label.toLowerCase())))\n\n let shouldContinueAutoSave = false\n\n const task = (async () => {\n const currentTags = savedTagsRef.current\n const currentIds = new Set(currentTags.map((tag) => tag.id))\n const submittedDraftKey = buildTagLabelKey(trimmed)\n\n setSaving(true)\n setError(null)\n try {\n const finalTagOptions = await Promise.all(\n uniqueLabels.map((normalized) => {\n const existing = optionsRef.current.get(normalized)\n if (existing) return existing\n const matchingLabel = trimmed.find((label) => label.toLowerCase() === normalized) ?? normalized\n return ensureTagOption(matchingLabel)\n }),\n )\n\n const finalIds = new Set(finalTagOptions.map((tag) => tag.id))\n const added = finalTagOptions.filter((tag) => !currentIds.has(tag.id))\n const removed = currentTags.filter((tag) => !finalIds.has(tag.id))\n\n if (added.length > 0 || removed.length > 0) {\n await onSave({ next: finalTagOptions, added, removed })\n }\n\n savedTagsRef.current = finalTagOptions\n onChange?.(finalTagOptions)\n if (autoSave) {\n const latestDraftKey = buildTagLabelKey(draftRef.current)\n if (latestDraftKey === submittedDraftKey) {\n autoSaveUserEditedRef.current = false\n setDraft(finalTagOptions.map((tag) => tag.label))\n } else {\n shouldContinueAutoSave = true\n }\n } else {\n setEditing(false)\n setDraft([])\n }\n if (labels.success && (added.length > 0 || removed.length > 0)) flash(labels.success, 'success')\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.updateError\n setError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n })()\n\n saveTaskRef.current = task\n try {\n await task\n } finally {\n saveTaskRef.current = null\n }\n if (shouldContinueAutoSave) {\n void handleSave()\n }\n }, [autoSave, ensureTagOption, hasPendingDraftChanges, labels.success, labels.updateError, onChange, onSave])\n\n const activeTags = editing ? draft : tags.map((tag) => tag.label)\n\n const handleEditingKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (!editing) return\n if (event.key === 'Escape') {\n event.preventDefault()\n cancelEditing()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (saving || isSubmitting) return\n void handleSave()\n }\n },\n [cancelEditing, editing, handleSave, isSubmitting, saving],\n )\n\n const autoSaveInitializedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (!autoSave || autoSaveInitializedRef.current) return\n autoSaveInitializedRef.current = true\n setEditing(true)\n setDraft(tags.map((tag) => tag.label))\n let cancelled = false\n loadOptions().then((fetched) => {\n if (cancelled) return\n syncFetchedOptions(fetched)\n }).catch(() => {})\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [autoSave])\n\n const autoSaveUserEditedRef = React.useRef(false)\n const originalOnChange = React.useCallback(\n (values: string[]) => {\n autoSaveUserEditedRef.current = true\n setDraft(values)\n },\n [],\n )\n\n React.useEffect(() => {\n if (!autoSave || !editing || !autoSaveUserEditedRef.current) return\n if (!hasPendingDraftChanges()) return\n void handleSave()\n }, [autoSave, draft, editing, handleSave, hasPendingDraftChanges])\n\n const flush = React.useCallback(async () => {\n if (saveTaskRef.current) {\n await saveTaskRef.current\n if (!autoSave || !editing || !hasPendingDraftChanges()) return\n }\n if (!autoSave || !editing || !hasPendingDraftChanges()) return\n await handleSave()\n }, [autoSave, editing, handleSave, hasPendingDraftChanges])\n\n React.useEffect(() => {\n if (!controllerRef) return\n controllerRef.current = { flush }\n return () => {\n if (controllerRef.current?.flush === flush) {\n controllerRef.current = null\n }\n }\n }, [controllerRef, flush])\n\n const disableInteraction = isSubmitting || !canEdit\n const autoSaveStatusLabel = saving\n ? (labels.saving ?? 'Saving\u2026')\n : (labels.autoSaveHint ?? null)\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between group\">\n <h2 className=\"text-sm font-semibold\">\n {title}\n </h2>\n {autoSave ? (\n autoSaveStatusLabel ? <p className=\"text-xs text-muted-foreground\">{autoSaveStatusLabel}</p> : null\n ) : (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={editing ? cancelEditing : startEditing}\n disabled={disableInteraction || saving}\n className={\n editing\n ? 'opacity-100 transition-opacity duration-150'\n : 'opacity-100 md:opacity-0 transition-opacity duration-150 md:group-hover:opacity-100 focus-visible:opacity-100'\n }\n >\n {editing ? <X className=\"h-4 w-4\" /> : <Pencil className=\"h-4 w-4\" />}\n <span className=\"sr-only\">\n {editing ? labels.cancel ?? 'Cancel' : labels.edit ?? 'Edit'}\n </span>\n </Button>\n )}\n </div>\n\n {editing ? (\n <div className=\"rounded-lg border bg-card p-4 space-y-3\">\n <DataLoader\n isLoading={loadingOptions}\n loadingMessage={labels.loading}\n spinnerSize=\"sm\"\n >\n <div className=\"space-y-3\" onKeyDown={handleEditingKeyDown}>\n <TagsInput\n value={activeTags}\n onChange={autoSave ? originalOnChange : (values) => setDraft(values)}\n placeholder={labels.placeholder}\n loadSuggestions={loadSuggestions}\n autoFocus={!autoSave}\n />\n {error ? <p className=\"text-xs text-status-error-text\">{error}</p> : null}\n {autoSave ? null : (\n <div className=\"flex items-center gap-2 mt-3 mb-2\">\n <Button type=\"button\" size=\"sm\" onClick={handleSave} disabled={saving || isSubmitting}>\n {saving ? (\n <span className=\"mr-2 h-4 w-4 animate-spin rounded-full border border-background border-t-primary\" />\n ) : null}\n {labels.saveShortcut}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={cancelEditing}\n disabled={saving || isSubmitting}\n >\n {labels.cancelShortcut}\n </Button>\n </div>\n )}\n </div>\n </DataLoader>\n </div>\n ) : (\n <div\n className=\"group/tags relative rounded-lg border bg-muted/30 p-4 transition-colors hover:border-primary/40 focus-visible:border-primary focus-visible:outline-none\"\n role={disableInteraction ? undefined : 'button'}\n tabIndex={disableInteraction ? -1 : 0}\n onClick={disableInteraction ? undefined : startEditing}\n onKeyDown={(event) => {\n if (disableInteraction) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n void startEditing()\n }\n }}\n >\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute right-3 top-3 text-muted-foreground opacity-0 transition-opacity duration-150 group-hover/tags:opacity-100 group-focus-within/tags:opacity-100\"\n >\n <Pencil className=\"h-4 w-4\" />\n </span>\n {tags.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {labels.empty}\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {tags.map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium\"\n style={tag.color ? { borderColor: tag.color, color: tag.color } : undefined}\n >\n {tag.label}\n </span>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n\nexport function TagsSection(props: TagsSectionProps) {\n const handle = ComponentReplacementHandles.section('ui.detail', 'TagsSection')\n const Resolved = useRegisteredComponent<TagsSectionProps>(\n handle,\n TagsSectionImpl as React.ComponentType<TagsSectionProps>,\n )\n\n return (\n <div data-component-handle={handle}>\n <Resolved {...props} />\n </div>\n )\n}\n\nexport default TagsSection\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -105,11 +105,10 @@ function DetailHeader({
105
105
  Button,
106
106
  {
107
107
  type: "button",
108
- variant: "outline",
108
+ variant: "destructive",
109
109
  size: "sm",
110
110
  onClick: onDelete,
111
111
  disabled: isDeleting,
112
- className: "text-red-600 border-red-200 hover:bg-red-50 rounded",
113
112
  children: [
114
113
  isDeleting ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 mr-2 animate-spin" }) : /* @__PURE__ */ jsx(Trash2, { className: "size-4 mr-2" }),
115
114
  resolvedDeleteLabel
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/forms/FormHeader.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Trash2, Loader2 } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FormActionButtons, type FormActionButtonsProps } from './FormActionButtons'\nimport { ActionsDropdown, type ActionItem } from './ActionsDropdown'\nimport { InjectionSpot } from '../injection/InjectionSpot'\n\n/** Base props shared by both modes */\ntype FormHeaderBaseProps = {\n /** Back link URL */\n backHref?: string\n /** Back link label */\n backLabel?: string\n}\n\n/** Edit mode: compact header for CrudForm pages */\nexport type FormHeaderEditProps = FormHeaderBaseProps & {\n mode?: 'edit'\n /** Small title next to the back link */\n title?: string\n /** Structured action buttons (Delete/Cancel/Save) */\n actions?: FormActionButtonsProps\n /** Custom right-side content (overrides `actions`) */\n actionsContent?: React.ReactNode\n}\n\n/** Detail mode: large header for view/detail pages */\nexport type FormHeaderDetailProps = FormHeaderBaseProps & {\n mode: 'detail'\n /** Large title -- string renders as h1; ReactNode for InlineTextEditor */\n title?: React.ReactNode\n /** Small uppercase entity type label above the title */\n entityTypeLabel?: string\n /** Subtitle text below the title */\n subtitle?: string\n /** Status badge or similar element below the title */\n statusBadge?: React.ReactNode\n /** Context actions grouped into an \"Actions\" dropdown (preferred) */\n menuActions?: ActionItem[]\n /** Optional label for actions dropdown trigger */\n menuLabel?: string\n /** Trigger style for actions dropdown */\n menuTriggerMode?: 'label' | 'icon'\n /** Accessible label used when trigger is icon-only */\n menuAriaLabel?: string\n /** Optional utility actions (icon-only) displayed before menu actions */\n utilityActions?: React.ReactNode\n /** Delete action -- rendered as a standalone destructive button next to the dropdown */\n onDelete?: () => void\n /** Delete button label */\n deleteLabel?: string\n /** Whether delete is in progress */\n isDeleting?: boolean\n /** Fallback: fully custom right-side content (overrides menuActions + onDelete) */\n actionsContent?: React.ReactNode\n}\n\nexport type FormHeaderProps = FormHeaderEditProps | FormHeaderDetailProps\n\nexport function FormHeader(props: FormHeaderProps) {\n const t = useT()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const resolvedBackLabel = props.backLabel ?? t('ui.navigation.back')\n const injectionContext = React.useMemo(\n () => ({\n path: pathname ?? '',\n query: searchParams?.toString() ?? '',\n }),\n [pathname, searchParams],\n )\n\n if (props.mode === 'detail') {\n return (\n <>\n <DetailHeader {...props} resolvedBackLabel={resolvedBackLabel} />\n <InjectionSpot spotId=\"form-header:detail\" context={injectionContext} />\n </>\n )\n }\n\n return (\n <>\n <EditHeader {...props} resolvedBackLabel={resolvedBackLabel} />\n <InjectionSpot spotId=\"form-header:edit\" context={injectionContext} />\n </>\n )\n}\n\nfunction EditHeader({\n backHref,\n resolvedBackLabel,\n title,\n actions,\n actionsContent,\n}: FormHeaderEditProps & { resolvedBackLabel: string }) {\n return (\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3\">\n <div className=\"flex items-center gap-3\">\n {backHref ? (\n <Link href={backHref} className=\"text-sm text-muted-foreground hover:text-foreground\">\n &larr; {resolvedBackLabel}\n </Link>\n ) : null}\n {title ? <div className=\"text-base font-medium\">{title}</div> : null}\n </div>\n {actionsContent ?? (actions ? <FormActionButtons {...actions} /> : null)}\n </div>\n )\n}\n\nfunction DetailHeader({\n backHref,\n resolvedBackLabel,\n title,\n entityTypeLabel,\n subtitle,\n statusBadge,\n menuActions,\n menuLabel,\n menuTriggerMode,\n menuAriaLabel,\n utilityActions,\n onDelete,\n deleteLabel,\n isDeleting,\n actionsContent,\n}: FormHeaderDetailProps & { resolvedBackLabel: string }) {\n const t = useT()\n const resolvedDeleteLabel = deleteLabel ?? t('ui.forms.actions.delete')\n\n const hasActions = actionsContent || utilityActions || menuActions?.length || onDelete\n\n return (\n <div className=\"flex flex-col gap-2 md:gap-3 md:flex-row md:items-center md:justify-between\">\n <div className=\"flex flex-wrap items-center gap-2 md:gap-3 min-w-0\">\n {backHref ? (\n <Link\n href={backHref}\n className=\"inline-flex items-center text-sm text-muted-foreground hover:text-foreground shrink-0\"\n >\n <span aria-hidden className=\"mr-1 text-base\">&larr;</span>\n <span className=\"sr-only\">{resolvedBackLabel}</span>\n </Link>\n ) : null}\n <div className=\"space-y-0.5 md:space-y-1 min-w-0\">\n {entityTypeLabel ? (\n <p className=\"text-xs uppercase text-muted-foreground\">{entityTypeLabel}</p>\n ) : null}\n {title ? (\n typeof title === 'string' ? (\n <h1 className=\"text-lg md:text-2xl font-semibold leading-tight truncate\">{title}</h1>\n ) : (\n <div className=\"text-lg md:text-2xl font-semibold leading-tight\">{title}</div>\n )\n ) : null}\n {statusBadge}\n {subtitle ? (\n <p className=\"text-xs md:text-sm text-muted-foreground\">{subtitle}</p>\n ) : null}\n </div>\n </div>\n {hasActions ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n {actionsContent ? actionsContent : (\n <>\n {utilityActions}\n {menuActions?.length ? (\n <ActionsDropdown\n items={menuActions}\n label={menuLabel}\n triggerMode={menuTriggerMode}\n ariaLabel={menuAriaLabel}\n />\n ) : null}\n {onDelete ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={onDelete}\n disabled={isDeleting}\n className=\"text-red-600 border-red-200 hover:bg-red-50 rounded\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 mr-2 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 mr-2\" />\n )}\n {resolvedDeleteLabel}\n </Button>\n ) : null}\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Trash2, Loader2 } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FormActionButtons, type FormActionButtonsProps } from './FormActionButtons'\nimport { ActionsDropdown, type ActionItem } from './ActionsDropdown'\nimport { InjectionSpot } from '../injection/InjectionSpot'\n\n/** Base props shared by both modes */\ntype FormHeaderBaseProps = {\n /** Back link URL */\n backHref?: string\n /** Back link label */\n backLabel?: string\n}\n\n/** Edit mode: compact header for CrudForm pages */\nexport type FormHeaderEditProps = FormHeaderBaseProps & {\n mode?: 'edit'\n /** Small title next to the back link */\n title?: string\n /** Structured action buttons (Delete/Cancel/Save) */\n actions?: FormActionButtonsProps\n /** Custom right-side content (overrides `actions`) */\n actionsContent?: React.ReactNode\n}\n\n/** Detail mode: large header for view/detail pages */\nexport type FormHeaderDetailProps = FormHeaderBaseProps & {\n mode: 'detail'\n /** Large title -- string renders as h1; ReactNode for InlineTextEditor */\n title?: React.ReactNode\n /** Small uppercase entity type label above the title */\n entityTypeLabel?: string\n /** Subtitle text below the title */\n subtitle?: string\n /** Status badge or similar element below the title */\n statusBadge?: React.ReactNode\n /** Context actions grouped into an \"Actions\" dropdown (preferred) */\n menuActions?: ActionItem[]\n /** Optional label for actions dropdown trigger */\n menuLabel?: string\n /** Trigger style for actions dropdown */\n menuTriggerMode?: 'label' | 'icon'\n /** Accessible label used when trigger is icon-only */\n menuAriaLabel?: string\n /** Optional utility actions (icon-only) displayed before menu actions */\n utilityActions?: React.ReactNode\n /** Delete action -- rendered as a standalone destructive button next to the dropdown */\n onDelete?: () => void\n /** Delete button label */\n deleteLabel?: string\n /** Whether delete is in progress */\n isDeleting?: boolean\n /** Fallback: fully custom right-side content (overrides menuActions + onDelete) */\n actionsContent?: React.ReactNode\n}\n\nexport type FormHeaderProps = FormHeaderEditProps | FormHeaderDetailProps\n\nexport function FormHeader(props: FormHeaderProps) {\n const t = useT()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const resolvedBackLabel = props.backLabel ?? t('ui.navigation.back')\n const injectionContext = React.useMemo(\n () => ({\n path: pathname ?? '',\n query: searchParams?.toString() ?? '',\n }),\n [pathname, searchParams],\n )\n\n if (props.mode === 'detail') {\n return (\n <>\n <DetailHeader {...props} resolvedBackLabel={resolvedBackLabel} />\n <InjectionSpot spotId=\"form-header:detail\" context={injectionContext} />\n </>\n )\n }\n\n return (\n <>\n <EditHeader {...props} resolvedBackLabel={resolvedBackLabel} />\n <InjectionSpot spotId=\"form-header:edit\" context={injectionContext} />\n </>\n )\n}\n\nfunction EditHeader({\n backHref,\n resolvedBackLabel,\n title,\n actions,\n actionsContent,\n}: FormHeaderEditProps & { resolvedBackLabel: string }) {\n return (\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3\">\n <div className=\"flex items-center gap-3\">\n {backHref ? (\n <Link href={backHref} className=\"text-sm text-muted-foreground hover:text-foreground\">\n &larr; {resolvedBackLabel}\n </Link>\n ) : null}\n {title ? <div className=\"text-base font-medium\">{title}</div> : null}\n </div>\n {actionsContent ?? (actions ? <FormActionButtons {...actions} /> : null)}\n </div>\n )\n}\n\nfunction DetailHeader({\n backHref,\n resolvedBackLabel,\n title,\n entityTypeLabel,\n subtitle,\n statusBadge,\n menuActions,\n menuLabel,\n menuTriggerMode,\n menuAriaLabel,\n utilityActions,\n onDelete,\n deleteLabel,\n isDeleting,\n actionsContent,\n}: FormHeaderDetailProps & { resolvedBackLabel: string }) {\n const t = useT()\n const resolvedDeleteLabel = deleteLabel ?? t('ui.forms.actions.delete')\n\n const hasActions = actionsContent || utilityActions || menuActions?.length || onDelete\n\n return (\n <div className=\"flex flex-col gap-2 md:gap-3 md:flex-row md:items-center md:justify-between\">\n <div className=\"flex flex-wrap items-center gap-2 md:gap-3 min-w-0\">\n {backHref ? (\n <Link\n href={backHref}\n className=\"inline-flex items-center text-sm text-muted-foreground hover:text-foreground shrink-0\"\n >\n <span aria-hidden className=\"mr-1 text-base\">&larr;</span>\n <span className=\"sr-only\">{resolvedBackLabel}</span>\n </Link>\n ) : null}\n <div className=\"space-y-0.5 md:space-y-1 min-w-0\">\n {entityTypeLabel ? (\n <p className=\"text-xs uppercase text-muted-foreground\">{entityTypeLabel}</p>\n ) : null}\n {title ? (\n typeof title === 'string' ? (\n <h1 className=\"text-lg md:text-2xl font-semibold leading-tight truncate\">{title}</h1>\n ) : (\n <div className=\"text-lg md:text-2xl font-semibold leading-tight\">{title}</div>\n )\n ) : null}\n {statusBadge}\n {subtitle ? (\n <p className=\"text-xs md:text-sm text-muted-foreground\">{subtitle}</p>\n ) : null}\n </div>\n </div>\n {hasActions ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n {actionsContent ? actionsContent : (\n <>\n {utilityActions}\n {menuActions?.length ? (\n <ActionsDropdown\n items={menuActions}\n label={menuLabel}\n triggerMode={menuTriggerMode}\n ariaLabel={menuAriaLabel}\n />\n ) : null}\n {onDelete ? (\n <Button\n type=\"button\"\n variant=\"destructive\"\n size=\"sm\"\n onClick={onDelete}\n disabled={isDeleting}\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 mr-2 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 mr-2\" />\n )}\n {resolvedDeleteLabel}\n </Button>\n ) : null}\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -47,9 +47,9 @@ function getStatusLabel(status, t) {
47
47
  return null;
48
48
  }
49
49
  function getKindStyles(kind) {
50
- if (kind === "event") return "border-blue-500/40 bg-blue-500/10 text-blue-950";
51
- if (kind === "exception") return "border-amber-500/40 bg-amber-500/10 text-amber-950";
52
- return "border-emerald-500/40 bg-emerald-500/10 text-emerald-950";
50
+ if (kind === "event") return "border-status-info-border bg-status-info-bg text-status-info-text";
51
+ if (kind === "exception") return "border-status-warning-border bg-status-warning-bg text-status-warning-text";
52
+ return "border-status-success-border bg-status-success-bg text-status-success-text";
53
53
  }
54
54
  function ScheduleAgenda({ items, range, timezone, onItemClick, onSlotClick, className }) {
55
55
  const t = useT();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/schedule/ScheduleAgenda.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '../../primitives/badge'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { expandRecurringItems } from './recurrence'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction startOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate())\n}\n\nfunction endOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999)\n}\n\nfunction eachDay(start: Date, end: Date): Date[] {\n const days: Date[] = []\n let cursor = startOfDay(start)\n const last = startOfDay(end)\n while (cursor <= last) {\n days.push(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n }\n return days\n}\n\nfunction overlapsDay(item: ScheduleItem, day: Date): boolean {\n const dayStart = startOfDay(day)\n const dayEnd = endOfDay(day)\n return item.startsAt <= dayEnd && item.endsAt >= dayStart\n}\n\nfunction formatDayLabel(day: Date): string {\n return day.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' })\n}\n\nfunction formatTimeRange(item: ScheduleItem, timezone?: string): string {\n const options: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }\n if (timezone) options.timeZone = timezone\n const startLabel = item.startsAt.toLocaleTimeString(undefined, options)\n const endLabel = item.endsAt.toLocaleTimeString(undefined, options)\n return `${startLabel}-${endLabel}`\n}\n\nfunction getStatusLabel(status: ScheduleItem['status'], t: (key: string, fallback?: string) => string): string | null {\n if (!status) return null\n if (status === 'draft') return t('schedule.item.status.draft', 'Draft')\n if (status === 'negotiation') return t('schedule.item.status.negotiation', 'Negotiation')\n if (status === 'confirmed') return t('schedule.item.status.confirmed', 'Confirmed')\n if (status === 'cancelled') return t('schedule.item.status.cancelled', 'Cancelled')\n return null\n}\n\nfunction getKindStyles(kind: ScheduleItem['kind']): string {\n if (kind === 'event') return 'border-blue-500/40 bg-blue-500/10 text-blue-950'\n if (kind === 'exception') return 'border-amber-500/40 bg-amber-500/10 text-amber-950'\n return 'border-emerald-500/40 bg-emerald-500/10 text-emerald-950'\n}\n\nexport type ScheduleAgendaProps = {\n items: ScheduleItem[]\n range: ScheduleRange\n timezone?: string\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n className?: string\n}\n\nexport function ScheduleAgenda({ items, range, timezone, onItemClick, onSlotClick, className }: ScheduleAgendaProps) {\n const t = useT()\n const days = React.useMemo(() => eachDay(range.start, range.end), [range])\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n\n return (\n <div className={cn('space-y-4', className)}>\n {days.map((day) => {\n const dayItems = expandedItems.filter((item) => overlapsDay(item, day))\n const slotStart = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9, 0, 0)\n const slotEnd = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10, 0, 0)\n return (\n <div key={day.toISOString()} className=\"rounded-xl border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold text-foreground\">{formatDayLabel(day)}</div>\n {onSlotClick ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSlotClick({ start: slotStart, end: slotEnd })}\n >\n {t('schedule.actions.add', 'Add')}\n </Button>\n ) : null}\n </div>\n <div className=\"mt-3 space-y-2\">\n {dayItems.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-3 text-xs text-muted-foreground\">\n {t('schedule.emptyDay', 'No scheduled items')}\n </div>\n ) : (\n dayItems.map((item) => {\n const statusLabel = getStatusLabel(item.status, t)\n return (\n <button\n key={item.id}\n type=\"button\"\n className={cn(\n 'flex w-full cursor-pointer flex-col gap-2 rounded-lg border px-3 py-2 text-left text-xs transition hover:shadow-sm',\n getKindStyles(item.kind)\n )}\n onClick={() => onItemClick?.(item)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">{item.title}</span>\n {statusLabel ? <Badge variant=\"secondary\">{statusLabel}</Badge> : null}\n </div>\n <div className=\"flex items-center justify-between text-overline text-muted-foreground\">\n <span>{formatTimeRange(item, timezone)}</span>\n <span className=\"capitalize\">{item.kind}</span>\n </div>\n </button>\n )\n })\n )}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '../../primitives/badge'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { expandRecurringItems } from './recurrence'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction startOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate())\n}\n\nfunction endOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999)\n}\n\nfunction eachDay(start: Date, end: Date): Date[] {\n const days: Date[] = []\n let cursor = startOfDay(start)\n const last = startOfDay(end)\n while (cursor <= last) {\n days.push(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n }\n return days\n}\n\nfunction overlapsDay(item: ScheduleItem, day: Date): boolean {\n const dayStart = startOfDay(day)\n const dayEnd = endOfDay(day)\n return item.startsAt <= dayEnd && item.endsAt >= dayStart\n}\n\nfunction formatDayLabel(day: Date): string {\n return day.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' })\n}\n\nfunction formatTimeRange(item: ScheduleItem, timezone?: string): string {\n const options: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }\n if (timezone) options.timeZone = timezone\n const startLabel = item.startsAt.toLocaleTimeString(undefined, options)\n const endLabel = item.endsAt.toLocaleTimeString(undefined, options)\n return `${startLabel}-${endLabel}`\n}\n\nfunction getStatusLabel(status: ScheduleItem['status'], t: (key: string, fallback?: string) => string): string | null {\n if (!status) return null\n if (status === 'draft') return t('schedule.item.status.draft', 'Draft')\n if (status === 'negotiation') return t('schedule.item.status.negotiation', 'Negotiation')\n if (status === 'confirmed') return t('schedule.item.status.confirmed', 'Confirmed')\n if (status === 'cancelled') return t('schedule.item.status.cancelled', 'Cancelled')\n return null\n}\n\nfunction getKindStyles(kind: ScheduleItem['kind']): string {\n if (kind === 'event') return 'border-status-info-border bg-status-info-bg text-status-info-text'\n if (kind === 'exception') return 'border-status-warning-border bg-status-warning-bg text-status-warning-text'\n return 'border-status-success-border bg-status-success-bg text-status-success-text'\n}\n\nexport type ScheduleAgendaProps = {\n items: ScheduleItem[]\n range: ScheduleRange\n timezone?: string\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n className?: string\n}\n\nexport function ScheduleAgenda({ items, range, timezone, onItemClick, onSlotClick, className }: ScheduleAgendaProps) {\n const t = useT()\n const days = React.useMemo(() => eachDay(range.start, range.end), [range])\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n\n return (\n <div className={cn('space-y-4', className)}>\n {days.map((day) => {\n const dayItems = expandedItems.filter((item) => overlapsDay(item, day))\n const slotStart = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9, 0, 0)\n const slotEnd = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10, 0, 0)\n return (\n <div key={day.toISOString()} className=\"rounded-xl border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold text-foreground\">{formatDayLabel(day)}</div>\n {onSlotClick ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSlotClick({ start: slotStart, end: slotEnd })}\n >\n {t('schedule.actions.add', 'Add')}\n </Button>\n ) : null}\n </div>\n <div className=\"mt-3 space-y-2\">\n {dayItems.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-3 text-xs text-muted-foreground\">\n {t('schedule.emptyDay', 'No scheduled items')}\n </div>\n ) : (\n dayItems.map((item) => {\n const statusLabel = getStatusLabel(item.status, t)\n return (\n <button\n key={item.id}\n type=\"button\"\n className={cn(\n 'flex w-full cursor-pointer flex-col gap-2 rounded-lg border px-3 py-2 text-left text-xs transition hover:shadow-sm',\n getKindStyles(item.kind)\n )}\n onClick={() => onItemClick?.(item)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">{item.title}</span>\n {statusLabel ? <Badge variant=\"secondary\">{statusLabel}</Badge> : null}\n </div>\n <div className=\"flex items-center justify-between text-overline text-muted-foreground\">\n <span>{formatTimeRange(item, timezone)}</span>\n <span className=\"capitalize\">{item.kind}</span>\n </div>\n </button>\n )\n })\n )}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -47,9 +47,9 @@ function getStatusLabel(status, t) {
47
47
  return null;
48
48
  }
49
49
  function getKindStyles(kind) {
50
- if (kind === "event") return "border-blue-500/40 bg-blue-500/10 text-blue-950";
51
- if (kind === "exception") return "border-amber-500/40 bg-amber-500/10 text-amber-950";
52
- return "border-emerald-500/40 bg-emerald-500/10 text-emerald-950";
50
+ if (kind === "event") return "border-status-info-border bg-status-info-bg text-status-info-text";
51
+ if (kind === "exception") return "border-status-warning-border bg-status-warning-bg text-status-warning-text";
52
+ return "border-status-success-border bg-status-success-bg text-status-success-text";
53
53
  }
54
54
  function ScheduleGrid({ items, range, timezone, onItemClick, onSlotClick, className }) {
55
55
  const t = useT();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/schedule/ScheduleGrid.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '../../primitives/badge'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { expandRecurringItems } from './recurrence'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction startOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate())\n}\n\nfunction endOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999)\n}\n\nfunction eachDay(start: Date, end: Date): Date[] {\n const days: Date[] = []\n let cursor = startOfDay(start)\n const last = startOfDay(end)\n while (cursor <= last) {\n days.push(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n }\n return days\n}\n\nfunction overlapsDay(item: ScheduleItem, day: Date): boolean {\n const dayStart = startOfDay(day)\n const dayEnd = endOfDay(day)\n return item.startsAt <= dayEnd && item.endsAt >= dayStart\n}\n\nfunction formatDayLabel(day: Date): string {\n return day.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })\n}\n\nfunction formatTimeRange(item: ScheduleItem, timezone?: string): string {\n const options: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }\n if (timezone) options.timeZone = timezone\n const startLabel = item.startsAt.toLocaleTimeString(undefined, options)\n const endLabel = item.endsAt.toLocaleTimeString(undefined, options)\n return `${startLabel}-${endLabel}`\n}\n\nfunction getStatusLabel(status: ScheduleItem['status'], t: (key: string, fallback?: string) => string): string | null {\n if (!status) return null\n if (status === 'draft') return t('schedule.item.status.draft', 'Draft')\n if (status === 'negotiation') return t('schedule.item.status.negotiation', 'Negotiation')\n if (status === 'confirmed') return t('schedule.item.status.confirmed', 'Confirmed')\n if (status === 'cancelled') return t('schedule.item.status.cancelled', 'Cancelled')\n return null\n}\n\nfunction getKindStyles(kind: ScheduleItem['kind']): string {\n if (kind === 'event') return 'border-blue-500/40 bg-blue-500/10 text-blue-950'\n if (kind === 'exception') return 'border-amber-500/40 bg-amber-500/10 text-amber-950'\n return 'border-emerald-500/40 bg-emerald-500/10 text-emerald-950'\n}\n\nexport type ScheduleGridProps = {\n items: ScheduleItem[]\n range: ScheduleRange\n timezone?: string\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n className?: string\n}\n\nexport function ScheduleGrid({ items, range, timezone, onItemClick, onSlotClick, className }: ScheduleGridProps) {\n const t = useT()\n const days = React.useMemo(() => eachDay(range.start, range.end), [range])\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n\n return (\n <div className={cn('grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3', className)}>\n {days.map((day) => {\n const dayItems = expandedItems.filter((item) => overlapsDay(item, day))\n const slotStart = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9, 0, 0)\n const slotEnd = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10, 0, 0)\n return (\n <div key={day.toISOString()} className=\"rounded-xl border bg-card p-4 shadow-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold text-foreground\">{formatDayLabel(day)}</div>\n {onSlotClick ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSlotClick({ start: slotStart, end: slotEnd })}\n >\n {t('schedule.actions.add', 'Add')}\n </Button>\n ) : null}\n </div>\n <div className=\"mt-3 space-y-2\">\n {dayItems.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-3 text-xs text-muted-foreground\">\n {t('schedule.emptyDay', 'No scheduled items')}\n </div>\n ) : (\n dayItems.map((item) => {\n const statusLabel = getStatusLabel(item.status, t)\n return (\n <button\n key={item.id}\n type=\"button\"\n className={cn(\n 'flex w-full cursor-pointer flex-col gap-2 rounded-lg border px-3 py-2 text-left text-xs transition hover:shadow-sm',\n getKindStyles(item.kind)\n )}\n onClick={() => onItemClick?.(item)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">{item.title}</span>\n {statusLabel ? <Badge variant=\"secondary\">{statusLabel}</Badge> : null}\n </div>\n <div className=\"flex items-center justify-between text-overline text-muted-foreground\">\n <span>{formatTimeRange(item, timezone)}</span>\n <span className=\"capitalize\">{item.kind}</span>\n </div>\n </button>\n )\n })\n )}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '../../primitives/badge'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { expandRecurringItems } from './recurrence'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction startOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate())\n}\n\nfunction endOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999)\n}\n\nfunction eachDay(start: Date, end: Date): Date[] {\n const days: Date[] = []\n let cursor = startOfDay(start)\n const last = startOfDay(end)\n while (cursor <= last) {\n days.push(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n }\n return days\n}\n\nfunction overlapsDay(item: ScheduleItem, day: Date): boolean {\n const dayStart = startOfDay(day)\n const dayEnd = endOfDay(day)\n return item.startsAt <= dayEnd && item.endsAt >= dayStart\n}\n\nfunction formatDayLabel(day: Date): string {\n return day.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })\n}\n\nfunction formatTimeRange(item: ScheduleItem, timezone?: string): string {\n const options: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }\n if (timezone) options.timeZone = timezone\n const startLabel = item.startsAt.toLocaleTimeString(undefined, options)\n const endLabel = item.endsAt.toLocaleTimeString(undefined, options)\n return `${startLabel}-${endLabel}`\n}\n\nfunction getStatusLabel(status: ScheduleItem['status'], t: (key: string, fallback?: string) => string): string | null {\n if (!status) return null\n if (status === 'draft') return t('schedule.item.status.draft', 'Draft')\n if (status === 'negotiation') return t('schedule.item.status.negotiation', 'Negotiation')\n if (status === 'confirmed') return t('schedule.item.status.confirmed', 'Confirmed')\n if (status === 'cancelled') return t('schedule.item.status.cancelled', 'Cancelled')\n return null\n}\n\nfunction getKindStyles(kind: ScheduleItem['kind']): string {\n if (kind === 'event') return 'border-status-info-border bg-status-info-bg text-status-info-text'\n if (kind === 'exception') return 'border-status-warning-border bg-status-warning-bg text-status-warning-text'\n return 'border-status-success-border bg-status-success-bg text-status-success-text'\n}\n\nexport type ScheduleGridProps = {\n items: ScheduleItem[]\n range: ScheduleRange\n timezone?: string\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n className?: string\n}\n\nexport function ScheduleGrid({ items, range, timezone, onItemClick, onSlotClick, className }: ScheduleGridProps) {\n const t = useT()\n const days = React.useMemo(() => eachDay(range.start, range.end), [range])\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n\n return (\n <div className={cn('grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3', className)}>\n {days.map((day) => {\n const dayItems = expandedItems.filter((item) => overlapsDay(item, day))\n const slotStart = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9, 0, 0)\n const slotEnd = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10, 0, 0)\n return (\n <div key={day.toISOString()} className=\"rounded-xl border bg-card p-4 shadow-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold text-foreground\">{formatDayLabel(day)}</div>\n {onSlotClick ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSlotClick({ start: slotStart, end: slotEnd })}\n >\n {t('schedule.actions.add', 'Add')}\n </Button>\n ) : null}\n </div>\n <div className=\"mt-3 space-y-2\">\n {dayItems.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-3 text-xs text-muted-foreground\">\n {t('schedule.emptyDay', 'No scheduled items')}\n </div>\n ) : (\n dayItems.map((item) => {\n const statusLabel = getStatusLabel(item.status, t)\n return (\n <button\n key={item.id}\n type=\"button\"\n className={cn(\n 'flex w-full cursor-pointer flex-col gap-2 rounded-lg border px-3 py-2 text-left text-xs transition hover:shadow-sm',\n getKindStyles(item.kind)\n )}\n onClick={() => onItemClick?.(item)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">{item.title}</span>\n {statusLabel ? <Badge variant=\"secondary\">{statusLabel}</Badge> : null}\n </div>\n <div className=\"flex items-center justify-between text-overline text-muted-foreground\">\n <span>{formatTimeRange(item, timezone)}</span>\n <span className=\"capitalize\">{item.kind}</span>\n </div>\n </button>\n )\n })\n )}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n"],
5
5
  "mappings": ";AAsFY,SACE,KADF;AApFZ,YAAY,WAAW;AAEvB,SAAS,UAAU;AACnB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,4BAA4B;AAErC,MAAM,SAAS,KAAK,KAAK,KAAK;AAE9B,SAAS,WAAW,OAAmB;AACrC,SAAO,IAAI,KAAK,MAAM,YAAY,GAAG,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC;AACxE;AAEA,SAAS,SAAS,OAAmB;AACnC,SAAO,IAAI,KAAK,MAAM,YAAY,GAAG,MAAM,SAAS,GAAG,MAAM,QAAQ,GAAG,IAAI,IAAI,IAAI,GAAG;AACzF;AAEA,SAAS,QAAQ,OAAa,KAAmB;AAC/C,QAAM,OAAe,CAAC;AACtB,MAAI,SAAS,WAAW,KAAK;AAC7B,QAAM,OAAO,WAAW,GAAG;AAC3B,SAAO,UAAU,MAAM;AACrB,SAAK,KAAK,IAAI,KAAK,MAAM,CAAC;AAC1B,aAAS,IAAI,KAAK,OAAO,QAAQ,IAAI,MAAM;AAAA,EAC7C;AACA,SAAO;AACT;AAEA,SAAS,YAAY,MAAoB,KAAoB;AAC3D,QAAM,WAAW,WAAW,GAAG;AAC/B,QAAM,SAAS,SAAS,GAAG;AAC3B,SAAO,KAAK,YAAY,UAAU,KAAK,UAAU;AACnD;AAEA,SAAS,eAAe,KAAmB;AACzC,SAAO,IAAI,mBAAmB,QAAW,EAAE,SAAS,SAAS,OAAO,SAAS,KAAK,UAAU,CAAC;AAC/F;AAEA,SAAS,gBAAgB,MAAoB,UAA2B;AACtE,QAAM,UAAsC,EAAE,MAAM,WAAW,QAAQ,UAAU;AACjF,MAAI,SAAU,SAAQ,WAAW;AACjC,QAAM,aAAa,KAAK,SAAS,mBAAmB,QAAW,OAAO;AACtE,QAAM,WAAW,KAAK,OAAO,mBAAmB,QAAW,OAAO;AAClE,SAAO,GAAG,UAAU,IAAI,QAAQ;AAClC;AAEA,SAAS,eAAe,QAAgC,GAA8D;AACpH,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,WAAW,QAAS,QAAO,EAAE,8BAA8B,OAAO;AACtE,MAAI,WAAW,cAAe,QAAO,EAAE,oCAAoC,aAAa;AACxF,MAAI,WAAW,YAAa,QAAO,EAAE,kCAAkC,WAAW;AAClF,MAAI,WAAW,YAAa,QAAO,EAAE,kCAAkC,WAAW;AAClF,SAAO;AACT;AAEA,SAAS,cAAc,MAAoC;AACzD,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,SAAS,YAAa,QAAO;AACjC,SAAO;AACT;AAWO,SAAS,aAAa,EAAE,OAAO,OAAO,UAAU,aAAa,aAAa,UAAU,GAAsB;AAC/G,QAAM,IAAI,KAAK;AACf,QAAM,OAAO,MAAM,QAAQ,MAAM,QAAQ,MAAM,OAAO,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC;AACzE,QAAM,gBAAgB,MAAM,QAAQ,MAAM,qBAAqB,OAAO,KAAK,GAAG,CAAC,OAAO,KAAK,CAAC;AAE5F,SACE,oBAAC,SAAI,WAAW,GAAG,wDAAwD,SAAS,GACjF,eAAK,IAAI,CAAC,QAAQ;AACjB,UAAM,WAAW,cAAc,OAAO,CAAC,SAAS,YAAY,MAAM,GAAG,CAAC;AACtE,UAAM,YAAY,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,SAAS,GAAG,IAAI,QAAQ,GAAG,GAAG,GAAG,CAAC;AACpF,UAAM,UAAU,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,SAAS,GAAG,IAAI,QAAQ,GAAG,IAAI,GAAG,CAAC;AACnF,WACE,qBAAC,SAA4B,WAAU,2CACrC;AAAA,2BAAC,SAAI,WAAU,2CACb;AAAA,4BAAC,SAAI,WAAU,yCAAyC,yBAAe,GAAG,GAAE;AAAA,QAC3E,cACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,MAAM,YAAY,EAAE,OAAO,WAAW,KAAK,QAAQ,CAAC;AAAA,YAE5D,YAAE,wBAAwB,KAAK;AAAA;AAAA,QAClC,IACE;AAAA,SACN;AAAA,MACA,oBAAC,SAAI,WAAU,kBACZ,mBAAS,WAAW,IACnB,oBAAC,SAAI,WAAU,qEACZ,YAAE,qBAAqB,oBAAoB,GAC9C,IAEA,SAAS,IAAI,CAAC,SAAS;AACrB,cAAM,cAAc,eAAe,KAAK,QAAQ,CAAC;AACjD,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,WAAW;AAAA,cACT;AAAA,cACA,cAAc,KAAK,IAAI;AAAA,YACzB;AAAA,YACA,SAAS,MAAM,cAAc,IAAI;AAAA,YAEjC;AAAA,mCAAC,SAAI,WAAU,2CACb;AAAA,oCAAC,UAAK,WAAU,iBAAiB,eAAK,OAAM;AAAA,gBAC3C,cAAc,oBAAC,SAAM,SAAQ,aAAa,uBAAY,IAAW;AAAA,iBACpE;AAAA,cACA,qBAAC,SAAI,WAAU,yEACb;AAAA,oCAAC,UAAM,0BAAgB,MAAM,QAAQ,GAAE;AAAA,gBACvC,oBAAC,UAAK,WAAU,cAAc,eAAK,MAAK;AAAA,iBAC1C;AAAA;AAAA;AAAA,UAfK,KAAK;AAAA,QAgBZ;AAAA,MAEJ,CAAC,GAEL;AAAA,SA5CQ,IAAI,YAAY,CA6C1B;AAAA,EAEJ,CAAC,GACH;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,22 @@ import { apiFetch } from "./api.js";
3
3
  import { raiseCrudError, readJsonSafe } from "./serverErrors.js";
4
4
  import { createScopedHeaderStack } from "./scopedHeaderStack.js";
5
5
  const scopedRequestHeaders = createScopedHeaderStack();
6
+ function isAbortError(error) {
7
+ return typeof error === "object" && error !== null && error.name === "AbortError";
8
+ }
9
+ function createAbortError() {
10
+ if (typeof DOMException === "function") {
11
+ return new DOMException("[internal] The operation was aborted.", "AbortError");
12
+ }
13
+ const error = new Error("[internal] The operation was aborted.");
14
+ error.name = "AbortError";
15
+ return error;
16
+ }
17
+ function resolveAbortSignal(input, init) {
18
+ if (init?.signal) return init.signal;
19
+ if (typeof Request !== "undefined" && input instanceof Request) return input.signal ?? null;
20
+ return null;
21
+ }
6
22
  function mergeHeaders(base, extra) {
7
23
  if (!base) return extra;
8
24
  if (typeof Headers !== "undefined" && base instanceof Headers) {
@@ -31,9 +47,13 @@ async function apiCall(input, init, options) {
31
47
  const source = typeof response.clone === "function" ? response.clone() : response;
32
48
  if (parser) result = await parser(source);
33
49
  else result = await readJsonSafe(source, fallback);
34
- } catch {
50
+ } catch (err) {
51
+ if (isAbortError(err)) throw err;
35
52
  result = fallback;
36
53
  }
54
+ if (result == null && resolveAbortSignal(input, init)?.aborted) {
55
+ throw createAbortError();
56
+ }
37
57
  return {
38
58
  ok: response.ok,
39
59
  status: response.status,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/utils/apiCall.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport { apiFetch } from './api'\nimport { raiseCrudError, readJsonSafe } from './serverErrors'\nimport { createScopedHeaderStack } from './scopedHeaderStack'\n\nexport type ApiCallOptions<TReturn> = {\n parse?: (res: Response) => Promise<TReturn | null>\n fallback?: TReturn | null\n}\n\nexport type ApiCallResult<TReturn> = {\n ok: boolean\n status: number\n result: TReturn | null\n response: Response\n cacheStatus: 'hit' | 'miss' | null\n}\n\nconst scopedRequestHeaders = createScopedHeaderStack()\n\nfunction mergeHeaders(base: HeadersInit | undefined, extra: Record<string, string>): HeadersInit {\n if (!base) return extra\n if (typeof Headers !== 'undefined' && base instanceof Headers) {\n const merged = new Headers(base)\n Object.entries(extra).forEach(([key, value]) => merged.set(key, value))\n return merged\n }\n if (Array.isArray(base)) {\n return [...base, ...Object.entries(extra)]\n }\n return { ...(base as Record<string, string>), ...extra }\n}\n\nexport async function withScopedApiRequestHeaders<T>(\n headers: Record<string, string>,\n run: () => Promise<T>,\n): Promise<T> {\n return scopedRequestHeaders.withScopedHeaders(headers, run)\n}\n\nexport async function apiCall<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init?: RequestInit,\n options?: ApiCallOptions<TReturn>,\n): Promise<ApiCallResult<TReturn>> {\n const scopedHeaders = scopedRequestHeaders.resolveScopedHeaders()\n const requestInit = Object.keys(scopedHeaders).length > 0\n ? { ...(init ?? {}), headers: mergeHeaders(init?.headers, scopedHeaders) }\n : init\n const response = await apiFetch(input, requestInit)\n const parser = options?.parse\n const fallback = options?.fallback ?? null\n let result: TReturn | null = null\n const rawCacheStatus =\n response.headers?.get?.('x-om-cache') ??\n response.headers?.get?.('x-cache-status') ??\n null\n const cacheStatus = rawCacheStatus === 'hit' || rawCacheStatus === 'miss' ? rawCacheStatus : null\n try {\n const source = typeof (response as Response & { clone?: () => Response }).clone === 'function'\n ? response.clone()\n : response\n if (parser) result = await parser(source)\n else result = await readJsonSafe<TReturn>(source, fallback)\n } catch {\n result = fallback\n }\n return {\n ok: response.ok,\n status: response.status,\n result,\n response,\n cacheStatus,\n }\n}\n\nexport type ApiCallOrThrowOptions<TReturn> = ApiCallOptions<TReturn> & {\n errorMessage?: string\n}\n\nexport async function apiCallOrThrow<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init?: RequestInit,\n options?: ApiCallOrThrowOptions<TReturn>,\n): Promise<ApiCallResult<TReturn>> {\n const { errorMessage, ...callOptions } = options ?? {}\n const call = await apiCall<TReturn>(input, init, callOptions)\n if (!call.ok) {\n await raiseCrudError(call.response, errorMessage)\n }\n return call\n}\n\nexport type ReadApiResultOrThrowOptions<TReturn> = ApiCallOrThrowOptions<TReturn> & {\n allowNullResult?: boolean\n emptyResultMessage?: string\n}\n\nexport async function readApiResultOrThrow<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init?: RequestInit,\n options?: ReadApiResultOrThrowOptions<TReturn> & { allowNullResult?: false },\n): Promise<TReturn>\nexport async function readApiResultOrThrow<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n options: ReadApiResultOrThrowOptions<TReturn> & { allowNullResult: true },\n): Promise<TReturn | null>\nexport async function readApiResultOrThrow<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init?: RequestInit,\n options?: ReadApiResultOrThrowOptions<TReturn>,\n): Promise<TReturn | null> {\n const { allowNullResult = false, emptyResultMessage, ...callOptions } = options ?? {}\n const call = await apiCallOrThrow<TReturn>(input, init, callOptions)\n if (call.result == null && !allowNullResult) {\n const fallback =\n emptyResultMessage ?? callOptions.errorMessage ?? `Missing response payload (${call.status})`\n throw new Error(fallback)\n }\n return call.result\n}\n"],
5
- "mappings": ";AAEA,SAAS,gBAAgB;AACzB,SAAS,gBAAgB,oBAAoB;AAC7C,SAAS,+BAA+B;AAexC,MAAM,uBAAuB,wBAAwB;AAErD,SAAS,aAAa,MAA+B,OAA4C;AAC/F,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,OAAO,YAAY,eAAe,gBAAgB,SAAS;AAC7D,UAAM,SAAS,IAAI,QAAQ,IAAI;AAC/B,WAAO,QAAQ,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM,OAAO,IAAI,KAAK,KAAK,CAAC;AACtE,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,CAAC,GAAG,MAAM,GAAG,OAAO,QAAQ,KAAK,CAAC;AAAA,EAC3C;AACA,SAAO,EAAE,GAAI,MAAiC,GAAG,MAAM;AACzD;AAEA,eAAsB,4BACpB,SACA,KACY;AACZ,SAAO,qBAAqB,kBAAkB,SAAS,GAAG;AAC5D;AAEA,eAAsB,QACpB,OACA,MACA,SACiC;AACjC,QAAM,gBAAgB,qBAAqB,qBAAqB;AAChE,QAAM,cAAc,OAAO,KAAK,aAAa,EAAE,SAAS,IACpD,EAAE,GAAI,QAAQ,CAAC,GAAI,SAAS,aAAa,MAAM,SAAS,aAAa,EAAE,IACvE;AACJ,QAAM,WAAW,MAAM,SAAS,OAAO,WAAW;AAClD,QAAM,SAAS,SAAS;AACxB,QAAM,WAAW,SAAS,YAAY;AACtC,MAAI,SAAyB;AAC7B,QAAM,iBACJ,SAAS,SAAS,MAAM,YAAY,KACpC,SAAS,SAAS,MAAM,gBAAgB,KACxC;AACF,QAAM,cAAc,mBAAmB,SAAS,mBAAmB,SAAS,iBAAiB;AAC7F,MAAI;AACF,UAAM,SAAS,OAAQ,SAAmD,UAAU,aAChF,SAAS,MAAM,IACf;AACJ,QAAI,OAAQ,UAAS,MAAM,OAAO,MAAM;AAAA,QACnC,UAAS,MAAM,aAAsB,QAAQ,QAAQ;AAAA,EAC5D,QAAQ;AACN,aAAS;AAAA,EACX;AACA,SAAO;AAAA,IACL,IAAI,SAAS;AAAA,IACb,QAAQ,SAAS;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMA,eAAsB,eACpB,OACA,MACA,SACiC;AACjC,QAAM,EAAE,cAAc,GAAG,YAAY,IAAI,WAAW,CAAC;AACrD,QAAM,OAAO,MAAM,QAAiB,OAAO,MAAM,WAAW;AAC5D,MAAI,CAAC,KAAK,IAAI;AACZ,UAAM,eAAe,KAAK,UAAU,YAAY;AAAA,EAClD;AACA,SAAO;AACT;AAiBA,eAAsB,qBACpB,OACA,MACA,SACyB;AACzB,QAAM,EAAE,kBAAkB,OAAO,oBAAoB,GAAG,YAAY,IAAI,WAAW,CAAC;AACpF,QAAM,OAAO,MAAM,eAAwB,OAAO,MAAM,WAAW;AACnE,MAAI,KAAK,UAAU,QAAQ,CAAC,iBAAiB;AAC3C,UAAM,WACJ,sBAAsB,YAAY,gBAAgB,6BAA6B,KAAK,MAAM;AAC5F,UAAM,IAAI,MAAM,QAAQ;AAAA,EAC1B;AACA,SAAO,KAAK;AACd;",
4
+ "sourcesContent": ["\"use client\"\n\nimport { apiFetch } from './api'\nimport { raiseCrudError, readJsonSafe } from './serverErrors'\nimport { createScopedHeaderStack } from './scopedHeaderStack'\n\nexport type ApiCallOptions<TReturn> = {\n parse?: (res: Response) => Promise<TReturn | null>\n fallback?: TReturn | null\n}\n\nexport type ApiCallResult<TReturn> = {\n ok: boolean\n status: number\n result: TReturn | null\n response: Response\n cacheStatus: 'hit' | 'miss' | null\n}\n\nconst scopedRequestHeaders = createScopedHeaderStack()\n\nfunction isAbortError(error: unknown): boolean {\n return typeof error === 'object'\n && error !== null\n && (error as { name?: unknown }).name === 'AbortError'\n}\n\nfunction createAbortError(): Error {\n if (typeof DOMException === 'function') {\n return new DOMException('[internal] The operation was aborted.', 'AbortError')\n }\n const error = new Error('[internal] The operation was aborted.')\n error.name = 'AbortError'\n return error\n}\n\nfunction resolveAbortSignal(input: RequestInfo | URL, init?: RequestInit): AbortSignal | null {\n if (init?.signal) return init.signal\n if (typeof Request !== 'undefined' && input instanceof Request) return input.signal ?? null\n return null\n}\n\nfunction mergeHeaders(base: HeadersInit | undefined, extra: Record<string, string>): HeadersInit {\n if (!base) return extra\n if (typeof Headers !== 'undefined' && base instanceof Headers) {\n const merged = new Headers(base)\n Object.entries(extra).forEach(([key, value]) => merged.set(key, value))\n return merged\n }\n if (Array.isArray(base)) {\n return [...base, ...Object.entries(extra)]\n }\n return { ...(base as Record<string, string>), ...extra }\n}\n\nexport async function withScopedApiRequestHeaders<T>(\n headers: Record<string, string>,\n run: () => Promise<T>,\n): Promise<T> {\n return scopedRequestHeaders.withScopedHeaders(headers, run)\n}\n\nexport async function apiCall<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init?: RequestInit,\n options?: ApiCallOptions<TReturn>,\n): Promise<ApiCallResult<TReturn>> {\n const scopedHeaders = scopedRequestHeaders.resolveScopedHeaders()\n const requestInit = Object.keys(scopedHeaders).length > 0\n ? { ...(init ?? {}), headers: mergeHeaders(init?.headers, scopedHeaders) }\n : init\n const response = await apiFetch(input, requestInit)\n const parser = options?.parse\n const fallback = options?.fallback ?? null\n let result: TReturn | null = null\n const rawCacheStatus =\n response.headers?.get?.('x-om-cache') ??\n response.headers?.get?.('x-cache-status') ??\n null\n const cacheStatus = rawCacheStatus === 'hit' || rawCacheStatus === 'miss' ? rawCacheStatus : null\n try {\n const source = typeof (response as Response & { clone?: () => Response }).clone === 'function'\n ? response.clone()\n : response\n if (parser) result = await parser(source)\n else result = await readJsonSafe<TReturn>(source, fallback)\n } catch (err) {\n if (isAbortError(err)) throw err\n result = fallback\n }\n // `readJsonSafe` swallows the abort that cancels an in-flight body read, so a\n // request aborted after its response headers arrived would otherwise look like\n // a successful call that returned an empty payload.\n if (result == null && resolveAbortSignal(input, init)?.aborted) {\n throw createAbortError()\n }\n return {\n ok: response.ok,\n status: response.status,\n result,\n response,\n cacheStatus,\n }\n}\n\nexport type ApiCallOrThrowOptions<TReturn> = ApiCallOptions<TReturn> & {\n errorMessage?: string\n}\n\nexport async function apiCallOrThrow<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init?: RequestInit,\n options?: ApiCallOrThrowOptions<TReturn>,\n): Promise<ApiCallResult<TReturn>> {\n const { errorMessage, ...callOptions } = options ?? {}\n const call = await apiCall<TReturn>(input, init, callOptions)\n if (!call.ok) {\n await raiseCrudError(call.response, errorMessage)\n }\n return call\n}\n\nexport type ReadApiResultOrThrowOptions<TReturn> = ApiCallOrThrowOptions<TReturn> & {\n allowNullResult?: boolean\n emptyResultMessage?: string\n}\n\nexport async function readApiResultOrThrow<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init?: RequestInit,\n options?: ReadApiResultOrThrowOptions<TReturn> & { allowNullResult?: false },\n): Promise<TReturn>\nexport async function readApiResultOrThrow<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n options: ReadApiResultOrThrowOptions<TReturn> & { allowNullResult: true },\n): Promise<TReturn | null>\nexport async function readApiResultOrThrow<TReturn = Record<string, unknown>>(\n input: RequestInfo | URL,\n init?: RequestInit,\n options?: ReadApiResultOrThrowOptions<TReturn>,\n): Promise<TReturn | null> {\n const { allowNullResult = false, emptyResultMessage, ...callOptions } = options ?? {}\n const call = await apiCallOrThrow<TReturn>(input, init, callOptions)\n if (call.result == null && !allowNullResult) {\n const fallback =\n emptyResultMessage ?? callOptions.errorMessage ?? `Missing response payload (${call.status})`\n throw new Error(fallback)\n }\n return call.result\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -22,7 +22,8 @@ function LanguageSwitcher() {
22
22
  en: t("common.languages.english", "English"),
23
23
  pl: t("common.languages.polish", "Polski"),
24
24
  es: t("common.languages.spanish", "Espa\xF1ol"),
25
- de: t("common.languages.german", "Deutsch")
25
+ de: t("common.languages.german", "Deutsch"),
26
+ ko: t("common.languages.korean", "\uD55C\uAD6D\uC5B4")
26
27
  };
27
28
  async function setLocale(locale) {
28
29
  if (locale === current) return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/frontend/LanguageSwitcher.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useId, useTransition } from 'react'\nimport { useLocale, useLocaleLocked, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\n\nexport function LanguageSwitcher() {\n const current = useLocale()\n const localeLocked = useLocaleLocked()\n const t = useT()\n const router = useRouter()\n const [pending, startTransition] = useTransition()\n const selectId = useId()\n\n const languageLabels: Record<Locale, string> = {\n en: t('common.languages.english', 'English'),\n pl: t('common.languages.polish', 'Polski'),\n es: t('common.languages.spanish', 'Espa\u00F1ol'),\n de: t('common.languages.german', 'Deutsch'),\n }\n\n async function setLocale(locale: Locale) {\n if (locale === current) return\n try {\n const res = await fetch('/api/auth/locale', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ locale }),\n })\n if (!res.ok) return\n startTransition(() => router.refresh())\n try {\n window.dispatchEvent(new Event('om:refresh-sidebar'))\n } catch {\n // Ignore if window is unavailable\n }\n } catch {\n // Ignore network errors; UX fallback keeps previous locale\n }\n }\n\n // Locale is pinned via OM_FORCE_LOCALE \u2014 switching is a no-op, so hide the control.\n if (localeLocked) return null\n\n return (\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <label htmlFor={selectId}>{t('common.language')}</label>\n <Select\n value={current}\n onValueChange={(value) => setLocale(value as Locale)}\n disabled={pending}\n >\n <SelectTrigger id={selectId} size=\"sm\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {locales.map((locale) => (\n <SelectItem key={locale} value={locale}>\n {languageLabels[locale]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )\n}\n"],
5
- "mappings": ";AAqDM,cACA,YADA;AApDN,SAAS,OAAO,qBAAqB;AACrC,SAAS,WAAW,iBAAiB,YAAY;AACjD,SAAS,iBAAiB;AAC1B,SAAS,eAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,SAAS,mBAAmB;AACjC,QAAM,UAAU,UAAU;AAC1B,QAAM,eAAe,gBAAgB;AACrC,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,SAAS,eAAe,IAAI,cAAc;AACjD,QAAM,WAAW,MAAM;AAEvB,QAAM,iBAAyC;AAAA,IAC7C,IAAI,EAAE,4BAA4B,SAAS;AAAA,IAC3C,IAAI,EAAE,2BAA2B,QAAQ;AAAA,IACzC,IAAI,EAAE,4BAA4B,YAAS;AAAA,IAC3C,IAAI,EAAE,2BAA2B,SAAS;AAAA,EAC5C;AAEA,iBAAe,UAAU,QAAgB;AACvC,QAAI,WAAW,QAAS;AACxB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,oBAAoB;AAAA,QAC1C,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,MACjC,CAAC;AACD,UAAI,CAAC,IAAI,GAAI;AACb,sBAAgB,MAAM,OAAO,QAAQ,CAAC;AACtC,UAAI;AACF,eAAO,cAAc,IAAI,MAAM,oBAAoB,CAAC;AAAA,MACtD,QAAQ;AAAA,MAER;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,MAAI,aAAc,QAAO;AAEzB,SACE,qBAAC,SAAI,WAAU,yDACb;AAAA,wBAAC,WAAM,SAAS,UAAW,YAAE,iBAAiB,GAAE;AAAA,IAChD;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,eAAe,CAAC,UAAU,UAAU,KAAe;AAAA,QACnD,UAAU;AAAA,QAEV;AAAA,8BAAC,iBAAc,IAAI,UAAU,MAAK,MAChC,8BAAC,eAAY,GACf;AAAA,UACA,oBAAC,iBACE,kBAAQ,IAAI,CAAC,WACZ,oBAAC,cAAwB,OAAO,QAC7B,yBAAe,MAAM,KADP,MAEjB,CACD,GACH;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\nimport { useId, useTransition } from 'react'\nimport { useLocale, useLocaleLocked, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\n\nexport function LanguageSwitcher() {\n const current = useLocale()\n const localeLocked = useLocaleLocked()\n const t = useT()\n const router = useRouter()\n const [pending, startTransition] = useTransition()\n const selectId = useId()\n\n const languageLabels: Record<Locale, string> = {\n en: t('common.languages.english', 'English'),\n pl: t('common.languages.polish', 'Polski'),\n es: t('common.languages.spanish', 'Espa\u00F1ol'),\n de: t('common.languages.german', 'Deutsch'),\n ko: t('common.languages.korean', '\uD55C\uAD6D\uC5B4'),\n }\n\n async function setLocale(locale: Locale) {\n if (locale === current) return\n try {\n const res = await fetch('/api/auth/locale', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ locale }),\n })\n if (!res.ok) return\n startTransition(() => router.refresh())\n try {\n window.dispatchEvent(new Event('om:refresh-sidebar'))\n } catch {\n // Ignore if window is unavailable\n }\n } catch {\n // Ignore network errors; UX fallback keeps previous locale\n }\n }\n\n // Locale is pinned via OM_FORCE_LOCALE \u2014 switching is a no-op, so hide the control.\n if (localeLocked) return null\n\n return (\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <label htmlFor={selectId}>{t('common.language')}</label>\n <Select\n value={current}\n onValueChange={(value) => setLocale(value as Locale)}\n disabled={pending}\n >\n <SelectTrigger id={selectId} size=\"sm\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {locales.map((locale) => (\n <SelectItem key={locale} value={locale}>\n {languageLabels[locale]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )\n}\n"],
5
+ "mappings": ";AAsDM,cACA,YADA;AArDN,SAAS,OAAO,qBAAqB;AACrC,SAAS,WAAW,iBAAiB,YAAY;AACjD,SAAS,iBAAiB;AAC1B,SAAS,eAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,SAAS,mBAAmB;AACjC,QAAM,UAAU,UAAU;AAC1B,QAAM,eAAe,gBAAgB;AACrC,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,SAAS,eAAe,IAAI,cAAc;AACjD,QAAM,WAAW,MAAM;AAEvB,QAAM,iBAAyC;AAAA,IAC7C,IAAI,EAAE,4BAA4B,SAAS;AAAA,IAC3C,IAAI,EAAE,2BAA2B,QAAQ;AAAA,IACzC,IAAI,EAAE,4BAA4B,YAAS;AAAA,IAC3C,IAAI,EAAE,2BAA2B,SAAS;AAAA,IAC1C,IAAI,EAAE,2BAA2B,oBAAK;AAAA,EACxC;AAEA,iBAAe,UAAU,QAAgB;AACvC,QAAI,WAAW,QAAS;AACxB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,oBAAoB;AAAA,QAC1C,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,MACjC,CAAC;AACD,UAAI,CAAC,IAAI,GAAI;AACb,sBAAgB,MAAM,OAAO,QAAQ,CAAC;AACtC,UAAI;AACF,eAAO,cAAc,IAAI,MAAM,oBAAoB,CAAC;AAAA,MACtD,QAAQ;AAAA,MAER;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,MAAI,aAAc,QAAO;AAEzB,SACE,qBAAC,SAAI,WAAU,yDACb;AAAA,wBAAC,WAAM,SAAS,UAAW,YAAE,iBAAiB,GAAE;AAAA,IAChD;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,eAAe,CAAC,UAAU,UAAU,KAAe;AAAA,QACnD,UAAU;AAAA,QAEV;AAAA,8BAAC,iBAAc,IAAI,UAAU,MAAK,MAChC,8BAAC,eAAY,GACf;AAAA,UACA,oBAAC,iBACE,kBAAQ,IAAI,CAAC,WACZ,oBAAC,cAAwB,OAAO,QAC7B,yBAAe,MAAM,KADP,MAEjB,CACD,GACH;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -8,10 +8,15 @@ const buttonVariants = cva(
8
8
  variants: {
9
9
  variant: {
10
10
  default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary-hover",
11
- destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 aria-invalid:ring-destructive dark:aria-invalid:ring-destructive dark:bg-destructive/10",
12
- "destructive-outline": "border border-destructive/30 bg-background text-destructive shadow-xs hover:bg-destructive/10 dark:border-destructive/40 dark:hover:bg-destructive/15",
13
- "destructive-soft": "bg-destructive/10 text-destructive hover:bg-destructive/15 dark:bg-destructive/15 dark:hover:bg-destructive/20",
14
- "destructive-ghost": "text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/15",
11
+ /* Destructive is quiet by design: red text on a calm surface. A wall
12
+ of solid red buttons trains users to ignore red. The filled form is
13
+ `destructive-solid`, reserved for the single point-of-no-return
14
+ confirmation inside a dialog — never for the button that opens it. */
15
+ destructive: "border border-destructive bg-background text-destructive shadow-xs hover:bg-destructive/10 aria-invalid:ring-destructive dark:aria-invalid:ring-destructive",
16
+ "destructive-solid": "bg-destructive text-white shadow-xs hover:bg-destructive/90 aria-invalid:ring-destructive dark:aria-invalid:ring-destructive",
17
+ "destructive-outline": "border border-destructive bg-background text-destructive shadow-xs hover:bg-destructive/10",
18
+ "destructive-soft": "bg-destructive/10 text-destructive hover:bg-destructive/15",
19
+ "destructive-ghost": "text-destructive hover:bg-destructive/10",
15
20
  outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
16
21
  secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
17
22
  ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/button.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\nimport { Slot } from '@radix-ui/react-slot'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-all disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled disabled:border-border-disabled disabled:shadow-none disabled:[background-image:none] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:outline-none focus-visible:shadow-focus aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary-hover',\n destructive:\n 'bg-destructive text-white shadow-xs hover:bg-destructive/90 aria-invalid:ring-destructive dark:aria-invalid:ring-destructive dark:bg-destructive/10',\n 'destructive-outline':\n 'border border-destructive/30 bg-background text-destructive shadow-xs hover:bg-destructive/10 dark:border-destructive/40 dark:hover:bg-destructive/15',\n 'destructive-soft':\n 'bg-destructive/10 text-destructive hover:bg-destructive/15 dark:bg-destructive/15 dark:hover:bg-destructive/20',\n 'destructive-ghost':\n 'text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/15',\n outline:\n 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n muted: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-9 px-4 py-2 has-[>svg]:px-3',\n sm: 'h-8 gap-1.5 px-3 has-[>svg]:px-2.5',\n lg: 'h-10 px-6 has-[>svg]:px-4',\n '2xs': 'h-7 gap-1 px-2 py-0.5 has-[>svg]:px-1.5 text-xs',\n icon: 'size-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n }\n)\n\nexport function Button({\n className,\n variant,\n size,\n asChild = false,\n ...props\n}: React.ComponentProps<'button'> &\n VariantProps<typeof buttonVariants> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : 'button'\n return <Comp data-slot=\"button\" className={cn(buttonVariants({ variant, size, className }))} {...props} />\n}\n\nexport { buttonVariants }\n\n"],
5
- "mappings": "AAkDS;AAjDT,SAAS,YAAY;AACrB,SAAS,WAA8B;AACvC,SAAS,UAAU;AAEnB,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,aACE;AAAA,QACF,uBACE;AAAA,QACF,oBACE;AAAA,QACF,qBACE;AAAA,QACF,SACE;AAAA,QACF,WAAW;AAAA,QACX,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,GAAG;AACL,GAC+D;AAC7D,QAAM,OAAO,UAAU,OAAO;AAC9B,SAAO,oBAAC,QAAK,aAAU,UAAS,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC,GAAI,GAAG,OAAO;AAC1G;",
4
+ "sourcesContent": ["import * as React from 'react'\nimport { Slot } from '@radix-ui/react-slot'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-all disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled disabled:border-border-disabled disabled:shadow-none disabled:[background-image:none] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:outline-none focus-visible:shadow-focus aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary-hover',\n /* Destructive is quiet by design: red text on a calm surface. A wall\n of solid red buttons trains users to ignore red. The filled form is\n `destructive-solid`, reserved for the single point-of-no-return\n confirmation inside a dialog \u2014 never for the button that opens it. */\n destructive:\n 'border border-destructive bg-background text-destructive shadow-xs hover:bg-destructive/10 aria-invalid:ring-destructive dark:aria-invalid:ring-destructive',\n 'destructive-solid':\n 'bg-destructive text-white shadow-xs hover:bg-destructive/90 aria-invalid:ring-destructive dark:aria-invalid:ring-destructive',\n 'destructive-outline':\n 'border border-destructive bg-background text-destructive shadow-xs hover:bg-destructive/10',\n 'destructive-soft':\n 'bg-destructive/10 text-destructive hover:bg-destructive/15',\n 'destructive-ghost':\n 'text-destructive hover:bg-destructive/10',\n outline:\n 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n muted: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-9 px-4 py-2 has-[>svg]:px-3',\n sm: 'h-8 gap-1.5 px-3 has-[>svg]:px-2.5',\n lg: 'h-10 px-6 has-[>svg]:px-4',\n '2xs': 'h-7 gap-1 px-2 py-0.5 has-[>svg]:px-1.5 text-xs',\n icon: 'size-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n }\n)\n\nexport function Button({\n className,\n variant,\n size,\n asChild = false,\n ...props\n}: React.ComponentProps<'button'> &\n VariantProps<typeof buttonVariants> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : 'button'\n return <Comp data-slot=\"button\" className={cn(buttonVariants({ variant, size, className }))} {...props} />\n}\n\nexport { buttonVariants }\n\n"],
5
+ "mappings": "AAwDS;AAvDT,SAAS,YAAY;AACrB,SAAS,WAA8B;AACvC,SAAS,UAAU;AAEnB,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,QAKT,aACE;AAAA,QACF,qBACE;AAAA,QACF,uBACE;AAAA,QACF,oBACE;AAAA,QACF,qBACE;AAAA,QACF,SACE;AAAA,QACF,WAAW;AAAA,QACX,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,GAAG;AACL,GAC+D;AAC7D,QAAM,OAAO,UAAU,OAAO;AAC9B,SAAO,oBAAC,QAAK,aAAU,UAAS,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC,GAAI,GAAG,OAAO;AAC1G;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.7-develop.6828.1.ab1620a63e",
3
+ "version": "0.6.7-develop.6842.1.8cd8a8883c",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -155,16 +155,16 @@
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.6.7-develop.6828.1.ab1620a63e",
158
+ "@open-mercato/shared": "0.6.7-develop.6842.1.8cd8a8883c",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
164
  "@figma/code-connect": "^1.3.4",
165
- "@open-mercato/shared": "0.6.7-develop.6828.1.ab1620a63e",
165
+ "@open-mercato/shared": "0.6.7-develop.6842.1.8cd8a8883c",
166
166
  "@testing-library/dom": "^10.4.1",
167
- "@testing-library/jest-dom": "^6.9.1",
167
+ "@testing-library/jest-dom": "^7.0.0",
168
168
  "@testing-library/react": "^16.3.1",
169
169
  "@types/jest": "^30.0.0",
170
170
  "@types/react": "^19.2.17",
@@ -164,7 +164,7 @@ export function JsonBuilder({
164
164
  disabled={disabled}
165
165
  />
166
166
  {parseError && (
167
- <div className="text-xs text-red-600">Invalid JSON format</div>
167
+ <div className="text-xs text-status-error-text">Invalid JSON format</div>
168
168
  )}
169
169
  </div>
170
170
  ) : (
@@ -185,7 +185,7 @@ export function JsonBuilder({
185
185
  </div>
186
186
  )}
187
187
 
188
- {error && <div className="text-xs text-red-600">{error}</div>}
188
+ {error && <div className="text-xs text-status-error-text">{error}</div>}
189
189
  {ConfirmDialogElement}
190
190
  </div>
191
191
  )
@@ -363,7 +363,7 @@ function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }
363
363
  type="button"
364
364
  variant="ghost"
365
365
  size="xs"
366
- className="text-muted-foreground hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
366
+ className="text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
367
367
  title="Remove item"
368
368
  onClick={onDelete}
369
369
  >
@@ -29,6 +29,7 @@ const localeLabels: Record<Locale, string> = {
29
29
  de: 'Deutsch',
30
30
  es: 'Español',
31
31
  pl: 'Polski',
32
+ ko: '한국어',
32
33
  }
33
34
 
34
35
  export function ProfileDropdown({
@@ -123,7 +123,7 @@ export function RowActions({ items = [] }: { items?: RowActionItem[] }) {
123
123
  <a
124
124
  key={idx}
125
125
  href={it.href}
126
- className={`block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? 'text-red-600' : ''}`}
126
+ className={`block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? 'text-destructive' : ''}`}
127
127
  role="menuitem"
128
128
  onClick={(event) => {
129
129
  event.stopPropagation()
@@ -138,7 +138,7 @@ export function RowActions({ items = [] }: { items?: RowActionItem[] }) {
138
138
  type="button"
139
139
  variant="ghost"
140
140
  size="sm"
141
- className={`w-full justify-start rounded-none font-normal ${it.destructive ? 'text-red-600' : ''}`}
141
+ className={`w-full justify-start rounded-none font-normal ${it.destructive ? 'text-destructive' : ''}`}
142
142
  role="menuitem"
143
143
  onClick={(event) => {
144
144
  event.stopPropagation()
@@ -277,7 +277,7 @@ export function ConfirmDialog({
277
277
  {resolvedConfirmText !== false && (
278
278
  <Button
279
279
  ref={confirmButtonRef}
280
- variant={variant === "destructive" ? "destructive" : "default"}
280
+ variant={variant === "destructive" ? "destructive-solid" : "default"}
281
281
  onClick={handleConfirm}
282
282
  disabled={loading}
283
283
  className="w-full sm:w-auto"