@open-mercato/ui 0.6.1-develop.3054.g0367114684 → 0.6.1-develop.3060.d6cca7ade1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/sidebar/SidebarCustomizationEditor.js +39 -15
- package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/__tests__/SidebarCustomizationEditor.test.tsx +81 -1
- package/src/backend/sidebar/SidebarCustomizationEditor.tsx +40 -13
|
@@ -167,6 +167,7 @@ function SidebarCustomizationEditor({
|
|
|
167
167
|
const [canApplyToRoles, setCanApplyToRoles] = React.useState(false);
|
|
168
168
|
const [addDialogOpen, setAddDialogOpen] = React.useState(false);
|
|
169
169
|
const [addDialogName, setAddDialogName] = React.useState("");
|
|
170
|
+
const [addDialogError, setAddDialogError] = React.useState(null);
|
|
170
171
|
const baseSnapshotRef = React.useRef(null);
|
|
171
172
|
const hasInitializedRef = React.useRef(false);
|
|
172
173
|
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
@@ -278,8 +279,8 @@ function SidebarCustomizationEditor({
|
|
|
278
279
|
setSelectedRoleIds((prev) => prev.includes(roleId) ? prev.filter((id) => id !== roleId) : [...prev, roleId]);
|
|
279
280
|
setDirty(true);
|
|
280
281
|
}, []);
|
|
281
|
-
const createNewVariant = React.useCallback(async (proposedName) => {
|
|
282
|
-
if (saving || deleting) return false;
|
|
282
|
+
const createNewVariant = React.useCallback(async (proposedName, options = {}) => {
|
|
283
|
+
if (saving || deleting) return { ok: false };
|
|
283
284
|
if (dirty && selectedVariantId !== null) {
|
|
284
285
|
const proceed = await confirmDialog({
|
|
285
286
|
title: t("appShell.sidebarCustomizationSwitchConfirmTitle", "Discard unsaved changes?"),
|
|
@@ -288,10 +289,10 @@ function SidebarCustomizationEditor({
|
|
|
288
289
|
cancelText: t("common.cancel", "Cancel"),
|
|
289
290
|
variant: "destructive"
|
|
290
291
|
});
|
|
291
|
-
if (!proceed) return false;
|
|
292
|
+
if (!proceed) return { ok: false };
|
|
292
293
|
}
|
|
293
294
|
setSaving(true);
|
|
294
|
-
setError(null);
|
|
295
|
+
if (!options.suppressPageError) setError(null);
|
|
295
296
|
try {
|
|
296
297
|
const baseSnapshot = baseSnapshotRef.current ?? buildBaseSnapshot();
|
|
297
298
|
baseSnapshotRef.current = baseSnapshot;
|
|
@@ -312,8 +313,9 @@ function SidebarCustomizationEditor({
|
|
|
312
313
|
mutationPayload: { name: trimmed.length > 0 ? trimmed : null }
|
|
313
314
|
});
|
|
314
315
|
if (!call.ok) {
|
|
315
|
-
|
|
316
|
-
|
|
316
|
+
const message = formatVariantApiError(call, t);
|
|
317
|
+
if (!options.suppressPageError) setError(message);
|
|
318
|
+
return { ok: false, error: message };
|
|
317
319
|
}
|
|
318
320
|
const created = call.result?.variant ?? null;
|
|
319
321
|
let nextList;
|
|
@@ -331,11 +333,12 @@ function SidebarCustomizationEditor({
|
|
|
331
333
|
selectVariantInternal(fresh, nextList);
|
|
332
334
|
}
|
|
333
335
|
flash(t("appShell.sidebarCustomizationVariantCreated", "Variant created."), "success");
|
|
334
|
-
return true;
|
|
336
|
+
return { ok: true };
|
|
335
337
|
} catch (err) {
|
|
336
338
|
console.error("Failed to create sidebar variant", err);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
+
const message = t("appShell.sidebarCustomizationSaveError");
|
|
340
|
+
if (!options.suppressPageError) setError(message);
|
|
341
|
+
return { ok: false, error: message };
|
|
339
342
|
} finally {
|
|
340
343
|
setSaving(false);
|
|
341
344
|
}
|
|
@@ -446,12 +449,16 @@ function SidebarCustomizationEditor({
|
|
|
446
449
|
onCanceled?.();
|
|
447
450
|
}, [onCanceled]);
|
|
448
451
|
const submitAddDialog = React.useCallback(async () => {
|
|
449
|
-
|
|
450
|
-
|
|
452
|
+
setAddDialogError(null);
|
|
453
|
+
const result = await createNewVariant(addDialogName, { suppressPageError: true });
|
|
454
|
+
if (result.ok) {
|
|
451
455
|
setAddDialogOpen(false);
|
|
452
456
|
setAddDialogName("");
|
|
457
|
+
setAddDialogError(null);
|
|
458
|
+
return;
|
|
453
459
|
}
|
|
454
|
-
|
|
460
|
+
setAddDialogError(result.error ?? t("appShell.sidebarCustomizationSaveError"));
|
|
461
|
+
}, [createNewVariant, addDialogName, t]);
|
|
455
462
|
const sanitizeSettingsPayload = React.useCallback(() => {
|
|
456
463
|
if (!draft || !baseSnapshotRef.current) return null;
|
|
457
464
|
const baseGroups = baseSnapshotRef.current;
|
|
@@ -719,6 +726,7 @@ function SidebarCustomizationEditor({
|
|
|
719
726
|
if (!next) {
|
|
720
727
|
setAddDialogOpen(false);
|
|
721
728
|
setAddDialogName("");
|
|
729
|
+
setAddDialogError(null);
|
|
722
730
|
}
|
|
723
731
|
},
|
|
724
732
|
children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
|
|
@@ -733,7 +741,10 @@ function SidebarCustomizationEditor({
|
|
|
733
741
|
{
|
|
734
742
|
autoFocus: true,
|
|
735
743
|
value: addDialogName,
|
|
736
|
-
onChange: (event) =>
|
|
744
|
+
onChange: (event) => {
|
|
745
|
+
setAddDialogName(event.target.value);
|
|
746
|
+
if (addDialogError) setAddDialogError(null);
|
|
747
|
+
},
|
|
737
748
|
onKeyDown: (event) => {
|
|
738
749
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
739
750
|
event.preventDefault();
|
|
@@ -741,9 +752,20 @@ function SidebarCustomizationEditor({
|
|
|
741
752
|
}
|
|
742
753
|
},
|
|
743
754
|
placeholder: t("appShell.sidebarCustomizationVariantNamePlaceholder", "My preferences"),
|
|
744
|
-
disabled: saving
|
|
755
|
+
disabled: saving,
|
|
756
|
+
"aria-invalid": addDialogError ? true : void 0,
|
|
757
|
+
"aria-describedby": addDialogError ? "sidebar-add-variant-error" : void 0
|
|
745
758
|
}
|
|
746
|
-
)
|
|
759
|
+
),
|
|
760
|
+
addDialogError ? /* @__PURE__ */ jsx(
|
|
761
|
+
"p",
|
|
762
|
+
{
|
|
763
|
+
id: "sidebar-add-variant-error",
|
|
764
|
+
role: "alert",
|
|
765
|
+
className: "text-xs text-destructive",
|
|
766
|
+
children: addDialogError
|
|
767
|
+
}
|
|
768
|
+
) : null
|
|
747
769
|
] }),
|
|
748
770
|
/* @__PURE__ */ jsxs(DialogFooter, { className: "mt-2", children: [
|
|
749
771
|
/* @__PURE__ */ jsx(
|
|
@@ -754,6 +776,7 @@ function SidebarCustomizationEditor({
|
|
|
754
776
|
onClick: () => {
|
|
755
777
|
setAddDialogOpen(false);
|
|
756
778
|
setAddDialogName("");
|
|
779
|
+
setAddDialogError(null);
|
|
757
780
|
},
|
|
758
781
|
disabled: saving,
|
|
759
782
|
children: t("appShell.sidebarCustomizationCancel")
|
|
@@ -832,6 +855,7 @@ function SidebarCustomizationEditor({
|
|
|
832
855
|
type: "button",
|
|
833
856
|
onClick: () => {
|
|
834
857
|
setAddDialogName("");
|
|
858
|
+
setAddDialogError(null);
|
|
835
859
|
setAddDialogOpen(true);
|
|
836
860
|
},
|
|
837
861
|
disabled: isBusy,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/sidebar/SidebarCustomizationEditor.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { ChevronUp, ChevronDown, GripVertical, RotateCcw, Trash2, Plus, Search, AlertTriangle } from 'lucide-react'\nimport { DndContext, closestCenter, PointerSensor, KeyboardSensor, useSensor, useSensors, type DragEndEvent } from '@dnd-kit/core'\nimport { SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport Image from 'next/image'\nimport { resolveInjectedIcon } from '../injection/resolveInjectedIcon'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport { Switch } from '../../primitives/switch'\nimport { Card, CardContent, CardHeader, CardTitle } from '../../primitives/card'\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../../primitives/dialog'\nimport { Tag } from '../../primitives/tag'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { apiCall } from '../utils/apiCall'\nimport { flash } from '../FlashMessages'\nimport { Page, PageBody } from '../Page'\nimport { useBackendChrome } from '../BackendChromeProvider'\nimport { useConfirmDialog } from '../confirm-dialog'\nimport { useGuardedMutation } from '../injection/useGuardedMutation'\nimport {\n applyCustomizationDraft,\n applyItemOrder,\n cloneSidebarGroups,\n collectSidebarDefaults,\n filterMainSidebarGroups,\n mergeGroupOrder,\n resolveGroupKey,\n resolveItemKey,\n type SidebarCustomizationDraft,\n type SidebarGroup,\n type SidebarItem,\n} from './customization-helpers'\n\nexport type SidebarCustomizationEditorProps = {\n onSaved?: () => void\n onCanceled?: () => void\n variantsApiPath?: string\n preferencesApiPath?: string\n groups?: SidebarGroup[]\n}\n\nconst VARIANTS_API_DEFAULT = '/api/auth/sidebar/variants'\nconst PREFERENCES_API_DEFAULT = '/api/auth/sidebar/preferences'\nconst REFRESH_SIDEBAR_EVENT = 'om:refresh-sidebar'\nconst NEW_VARIANT_KEY = '__new__'\n\n// Surface server-provided error messages directly when present (4xx with `error` field\n// like 409 duplicate-name); fall back to the generic copy + status code for opaque 5xx.\nfunction formatVariantApiError(\n call: { ok: boolean; status: number; result: unknown },\n t: (key: string, fallback?: string) => string,\n): string {\n const detail = (call.result as { error?: unknown } | null)?.error\n if (typeof detail === 'string' && detail.length > 0 && call.status >= 400 && call.status < 500) {\n return detail\n }\n if (typeof detail === 'string' && detail.length > 0) {\n return `${t('appShell.sidebarCustomizationSaveError')} (${call.status}: ${detail})`\n }\n return `${t('appShell.sidebarCustomizationSaveError')} (${call.status})`\n}\n\ntype RoleTarget = {\n id: string\n name: string\n hasPreference: boolean\n}\n\ntype VariantSettings = {\n version: number\n groupOrder: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItems: string[]\n itemOrder?: Record<string, string[]>\n}\n\ntype Variant = {\n id: string\n name: string\n isActive: boolean\n settings: VariantSettings\n createdAt: string\n updatedAt: string | null\n}\n\ntype VariantListResponse = { locale: string; variants: Variant[] }\ntype VariantSingleResponse = { locale: string; variant: Variant }\n\nfunction findItemByKey(items: SidebarItem[], targetKey: string): SidebarItem | null {\n for (const item of items) {\n if (resolveItemKey(item) === targetKey) return item\n if (item.children && item.children.length > 0) {\n const found = findItemByKey(item.children, targetKey)\n if (found) return found\n }\n }\n return null\n}\n\nfunction collectDescendantKeys(item: SidebarItem): string[] {\n const out: string[] = []\n const walk = (node: SidebarItem) => {\n if (!node.children) return\n for (const child of node.children) {\n out.push(resolveItemKey(child))\n walk(child)\n }\n }\n walk(item)\n return out\n}\n\nfunction parseDraftFromSettings(\n rawSettings: VariantSettings | null | undefined,\n baseSnapshot: SidebarGroup[],\n): SidebarCustomizationDraft {\n const responseOrder = Array.isArray(rawSettings?.groupOrder)\n ? rawSettings.groupOrder\n .map((id) => (typeof id === 'string' ? id.trim() : ''))\n .filter((id) => id.length > 0)\n : []\n const responseGroupLabels: Record<string, string> = {}\n if (rawSettings?.groupLabels && typeof rawSettings.groupLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.groupLabels)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseGroupLabels[trimmedKey] = value\n }\n }\n const responseItemLabels: Record<string, string> = {}\n if (rawSettings?.itemLabels && typeof rawSettings.itemLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.itemLabels)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseItemLabels[trimmedKey] = value\n }\n }\n const responseHiddenItems = Array.isArray(rawSettings?.hiddenItems)\n ? rawSettings.hiddenItems\n .map((itemId) => (typeof itemId === 'string' ? itemId.trim() : ''))\n .filter((itemId) => itemId.length > 0)\n : []\n const responseItemOrder: Record<string, string[]> = {}\n if (rawSettings?.itemOrder && typeof rawSettings.itemOrder === 'object') {\n for (const [groupKey, list] of Object.entries(rawSettings.itemOrder)) {\n if (!Array.isArray(list)) continue\n const trimmedGroup = groupKey.trim()\n if (!trimmedGroup) continue\n const seen = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n if (typeof itemKey !== 'string') continue\n const trimmedItem = itemKey.trim()\n if (!trimmedItem || seen.has(trimmedItem)) continue\n seen.add(trimmedItem)\n values.push(trimmedItem)\n }\n if (values.length > 0) responseItemOrder[trimmedGroup] = values\n }\n }\n const currentIds = baseSnapshot.map((group) => resolveGroupKey(group))\n const order = mergeGroupOrder(responseOrder, currentIds)\n const { itemDefaults } = collectSidebarDefaults(baseSnapshot)\n const hiddenItemIds: Record<string, boolean> = {}\n for (const itemId of responseHiddenItems) {\n if (!itemDefaults.has(itemId)) continue\n hiddenItemIds[itemId] = true\n }\n return {\n order,\n groupLabels: responseGroupLabels,\n itemLabels: responseItemLabels,\n hiddenItemIds,\n itemOrder: responseItemOrder,\n }\n}\n\nfunction emptyDraftFor(baseSnapshot: SidebarGroup[]): SidebarCustomizationDraft {\n return {\n order: baseSnapshot.map((group) => resolveGroupKey(group)),\n groupLabels: {},\n itemLabels: {},\n hiddenItemIds: {},\n itemOrder: {},\n }\n}\n\nexport function SidebarCustomizationEditor({\n onSaved,\n onCanceled,\n variantsApiPath = VARIANTS_API_DEFAULT,\n preferencesApiPath = PREFERENCES_API_DEFAULT,\n groups: groupsProp,\n}: SidebarCustomizationEditorProps) {\n const t = useT()\n const locale = useLocale()\n const localeLabel = (locale || '').toUpperCase()\n const { payload: chromePayload, isLoading: chromeIsLoading } = useBackendChrome()\n const groupsFromChrome = chromePayload?.groups as SidebarGroup[] | undefined\n const sourceGroups = groupsProp ?? groupsFromChrome ?? []\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [deleting, setDeleting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [variants, setVariants] = React.useState<Variant[]>([])\n const [selectedVariantId, setSelectedVariantId] = React.useState<string | null>(null)\n const [variantName, setVariantName] = React.useState('')\n const [draft, setDraft] = React.useState<SidebarCustomizationDraft | null>(null)\n const [previewGroups, setPreviewGroups] = React.useState<SidebarGroup[]>([])\n const [dirty, setDirty] = React.useState(false)\n const [availableRoleTargets, setAvailableRoleTargets] = React.useState<RoleTarget[]>([])\n const [selectedRoleIds, setSelectedRoleIds] = React.useState<string[]>([])\n const [canApplyToRoles, setCanApplyToRoles] = React.useState(false)\n const [addDialogOpen, setAddDialogOpen] = React.useState(false)\n const [addDialogName, setAddDialogName] = React.useState('')\n const baseSnapshotRef = React.useRef<SidebarGroup[] | null>(null)\n const hasInitializedRef = React.useRef(false)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n variantId?: string | null\n operation: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'sidebar-customization',\n blockedMessage: t('appShell.sidebarCustomizationSaveError'),\n })\n\n const buildMutationContext = React.useCallback(\n (operation: string, variantId?: string | null) => ({\n formId: 'sidebar-customization',\n variantId: variantId ?? null,\n operation,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const isNewVariant = selectedVariantId === null\n const selectedVariant = React.useMemo(\n () => (selectedVariantId ? variants.find((v) => v.id === selectedVariantId) ?? null : null),\n [selectedVariantId, variants],\n )\n\n const updateDraft = React.useCallback((updater: (draft: SidebarCustomizationDraft) => SidebarCustomizationDraft) => {\n setDraft((prev) => {\n if (!prev) return prev\n const next = updater(prev)\n if (baseSnapshotRef.current) {\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, next))\n }\n return next\n })\n setDirty(true)\n }, [])\n\n const buildBaseSnapshot = React.useCallback((): SidebarGroup[] => {\n return filterMainSidebarGroups(cloneSidebarGroups(sourceGroups))\n }, [sourceGroups])\n\n const loadVariantsList = React.useCallback(async (): Promise<Variant[]> => {\n // Cache-bust to prevent stale browser/Next caches from masking just-created variants.\n const url = `${variantsApiPath}?_=${Date.now()}`\n const call = await apiCall<VariantListResponse>(url, { cache: 'no-store' })\n if (!call.ok) {\n throw new Error('list-failed')\n }\n return call.result?.variants ?? []\n }, [variantsApiPath])\n\n const loadRolesPayload = React.useCallback(async (): Promise<{ canApplyToRoles: boolean; roles: RoleTarget[] }> => {\n const call = await apiCall<{ canApplyToRoles?: boolean; roles?: Array<{ id?: string; name?: string; hasPreference?: boolean }> }>(preferencesApiPath)\n if (!call.ok) {\n return { canApplyToRoles: false, roles: [] }\n }\n const data = call.result ?? null\n const can = data?.canApplyToRoles === true\n const roles = Array.isArray(data?.roles)\n ? (data!.roles as Array<{ id?: string; name?: string; hasPreference?: boolean }>)\n .filter((r) => typeof r?.id === 'string' && typeof r?.name === 'string')\n .map((r) => ({ id: r.id as string, name: r.name as string, hasPreference: r.hasPreference === true }))\n : []\n return { canApplyToRoles: can, roles }\n }, [preferencesApiPath])\n\n const selectVariantInternal = React.useCallback((variant: Variant | null, list: Variant[]) => {\n const baseSnapshot = baseSnapshotRef.current ?? buildBaseSnapshot()\n baseSnapshotRef.current = baseSnapshot\n if (variant) {\n const initialDraft = parseDraftFromSettings(variant.settings, baseSnapshot)\n setSelectedVariantId(variant.id)\n setVariantName(variant.name)\n setDraft(initialDraft)\n setPreviewGroups(applyCustomizationDraft(baseSnapshot, initialDraft))\n } else {\n const empty = emptyDraftFor(baseSnapshot)\n setSelectedVariantId(null)\n // Suggest a default name based on the existing variants count.\n const usedNumbers = new Set<number>()\n for (const v of list) {\n if (v.name === 'My preferences') usedNumbers.add(1)\n const match = v.name.match(/^My preferences\\s+(\\d+)$/)\n if (match) usedNumbers.add(Number.parseInt(match[1], 10))\n }\n let next = 1\n while (usedNumbers.has(next)) next += 1\n const suggestion = next === 1 ? 'My preferences' : `My preferences ${next}`\n setVariantName(suggestion)\n setDraft(empty)\n setPreviewGroups(applyCustomizationDraft(baseSnapshot, empty))\n }\n setDirty(false)\n }, [buildBaseSnapshot])\n\n // Initial load. No cancelled flag because React Strict Mode in dev runs effects twice\n // and the cleanup-driven cancellation made the only init pass abort silently \u2014 leaving\n // `loading` true forever and the editor stuck on the \"Loading\u2026\" placeholder. The init\n // gate (`hasInitializedRef`) prevents the second Strict-Mode run from doubling work.\n React.useEffect(() => {\n if (hasInitializedRef.current) return\n if (sourceGroups.length === 0) return\n hasInitializedRef.current = true\n async function init() {\n setLoading(true)\n setError(null)\n try {\n const [list, rolesPayload] = await Promise.all([\n loadVariantsList(),\n loadRolesPayload(),\n ])\n setVariants(list)\n setCanApplyToRoles(rolesPayload.canApplyToRoles)\n setAvailableRoleTargets(rolesPayload.roles)\n const active = list.find((v) => v.isActive)\n const initial = active ?? list[0] ?? null\n selectVariantInternal(initial, list)\n setSelectedRoleIds([])\n } catch (err) {\n console.error('Failed to load sidebar variants', err)\n setError(t('appShell.sidebarCustomizationLoadError'))\n } finally {\n setLoading(false)\n }\n }\n void init()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sourceGroups.length])\n\n const toggleRoleSelection = React.useCallback((roleId: string) => {\n setSelectedRoleIds((prev) => (prev.includes(roleId) ? prev.filter((id) => id !== roleId) : [...prev, roleId]))\n setDirty(true)\n }, [])\n\n const createNewVariant = React.useCallback(async (proposedName?: string): Promise<boolean> => {\n if (saving || deleting) return false\n if (dirty && selectedVariantId !== null) {\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationSwitchConfirmTitle', 'Discard unsaved changes?'),\n text: t('appShell.sidebarCustomizationSwitchConfirmText', 'You have unsaved changes for the current variant. Switching will discard them.'),\n confirmText: t('appShell.sidebarCustomizationSwitchConfirmYes', 'Discard and switch'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return false\n }\n setSaving(true)\n setError(null)\n try {\n const baseSnapshot = baseSnapshotRef.current ?? buildBaseSnapshot()\n baseSnapshotRef.current = baseSnapshot\n const groupOrder = baseSnapshot.map((g) => resolveGroupKey(g))\n const trimmed = (proposedName ?? '').trim()\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(variantsApiPath, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n // If name is omitted, server auto-names (\"My preferences\", \"My preferences 2\", \u2026).\n name: trimmed.length > 0 ? trimmed : undefined,\n settings: { groupOrder, groupLabels: {}, itemLabels: {}, hiddenItems: [], itemOrder: {} },\n isActive: true,\n }),\n }),\n context: buildMutationContext('createVariant'),\n mutationPayload: { name: trimmed.length > 0 ? trimmed : null },\n })\n if (!call.ok) {\n setError(formatVariantApiError(call, t))\n return false\n }\n const created = call.result?.variant ?? null\n // Trust POST response as authoritative; refetch in background for any side-effects\n // (e.g. server-side deactivation of previous active variant).\n let nextList: Variant[]\n try {\n nextList = await loadVariantsList()\n } catch {\n nextList = variants\n }\n // Defensive merge: ensure the just-created variant is in the list even if the\n // refetch happened to be served from a stale cache.\n if (created && !nextList.some((v) => v.id === created.id)) {\n nextList = [...nextList, created]\n }\n setVariants(nextList)\n if (created) {\n const fresh = nextList.find((v) => v.id === created.id) ?? created\n selectVariantInternal(fresh, nextList)\n }\n flash(t('appShell.sidebarCustomizationVariantCreated', 'Variant created.'), 'success')\n return true\n } catch (err) {\n console.error('Failed to create sidebar variant', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n return false\n } finally {\n setSaving(false)\n }\n }, [saving, deleting, dirty, selectedVariantId, confirmDialog, t, buildBaseSnapshot, variantsApiPath, loadVariantsList, selectVariantInternal, variants, runMutation, buildMutationContext])\n\n const handleVariantSwitch = React.useCallback(async (key: string) => {\n if (saving || deleting) return\n if (key === selectedVariantId) return\n if (key === NEW_VARIANT_KEY && isNewVariant) return\n if (dirty) {\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationSwitchConfirmTitle', 'Discard unsaved changes?'),\n text: t('appShell.sidebarCustomizationSwitchConfirmText', 'You have unsaved changes for the current variant. Switching will discard them.'),\n confirmText: t('appShell.sidebarCustomizationSwitchConfirmYes', 'Discard and switch'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return\n }\n if (key === NEW_VARIANT_KEY) {\n selectVariantInternal(null, variants)\n return\n }\n const next = variants.find((v) => v.id === key) ?? null\n selectVariantInternal(next, variants)\n }, [saving, deleting, selectedVariantId, isNewVariant, dirty, confirmDialog, t, variants, selectVariantInternal])\n\n const moveGroup = React.useCallback((groupId: string, offset: number) => {\n updateDraft((draft) => {\n const order = [...draft.order]\n const index = order.indexOf(groupId)\n if (index === -1) return draft\n const nextIndex = Math.max(0, Math.min(order.length - 1, index + offset))\n if (nextIndex === index) return draft\n order.splice(index, 1)\n order.splice(nextIndex, 0, groupId)\n return { ...draft, order }\n })\n }, [updateDraft])\n\n const dndSensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor),\n )\n\n const handleItemDragEnd = React.useCallback((groupKey: string, currentItemKeys: string[]) => (event: DragEndEvent) => {\n const { active, over } = event\n if (!over || active.id === over.id) return\n const fromId = String(active.id)\n const toId = String(over.id)\n updateDraft((draft) => {\n const baseOrder = draft.itemOrder?.[groupKey]?.length\n ? [...draft.itemOrder[groupKey]]\n : [...currentItemKeys]\n const fromIndex = baseOrder.indexOf(fromId)\n const toIndex = baseOrder.indexOf(toId)\n if (fromIndex === -1 || toIndex === -1) return draft\n const nextOrder = arrayMove(baseOrder, fromIndex, toIndex)\n return {\n ...draft,\n itemOrder: { ...(draft.itemOrder ?? {}), [groupKey]: nextOrder },\n }\n })\n }, [updateDraft])\n\n const setGroupLabel = React.useCallback((groupId: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.groupLabels }\n if (value.trim().length === 0) delete next[groupId]\n else next[groupId] = value\n return { ...draft, groupLabels: next }\n })\n }, [updateDraft])\n\n const setItemLabel = React.useCallback((itemId: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.itemLabels }\n if (value.trim().length === 0) delete next[itemId]\n else next[itemId] = value\n return { ...draft, itemLabels: next }\n })\n }, [updateDraft])\n\n const setItemHidden = React.useCallback((itemId: string, hidden: boolean) => {\n updateDraft((draft) => {\n const next = { ...draft.hiddenItemIds }\n const apply = (id: string) => {\n if (hidden) next[id] = true\n else delete next[id]\n }\n apply(itemId)\n // Cascade: hiding a parent hides every descendant; showing it reveals them too.\n if (baseSnapshotRef.current) {\n for (const group of baseSnapshotRef.current) {\n const target = findItemByKey(group.items, itemId)\n if (!target) continue\n for (const descendantKey of collectDescendantKeys(target)) apply(descendantKey)\n break\n }\n }\n return { ...draft, hiddenItemIds: next }\n })\n }, [updateDraft])\n\n const reset = React.useCallback(() => {\n if (!baseSnapshotRef.current) return\n if (selectedVariant) {\n const initialDraft = parseDraftFromSettings(selectedVariant.settings, baseSnapshotRef.current)\n setDraft(initialDraft)\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, initialDraft))\n } else {\n const empty = emptyDraftFor(baseSnapshotRef.current)\n setDraft(empty)\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, empty))\n }\n setDirty(false)\n }, [selectedVariant])\n\n const cancel = React.useCallback(() => {\n onCanceled?.()\n }, [onCanceled])\n\n const submitAddDialog = React.useCallback(async () => {\n const ok = await createNewVariant(addDialogName)\n if (ok) {\n setAddDialogOpen(false)\n setAddDialogName('')\n }\n }, [createNewVariant, addDialogName])\n\n const sanitizeSettingsPayload = React.useCallback(() => {\n if (!draft || !baseSnapshotRef.current) return null\n const baseGroups = baseSnapshotRef.current\n const { groupDefaults, itemDefaults } = collectSidebarDefaults(baseGroups)\n const sanitizedGroupLabels: Record<string, string> = {}\n for (const [key, value] of Object.entries(draft.groupLabels)) {\n const trimmed = value.trim()\n const base = groupDefaults.get(key)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedGroupLabels[key] = trimmed\n }\n const sanitizedItemLabels: Record<string, string> = {}\n for (const [itemId, value] of Object.entries(draft.itemLabels)) {\n const trimmed = value.trim()\n const base = itemDefaults.get(itemId)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedItemLabels[itemId] = trimmed\n }\n const sanitizedHiddenItems: string[] = []\n for (const [itemId, hidden] of Object.entries(draft.hiddenItemIds)) {\n if (!hidden) continue\n if (!itemDefaults.has(itemId)) continue\n sanitizedHiddenItems.push(itemId)\n }\n // Build a Set of valid group keys to drop stale itemOrder entries.\n const groupKeys = new Set<string>()\n for (const group of baseGroups) groupKeys.add(resolveGroupKey(group))\n const sanitizedItemOrder: Record<string, string[]> = {}\n for (const [groupKey, list] of Object.entries(draft.itemOrder ?? {})) {\n if (!groupKeys.has(groupKey)) continue\n const seen = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n if (seen.has(itemKey)) continue\n if (!itemDefaults.has(itemKey)) continue\n seen.add(itemKey)\n values.push(itemKey)\n }\n if (values.length > 0) sanitizedItemOrder[groupKey] = values\n }\n return {\n groupOrder: draft.order,\n groupLabels: sanitizedGroupLabels,\n itemLabels: sanitizedItemLabels,\n hiddenItems: sanitizedHiddenItems,\n itemOrder: sanitizedItemOrder,\n }\n }, [draft])\n\n const save = React.useCallback(async () => {\n const settings = sanitizeSettingsPayload()\n if (!settings) return\n setSaving(true)\n setError(null)\n try {\n const trimmedName = variantName.trim()\n const isCurrentlyActive = selectedVariant?.isActive ?? false\n let savedVariant: Variant | null = null\n if (isNewVariant) {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(variantsApiPath, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: trimmedName.length > 0 ? trimmedName : undefined,\n settings,\n // New variants are activated by default \u2014 there's only one active per scope,\n // others get auto-deactivated server-side.\n isActive: true,\n }),\n }),\n context: buildMutationContext('saveVariant'),\n mutationPayload: { name: trimmedName.length > 0 ? trimmedName : null, isActive: true },\n })\n if (!call.ok) {\n setError(formatVariantApiError(call, t))\n return\n }\n savedVariant = call.result?.variant ?? null\n } else if (selectedVariantId) {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(`${variantsApiPath}/${encodeURIComponent(selectedVariantId)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: trimmedName.length > 0 ? trimmedName : undefined,\n settings,\n isActive: isCurrentlyActive,\n }),\n }),\n context: buildMutationContext('saveVariant', selectedVariantId),\n mutationPayload: {\n id: selectedVariantId,\n name: trimmedName.length > 0 ? trimmedName : null,\n isActive: isCurrentlyActive,\n },\n })\n if (!call.ok) {\n setError(formatVariantApiError(call, t))\n return\n }\n savedVariant = call.result?.variant ?? null\n }\n // Sync user prefs and (optionally) push to roles via the legacy preferences endpoint.\n // The variant entity is the canonical \"saved layout\"; the preferences endpoint is what\n // the AppShell sidebar actually reads. Without this sync, the saved variant wouldn't\n // become the user's live sidebar.\n const preferencesPayload: Record<string, unknown> = {\n groupOrder: settings.groupOrder,\n groupLabels: settings.groupLabels,\n itemLabels: settings.itemLabels,\n hiddenItems: settings.hiddenItems,\n itemOrder: settings.itemOrder,\n }\n if (canApplyToRoles) {\n const applyToRolesPayload = [...selectedRoleIds]\n const clearRoleIdsPayload = availableRoleTargets\n .filter((role) => role.hasPreference && !selectedRoleIds.includes(role.id))\n .map((role) => role.id)\n preferencesPayload.applyToRoles = applyToRolesPayload\n preferencesPayload.clearRoleIds = clearRoleIdsPayload\n }\n const preferencesCall = await runMutation({\n operation: () =>\n apiCall(preferencesApiPath, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(preferencesPayload),\n }),\n context: buildMutationContext('savePreferences', selectedVariantId),\n mutationPayload: preferencesPayload,\n })\n if (!preferencesCall.ok) {\n // The variant entity is the canonical layout; the preferences sync is what the\n // AppShell sidebar actually reads. A failed sync would leave the saved variant\n // not reflected live, so surface it as a save error rather than flashing success.\n setError(formatVariantApiError(preferencesCall, t))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n // Refresh the list so isActive flags are accurate, plus refresh roles so hasPreference flags update.\n const [list, rolesPayload] = await Promise.all([\n loadVariantsList(),\n loadRolesPayload(),\n ])\n // Defensive: ensure the just-saved variant lands in the list even if the refetch\n // was served stale (browser HTTP cache, etc.).\n const mergedList = savedVariant && !list.some((v) => v.id === savedVariant!.id)\n ? [...list, savedVariant]\n : list\n setVariants(mergedList)\n setCanApplyToRoles(rolesPayload.canApplyToRoles)\n setAvailableRoleTargets(rolesPayload.roles)\n if (savedVariant) {\n const fresh = mergedList.find((v) => v.id === savedVariant!.id) ?? savedVariant\n selectVariantInternal(fresh, mergedList)\n } else {\n const active = mergedList.find((v) => v.isActive) ?? mergedList[0] ?? null\n selectVariantInternal(active, mergedList)\n }\n flash(\n isNewVariant\n ? t('appShell.sidebarCustomizationVariantCreated', 'Variant created.')\n : t('appShell.sidebarCustomizationVariantSaved', 'Variant saved.'),\n 'success',\n )\n onSaved?.()\n } catch (err) {\n console.error('Failed to save sidebar variant', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n } finally {\n setSaving(false)\n }\n }, [draft, variantName, isNewVariant, selectedVariant, selectedVariantId, variantsApiPath, preferencesApiPath, canApplyToRoles, selectedRoleIds, availableRoleTargets, t, sanitizeSettingsPayload, loadVariantsList, loadRolesPayload, selectVariantInternal, onSaved, runMutation, buildMutationContext])\n\n const toggleActive = React.useCallback(async (next: boolean) => {\n if (!selectedVariant || saving || deleting) return\n setError(null)\n try {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(`${variantsApiPath}/${encodeURIComponent(selectedVariant.id)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ isActive: next }),\n }),\n context: buildMutationContext('toggleVariantActive', selectedVariant.id),\n mutationPayload: { id: selectedVariant.id, isActive: next },\n })\n if (!call.ok) {\n setError(t('appShell.sidebarCustomizationSaveError'))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n const list = await loadVariantsList()\n setVariants(list)\n const fresh = list.find((v) => v.id === selectedVariant.id) ?? selectedVariant\n selectVariantInternal(fresh, list)\n } catch (err) {\n console.error('Failed to toggle variant active state', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n }\n }, [selectedVariant, saving, deleting, variantsApiPath, t, loadVariantsList, selectVariantInternal, runMutation, buildMutationContext])\n\n const deleteVariant = React.useCallback(async () => {\n if (!selectedVariant) return\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationDeleteVariantTitle', 'Delete variant?'),\n text: t(\n 'appShell.sidebarCustomizationDeleteVariantText',\n 'This variant will be removed from your library.',\n ),\n confirmText: t('appShell.sidebarCustomizationDeleteVariantConfirm', 'Delete variant'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return\n setDeleting(true)\n setError(null)\n try {\n const call = await runMutation({\n operation: () =>\n apiCall(`${variantsApiPath}/${encodeURIComponent(selectedVariant.id)}`, { method: 'DELETE' }),\n context: buildMutationContext('deleteVariant', selectedVariant.id),\n mutationPayload: { id: selectedVariant.id },\n })\n if (!call.ok) {\n setError(t('appShell.sidebarCustomizationSaveError'))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n const list = await loadVariantsList()\n setVariants(list)\n const fallback = list[0] ?? null\n selectVariantInternal(fallback, list)\n } catch (err) {\n console.error('Failed to delete variant', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n } finally {\n setDeleting(false)\n }\n }, [selectedVariant, confirmDialog, t, variantsApiPath, loadVariantsList, selectVariantInternal, runMutation, buildMutationContext])\n\n const isBusy = saving || deleting\n\n if (loading && !draft) {\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"space-y-6\">\n <div className=\"space-y-2\">\n <div className=\"h-7 w-64 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-96 animate-pulse rounded bg-muted/60\" />\n </div>\n <div className=\"h-64 animate-pulse rounded-lg border bg-muted/30\" />\n </div>\n </>\n )\n }\n\n if (!draft || !baseSnapshotRef.current) {\n // While chrome payload streams in or the initial fetch runs, show a neutral loading\n // state instead of the error fallback (otherwise the first visit looks like a crash).\n const stillLoading = loading || chromeIsLoading || sourceGroups.length === 0\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"rounded-lg border border-dashed bg-muted/30 p-6 text-sm text-muted-foreground\">\n {stillLoading\n ? t('appShell.sidebarCustomizationLoading', 'Loading\u2026')\n : (error ?? t('appShell.sidebarCustomizationLoadError'))}\n </div>\n </>\n )\n }\n\n const baseGroupsForDefaults = baseSnapshotRef.current\n const baseGroupMap = new Map<string, SidebarGroup>()\n for (const group of baseGroupsForDefaults) {\n baseGroupMap.set(resolveGroupKey(group), group)\n }\n const orderedGroupIds = mergeGroupOrder(draft.order, Array.from(baseGroupMap.keys()))\n const totalGroups = orderedGroupIds.length\n\n const selectValue = isNewVariant ? NEW_VARIANT_KEY : selectedVariantId ?? NEW_VARIANT_KEY\n const showVariantPicker = variants.length > 0 || isNewVariant\n\n return (\n <>\n {ConfirmDialogElement}\n <Dialog\n open={addDialogOpen}\n onOpenChange={(next) => {\n if (!next) {\n setAddDialogOpen(false)\n setAddDialogName('')\n }\n }}\n >\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>\n {t('appShell.sidebarCustomizationAddDialogTitle', 'Add new variant')}\n </DialogTitle>\n <DialogDescription>\n {t('appShell.sidebarCustomizationAddDialogDescription', 'Choose a name for the new sidebar variant. Leave blank to auto-name it.')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantNameLabel', 'Variant name')}\n </label>\n <Input\n autoFocus\n value={addDialogName}\n onChange={(event) => setAddDialogName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault()\n void submitAddDialog()\n }\n }}\n placeholder={t('appShell.sidebarCustomizationVariantNamePlaceholder', 'My preferences')}\n disabled={saving}\n />\n </div>\n <DialogFooter className=\"mt-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n setAddDialogOpen(false)\n setAddDialogName('')\n }}\n disabled={saving}\n >\n {t('appShell.sidebarCustomizationCancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => { void submitAddDialog() }}\n disabled={saving}\n >\n {saving\n ? t('appShell.sidebarCustomizationCreating', 'Creating\u2026')\n : t('appShell.sidebarCustomizationCreateVariant', 'Create variant')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Page>\n <header className=\"space-y-1\">\n <h1 className=\"text-xl sm:text-2xl font-semibold leading-tight\">\n {t('appShell.sidebarCustomizationHeading')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationHint', { locale: localeLabel })}\n </p>\n </header>\n\n {error ? (\n <div className=\"rounded-lg border border-destructive/40 bg-destructive/5 px-4 py-3 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n\n {/* Two-column: editor (variant + roles + order) + preview */}\n <PageBody className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,360px)]\">\n <div className=\"space-y-6\">\n {(() => {\n const showRolesCard = canApplyToRoles && availableRoleTargets.length > 0\n if (!showVariantPicker && !showRolesCard) return null\n return (\n <Card>\n <CardContent className=\"flex flex-col gap-6\">\n {showVariantPicker ? (\n <div className=\"flex flex-col gap-4\">\n {/* Row: combobox-style name input with chevron picker + DS-compliant add button */}\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantNameLabel', 'Variant name')}\n </label>\n <div className=\"flex items-stretch gap-2\">\n {/* Combobox group: input + chevron picker share a single visual border. */}\n <div className=\"relative flex flex-1 items-stretch\">\n <Input\n value={variantName}\n onChange={(event) => {\n setVariantName(event.target.value)\n setDirty(true)\n }}\n placeholder={t('appShell.sidebarCustomizationVariantNamePlaceholder', 'My preferences')}\n disabled={isBusy}\n className=\"w-full pr-10\"\n />\n <Select\n value={selectValue}\n onValueChange={(value) => { void handleVariantSwitch(value) }}\n disabled={isBusy || loading}\n >\n <SelectTrigger\n className=\"pointer-events-none absolute inset-0 h-full w-full justify-end border-0 bg-transparent px-3 shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 [&>span]:hidden [&>svg]:pointer-events-auto\"\n aria-label={t('appShell.sidebarCustomizationVariantPickerLabel', 'Pick variant')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {variants.length > 0 ? (\n variants.map((variant) => (\n <SelectItem key={variant.id} value={variant.id}>\n {variant.name}\n </SelectItem>\n ))\n ) : (\n <SelectItem value={NEW_VARIANT_KEY} disabled>\n {t('appShell.sidebarCustomizationVariantsEmpty', 'No saved variants yet')}\n </SelectItem>\n )}\n </SelectContent>\n </Select>\n </div>\n <Button\n type=\"button\"\n onClick={() => {\n setAddDialogName('')\n setAddDialogOpen(true)\n }}\n disabled={isBusy}\n title={t('appShell.sidebarCustomizationVariantNew', 'Add new variant')}\n >\n <Plus className=\"size-4\" />\n {t('appShell.sidebarCustomizationCreateNew', 'Create new')}\n </Button>\n </div>\n </div>\n\n {isNewVariant ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationVariantNewHint', 'Saving will create a new variant. If you leave the name blank, it will be auto-named.')}\n </p>\n ) : null}\n\n {/* Row: active switch */}\n <div className=\"flex items-center gap-2\">\n <Switch\n checked={selectedVariant?.isActive ?? isNewVariant}\n onCheckedChange={(next) => {\n if (isNewVariant) return\n void toggleActive(next === true)\n }}\n disabled={isBusy || isNewVariant}\n aria-label={t('appShell.sidebarCustomizationVariantActiveLabel', 'Active')}\n />\n <span className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantActiveLabel', 'Active')}\n </span>\n </div>\n </div>\n ) : null}\n\n {showVariantPicker && showRolesCard ? (\n <div className=\"-mx-6 border-t\" aria-hidden />\n ) : null}\n\n {showRolesCard ? (\n <div className=\"flex flex-col gap-3\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold leading-none text-foreground\">\n {t('appShell.sidebarApplyToRolesTitle')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">{t('appShell.sidebarApplyToRolesDescription')}</p>\n </div>\n <div className=\"flex flex-col gap-1.5 max-w-sm\">\n {availableRoleTargets.map((role) => {\n const checked = selectedRoleIds.includes(role.id)\n const willClear = role.hasPreference && !checked\n return (\n <label\n key={role.id}\n className=\"flex cursor-pointer items-center gap-3 rounded-lg border bg-background px-3 py-2 text-sm transition-colors hover:bg-muted\"\n >\n <Switch\n checked={checked}\n onCheckedChange={() => toggleRoleSelection(role.id)}\n disabled={isBusy}\n />\n <span className=\"flex-1 truncate font-medium text-foreground\">{role.name}</span>\n {role.hasPreference ? (\n <Tag variant={willClear ? 'error' : 'info'} dot={!willClear}>\n {willClear ? <AlertTriangle className=\"size-3\" aria-hidden /> : null}\n {willClear ? t('appShell.sidebarRoleWillClear') : t('appShell.sidebarRoleHasPreset')}\n </Tag>\n ) : null}\n </label>\n )\n })}\n </div>\n </div>\n ) : null}\n\n {/* Footer: Reset / Cancel / Save (right) + Delete (left). All gated on dirty\n except Delete which acts on the persisted variant regardless of edits. */}\n <div className=\"-mx-6 border-t\" aria-hidden />\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n {selectedVariant ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => { void deleteVariant() }}\n disabled={isBusy}\n className=\"text-destructive hover:text-destructive\"\n >\n <Trash2 className=\"size-4\" />\n {deleting\n ? t('appShell.sidebarCustomizationDeleteVariantInProgress', 'Deleting\u2026')\n : t('appShell.sidebarCustomizationDeleteVariant', 'Delete variant')}\n </Button>\n ) : <span />}\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={reset}\n disabled={isBusy || !dirty}\n >\n {t('appShell.sidebarCustomizationReset')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={cancel}\n disabled={isBusy || !dirty}\n >\n {t('appShell.sidebarCustomizationCancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={save}\n disabled={isBusy || (!isNewVariant && !dirty)}\n >\n {saving\n ? (isNewVariant\n ? t('appShell.sidebarCustomizationCreating', 'Creating\u2026')\n : t('appShell.sidebarCustomizationSaving'))\n : (isNewVariant\n ? t('appShell.sidebarCustomizationCreateVariant', 'Create variant')\n : t('appShell.sidebarCustomizationSave'))}\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n })()}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">\n {t('appShell.sidebarCustomizationOrderHeading', 'Order & visibility')}\n </CardTitle>\n <p className=\"text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationOrderDescription', 'Reorder groups, rename them, and toggle individual items on or off.')}\n </p>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {orderedGroupIds.map((groupId, index) => {\n const baseGroup = baseGroupMap.get(groupId)\n if (!baseGroup) return null\n const placeholder = baseGroup.defaultName ?? baseGroup.name\n const value = draft.groupLabels[groupId] ?? ''\n const trimmedValue = value.trim()\n const isGroupModified = trimmedValue.length > 0 && trimmedValue !== placeholder\n return (\n <div key={groupId} className=\"rounded-lg border bg-background\">\n <div className=\"flex items-start gap-3 border-b px-4 py-3\">\n <div className=\"flex flex-1 flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationGroupLabel')}\n </label>\n <div className=\"flex items-center gap-2\">\n <Input\n value={value}\n onChange={(event) => setGroupLabel(groupId, event.target.value)}\n placeholder={placeholder}\n disabled={isBusy}\n className=\"flex-1\"\n />\n {isGroupModified ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setGroupLabel(groupId, '')}\n disabled={isBusy}\n aria-label={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n title={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n >\n <RotateCcw className=\"size-3.5\" />\n </IconButton>\n ) : null}\n </div>\n {isGroupModified ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationDefault', 'Default:')}{' '}\n <span className=\"font-medium text-foreground/80\">{placeholder}</span>\n </p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-1 mt-[26px]\">\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, -1)}\n disabled={index === 0 || isBusy}\n aria-label={t('appShell.sidebarCustomizationMoveUp')}\n title={t('appShell.sidebarCustomizationMoveUp')}\n >\n <ChevronUp className=\"size-4\" />\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, 1)}\n disabled={index === totalGroups - 1 || isBusy}\n aria-label={t('appShell.sidebarCustomizationMoveDown')}\n title={t('appShell.sidebarCustomizationMoveDown')}\n >\n <ChevronDown className=\"size-4\" />\n </IconButton>\n </div>\n </div>\n <div className=\"flex flex-col divide-y\">\n <ItemRows\n items={baseGroup.items}\n draft={draft}\n saving={isBusy}\n onLabelChange={setItemLabel}\n onHiddenChange={setItemHidden}\n t={t}\n groupKey={groupId}\n sensors={dndSensors}\n onDragEnd={handleItemDragEnd(groupId, baseGroup.items.map((item) => resolveItemKey(item)))}\n />\n </div>\n </div>\n )\n })}\n </CardContent>\n </Card>\n </div>\n\n <aside className=\"hidden lg:block\">\n <div className=\"sticky top-6\">\n <div className=\"relative\">\n <span className=\"absolute left-1/2 top-0 z-10 -translate-x-1/2 -translate-y-1/2 rounded-md bg-accent-indigo px-3 py-1 text-xs font-semibold uppercase tracking-wider text-accent-indigo-foreground shadow-sm\">\n {t('appShell.sidebarCustomizationPreview', 'Preview')}\n </span>\n <SidebarPreview\n groups={previewGroups}\n productName={t('appShell.productName', 'Open Mercato')}\n pickFirstActive\n />\n </div>\n </div>\n </aside>\n </PageBody>\n </Page>\n </>\n )\n}\n\ntype ItemRowProps = {\n item: SidebarItem\n draft: SidebarCustomizationDraft\n saving: boolean\n onLabelChange: (itemId: string, value: string) => void\n onHiddenChange: (itemId: string, hidden: boolean) => void\n t: ReturnType<typeof useT>\n depth: number\n dragHandle?: React.ReactNode\n /** True when an ancestor in the tree is hidden \u2014 child controls become read-only. */\n ancestorHidden?: boolean\n}\n\nfunction ItemRow({ item, draft, saving, onLabelChange, onHiddenChange, t, depth, dragHandle, ancestorHidden = false }: ItemRowProps) {\n const itemKey = resolveItemKey(item)\n const placeholder = item.defaultTitle ?? item.title\n const value = draft.itemLabels[itemKey] ?? ''\n const trimmedValue = value.trim()\n const isModified = trimmedValue.length > 0 && trimmedValue !== placeholder\n const hidden = draft.hiddenItemIds[itemKey] === true\n const effectivelyDimmed = hidden || ancestorHidden\n return (\n <div\n className=\"flex items-start gap-3 px-4 py-3 transition-colors hover:bg-muted/40\"\n style={depth ? { paddingLeft: 16 + depth * 24 } : undefined}\n >\n {dragHandle ?? (depth > 0 ? <span className=\"w-4 shrink-0\" aria-hidden /> : null)}\n <div className={`min-w-0 flex-1 flex flex-col gap-1.5 ${effectivelyDimmed ? 'opacity-60' : ''}`}>\n <div className=\"flex items-center gap-2\">\n <div className=\"min-w-0 flex-1\">\n <Input\n value={value}\n onChange={(event) => onLabelChange(itemKey, event.target.value)}\n placeholder={placeholder}\n disabled={saving}\n />\n </div>\n {isModified ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onLabelChange(itemKey, '')}\n disabled={saving}\n aria-label={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n title={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n >\n <RotateCcw className=\"size-3.5\" />\n </IconButton>\n ) : null}\n </div>\n {isModified ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationDefault', 'Default:')}{' '}\n <span className=\"font-medium text-foreground/80\">{placeholder}</span>\n </p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2 pt-1.5\">\n {hidden ? (\n <span className=\"rounded-full border border-border bg-muted px-2 py-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('appShell.sidebarCustomizationHiddenBadge', 'Hidden')}\n </span>\n ) : null}\n <Switch\n checked={!hidden}\n onCheckedChange={(next) => onHiddenChange(itemKey, next !== true)}\n disabled={saving || ancestorHidden}\n aria-label={t('appShell.sidebarCustomizationShowItem')}\n title={ancestorHidden ? t('appShell.sidebarCustomizationParentHiddenHint', 'Parent is hidden \u2014 show parent first.') : undefined}\n />\n </div>\n </div>\n )\n}\n\ntype SortableItemRowProps = ItemRowProps & { id: string }\n\nfunction SortableItemRow({ id, ...rowProps }: SortableItemRowProps) {\n const t = useT()\n const { attributes, listeners, setNodeRef, transform, transition, isDragging, setActivatorNodeRef } = useSortable({ id })\n const style: React.CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n }\n const dragHandle = (\n <IconButton\n ref={setActivatorNodeRef}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"shrink-0 mt-1.5 cursor-grab touch-none active:cursor-grabbing\"\n aria-label={t('appShell.sidebarCustomizationDragToReorder', 'Drag to reorder')}\n disabled={rowProps.saving}\n {...attributes}\n {...listeners}\n >\n <GripVertical className=\"size-4\" />\n </IconButton>\n )\n return (\n <div ref={setNodeRef} style={style}>\n <ItemRow {...rowProps} dragHandle={dragHandle} />\n </div>\n )\n}\n\ntype ItemRowsProps = {\n items: SidebarItem[]\n draft: SidebarCustomizationDraft\n saving: boolean\n onLabelChange: (itemId: string, value: string) => void\n onHiddenChange: (itemId: string, hidden: boolean) => void\n t: ReturnType<typeof useT>\n depth?: number\n groupKey?: string\n sensors?: ReturnType<typeof useSensors>\n onDragEnd?: (event: DragEndEvent) => void\n ancestorHidden?: boolean\n}\n\nfunction ItemRows({\n items,\n draft,\n saving,\n onLabelChange,\n onHiddenChange,\n t,\n depth = 0,\n groupKey,\n sensors,\n onDragEnd,\n ancestorHidden = false,\n}: ItemRowsProps) {\n if (items.length === 0) return null\n\n const renderRecursiveChildren = (item: SidebarItem, parentHidden: boolean) =>\n item.children && item.children.length > 0 ? (\n <ItemRows\n items={item.children}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth + 1}\n ancestorHidden={parentHidden}\n />\n ) : null\n\n // Top-level rows in a group \u2192 enable DnD reordering.\n if (depth === 0 && groupKey && sensors && onDragEnd) {\n const ordered = applyItemOrder(items, resolveItemKey, draft.itemOrder?.[groupKey])\n const ids = ordered.map((item) => resolveItemKey(item))\n return (\n <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>\n <SortableContext items={ids} strategy={verticalListSortingStrategy}>\n {ordered.map((item) => {\n const itemKey = resolveItemKey(item)\n const ownHidden = draft.hiddenItemIds[itemKey] === true\n return (\n <React.Fragment key={itemKey}>\n <SortableItemRow\n id={itemKey}\n item={item}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth}\n ancestorHidden={ancestorHidden}\n />\n {renderRecursiveChildren(item, ancestorHidden || ownHidden)}\n </React.Fragment>\n )\n })}\n </SortableContext>\n </DndContext>\n )\n }\n\n // Nested children \u2192 static rendering, no drag handle.\n return (\n <>\n {items.map((item) => {\n const itemKey = resolveItemKey(item)\n const ownHidden = draft.hiddenItemIds[itemKey] === true\n return (\n <React.Fragment key={itemKey}>\n <ItemRow\n item={item}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth}\n ancestorHidden={ancestorHidden}\n />\n {renderRecursiveChildren(item, ancestorHidden || ownHidden)}\n </React.Fragment>\n )\n })}\n </>\n )\n}\n\nfunction SidebarPreviewIcon({ item }: { item: SidebarItem }) {\n if (item.icon) return <>{item.icon}</>\n if (item.iconName) {\n const resolved = resolveInjectedIcon(item.iconName)\n if (resolved) return <>{resolved}</>\n }\n if (item.iconMarkup) {\n return <span aria-hidden=\"true\" dangerouslySetInnerHTML={{ __html: item.iconMarkup }} />\n }\n // Fallback default icon \u2014 same shape as AppShell's DefaultIcon\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n )\n}\n\nfunction SidebarPreview({\n groups,\n productName,\n pickFirstActive,\n}: {\n groups: SidebarGroup[]\n productName: string\n pickFirstActive: boolean\n}) {\n const t = useT()\n // Pre-compute the first visible item so we can render it as the \"active\" preview state.\n // This shows the user what the active state of their sidebar will look like.\n const activeKey = React.useMemo<string | null>(() => {\n if (!pickFirstActive) return null\n for (const group of groups) {\n for (const item of group.items) {\n if (item.hidden === true) continue\n return resolveItemKey(item)\n }\n }\n return null\n }, [groups, pickFirstActive])\n\n return (\n <div className=\"relative w-[240px] overflow-hidden rounded-xl border bg-background shadow-sm\">\n {/* Match AppShell's outer aside: border-r, py-4, px-3 \u2014 minus border-r since the\n card border already serves that purpose, plus rounded so it reads as a preview tile. */}\n <div className=\"flex flex-col gap-3 px-3 py-4\">\n {/* Brand block \u2014 same classes as AppShell brand tile */}\n <div className=\"mb-2\">\n <div className=\"flex items-center gap-3 rounded-xl p-3\">\n <Image\n src=\"/open-mercato.svg\"\n alt={productName}\n width={40}\n height={40}\n className=\"rounded-full shrink-0\"\n />\n <span className=\"text-sm font-medium text-foreground truncate\">{productName}</span>\n </div>\n </div>\n {/* Search input mock \u2014 same container styling as the real sidebar */}\n <div className=\"mb-2 flex items-center gap-2 rounded-lg border border-border bg-background pl-2.5 pr-2 py-2 shadow-sm\">\n <Search className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 text-sm text-muted-foreground/70 truncate\">\n {t('appShell.sidebarCustomizationPreviewSearchPlaceholder', 'Search...')}\n </span>\n </div>\n {groups.length === 0 ? (\n <p className=\"px-2 text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationPreviewEmpty', 'No groups to preview.')}\n </p>\n ) : (\n <nav className=\"flex flex-col gap-2\">\n {groups.map((group, gi) => {\n const visibleItems = group.items.filter((item) => item.hidden !== true)\n if (visibleItems.length === 0) return null\n return (\n <div key={resolveGroupKey(group)}>\n <div className=\"w-full px-1 justify-between flex text-xs font-medium uppercase tracking-wider text-muted-foreground/70 py-1\">\n <span>{group.name}</span>\n </div>\n <div className=\"flex flex-col gap-1\">\n {visibleItems.map((item) => {\n const itemKey = resolveItemKey(item)\n const isActive = activeKey === itemKey\n return (\n <div\n key={itemKey}\n className={`relative text-sm font-medium rounded-lg inline-flex items-center w-full px-3 py-2 gap-2 ${\n isActive ? 'bg-muted text-foreground' : 'text-muted-foreground'\n }`}\n >\n {isActive ? (\n <span\n aria-hidden\n className=\"absolute left-[-12px] top-2 w-1 h-5 rounded-r bg-foreground\"\n />\n ) : null}\n <span className=\"flex items-center justify-center shrink-0\">\n <SidebarPreviewIcon item={item} />\n </span>\n <span className=\"truncate\">{item.title}</span>\n </div>\n )\n })}\n </div>\n {gi < groups.length - 1 ? <div className=\"my-2 border-t -ml-3 -mr-4\" /> : null}\n </div>\n )\n })}\n </nav>\n )}\n </div>\n </div>\n )\n}\n\nexport default SidebarCustomizationEditor\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { ChevronUp, ChevronDown, GripVertical, RotateCcw, Trash2, Plus, Search, AlertTriangle } from 'lucide-react'\nimport { DndContext, closestCenter, PointerSensor, KeyboardSensor, useSensor, useSensors, type DragEndEvent } from '@dnd-kit/core'\nimport { SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport Image from 'next/image'\nimport { resolveInjectedIcon } from '../injection/resolveInjectedIcon'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport { Switch } from '../../primitives/switch'\nimport { Card, CardContent, CardHeader, CardTitle } from '../../primitives/card'\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../../primitives/dialog'\nimport { Tag } from '../../primitives/tag'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { apiCall } from '../utils/apiCall'\nimport { flash } from '../FlashMessages'\nimport { Page, PageBody } from '../Page'\nimport { useBackendChrome } from '../BackendChromeProvider'\nimport { useConfirmDialog } from '../confirm-dialog'\nimport { useGuardedMutation } from '../injection/useGuardedMutation'\nimport {\n applyCustomizationDraft,\n applyItemOrder,\n cloneSidebarGroups,\n collectSidebarDefaults,\n filterMainSidebarGroups,\n mergeGroupOrder,\n resolveGroupKey,\n resolveItemKey,\n type SidebarCustomizationDraft,\n type SidebarGroup,\n type SidebarItem,\n} from './customization-helpers'\n\nexport type SidebarCustomizationEditorProps = {\n onSaved?: () => void\n onCanceled?: () => void\n variantsApiPath?: string\n preferencesApiPath?: string\n groups?: SidebarGroup[]\n}\n\nconst VARIANTS_API_DEFAULT = '/api/auth/sidebar/variants'\nconst PREFERENCES_API_DEFAULT = '/api/auth/sidebar/preferences'\nconst REFRESH_SIDEBAR_EVENT = 'om:refresh-sidebar'\nconst NEW_VARIANT_KEY = '__new__'\n\n// Surface server-provided error messages directly when present (4xx with `error` field\n// like 409 duplicate-name); fall back to the generic copy + status code for opaque 5xx.\nfunction formatVariantApiError(\n call: { ok: boolean; status: number; result: unknown },\n t: (key: string, fallback?: string) => string,\n): string {\n const detail = (call.result as { error?: unknown } | null)?.error\n if (typeof detail === 'string' && detail.length > 0 && call.status >= 400 && call.status < 500) {\n return detail\n }\n if (typeof detail === 'string' && detail.length > 0) {\n return `${t('appShell.sidebarCustomizationSaveError')} (${call.status}: ${detail})`\n }\n return `${t('appShell.sidebarCustomizationSaveError')} (${call.status})`\n}\n\ntype RoleTarget = {\n id: string\n name: string\n hasPreference: boolean\n}\n\ntype VariantSettings = {\n version: number\n groupOrder: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItems: string[]\n itemOrder?: Record<string, string[]>\n}\n\ntype Variant = {\n id: string\n name: string\n isActive: boolean\n settings: VariantSettings\n createdAt: string\n updatedAt: string | null\n}\n\ntype VariantListResponse = { locale: string; variants: Variant[] }\ntype VariantSingleResponse = { locale: string; variant: Variant }\n\nfunction findItemByKey(items: SidebarItem[], targetKey: string): SidebarItem | null {\n for (const item of items) {\n if (resolveItemKey(item) === targetKey) return item\n if (item.children && item.children.length > 0) {\n const found = findItemByKey(item.children, targetKey)\n if (found) return found\n }\n }\n return null\n}\n\nfunction collectDescendantKeys(item: SidebarItem): string[] {\n const out: string[] = []\n const walk = (node: SidebarItem) => {\n if (!node.children) return\n for (const child of node.children) {\n out.push(resolveItemKey(child))\n walk(child)\n }\n }\n walk(item)\n return out\n}\n\nfunction parseDraftFromSettings(\n rawSettings: VariantSettings | null | undefined,\n baseSnapshot: SidebarGroup[],\n): SidebarCustomizationDraft {\n const responseOrder = Array.isArray(rawSettings?.groupOrder)\n ? rawSettings.groupOrder\n .map((id) => (typeof id === 'string' ? id.trim() : ''))\n .filter((id) => id.length > 0)\n : []\n const responseGroupLabels: Record<string, string> = {}\n if (rawSettings?.groupLabels && typeof rawSettings.groupLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.groupLabels)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseGroupLabels[trimmedKey] = value\n }\n }\n const responseItemLabels: Record<string, string> = {}\n if (rawSettings?.itemLabels && typeof rawSettings.itemLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.itemLabels)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseItemLabels[trimmedKey] = value\n }\n }\n const responseHiddenItems = Array.isArray(rawSettings?.hiddenItems)\n ? rawSettings.hiddenItems\n .map((itemId) => (typeof itemId === 'string' ? itemId.trim() : ''))\n .filter((itemId) => itemId.length > 0)\n : []\n const responseItemOrder: Record<string, string[]> = {}\n if (rawSettings?.itemOrder && typeof rawSettings.itemOrder === 'object') {\n for (const [groupKey, list] of Object.entries(rawSettings.itemOrder)) {\n if (!Array.isArray(list)) continue\n const trimmedGroup = groupKey.trim()\n if (!trimmedGroup) continue\n const seen = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n if (typeof itemKey !== 'string') continue\n const trimmedItem = itemKey.trim()\n if (!trimmedItem || seen.has(trimmedItem)) continue\n seen.add(trimmedItem)\n values.push(trimmedItem)\n }\n if (values.length > 0) responseItemOrder[trimmedGroup] = values\n }\n }\n const currentIds = baseSnapshot.map((group) => resolveGroupKey(group))\n const order = mergeGroupOrder(responseOrder, currentIds)\n const { itemDefaults } = collectSidebarDefaults(baseSnapshot)\n const hiddenItemIds: Record<string, boolean> = {}\n for (const itemId of responseHiddenItems) {\n if (!itemDefaults.has(itemId)) continue\n hiddenItemIds[itemId] = true\n }\n return {\n order,\n groupLabels: responseGroupLabels,\n itemLabels: responseItemLabels,\n hiddenItemIds,\n itemOrder: responseItemOrder,\n }\n}\n\nfunction emptyDraftFor(baseSnapshot: SidebarGroup[]): SidebarCustomizationDraft {\n return {\n order: baseSnapshot.map((group) => resolveGroupKey(group)),\n groupLabels: {},\n itemLabels: {},\n hiddenItemIds: {},\n itemOrder: {},\n }\n}\n\nexport function SidebarCustomizationEditor({\n onSaved,\n onCanceled,\n variantsApiPath = VARIANTS_API_DEFAULT,\n preferencesApiPath = PREFERENCES_API_DEFAULT,\n groups: groupsProp,\n}: SidebarCustomizationEditorProps) {\n const t = useT()\n const locale = useLocale()\n const localeLabel = (locale || '').toUpperCase()\n const { payload: chromePayload, isLoading: chromeIsLoading } = useBackendChrome()\n const groupsFromChrome = chromePayload?.groups as SidebarGroup[] | undefined\n const sourceGroups = groupsProp ?? groupsFromChrome ?? []\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [deleting, setDeleting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [variants, setVariants] = React.useState<Variant[]>([])\n const [selectedVariantId, setSelectedVariantId] = React.useState<string | null>(null)\n const [variantName, setVariantName] = React.useState('')\n const [draft, setDraft] = React.useState<SidebarCustomizationDraft | null>(null)\n const [previewGroups, setPreviewGroups] = React.useState<SidebarGroup[]>([])\n const [dirty, setDirty] = React.useState(false)\n const [availableRoleTargets, setAvailableRoleTargets] = React.useState<RoleTarget[]>([])\n const [selectedRoleIds, setSelectedRoleIds] = React.useState<string[]>([])\n const [canApplyToRoles, setCanApplyToRoles] = React.useState(false)\n const [addDialogOpen, setAddDialogOpen] = React.useState(false)\n const [addDialogName, setAddDialogName] = React.useState('')\n const [addDialogError, setAddDialogError] = React.useState<string | null>(null)\n const baseSnapshotRef = React.useRef<SidebarGroup[] | null>(null)\n const hasInitializedRef = React.useRef(false)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n variantId?: string | null\n operation: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'sidebar-customization',\n blockedMessage: t('appShell.sidebarCustomizationSaveError'),\n })\n\n const buildMutationContext = React.useCallback(\n (operation: string, variantId?: string | null) => ({\n formId: 'sidebar-customization',\n variantId: variantId ?? null,\n operation,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const isNewVariant = selectedVariantId === null\n const selectedVariant = React.useMemo(\n () => (selectedVariantId ? variants.find((v) => v.id === selectedVariantId) ?? null : null),\n [selectedVariantId, variants],\n )\n\n const updateDraft = React.useCallback((updater: (draft: SidebarCustomizationDraft) => SidebarCustomizationDraft) => {\n setDraft((prev) => {\n if (!prev) return prev\n const next = updater(prev)\n if (baseSnapshotRef.current) {\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, next))\n }\n return next\n })\n setDirty(true)\n }, [])\n\n const buildBaseSnapshot = React.useCallback((): SidebarGroup[] => {\n return filterMainSidebarGroups(cloneSidebarGroups(sourceGroups))\n }, [sourceGroups])\n\n const loadVariantsList = React.useCallback(async (): Promise<Variant[]> => {\n // Cache-bust to prevent stale browser/Next caches from masking just-created variants.\n const url = `${variantsApiPath}?_=${Date.now()}`\n const call = await apiCall<VariantListResponse>(url, { cache: 'no-store' })\n if (!call.ok) {\n throw new Error('list-failed')\n }\n return call.result?.variants ?? []\n }, [variantsApiPath])\n\n const loadRolesPayload = React.useCallback(async (): Promise<{ canApplyToRoles: boolean; roles: RoleTarget[] }> => {\n const call = await apiCall<{ canApplyToRoles?: boolean; roles?: Array<{ id?: string; name?: string; hasPreference?: boolean }> }>(preferencesApiPath)\n if (!call.ok) {\n return { canApplyToRoles: false, roles: [] }\n }\n const data = call.result ?? null\n const can = data?.canApplyToRoles === true\n const roles = Array.isArray(data?.roles)\n ? (data!.roles as Array<{ id?: string; name?: string; hasPreference?: boolean }>)\n .filter((r) => typeof r?.id === 'string' && typeof r?.name === 'string')\n .map((r) => ({ id: r.id as string, name: r.name as string, hasPreference: r.hasPreference === true }))\n : []\n return { canApplyToRoles: can, roles }\n }, [preferencesApiPath])\n\n const selectVariantInternal = React.useCallback((variant: Variant | null, list: Variant[]) => {\n const baseSnapshot = baseSnapshotRef.current ?? buildBaseSnapshot()\n baseSnapshotRef.current = baseSnapshot\n if (variant) {\n const initialDraft = parseDraftFromSettings(variant.settings, baseSnapshot)\n setSelectedVariantId(variant.id)\n setVariantName(variant.name)\n setDraft(initialDraft)\n setPreviewGroups(applyCustomizationDraft(baseSnapshot, initialDraft))\n } else {\n const empty = emptyDraftFor(baseSnapshot)\n setSelectedVariantId(null)\n // Suggest a default name based on the existing variants count.\n const usedNumbers = new Set<number>()\n for (const v of list) {\n if (v.name === 'My preferences') usedNumbers.add(1)\n const match = v.name.match(/^My preferences\\s+(\\d+)$/)\n if (match) usedNumbers.add(Number.parseInt(match[1], 10))\n }\n let next = 1\n while (usedNumbers.has(next)) next += 1\n const suggestion = next === 1 ? 'My preferences' : `My preferences ${next}`\n setVariantName(suggestion)\n setDraft(empty)\n setPreviewGroups(applyCustomizationDraft(baseSnapshot, empty))\n }\n setDirty(false)\n }, [buildBaseSnapshot])\n\n // Initial load. No cancelled flag because React Strict Mode in dev runs effects twice\n // and the cleanup-driven cancellation made the only init pass abort silently \u2014 leaving\n // `loading` true forever and the editor stuck on the \"Loading\u2026\" placeholder. The init\n // gate (`hasInitializedRef`) prevents the second Strict-Mode run from doubling work.\n React.useEffect(() => {\n if (hasInitializedRef.current) return\n if (sourceGroups.length === 0) return\n hasInitializedRef.current = true\n async function init() {\n setLoading(true)\n setError(null)\n try {\n const [list, rolesPayload] = await Promise.all([\n loadVariantsList(),\n loadRolesPayload(),\n ])\n setVariants(list)\n setCanApplyToRoles(rolesPayload.canApplyToRoles)\n setAvailableRoleTargets(rolesPayload.roles)\n const active = list.find((v) => v.isActive)\n const initial = active ?? list[0] ?? null\n selectVariantInternal(initial, list)\n setSelectedRoleIds([])\n } catch (err) {\n console.error('Failed to load sidebar variants', err)\n setError(t('appShell.sidebarCustomizationLoadError'))\n } finally {\n setLoading(false)\n }\n }\n void init()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sourceGroups.length])\n\n const toggleRoleSelection = React.useCallback((roleId: string) => {\n setSelectedRoleIds((prev) => (prev.includes(roleId) ? prev.filter((id) => id !== roleId) : [...prev, roleId]))\n setDirty(true)\n }, [])\n\n const createNewVariant = React.useCallback(async (\n proposedName?: string,\n options: { suppressPageError?: boolean } = {},\n ): Promise<{ ok: boolean; error?: string }> => {\n if (saving || deleting) return { ok: false }\n if (dirty && selectedVariantId !== null) {\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationSwitchConfirmTitle', 'Discard unsaved changes?'),\n text: t('appShell.sidebarCustomizationSwitchConfirmText', 'You have unsaved changes for the current variant. Switching will discard them.'),\n confirmText: t('appShell.sidebarCustomizationSwitchConfirmYes', 'Discard and switch'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return { ok: false }\n }\n setSaving(true)\n if (!options.suppressPageError) setError(null)\n try {\n const baseSnapshot = baseSnapshotRef.current ?? buildBaseSnapshot()\n baseSnapshotRef.current = baseSnapshot\n const groupOrder = baseSnapshot.map((g) => resolveGroupKey(g))\n const trimmed = (proposedName ?? '').trim()\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(variantsApiPath, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n // If name is omitted, server auto-names (\"My preferences\", \"My preferences 2\", \u2026).\n name: trimmed.length > 0 ? trimmed : undefined,\n settings: { groupOrder, groupLabels: {}, itemLabels: {}, hiddenItems: [], itemOrder: {} },\n isActive: true,\n }),\n }),\n context: buildMutationContext('createVariant'),\n mutationPayload: { name: trimmed.length > 0 ? trimmed : null },\n })\n if (!call.ok) {\n const message = formatVariantApiError(call, t)\n if (!options.suppressPageError) setError(message)\n return { ok: false, error: message }\n }\n const created = call.result?.variant ?? null\n // Trust POST response as authoritative; refetch in background for any side-effects\n // (e.g. server-side deactivation of previous active variant).\n let nextList: Variant[]\n try {\n nextList = await loadVariantsList()\n } catch {\n nextList = variants\n }\n // Defensive merge: ensure the just-created variant is in the list even if the\n // refetch happened to be served from a stale cache.\n if (created && !nextList.some((v) => v.id === created.id)) {\n nextList = [...nextList, created]\n }\n setVariants(nextList)\n if (created) {\n const fresh = nextList.find((v) => v.id === created.id) ?? created\n selectVariantInternal(fresh, nextList)\n }\n flash(t('appShell.sidebarCustomizationVariantCreated', 'Variant created.'), 'success')\n return { ok: true }\n } catch (err) {\n console.error('Failed to create sidebar variant', err)\n const message = t('appShell.sidebarCustomizationSaveError')\n if (!options.suppressPageError) setError(message)\n return { ok: false, error: message }\n } finally {\n setSaving(false)\n }\n }, [saving, deleting, dirty, selectedVariantId, confirmDialog, t, buildBaseSnapshot, variantsApiPath, loadVariantsList, selectVariantInternal, variants, runMutation, buildMutationContext])\n\n const handleVariantSwitch = React.useCallback(async (key: string) => {\n if (saving || deleting) return\n if (key === selectedVariantId) return\n if (key === NEW_VARIANT_KEY && isNewVariant) return\n if (dirty) {\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationSwitchConfirmTitle', 'Discard unsaved changes?'),\n text: t('appShell.sidebarCustomizationSwitchConfirmText', 'You have unsaved changes for the current variant. Switching will discard them.'),\n confirmText: t('appShell.sidebarCustomizationSwitchConfirmYes', 'Discard and switch'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return\n }\n if (key === NEW_VARIANT_KEY) {\n selectVariantInternal(null, variants)\n return\n }\n const next = variants.find((v) => v.id === key) ?? null\n selectVariantInternal(next, variants)\n }, [saving, deleting, selectedVariantId, isNewVariant, dirty, confirmDialog, t, variants, selectVariantInternal])\n\n const moveGroup = React.useCallback((groupId: string, offset: number) => {\n updateDraft((draft) => {\n const order = [...draft.order]\n const index = order.indexOf(groupId)\n if (index === -1) return draft\n const nextIndex = Math.max(0, Math.min(order.length - 1, index + offset))\n if (nextIndex === index) return draft\n order.splice(index, 1)\n order.splice(nextIndex, 0, groupId)\n return { ...draft, order }\n })\n }, [updateDraft])\n\n const dndSensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor),\n )\n\n const handleItemDragEnd = React.useCallback((groupKey: string, currentItemKeys: string[]) => (event: DragEndEvent) => {\n const { active, over } = event\n if (!over || active.id === over.id) return\n const fromId = String(active.id)\n const toId = String(over.id)\n updateDraft((draft) => {\n const baseOrder = draft.itemOrder?.[groupKey]?.length\n ? [...draft.itemOrder[groupKey]]\n : [...currentItemKeys]\n const fromIndex = baseOrder.indexOf(fromId)\n const toIndex = baseOrder.indexOf(toId)\n if (fromIndex === -1 || toIndex === -1) return draft\n const nextOrder = arrayMove(baseOrder, fromIndex, toIndex)\n return {\n ...draft,\n itemOrder: { ...(draft.itemOrder ?? {}), [groupKey]: nextOrder },\n }\n })\n }, [updateDraft])\n\n const setGroupLabel = React.useCallback((groupId: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.groupLabels }\n if (value.trim().length === 0) delete next[groupId]\n else next[groupId] = value\n return { ...draft, groupLabels: next }\n })\n }, [updateDraft])\n\n const setItemLabel = React.useCallback((itemId: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.itemLabels }\n if (value.trim().length === 0) delete next[itemId]\n else next[itemId] = value\n return { ...draft, itemLabels: next }\n })\n }, [updateDraft])\n\n const setItemHidden = React.useCallback((itemId: string, hidden: boolean) => {\n updateDraft((draft) => {\n const next = { ...draft.hiddenItemIds }\n const apply = (id: string) => {\n if (hidden) next[id] = true\n else delete next[id]\n }\n apply(itemId)\n // Cascade: hiding a parent hides every descendant; showing it reveals them too.\n if (baseSnapshotRef.current) {\n for (const group of baseSnapshotRef.current) {\n const target = findItemByKey(group.items, itemId)\n if (!target) continue\n for (const descendantKey of collectDescendantKeys(target)) apply(descendantKey)\n break\n }\n }\n return { ...draft, hiddenItemIds: next }\n })\n }, [updateDraft])\n\n const reset = React.useCallback(() => {\n if (!baseSnapshotRef.current) return\n if (selectedVariant) {\n const initialDraft = parseDraftFromSettings(selectedVariant.settings, baseSnapshotRef.current)\n setDraft(initialDraft)\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, initialDraft))\n } else {\n const empty = emptyDraftFor(baseSnapshotRef.current)\n setDraft(empty)\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, empty))\n }\n setDirty(false)\n }, [selectedVariant])\n\n const cancel = React.useCallback(() => {\n onCanceled?.()\n }, [onCanceled])\n\n const submitAddDialog = React.useCallback(async () => {\n setAddDialogError(null)\n const result = await createNewVariant(addDialogName, { suppressPageError: true })\n if (result.ok) {\n setAddDialogOpen(false)\n setAddDialogName('')\n setAddDialogError(null)\n return\n }\n setAddDialogError(result.error ?? t('appShell.sidebarCustomizationSaveError'))\n }, [createNewVariant, addDialogName, t])\n\n const sanitizeSettingsPayload = React.useCallback(() => {\n if (!draft || !baseSnapshotRef.current) return null\n const baseGroups = baseSnapshotRef.current\n const { groupDefaults, itemDefaults } = collectSidebarDefaults(baseGroups)\n const sanitizedGroupLabels: Record<string, string> = {}\n for (const [key, value] of Object.entries(draft.groupLabels)) {\n const trimmed = value.trim()\n const base = groupDefaults.get(key)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedGroupLabels[key] = trimmed\n }\n const sanitizedItemLabels: Record<string, string> = {}\n for (const [itemId, value] of Object.entries(draft.itemLabels)) {\n const trimmed = value.trim()\n const base = itemDefaults.get(itemId)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedItemLabels[itemId] = trimmed\n }\n const sanitizedHiddenItems: string[] = []\n for (const [itemId, hidden] of Object.entries(draft.hiddenItemIds)) {\n if (!hidden) continue\n if (!itemDefaults.has(itemId)) continue\n sanitizedHiddenItems.push(itemId)\n }\n // Build a Set of valid group keys to drop stale itemOrder entries.\n const groupKeys = new Set<string>()\n for (const group of baseGroups) groupKeys.add(resolveGroupKey(group))\n const sanitizedItemOrder: Record<string, string[]> = {}\n for (const [groupKey, list] of Object.entries(draft.itemOrder ?? {})) {\n if (!groupKeys.has(groupKey)) continue\n const seen = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n if (seen.has(itemKey)) continue\n if (!itemDefaults.has(itemKey)) continue\n seen.add(itemKey)\n values.push(itemKey)\n }\n if (values.length > 0) sanitizedItemOrder[groupKey] = values\n }\n return {\n groupOrder: draft.order,\n groupLabels: sanitizedGroupLabels,\n itemLabels: sanitizedItemLabels,\n hiddenItems: sanitizedHiddenItems,\n itemOrder: sanitizedItemOrder,\n }\n }, [draft])\n\n const save = React.useCallback(async () => {\n const settings = sanitizeSettingsPayload()\n if (!settings) return\n setSaving(true)\n setError(null)\n try {\n const trimmedName = variantName.trim()\n const isCurrentlyActive = selectedVariant?.isActive ?? false\n let savedVariant: Variant | null = null\n if (isNewVariant) {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(variantsApiPath, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: trimmedName.length > 0 ? trimmedName : undefined,\n settings,\n // New variants are activated by default \u2014 there's only one active per scope,\n // others get auto-deactivated server-side.\n isActive: true,\n }),\n }),\n context: buildMutationContext('saveVariant'),\n mutationPayload: { name: trimmedName.length > 0 ? trimmedName : null, isActive: true },\n })\n if (!call.ok) {\n setError(formatVariantApiError(call, t))\n return\n }\n savedVariant = call.result?.variant ?? null\n } else if (selectedVariantId) {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(`${variantsApiPath}/${encodeURIComponent(selectedVariantId)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: trimmedName.length > 0 ? trimmedName : undefined,\n settings,\n isActive: isCurrentlyActive,\n }),\n }),\n context: buildMutationContext('saveVariant', selectedVariantId),\n mutationPayload: {\n id: selectedVariantId,\n name: trimmedName.length > 0 ? trimmedName : null,\n isActive: isCurrentlyActive,\n },\n })\n if (!call.ok) {\n setError(formatVariantApiError(call, t))\n return\n }\n savedVariant = call.result?.variant ?? null\n }\n // Sync user prefs and (optionally) push to roles via the legacy preferences endpoint.\n // The variant entity is the canonical \"saved layout\"; the preferences endpoint is what\n // the AppShell sidebar actually reads. Without this sync, the saved variant wouldn't\n // become the user's live sidebar.\n const preferencesPayload: Record<string, unknown> = {\n groupOrder: settings.groupOrder,\n groupLabels: settings.groupLabels,\n itemLabels: settings.itemLabels,\n hiddenItems: settings.hiddenItems,\n itemOrder: settings.itemOrder,\n }\n if (canApplyToRoles) {\n const applyToRolesPayload = [...selectedRoleIds]\n const clearRoleIdsPayload = availableRoleTargets\n .filter((role) => role.hasPreference && !selectedRoleIds.includes(role.id))\n .map((role) => role.id)\n preferencesPayload.applyToRoles = applyToRolesPayload\n preferencesPayload.clearRoleIds = clearRoleIdsPayload\n }\n const preferencesCall = await runMutation({\n operation: () =>\n apiCall(preferencesApiPath, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(preferencesPayload),\n }),\n context: buildMutationContext('savePreferences', selectedVariantId),\n mutationPayload: preferencesPayload,\n })\n if (!preferencesCall.ok) {\n // The variant entity is the canonical layout; the preferences sync is what the\n // AppShell sidebar actually reads. A failed sync would leave the saved variant\n // not reflected live, so surface it as a save error rather than flashing success.\n setError(formatVariantApiError(preferencesCall, t))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n // Refresh the list so isActive flags are accurate, plus refresh roles so hasPreference flags update.\n const [list, rolesPayload] = await Promise.all([\n loadVariantsList(),\n loadRolesPayload(),\n ])\n // Defensive: ensure the just-saved variant lands in the list even if the refetch\n // was served stale (browser HTTP cache, etc.).\n const mergedList = savedVariant && !list.some((v) => v.id === savedVariant!.id)\n ? [...list, savedVariant]\n : list\n setVariants(mergedList)\n setCanApplyToRoles(rolesPayload.canApplyToRoles)\n setAvailableRoleTargets(rolesPayload.roles)\n if (savedVariant) {\n const fresh = mergedList.find((v) => v.id === savedVariant!.id) ?? savedVariant\n selectVariantInternal(fresh, mergedList)\n } else {\n const active = mergedList.find((v) => v.isActive) ?? mergedList[0] ?? null\n selectVariantInternal(active, mergedList)\n }\n flash(\n isNewVariant\n ? t('appShell.sidebarCustomizationVariantCreated', 'Variant created.')\n : t('appShell.sidebarCustomizationVariantSaved', 'Variant saved.'),\n 'success',\n )\n onSaved?.()\n } catch (err) {\n console.error('Failed to save sidebar variant', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n } finally {\n setSaving(false)\n }\n }, [draft, variantName, isNewVariant, selectedVariant, selectedVariantId, variantsApiPath, preferencesApiPath, canApplyToRoles, selectedRoleIds, availableRoleTargets, t, sanitizeSettingsPayload, loadVariantsList, loadRolesPayload, selectVariantInternal, onSaved, runMutation, buildMutationContext])\n\n const toggleActive = React.useCallback(async (next: boolean) => {\n if (!selectedVariant || saving || deleting) return\n setError(null)\n try {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(`${variantsApiPath}/${encodeURIComponent(selectedVariant.id)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ isActive: next }),\n }),\n context: buildMutationContext('toggleVariantActive', selectedVariant.id),\n mutationPayload: { id: selectedVariant.id, isActive: next },\n })\n if (!call.ok) {\n setError(t('appShell.sidebarCustomizationSaveError'))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n const list = await loadVariantsList()\n setVariants(list)\n const fresh = list.find((v) => v.id === selectedVariant.id) ?? selectedVariant\n selectVariantInternal(fresh, list)\n } catch (err) {\n console.error('Failed to toggle variant active state', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n }\n }, [selectedVariant, saving, deleting, variantsApiPath, t, loadVariantsList, selectVariantInternal, runMutation, buildMutationContext])\n\n const deleteVariant = React.useCallback(async () => {\n if (!selectedVariant) return\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationDeleteVariantTitle', 'Delete variant?'),\n text: t(\n 'appShell.sidebarCustomizationDeleteVariantText',\n 'This variant will be removed from your library.',\n ),\n confirmText: t('appShell.sidebarCustomizationDeleteVariantConfirm', 'Delete variant'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return\n setDeleting(true)\n setError(null)\n try {\n const call = await runMutation({\n operation: () =>\n apiCall(`${variantsApiPath}/${encodeURIComponent(selectedVariant.id)}`, { method: 'DELETE' }),\n context: buildMutationContext('deleteVariant', selectedVariant.id),\n mutationPayload: { id: selectedVariant.id },\n })\n if (!call.ok) {\n setError(t('appShell.sidebarCustomizationSaveError'))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n const list = await loadVariantsList()\n setVariants(list)\n const fallback = list[0] ?? null\n selectVariantInternal(fallback, list)\n } catch (err) {\n console.error('Failed to delete variant', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n } finally {\n setDeleting(false)\n }\n }, [selectedVariant, confirmDialog, t, variantsApiPath, loadVariantsList, selectVariantInternal, runMutation, buildMutationContext])\n\n const isBusy = saving || deleting\n\n if (loading && !draft) {\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"space-y-6\">\n <div className=\"space-y-2\">\n <div className=\"h-7 w-64 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-96 animate-pulse rounded bg-muted/60\" />\n </div>\n <div className=\"h-64 animate-pulse rounded-lg border bg-muted/30\" />\n </div>\n </>\n )\n }\n\n if (!draft || !baseSnapshotRef.current) {\n // While chrome payload streams in or the initial fetch runs, show a neutral loading\n // state instead of the error fallback (otherwise the first visit looks like a crash).\n const stillLoading = loading || chromeIsLoading || sourceGroups.length === 0\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"rounded-lg border border-dashed bg-muted/30 p-6 text-sm text-muted-foreground\">\n {stillLoading\n ? t('appShell.sidebarCustomizationLoading', 'Loading\u2026')\n : (error ?? t('appShell.sidebarCustomizationLoadError'))}\n </div>\n </>\n )\n }\n\n const baseGroupsForDefaults = baseSnapshotRef.current\n const baseGroupMap = new Map<string, SidebarGroup>()\n for (const group of baseGroupsForDefaults) {\n baseGroupMap.set(resolveGroupKey(group), group)\n }\n const orderedGroupIds = mergeGroupOrder(draft.order, Array.from(baseGroupMap.keys()))\n const totalGroups = orderedGroupIds.length\n\n const selectValue = isNewVariant ? NEW_VARIANT_KEY : selectedVariantId ?? NEW_VARIANT_KEY\n const showVariantPicker = variants.length > 0 || isNewVariant\n\n return (\n <>\n {ConfirmDialogElement}\n <Dialog\n open={addDialogOpen}\n onOpenChange={(next) => {\n if (!next) {\n setAddDialogOpen(false)\n setAddDialogName('')\n setAddDialogError(null)\n }\n }}\n >\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>\n {t('appShell.sidebarCustomizationAddDialogTitle', 'Add new variant')}\n </DialogTitle>\n <DialogDescription>\n {t('appShell.sidebarCustomizationAddDialogDescription', 'Choose a name for the new sidebar variant. Leave blank to auto-name it.')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantNameLabel', 'Variant name')}\n </label>\n <Input\n autoFocus\n value={addDialogName}\n onChange={(event) => {\n setAddDialogName(event.target.value)\n if (addDialogError) setAddDialogError(null)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault()\n void submitAddDialog()\n }\n }}\n placeholder={t('appShell.sidebarCustomizationVariantNamePlaceholder', 'My preferences')}\n disabled={saving}\n aria-invalid={addDialogError ? true : undefined}\n aria-describedby={addDialogError ? 'sidebar-add-variant-error' : undefined}\n />\n {addDialogError ? (\n <p\n id=\"sidebar-add-variant-error\"\n role=\"alert\"\n className=\"text-xs text-destructive\"\n >\n {addDialogError}\n </p>\n ) : null}\n </div>\n <DialogFooter className=\"mt-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n setAddDialogOpen(false)\n setAddDialogName('')\n setAddDialogError(null)\n }}\n disabled={saving}\n >\n {t('appShell.sidebarCustomizationCancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => { void submitAddDialog() }}\n disabled={saving}\n >\n {saving\n ? t('appShell.sidebarCustomizationCreating', 'Creating\u2026')\n : t('appShell.sidebarCustomizationCreateVariant', 'Create variant')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Page>\n <header className=\"space-y-1\">\n <h1 className=\"text-xl sm:text-2xl font-semibold leading-tight\">\n {t('appShell.sidebarCustomizationHeading')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationHint', { locale: localeLabel })}\n </p>\n </header>\n\n {error ? (\n <div className=\"rounded-lg border border-destructive/40 bg-destructive/5 px-4 py-3 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n\n {/* Two-column: editor (variant + roles + order) + preview */}\n <PageBody className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,360px)]\">\n <div className=\"space-y-6\">\n {(() => {\n const showRolesCard = canApplyToRoles && availableRoleTargets.length > 0\n if (!showVariantPicker && !showRolesCard) return null\n return (\n <Card>\n <CardContent className=\"flex flex-col gap-6\">\n {showVariantPicker ? (\n <div className=\"flex flex-col gap-4\">\n {/* Row: combobox-style name input with chevron picker + DS-compliant add button */}\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantNameLabel', 'Variant name')}\n </label>\n <div className=\"flex items-stretch gap-2\">\n {/* Combobox group: input + chevron picker share a single visual border. */}\n <div className=\"relative flex flex-1 items-stretch\">\n <Input\n value={variantName}\n onChange={(event) => {\n setVariantName(event.target.value)\n setDirty(true)\n }}\n placeholder={t('appShell.sidebarCustomizationVariantNamePlaceholder', 'My preferences')}\n disabled={isBusy}\n className=\"w-full pr-10\"\n />\n <Select\n value={selectValue}\n onValueChange={(value) => { void handleVariantSwitch(value) }}\n disabled={isBusy || loading}\n >\n <SelectTrigger\n className=\"pointer-events-none absolute inset-0 h-full w-full justify-end border-0 bg-transparent px-3 shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 [&>span]:hidden [&>svg]:pointer-events-auto\"\n aria-label={t('appShell.sidebarCustomizationVariantPickerLabel', 'Pick variant')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {variants.length > 0 ? (\n variants.map((variant) => (\n <SelectItem key={variant.id} value={variant.id}>\n {variant.name}\n </SelectItem>\n ))\n ) : (\n <SelectItem value={NEW_VARIANT_KEY} disabled>\n {t('appShell.sidebarCustomizationVariantsEmpty', 'No saved variants yet')}\n </SelectItem>\n )}\n </SelectContent>\n </Select>\n </div>\n <Button\n type=\"button\"\n onClick={() => {\n setAddDialogName('')\n setAddDialogError(null)\n setAddDialogOpen(true)\n }}\n disabled={isBusy}\n title={t('appShell.sidebarCustomizationVariantNew', 'Add new variant')}\n >\n <Plus className=\"size-4\" />\n {t('appShell.sidebarCustomizationCreateNew', 'Create new')}\n </Button>\n </div>\n </div>\n\n {isNewVariant ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationVariantNewHint', 'Saving will create a new variant. If you leave the name blank, it will be auto-named.')}\n </p>\n ) : null}\n\n {/* Row: active switch */}\n <div className=\"flex items-center gap-2\">\n <Switch\n checked={selectedVariant?.isActive ?? isNewVariant}\n onCheckedChange={(next) => {\n if (isNewVariant) return\n void toggleActive(next === true)\n }}\n disabled={isBusy || isNewVariant}\n aria-label={t('appShell.sidebarCustomizationVariantActiveLabel', 'Active')}\n />\n <span className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantActiveLabel', 'Active')}\n </span>\n </div>\n </div>\n ) : null}\n\n {showVariantPicker && showRolesCard ? (\n <div className=\"-mx-6 border-t\" aria-hidden />\n ) : null}\n\n {showRolesCard ? (\n <div className=\"flex flex-col gap-3\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold leading-none text-foreground\">\n {t('appShell.sidebarApplyToRolesTitle')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">{t('appShell.sidebarApplyToRolesDescription')}</p>\n </div>\n <div className=\"flex flex-col gap-1.5 max-w-sm\">\n {availableRoleTargets.map((role) => {\n const checked = selectedRoleIds.includes(role.id)\n const willClear = role.hasPreference && !checked\n return (\n <label\n key={role.id}\n className=\"flex cursor-pointer items-center gap-3 rounded-lg border bg-background px-3 py-2 text-sm transition-colors hover:bg-muted\"\n >\n <Switch\n checked={checked}\n onCheckedChange={() => toggleRoleSelection(role.id)}\n disabled={isBusy}\n />\n <span className=\"flex-1 truncate font-medium text-foreground\">{role.name}</span>\n {role.hasPreference ? (\n <Tag variant={willClear ? 'error' : 'info'} dot={!willClear}>\n {willClear ? <AlertTriangle className=\"size-3\" aria-hidden /> : null}\n {willClear ? t('appShell.sidebarRoleWillClear') : t('appShell.sidebarRoleHasPreset')}\n </Tag>\n ) : null}\n </label>\n )\n })}\n </div>\n </div>\n ) : null}\n\n {/* Footer: Reset / Cancel / Save (right) + Delete (left). All gated on dirty\n except Delete which acts on the persisted variant regardless of edits. */}\n <div className=\"-mx-6 border-t\" aria-hidden />\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n {selectedVariant ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => { void deleteVariant() }}\n disabled={isBusy}\n className=\"text-destructive hover:text-destructive\"\n >\n <Trash2 className=\"size-4\" />\n {deleting\n ? t('appShell.sidebarCustomizationDeleteVariantInProgress', 'Deleting\u2026')\n : t('appShell.sidebarCustomizationDeleteVariant', 'Delete variant')}\n </Button>\n ) : <span />}\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={reset}\n disabled={isBusy || !dirty}\n >\n {t('appShell.sidebarCustomizationReset')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={cancel}\n disabled={isBusy || !dirty}\n >\n {t('appShell.sidebarCustomizationCancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={save}\n disabled={isBusy || (!isNewVariant && !dirty)}\n >\n {saving\n ? (isNewVariant\n ? t('appShell.sidebarCustomizationCreating', 'Creating\u2026')\n : t('appShell.sidebarCustomizationSaving'))\n : (isNewVariant\n ? t('appShell.sidebarCustomizationCreateVariant', 'Create variant')\n : t('appShell.sidebarCustomizationSave'))}\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n })()}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">\n {t('appShell.sidebarCustomizationOrderHeading', 'Order & visibility')}\n </CardTitle>\n <p className=\"text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationOrderDescription', 'Reorder groups, rename them, and toggle individual items on or off.')}\n </p>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {orderedGroupIds.map((groupId, index) => {\n const baseGroup = baseGroupMap.get(groupId)\n if (!baseGroup) return null\n const placeholder = baseGroup.defaultName ?? baseGroup.name\n const value = draft.groupLabels[groupId] ?? ''\n const trimmedValue = value.trim()\n const isGroupModified = trimmedValue.length > 0 && trimmedValue !== placeholder\n return (\n <div key={groupId} className=\"rounded-lg border bg-background\">\n <div className=\"flex items-start gap-3 border-b px-4 py-3\">\n <div className=\"flex flex-1 flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationGroupLabel')}\n </label>\n <div className=\"flex items-center gap-2\">\n <Input\n value={value}\n onChange={(event) => setGroupLabel(groupId, event.target.value)}\n placeholder={placeholder}\n disabled={isBusy}\n className=\"flex-1\"\n />\n {isGroupModified ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setGroupLabel(groupId, '')}\n disabled={isBusy}\n aria-label={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n title={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n >\n <RotateCcw className=\"size-3.5\" />\n </IconButton>\n ) : null}\n </div>\n {isGroupModified ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationDefault', 'Default:')}{' '}\n <span className=\"font-medium text-foreground/80\">{placeholder}</span>\n </p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-1 mt-[26px]\">\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, -1)}\n disabled={index === 0 || isBusy}\n aria-label={t('appShell.sidebarCustomizationMoveUp')}\n title={t('appShell.sidebarCustomizationMoveUp')}\n >\n <ChevronUp className=\"size-4\" />\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, 1)}\n disabled={index === totalGroups - 1 || isBusy}\n aria-label={t('appShell.sidebarCustomizationMoveDown')}\n title={t('appShell.sidebarCustomizationMoveDown')}\n >\n <ChevronDown className=\"size-4\" />\n </IconButton>\n </div>\n </div>\n <div className=\"flex flex-col divide-y\">\n <ItemRows\n items={baseGroup.items}\n draft={draft}\n saving={isBusy}\n onLabelChange={setItemLabel}\n onHiddenChange={setItemHidden}\n t={t}\n groupKey={groupId}\n sensors={dndSensors}\n onDragEnd={handleItemDragEnd(groupId, baseGroup.items.map((item) => resolveItemKey(item)))}\n />\n </div>\n </div>\n )\n })}\n </CardContent>\n </Card>\n </div>\n\n <aside className=\"hidden lg:block\">\n <div className=\"sticky top-6\">\n <div className=\"relative\">\n <span className=\"absolute left-1/2 top-0 z-10 -translate-x-1/2 -translate-y-1/2 rounded-md bg-accent-indigo px-3 py-1 text-xs font-semibold uppercase tracking-wider text-accent-indigo-foreground shadow-sm\">\n {t('appShell.sidebarCustomizationPreview', 'Preview')}\n </span>\n <SidebarPreview\n groups={previewGroups}\n productName={t('appShell.productName', 'Open Mercato')}\n pickFirstActive\n />\n </div>\n </div>\n </aside>\n </PageBody>\n </Page>\n </>\n )\n}\n\ntype ItemRowProps = {\n item: SidebarItem\n draft: SidebarCustomizationDraft\n saving: boolean\n onLabelChange: (itemId: string, value: string) => void\n onHiddenChange: (itemId: string, hidden: boolean) => void\n t: ReturnType<typeof useT>\n depth: number\n dragHandle?: React.ReactNode\n /** True when an ancestor in the tree is hidden \u2014 child controls become read-only. */\n ancestorHidden?: boolean\n}\n\nfunction ItemRow({ item, draft, saving, onLabelChange, onHiddenChange, t, depth, dragHandle, ancestorHidden = false }: ItemRowProps) {\n const itemKey = resolveItemKey(item)\n const placeholder = item.defaultTitle ?? item.title\n const value = draft.itemLabels[itemKey] ?? ''\n const trimmedValue = value.trim()\n const isModified = trimmedValue.length > 0 && trimmedValue !== placeholder\n const hidden = draft.hiddenItemIds[itemKey] === true\n const effectivelyDimmed = hidden || ancestorHidden\n return (\n <div\n className=\"flex items-start gap-3 px-4 py-3 transition-colors hover:bg-muted/40\"\n style={depth ? { paddingLeft: 16 + depth * 24 } : undefined}\n >\n {dragHandle ?? (depth > 0 ? <span className=\"w-4 shrink-0\" aria-hidden /> : null)}\n <div className={`min-w-0 flex-1 flex flex-col gap-1.5 ${effectivelyDimmed ? 'opacity-60' : ''}`}>\n <div className=\"flex items-center gap-2\">\n <div className=\"min-w-0 flex-1\">\n <Input\n value={value}\n onChange={(event) => onLabelChange(itemKey, event.target.value)}\n placeholder={placeholder}\n disabled={saving}\n />\n </div>\n {isModified ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onLabelChange(itemKey, '')}\n disabled={saving}\n aria-label={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n title={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n >\n <RotateCcw className=\"size-3.5\" />\n </IconButton>\n ) : null}\n </div>\n {isModified ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationDefault', 'Default:')}{' '}\n <span className=\"font-medium text-foreground/80\">{placeholder}</span>\n </p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2 pt-1.5\">\n {hidden ? (\n <span className=\"rounded-full border border-border bg-muted px-2 py-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('appShell.sidebarCustomizationHiddenBadge', 'Hidden')}\n </span>\n ) : null}\n <Switch\n checked={!hidden}\n onCheckedChange={(next) => onHiddenChange(itemKey, next !== true)}\n disabled={saving || ancestorHidden}\n aria-label={t('appShell.sidebarCustomizationShowItem')}\n title={ancestorHidden ? t('appShell.sidebarCustomizationParentHiddenHint', 'Parent is hidden \u2014 show parent first.') : undefined}\n />\n </div>\n </div>\n )\n}\n\ntype SortableItemRowProps = ItemRowProps & { id: string }\n\nfunction SortableItemRow({ id, ...rowProps }: SortableItemRowProps) {\n const t = useT()\n const { attributes, listeners, setNodeRef, transform, transition, isDragging, setActivatorNodeRef } = useSortable({ id })\n const style: React.CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n }\n const dragHandle = (\n <IconButton\n ref={setActivatorNodeRef}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"shrink-0 mt-1.5 cursor-grab touch-none active:cursor-grabbing\"\n aria-label={t('appShell.sidebarCustomizationDragToReorder', 'Drag to reorder')}\n disabled={rowProps.saving}\n {...attributes}\n {...listeners}\n >\n <GripVertical className=\"size-4\" />\n </IconButton>\n )\n return (\n <div ref={setNodeRef} style={style}>\n <ItemRow {...rowProps} dragHandle={dragHandle} />\n </div>\n )\n}\n\ntype ItemRowsProps = {\n items: SidebarItem[]\n draft: SidebarCustomizationDraft\n saving: boolean\n onLabelChange: (itemId: string, value: string) => void\n onHiddenChange: (itemId: string, hidden: boolean) => void\n t: ReturnType<typeof useT>\n depth?: number\n groupKey?: string\n sensors?: ReturnType<typeof useSensors>\n onDragEnd?: (event: DragEndEvent) => void\n ancestorHidden?: boolean\n}\n\nfunction ItemRows({\n items,\n draft,\n saving,\n onLabelChange,\n onHiddenChange,\n t,\n depth = 0,\n groupKey,\n sensors,\n onDragEnd,\n ancestorHidden = false,\n}: ItemRowsProps) {\n if (items.length === 0) return null\n\n const renderRecursiveChildren = (item: SidebarItem, parentHidden: boolean) =>\n item.children && item.children.length > 0 ? (\n <ItemRows\n items={item.children}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth + 1}\n ancestorHidden={parentHidden}\n />\n ) : null\n\n // Top-level rows in a group \u2192 enable DnD reordering.\n if (depth === 0 && groupKey && sensors && onDragEnd) {\n const ordered = applyItemOrder(items, resolveItemKey, draft.itemOrder?.[groupKey])\n const ids = ordered.map((item) => resolveItemKey(item))\n return (\n <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>\n <SortableContext items={ids} strategy={verticalListSortingStrategy}>\n {ordered.map((item) => {\n const itemKey = resolveItemKey(item)\n const ownHidden = draft.hiddenItemIds[itemKey] === true\n return (\n <React.Fragment key={itemKey}>\n <SortableItemRow\n id={itemKey}\n item={item}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth}\n ancestorHidden={ancestorHidden}\n />\n {renderRecursiveChildren(item, ancestorHidden || ownHidden)}\n </React.Fragment>\n )\n })}\n </SortableContext>\n </DndContext>\n )\n }\n\n // Nested children \u2192 static rendering, no drag handle.\n return (\n <>\n {items.map((item) => {\n const itemKey = resolveItemKey(item)\n const ownHidden = draft.hiddenItemIds[itemKey] === true\n return (\n <React.Fragment key={itemKey}>\n <ItemRow\n item={item}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth}\n ancestorHidden={ancestorHidden}\n />\n {renderRecursiveChildren(item, ancestorHidden || ownHidden)}\n </React.Fragment>\n )\n })}\n </>\n )\n}\n\nfunction SidebarPreviewIcon({ item }: { item: SidebarItem }) {\n if (item.icon) return <>{item.icon}</>\n if (item.iconName) {\n const resolved = resolveInjectedIcon(item.iconName)\n if (resolved) return <>{resolved}</>\n }\n if (item.iconMarkup) {\n return <span aria-hidden=\"true\" dangerouslySetInnerHTML={{ __html: item.iconMarkup }} />\n }\n // Fallback default icon \u2014 same shape as AppShell's DefaultIcon\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n )\n}\n\nfunction SidebarPreview({\n groups,\n productName,\n pickFirstActive,\n}: {\n groups: SidebarGroup[]\n productName: string\n pickFirstActive: boolean\n}) {\n const t = useT()\n // Pre-compute the first visible item so we can render it as the \"active\" preview state.\n // This shows the user what the active state of their sidebar will look like.\n const activeKey = React.useMemo<string | null>(() => {\n if (!pickFirstActive) return null\n for (const group of groups) {\n for (const item of group.items) {\n if (item.hidden === true) continue\n return resolveItemKey(item)\n }\n }\n return null\n }, [groups, pickFirstActive])\n\n return (\n <div className=\"relative w-[240px] overflow-hidden rounded-xl border bg-background shadow-sm\">\n {/* Match AppShell's outer aside: border-r, py-4, px-3 \u2014 minus border-r since the\n card border already serves that purpose, plus rounded so it reads as a preview tile. */}\n <div className=\"flex flex-col gap-3 px-3 py-4\">\n {/* Brand block \u2014 same classes as AppShell brand tile */}\n <div className=\"mb-2\">\n <div className=\"flex items-center gap-3 rounded-xl p-3\">\n <Image\n src=\"/open-mercato.svg\"\n alt={productName}\n width={40}\n height={40}\n className=\"rounded-full shrink-0\"\n />\n <span className=\"text-sm font-medium text-foreground truncate\">{productName}</span>\n </div>\n </div>\n {/* Search input mock \u2014 same container styling as the real sidebar */}\n <div className=\"mb-2 flex items-center gap-2 rounded-lg border border-border bg-background pl-2.5 pr-2 py-2 shadow-sm\">\n <Search className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 text-sm text-muted-foreground/70 truncate\">\n {t('appShell.sidebarCustomizationPreviewSearchPlaceholder', 'Search...')}\n </span>\n </div>\n {groups.length === 0 ? (\n <p className=\"px-2 text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationPreviewEmpty', 'No groups to preview.')}\n </p>\n ) : (\n <nav className=\"flex flex-col gap-2\">\n {groups.map((group, gi) => {\n const visibleItems = group.items.filter((item) => item.hidden !== true)\n if (visibleItems.length === 0) return null\n return (\n <div key={resolveGroupKey(group)}>\n <div className=\"w-full px-1 justify-between flex text-xs font-medium uppercase tracking-wider text-muted-foreground/70 py-1\">\n <span>{group.name}</span>\n </div>\n <div className=\"flex flex-col gap-1\">\n {visibleItems.map((item) => {\n const itemKey = resolveItemKey(item)\n const isActive = activeKey === itemKey\n return (\n <div\n key={itemKey}\n className={`relative text-sm font-medium rounded-lg inline-flex items-center w-full px-3 py-2 gap-2 ${\n isActive ? 'bg-muted text-foreground' : 'text-muted-foreground'\n }`}\n >\n {isActive ? (\n <span\n aria-hidden\n className=\"absolute left-[-12px] top-2 w-1 h-5 rounded-r bg-foreground\"\n />\n ) : null}\n <span className=\"flex items-center justify-center shrink-0\">\n <SidebarPreviewIcon item={item} />\n </span>\n <span className=\"truncate\">{item.title}</span>\n </div>\n )\n })}\n </div>\n {gi < groups.length - 1 ? <div className=\"my-2 border-t -ml-3 -mr-4\" /> : null}\n </div>\n )\n })}\n </nav>\n )}\n </div>\n </div>\n )\n}\n\nexport default SidebarCustomizationEditor\n"],
|
|
5
|
+
"mappings": ";AAozBM,mBAIM,KADF,YAHJ;AAnzBN,YAAY,WAAW;AACvB,SAAS,WAAW,aAAa,cAAc,WAAW,QAAQ,MAAM,QAAQ,qBAAqB;AACrG,SAAS,YAAY,eAAe,eAAe,gBAAgB,WAAW,kBAAqC;AACnH,SAAS,iBAAiB,6BAA6B,aAAa,iBAAiB;AACrF,SAAS,WAAW;AACpB,OAAO,WAAW;AAClB,SAAS,2BAA2B;AACpC,SAAS,MAAM,iBAAiB;AAChC,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,MAAM,aAAa,YAAY,iBAAiB;AACzD,SAAS,QAAQ,eAAe,mBAAmB,cAAc,cAAc,mBAAmB;AAClG,SAAS,WAAW;AACpB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,wBAAwB;AACjC,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAUP,MAAM,uBAAuB;AAC7B,MAAM,0BAA0B;AAChC,MAAM,wBAAwB;AAC9B,MAAM,kBAAkB;AAIxB,SAAS,sBACP,MACA,GACQ;AACR,QAAM,SAAU,KAAK,QAAuC;AAC5D,MAAI,OAAO,WAAW,YAAY,OAAO,SAAS,KAAK,KAAK,UAAU,OAAO,KAAK,SAAS,KAAK;AAC9F,WAAO;AAAA,EACT;AACA,MAAI,OAAO,WAAW,YAAY,OAAO,SAAS,GAAG;AACnD,WAAO,GAAG,EAAE,wCAAwC,CAAC,KAAK,KAAK,MAAM,KAAK,MAAM;AAAA,EAClF;AACA,SAAO,GAAG,EAAE,wCAAwC,CAAC,KAAK,KAAK,MAAM;AACvE;AA6BA,SAAS,cAAc,OAAsB,WAAuC;AAClF,aAAW,QAAQ,OAAO;AACxB,QAAI,eAAe,IAAI,MAAM,UAAW,QAAO;AAC/C,QAAI,KAAK,YAAY,KAAK,SAAS,SAAS,GAAG;AAC7C,YAAM,QAAQ,cAAc,KAAK,UAAU,SAAS;AACpD,UAAI,MAAO,QAAO;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,MAA6B;AAC1D,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,CAAC,SAAsB;AAClC,QAAI,CAAC,KAAK,SAAU;AACpB,eAAW,SAAS,KAAK,UAAU;AACjC,UAAI,KAAK,eAAe,KAAK,CAAC;AAC9B,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACA,OAAK,IAAI;AACT,SAAO;AACT;AAEA,SAAS,uBACP,aACA,cAC2B;AAC3B,QAAM,gBAAgB,MAAM,QAAQ,aAAa,UAAU,IACvD,YAAY,WACT,IAAI,CAAC,OAAQ,OAAO,OAAO,WAAW,GAAG,KAAK,IAAI,EAAG,EACrD,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,IAC/B,CAAC;AACL,QAAM,sBAA8C,CAAC;AACrD,MAAI,aAAa,eAAe,OAAO,YAAY,gBAAgB,UAAU;AAC3E,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,WAAW,GAAG;AAClE,UAAI,OAAO,UAAU,SAAU;AAC/B,YAAM,aAAa,IAAI,KAAK;AAC5B,UAAI,CAAC,WAAY;AACjB,0BAAoB,UAAU,IAAI;AAAA,IACpC;AAAA,EACF;AACA,QAAM,qBAA6C,CAAC;AACpD,MAAI,aAAa,cAAc,OAAO,YAAY,eAAe,UAAU;AACzE,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,UAAU,GAAG;AACjE,UAAI,OAAO,UAAU,SAAU;AAC/B,YAAM,aAAa,IAAI,KAAK;AAC5B,UAAI,CAAC,WAAY;AACjB,yBAAmB,UAAU,IAAI;AAAA,IACnC;AAAA,EACF;AACA,QAAM,sBAAsB,MAAM,QAAQ,aAAa,WAAW,IAC9D,YAAY,YACT,IAAI,CAAC,WAAY,OAAO,WAAW,WAAW,OAAO,KAAK,IAAI,EAAG,EACjE,OAAO,CAAC,WAAW,OAAO,SAAS,CAAC,IACvC,CAAC;AACL,QAAM,oBAA8C,CAAC;AACrD,MAAI,aAAa,aAAa,OAAO,YAAY,cAAc,UAAU;AACvE,eAAW,CAAC,UAAU,IAAI,KAAK,OAAO,QAAQ,YAAY,SAAS,GAAG;AACpE,UAAI,CAAC,MAAM,QAAQ,IAAI,EAAG;AAC1B,YAAM,eAAe,SAAS,KAAK;AACnC,UAAI,CAAC,aAAc;AACnB,YAAM,OAAO,oBAAI,IAAY;AAC7B,YAAM,SAAmB,CAAC;AAC1B,iBAAW,WAAW,MAAM;AAC1B,YAAI,OAAO,YAAY,SAAU;AACjC,cAAM,cAAc,QAAQ,KAAK;AACjC,YAAI,CAAC,eAAe,KAAK,IAAI,WAAW,EAAG;AAC3C,aAAK,IAAI,WAAW;AACpB,eAAO,KAAK,WAAW;AAAA,MACzB;AACA,UAAI,OAAO,SAAS,EAAG,mBAAkB,YAAY,IAAI;AAAA,IAC3D;AAAA,EACF;AACA,QAAM,aAAa,aAAa,IAAI,CAAC,UAAU,gBAAgB,KAAK,CAAC;AACrE,QAAM,QAAQ,gBAAgB,eAAe,UAAU;AACvD,QAAM,EAAE,aAAa,IAAI,uBAAuB,YAAY;AAC5D,QAAM,gBAAyC,CAAC;AAChD,aAAW,UAAU,qBAAqB;AACxC,QAAI,CAAC,aAAa,IAAI,MAAM,EAAG;AAC/B,kBAAc,MAAM,IAAI;AAAA,EAC1B;AACA,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,IACb,YAAY;AAAA,IACZ;AAAA,IACA,WAAW;AAAA,EACb;AACF;AAEA,SAAS,cAAc,cAAyD;AAC9E,SAAO;AAAA,IACL,OAAO,aAAa,IAAI,CAAC,UAAU,gBAAgB,KAAK,CAAC;AAAA,IACzD,aAAa,CAAC;AAAA,IACd,YAAY,CAAC;AAAA,IACb,eAAe,CAAC;AAAA,IAChB,WAAW,CAAC;AAAA,EACd;AACF;AAEO,SAAS,2BAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,QAAQ;AACV,GAAoC;AAClC,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,UAAU,IAAI,YAAY;AAC/C,QAAM,EAAE,SAAS,eAAe,WAAW,gBAAgB,IAAI,iBAAiB;AAChF,QAAM,mBAAmB,eAAe;AACxC,QAAM,eAAe,cAAc,oBAAoB,CAAC;AACxD,QAAM,EAAE,SAAS,eAAe,qBAAqB,IAAI,iBAAiB;AAE1E,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAoB,CAAC,CAAC;AAC5D,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAwB,IAAI;AACpF,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA2C,IAAI;AAC/E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC3E,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,KAAK;AAC9C,QAAM,CAAC,sBAAsB,uBAAuB,IAAI,MAAM,SAAuB,CAAC,CAAC;AACvF,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAmB,CAAC,CAAC;AACzE,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,KAAK;AAClE,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,KAAK;AAC9D,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,EAAE;AAC3D,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAwB,IAAI;AAC9E,QAAM,kBAAkB,MAAM,OAA8B,IAAI;AAChE,QAAM,oBAAoB,MAAM,OAAO,KAAK;AAE5C,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAKxC;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,wCAAwC;AAAA,EAC5D,CAAC;AAED,QAAM,uBAAuB,MAAM;AAAA,IACjC,CAAC,WAAmB,eAA+B;AAAA,MACjD,QAAQ;AAAA,MACR,WAAW,aAAa;AAAA,MACxB;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,eAAe,sBAAsB;AAC3C,QAAM,kBAAkB,MAAM;AAAA,IAC5B,MAAO,oBAAoB,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,iBAAiB,KAAK,OAAO;AAAA,IACtF,CAAC,mBAAmB,QAAQ;AAAA,EAC9B;AAEA,QAAM,cAAc,MAAM,YAAY,CAAC,YAA6E;AAClH,aAAS,CAAC,SAAS;AACjB,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,OAAO,QAAQ,IAAI;AACzB,UAAI,gBAAgB,SAAS;AAC3B,yBAAiB,wBAAwB,gBAAgB,SAAS,IAAI,CAAC;AAAA,MACzE;AACA,aAAO;AAAA,IACT,CAAC;AACD,aAAS,IAAI;AAAA,EACf,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoB,MAAM,YAAY,MAAsB;AAChE,WAAO,wBAAwB,mBAAmB,YAAY,CAAC;AAAA,EACjE,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,mBAAmB,MAAM,YAAY,YAAgC;AAEzE,UAAM,MAAM,GAAG,eAAe,MAAM,KAAK,IAAI,CAAC;AAC9C,UAAM,OAAO,MAAM,QAA6B,KAAK,EAAE,OAAO,WAAW,CAAC;AAC1E,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI,MAAM,aAAa;AAAA,IAC/B;AACA,WAAO,KAAK,QAAQ,YAAY,CAAC;AAAA,EACnC,GAAG,CAAC,eAAe,CAAC;AAEpB,QAAM,mBAAmB,MAAM,YAAY,YAAwE;AACjH,UAAM,OAAO,MAAM,QAA+G,kBAAkB;AACpJ,QAAI,CAAC,KAAK,IAAI;AACZ,aAAO,EAAE,iBAAiB,OAAO,OAAO,CAAC,EAAE;AAAA,IAC7C;AACA,UAAM,OAAO,KAAK,UAAU;AAC5B,UAAM,MAAM,MAAM,oBAAoB;AACtC,UAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAClC,KAAM,MACJ,OAAO,CAAC,MAAM,OAAO,GAAG,OAAO,YAAY,OAAO,GAAG,SAAS,QAAQ,EACtE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAc,MAAM,EAAE,MAAgB,eAAe,EAAE,kBAAkB,KAAK,EAAE,IACvG,CAAC;AACL,WAAO,EAAE,iBAAiB,KAAK,MAAM;AAAA,EACvC,GAAG,CAAC,kBAAkB,CAAC;AAEvB,QAAM,wBAAwB,MAAM,YAAY,CAAC,SAAyB,SAAoB;AAC5F,UAAM,eAAe,gBAAgB,WAAW,kBAAkB;AAClE,oBAAgB,UAAU;AAC1B,QAAI,SAAS;AACX,YAAM,eAAe,uBAAuB,QAAQ,UAAU,YAAY;AAC1E,2BAAqB,QAAQ,EAAE;AAC/B,qBAAe,QAAQ,IAAI;AAC3B,eAAS,YAAY;AACrB,uBAAiB,wBAAwB,cAAc,YAAY,CAAC;AAAA,IACtE,OAAO;AACL,YAAM,QAAQ,cAAc,YAAY;AACxC,2BAAqB,IAAI;AAEzB,YAAM,cAAc,oBAAI,IAAY;AACpC,iBAAW,KAAK,MAAM;AACpB,YAAI,EAAE,SAAS,iBAAkB,aAAY,IAAI,CAAC;AAClD,cAAM,QAAQ,EAAE,KAAK,MAAM,0BAA0B;AACrD,YAAI,MAAO,aAAY,IAAI,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC;AAAA,MAC1D;AACA,UAAI,OAAO;AACX,aAAO,YAAY,IAAI,IAAI,EAAG,SAAQ;AACtC,YAAM,aAAa,SAAS,IAAI,mBAAmB,kBAAkB,IAAI;AACzE,qBAAe,UAAU;AACzB,eAAS,KAAK;AACd,uBAAiB,wBAAwB,cAAc,KAAK,CAAC;AAAA,IAC/D;AACA,aAAS,KAAK;AAAA,EAChB,GAAG,CAAC,iBAAiB,CAAC;AAMtB,QAAM,UAAU,MAAM;AACpB,QAAI,kBAAkB,QAAS;AAC/B,QAAI,aAAa,WAAW,EAAG;AAC/B,sBAAkB,UAAU;AAC5B,mBAAe,OAAO;AACpB,iBAAW,IAAI;AACf,eAAS,IAAI;AACb,UAAI;AACF,cAAM,CAAC,MAAM,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,UAC7C,iBAAiB;AAAA,UACjB,iBAAiB;AAAA,QACnB,CAAC;AACD,oBAAY,IAAI;AAChB,2BAAmB,aAAa,eAAe;AAC/C,gCAAwB,aAAa,KAAK;AAC1C,cAAM,SAAS,KAAK,KAAK,CAAC,MAAM,EAAE,QAAQ;AAC1C,cAAM,UAAU,UAAU,KAAK,CAAC,KAAK;AACrC,8BAAsB,SAAS,IAAI;AACnC,2BAAmB,CAAC,CAAC;AAAA,MACvB,SAAS,KAAK;AACZ,gBAAQ,MAAM,mCAAmC,GAAG;AACpD,iBAAS,EAAE,wCAAwC,CAAC;AAAA,MACtD,UAAE;AACA,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF;AACA,SAAK,KAAK;AAAA,EAEZ,GAAG,CAAC,aAAa,MAAM,CAAC;AAExB,QAAM,sBAAsB,MAAM,YAAY,CAAC,WAAmB;AAChE,uBAAmB,CAAC,SAAU,KAAK,SAAS,MAAM,IAAI,KAAK,OAAO,CAAC,OAAO,OAAO,MAAM,IAAI,CAAC,GAAG,MAAM,MAAM,CAAE;AAC7G,aAAS,IAAI;AAAA,EACf,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAmB,MAAM,YAAY,OACzC,cACA,UAA2C,CAAC,MACC;AAC7C,QAAI,UAAU,SAAU,QAAO,EAAE,IAAI,MAAM;AAC3C,QAAI,SAAS,sBAAsB,MAAM;AACvC,YAAM,UAAU,MAAM,cAAc;AAAA,QAClC,OAAO,EAAE,mDAAmD,0BAA0B;AAAA,QACtF,MAAM,EAAE,kDAAkD,gFAAgF;AAAA,QAC1I,aAAa,EAAE,iDAAiD,oBAAoB;AAAA,QACpF,YAAY,EAAE,iBAAiB,QAAQ;AAAA,QACvC,SAAS;AAAA,MACX,CAAC;AACD,UAAI,CAAC,QAAS,QAAO,EAAE,IAAI,MAAM;AAAA,IACnC;AACA,cAAU,IAAI;AACd,QAAI,CAAC,QAAQ,kBAAmB,UAAS,IAAI;AAC7C,QAAI;AACF,YAAM,eAAe,gBAAgB,WAAW,kBAAkB;AAClE,sBAAgB,UAAU;AAC1B,YAAM,aAAa,aAAa,IAAI,CAAC,MAAM,gBAAgB,CAAC,CAAC;AAC7D,YAAM,WAAW,gBAAgB,IAAI,KAAK;AAC1C,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,MACT,QAA+B,iBAAiB;AAAA,UAC9C,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA;AAAA,YAEnB,MAAM,QAAQ,SAAS,IAAI,UAAU;AAAA,YACrC,UAAU,EAAE,YAAY,aAAa,CAAC,GAAG,YAAY,CAAC,GAAG,aAAa,CAAC,GAAG,WAAW,CAAC,EAAE;AAAA,YACxF,UAAU;AAAA,UACZ,CAAC;AAAA,QACH,CAAC;AAAA,QACH,SAAS,qBAAqB,eAAe;AAAA,QAC7C,iBAAiB,EAAE,MAAM,QAAQ,SAAS,IAAI,UAAU,KAAK;AAAA,MAC/D,CAAC;AACD,UAAI,CAAC,KAAK,IAAI;AACZ,cAAM,UAAU,sBAAsB,MAAM,CAAC;AAC7C,YAAI,CAAC,QAAQ,kBAAmB,UAAS,OAAO;AAChD,eAAO,EAAE,IAAI,OAAO,OAAO,QAAQ;AAAA,MACrC;AACA,YAAM,UAAU,KAAK,QAAQ,WAAW;AAGxC,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,iBAAiB;AAAA,MACpC,QAAQ;AACN,mBAAW;AAAA,MACb;AAGA,UAAI,WAAW,CAAC,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE,GAAG;AACzD,mBAAW,CAAC,GAAG,UAAU,OAAO;AAAA,MAClC;AACA,kBAAY,QAAQ;AACpB,UAAI,SAAS;AACX,cAAM,QAAQ,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE,KAAK;AAC3D,8BAAsB,OAAO,QAAQ;AAAA,MACvC;AACA,YAAM,EAAE,+CAA+C,kBAAkB,GAAG,SAAS;AACrF,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB,SAAS,KAAK;AACZ,cAAQ,MAAM,oCAAoC,GAAG;AACrD,YAAM,UAAU,EAAE,wCAAwC;AAC1D,UAAI,CAAC,QAAQ,kBAAmB,UAAS,OAAO;AAChD,aAAO,EAAE,IAAI,OAAO,OAAO,QAAQ;AAAA,IACrC,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,OAAO,mBAAmB,eAAe,GAAG,mBAAmB,iBAAiB,kBAAkB,uBAAuB,UAAU,aAAa,oBAAoB,CAAC;AAE3L,QAAM,sBAAsB,MAAM,YAAY,OAAO,QAAgB;AACnE,QAAI,UAAU,SAAU;AACxB,QAAI,QAAQ,kBAAmB;AAC/B,QAAI,QAAQ,mBAAmB,aAAc;AAC7C,QAAI,OAAO;AACT,YAAM,UAAU,MAAM,cAAc;AAAA,QAClC,OAAO,EAAE,mDAAmD,0BAA0B;AAAA,QACtF,MAAM,EAAE,kDAAkD,gFAAgF;AAAA,QAC1I,aAAa,EAAE,iDAAiD,oBAAoB;AAAA,QACpF,YAAY,EAAE,iBAAiB,QAAQ;AAAA,QACvC,SAAS;AAAA,MACX,CAAC;AACD,UAAI,CAAC,QAAS;AAAA,IAChB;AACA,QAAI,QAAQ,iBAAiB;AAC3B,4BAAsB,MAAM,QAAQ;AACpC;AAAA,IACF;AACA,UAAM,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK;AACnD,0BAAsB,MAAM,QAAQ;AAAA,EACtC,GAAG,CAAC,QAAQ,UAAU,mBAAmB,cAAc,OAAO,eAAe,GAAG,UAAU,qBAAqB,CAAC;AAEhH,QAAM,YAAY,MAAM,YAAY,CAAC,SAAiB,WAAmB;AACvE,gBAAY,CAACA,WAAU;AACrB,YAAM,QAAQ,CAAC,GAAGA,OAAM,KAAK;AAC7B,YAAM,QAAQ,MAAM,QAAQ,OAAO;AACnC,UAAI,UAAU,GAAI,QAAOA;AACzB,YAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,SAAS,GAAG,QAAQ,MAAM,CAAC;AACxE,UAAI,cAAc,MAAO,QAAOA;AAChC,YAAM,OAAO,OAAO,CAAC;AACrB,YAAM,OAAO,WAAW,GAAG,OAAO;AAClC,aAAO,EAAE,GAAGA,QAAO,MAAM;AAAA,IAC3B,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,aAAa;AAAA,IACjB,UAAU,eAAe,EAAE,sBAAsB,EAAE,UAAU,EAAE,EAAE,CAAC;AAAA,IAClE,UAAU,cAAc;AAAA,EAC1B;AAEA,QAAM,oBAAoB,MAAM,YAAY,CAAC,UAAkB,oBAA8B,CAAC,UAAwB;AACpH,UAAM,EAAE,QAAQ,KAAK,IAAI;AACzB,QAAI,CAAC,QAAQ,OAAO,OAAO,KAAK,GAAI;AACpC,UAAM,SAAS,OAAO,OAAO,EAAE;AAC/B,UAAM,OAAO,OAAO,KAAK,EAAE;AAC3B,gBAAY,CAACA,WAAU;AACrB,YAAM,YAAYA,OAAM,YAAY,QAAQ,GAAG,SAC3C,CAAC,GAAGA,OAAM,UAAU,QAAQ,CAAC,IAC7B,CAAC,GAAG,eAAe;AACvB,YAAM,YAAY,UAAU,QAAQ,MAAM;AAC1C,YAAM,UAAU,UAAU,QAAQ,IAAI;AACtC,UAAI,cAAc,MAAM,YAAY,GAAI,QAAOA;AAC/C,YAAM,YAAY,UAAU,WAAW,WAAW,OAAO;AACzD,aAAO;AAAA,QACL,GAAGA;AAAA,QACH,WAAW,EAAE,GAAIA,OAAM,aAAa,CAAC,GAAI,CAAC,QAAQ,GAAG,UAAU;AAAA,MACjE;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,gBAAgB,MAAM,YAAY,CAAC,SAAiB,UAAkB;AAC1E,gBAAY,CAACA,WAAU;AACrB,YAAM,OAAO,EAAE,GAAGA,OAAM,YAAY;AACpC,UAAI,MAAM,KAAK,EAAE,WAAW,EAAG,QAAO,KAAK,OAAO;AAAA,UAC7C,MAAK,OAAO,IAAI;AACrB,aAAO,EAAE,GAAGA,QAAO,aAAa,KAAK;AAAA,IACvC,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,eAAe,MAAM,YAAY,CAAC,QAAgB,UAAkB;AACxE,gBAAY,CAACA,WAAU;AACrB,YAAM,OAAO,EAAE,GAAGA,OAAM,WAAW;AACnC,UAAI,MAAM,KAAK,EAAE,WAAW,EAAG,QAAO,KAAK,MAAM;AAAA,UAC5C,MAAK,MAAM,IAAI;AACpB,aAAO,EAAE,GAAGA,QAAO,YAAY,KAAK;AAAA,IACtC,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,gBAAgB,MAAM,YAAY,CAAC,QAAgB,WAAoB;AAC3E,gBAAY,CAACA,WAAU;AACrB,YAAM,OAAO,EAAE,GAAGA,OAAM,cAAc;AACtC,YAAM,QAAQ,CAAC,OAAe;AAC5B,YAAI,OAAQ,MAAK,EAAE,IAAI;AAAA,YAClB,QAAO,KAAK,EAAE;AAAA,MACrB;AACA,YAAM,MAAM;AAEZ,UAAI,gBAAgB,SAAS;AAC3B,mBAAW,SAAS,gBAAgB,SAAS;AAC3C,gBAAM,SAAS,cAAc,MAAM,OAAO,MAAM;AAChD,cAAI,CAAC,OAAQ;AACb,qBAAW,iBAAiB,sBAAsB,MAAM,EAAG,OAAM,aAAa;AAC9E;AAAA,QACF;AAAA,MACF;AACA,aAAO,EAAE,GAAGA,QAAO,eAAe,KAAK;AAAA,IACzC,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,QAAQ,MAAM,YAAY,MAAM;AACpC,QAAI,CAAC,gBAAgB,QAAS;AAC9B,QAAI,iBAAiB;AACnB,YAAM,eAAe,uBAAuB,gBAAgB,UAAU,gBAAgB,OAAO;AAC7F,eAAS,YAAY;AACrB,uBAAiB,wBAAwB,gBAAgB,SAAS,YAAY,CAAC;AAAA,IACjF,OAAO;AACL,YAAM,QAAQ,cAAc,gBAAgB,OAAO;AACnD,eAAS,KAAK;AACd,uBAAiB,wBAAwB,gBAAgB,SAAS,KAAK,CAAC;AAAA,IAC1E;AACA,aAAS,KAAK;AAAA,EAChB,GAAG,CAAC,eAAe,CAAC;AAEpB,QAAM,SAAS,MAAM,YAAY,MAAM;AACrC,iBAAa;AAAA,EACf,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,kBAAkB,MAAM,YAAY,YAAY;AACpD,sBAAkB,IAAI;AACtB,UAAM,SAAS,MAAM,iBAAiB,eAAe,EAAE,mBAAmB,KAAK,CAAC;AAChF,QAAI,OAAO,IAAI;AACb,uBAAiB,KAAK;AACtB,uBAAiB,EAAE;AACnB,wBAAkB,IAAI;AACtB;AAAA,IACF;AACA,sBAAkB,OAAO,SAAS,EAAE,wCAAwC,CAAC;AAAA,EAC/E,GAAG,CAAC,kBAAkB,eAAe,CAAC,CAAC;AAEvC,QAAM,0BAA0B,MAAM,YAAY,MAAM;AACtD,QAAI,CAAC,SAAS,CAAC,gBAAgB,QAAS,QAAO;AAC/C,UAAM,aAAa,gBAAgB;AACnC,UAAM,EAAE,eAAe,aAAa,IAAI,uBAAuB,UAAU;AACzE,UAAM,uBAA+C,CAAC;AACtD,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,WAAW,GAAG;AAC5D,YAAM,UAAU,MAAM,KAAK;AAC3B,YAAM,OAAO,cAAc,IAAI,GAAG;AAClC,UAAI,CAAC,WAAW,CAAC,KAAM;AACvB,UAAI,YAAY,KAAM,sBAAqB,GAAG,IAAI;AAAA,IACpD;AACA,UAAM,sBAA8C,CAAC;AACrD,eAAW,CAAC,QAAQ,KAAK,KAAK,OAAO,QAAQ,MAAM,UAAU,GAAG;AAC9D,YAAM,UAAU,MAAM,KAAK;AAC3B,YAAM,OAAO,aAAa,IAAI,MAAM;AACpC,UAAI,CAAC,WAAW,CAAC,KAAM;AACvB,UAAI,YAAY,KAAM,qBAAoB,MAAM,IAAI;AAAA,IACtD;AACA,UAAM,uBAAiC,CAAC;AACxC,eAAW,CAAC,QAAQ,MAAM,KAAK,OAAO,QAAQ,MAAM,aAAa,GAAG;AAClE,UAAI,CAAC,OAAQ;AACb,UAAI,CAAC,aAAa,IAAI,MAAM,EAAG;AAC/B,2BAAqB,KAAK,MAAM;AAAA,IAClC;AAEA,UAAM,YAAY,oBAAI,IAAY;AAClC,eAAW,SAAS,WAAY,WAAU,IAAI,gBAAgB,KAAK,CAAC;AACpE,UAAM,qBAA+C,CAAC;AACtD,eAAW,CAAC,UAAU,IAAI,KAAK,OAAO,QAAQ,MAAM,aAAa,CAAC,CAAC,GAAG;AACpE,UAAI,CAAC,UAAU,IAAI,QAAQ,EAAG;AAC9B,YAAM,OAAO,oBAAI,IAAY;AAC7B,YAAM,SAAmB,CAAC;AAC1B,iBAAW,WAAW,MAAM;AAC1B,YAAI,KAAK,IAAI,OAAO,EAAG;AACvB,YAAI,CAAC,aAAa,IAAI,OAAO,EAAG;AAChC,aAAK,IAAI,OAAO;AAChB,eAAO,KAAK,OAAO;AAAA,MACrB;AACA,UAAI,OAAO,SAAS,EAAG,oBAAmB,QAAQ,IAAI;AAAA,IACxD;AACA,WAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,WAAW;AAAA,IACb;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,OAAO,MAAM,YAAY,YAAY;AACzC,UAAM,WAAW,wBAAwB;AACzC,QAAI,CAAC,SAAU;AACf,cAAU,IAAI;AACd,aAAS,IAAI;AACb,QAAI;AACF,YAAM,cAAc,YAAY,KAAK;AACrC,YAAM,oBAAoB,iBAAiB,YAAY;AACvD,UAAI,eAA+B;AACnC,UAAI,cAAc;AAChB,cAAM,OAAO,MAAM,YAAY;AAAA,UAC7B,WAAW,MACT,QAA+B,iBAAiB;AAAA,YAC9C,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU;AAAA,cACnB,MAAM,YAAY,SAAS,IAAI,cAAc;AAAA,cAC7C;AAAA;AAAA;AAAA,cAGA,UAAU;AAAA,YACZ,CAAC;AAAA,UACH,CAAC;AAAA,UACH,SAAS,qBAAqB,aAAa;AAAA,UAC3C,iBAAiB,EAAE,MAAM,YAAY,SAAS,IAAI,cAAc,MAAM,UAAU,KAAK;AAAA,QACvF,CAAC;AACD,YAAI,CAAC,KAAK,IAAI;AACZ,mBAAS,sBAAsB,MAAM,CAAC,CAAC;AACvC;AAAA,QACF;AACA,uBAAe,KAAK,QAAQ,WAAW;AAAA,MACzC,WAAW,mBAAmB;AAC5B,cAAM,OAAO,MAAM,YAAY;AAAA,UAC7B,WAAW,MACT,QAA+B,GAAG,eAAe,IAAI,mBAAmB,iBAAiB,CAAC,IAAI;AAAA,YAC5F,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU;AAAA,cACnB,MAAM,YAAY,SAAS,IAAI,cAAc;AAAA,cAC7C;AAAA,cACA,UAAU;AAAA,YACZ,CAAC;AAAA,UACH,CAAC;AAAA,UACH,SAAS,qBAAqB,eAAe,iBAAiB;AAAA,UAC9D,iBAAiB;AAAA,YACf,IAAI;AAAA,YACJ,MAAM,YAAY,SAAS,IAAI,cAAc;AAAA,YAC7C,UAAU;AAAA,UACZ;AAAA,QACF,CAAC;AACD,YAAI,CAAC,KAAK,IAAI;AACZ,mBAAS,sBAAsB,MAAM,CAAC,CAAC;AACvC;AAAA,QACF;AACA,uBAAe,KAAK,QAAQ,WAAW;AAAA,MACzC;AAKA,YAAM,qBAA8C;AAAA,QAClD,YAAY,SAAS;AAAA,QACrB,aAAa,SAAS;AAAA,QACtB,YAAY,SAAS;AAAA,QACrB,aAAa,SAAS;AAAA,QACtB,WAAW,SAAS;AAAA,MACtB;AACA,UAAI,iBAAiB;AACnB,cAAM,sBAAsB,CAAC,GAAG,eAAe;AAC/C,cAAM,sBAAsB,qBACzB,OAAO,CAAC,SAAS,KAAK,iBAAiB,CAAC,gBAAgB,SAAS,KAAK,EAAE,CAAC,EACzE,IAAI,CAAC,SAAS,KAAK,EAAE;AACxB,2BAAmB,eAAe;AAClC,2BAAmB,eAAe;AAAA,MACpC;AACA,YAAM,kBAAkB,MAAM,YAAY;AAAA,QACxC,WAAW,MACT,QAAQ,oBAAoB;AAAA,UAC1B,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,kBAAkB;AAAA,QACzC,CAAC;AAAA,QACH,SAAS,qBAAqB,mBAAmB,iBAAiB;AAAA,QAClE,iBAAiB;AAAA,MACnB,CAAC;AACD,UAAI,CAAC,gBAAgB,IAAI;AAIvB,iBAAS,sBAAsB,iBAAiB,CAAC,CAAC;AAClD;AAAA,MACF;AACA,UAAI;AAAE,eAAO,cAAc,IAAI,MAAM,qBAAqB,CAAC;AAAA,MAAE,QAAQ;AAAA,MAA8E;AAEnJ,YAAM,CAAC,MAAM,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC7C,iBAAiB;AAAA,QACjB,iBAAiB;AAAA,MACnB,CAAC;AAGD,YAAM,aAAa,gBAAgB,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,aAAc,EAAE,IAC1E,CAAC,GAAG,MAAM,YAAY,IACtB;AACJ,kBAAY,UAAU;AACtB,yBAAmB,aAAa,eAAe;AAC/C,8BAAwB,aAAa,KAAK;AAC1C,UAAI,cAAc;AAChB,cAAM,QAAQ,WAAW,KAAK,CAAC,MAAM,EAAE,OAAO,aAAc,EAAE,KAAK;AACnE,8BAAsB,OAAO,UAAU;AAAA,MACzC,OAAO;AACL,cAAM,SAAS,WAAW,KAAK,CAAC,MAAM,EAAE,QAAQ,KAAK,WAAW,CAAC,KAAK;AACtE,8BAAsB,QAAQ,UAAU;AAAA,MAC1C;AACA;AAAA,QACE,eACI,EAAE,+CAA+C,kBAAkB,IACnE,EAAE,6CAA6C,gBAAgB;AAAA,QACnE;AAAA,MACF;AACA,gBAAU;AAAA,IACZ,SAAS,KAAK;AACZ,cAAQ,MAAM,kCAAkC,GAAG;AACnD,eAAS,EAAE,wCAAwC,CAAC;AAAA,IACtD,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,OAAO,aAAa,cAAc,iBAAiB,mBAAmB,iBAAiB,oBAAoB,iBAAiB,iBAAiB,sBAAsB,GAAG,yBAAyB,kBAAkB,kBAAkB,uBAAuB,SAAS,aAAa,oBAAoB,CAAC;AAEzS,QAAM,eAAe,MAAM,YAAY,OAAO,SAAkB;AAC9D,QAAI,CAAC,mBAAmB,UAAU,SAAU;AAC5C,aAAS,IAAI;AACb,QAAI;AACF,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,MACT,QAA+B,GAAG,eAAe,IAAI,mBAAmB,gBAAgB,EAAE,CAAC,IAAI;AAAA,UAC7F,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,UAAU,KAAK,CAAC;AAAA,QACzC,CAAC;AAAA,QACH,SAAS,qBAAqB,uBAAuB,gBAAgB,EAAE;AAAA,QACvE,iBAAiB,EAAE,IAAI,gBAAgB,IAAI,UAAU,KAAK;AAAA,MAC5D,CAAC;AACD,UAAI,CAAC,KAAK,IAAI;AACZ,iBAAS,EAAE,wCAAwC,CAAC;AACpD;AAAA,MACF;AACA,UAAI;AAAE,eAAO,cAAc,IAAI,MAAM,qBAAqB,CAAC;AAAA,MAAE,QAAQ;AAAA,MAA8E;AACnJ,YAAM,OAAO,MAAM,iBAAiB;AACpC,kBAAY,IAAI;AAChB,YAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,gBAAgB,EAAE,KAAK;AAC/D,4BAAsB,OAAO,IAAI;AAAA,IACnC,SAAS,KAAK;AACZ,cAAQ,MAAM,yCAAyC,GAAG;AAC1D,eAAS,EAAE,wCAAwC,CAAC;AAAA,IACtD;AAAA,EACF,GAAG,CAAC,iBAAiB,QAAQ,UAAU,iBAAiB,GAAG,kBAAkB,uBAAuB,aAAa,oBAAoB,CAAC;AAEtI,QAAM,gBAAgB,MAAM,YAAY,YAAY;AAClD,QAAI,CAAC,gBAAiB;AACtB,UAAM,UAAU,MAAM,cAAc;AAAA,MAClC,OAAO,EAAE,mDAAmD,iBAAiB;AAAA,MAC7E,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,MACF;AAAA,MACA,aAAa,EAAE,qDAAqD,gBAAgB;AAAA,MACpF,YAAY,EAAE,iBAAiB,QAAQ;AAAA,MACvC,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,QAAS;AACd,gBAAY,IAAI;AAChB,aAAS,IAAI;AACb,QAAI;AACF,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,MACT,QAAQ,GAAG,eAAe,IAAI,mBAAmB,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,SAAS,CAAC;AAAA,QAC9F,SAAS,qBAAqB,iBAAiB,gBAAgB,EAAE;AAAA,QACjE,iBAAiB,EAAE,IAAI,gBAAgB,GAAG;AAAA,MAC5C,CAAC;AACD,UAAI,CAAC,KAAK,IAAI;AACZ,iBAAS,EAAE,wCAAwC,CAAC;AACpD;AAAA,MACF;AACA,UAAI;AAAE,eAAO,cAAc,IAAI,MAAM,qBAAqB,CAAC;AAAA,MAAE,QAAQ;AAAA,MAA8E;AACnJ,YAAM,OAAO,MAAM,iBAAiB;AACpC,kBAAY,IAAI;AAChB,YAAM,WAAW,KAAK,CAAC,KAAK;AAC5B,4BAAsB,UAAU,IAAI;AAAA,IACtC,SAAS,KAAK;AACZ,cAAQ,MAAM,4BAA4B,GAAG;AAC7C,eAAS,EAAE,wCAAwC,CAAC;AAAA,IACtD,UAAE;AACA,kBAAY,KAAK;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,iBAAiB,eAAe,GAAG,iBAAiB,kBAAkB,uBAAuB,aAAa,oBAAoB,CAAC;AAEnI,QAAM,SAAS,UAAU;AAEzB,MAAI,WAAW,CAAC,OAAO;AACrB,WACE,iCACG;AAAA;AAAA,MACD,qBAAC,SAAI,WAAU,aACb;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAI,WAAU,2CAA0C;AAAA,UACzD,oBAAC,SAAI,WAAU,8CAA6C;AAAA,WAC9D;AAAA,QACA,oBAAC,SAAI,WAAU,oDAAmD;AAAA,SACpE;AAAA,OACF;AAAA,EAEJ;AAEA,MAAI,CAAC,SAAS,CAAC,gBAAgB,SAAS;AAGtC,UAAM,eAAe,WAAW,mBAAmB,aAAa,WAAW;AAC3E,WACE,iCACG;AAAA;AAAA,MACD,oBAAC,SAAI,WAAU,iFACZ,yBACG,EAAE,wCAAwC,eAAU,IACnD,SAAS,EAAE,wCAAwC,GAC1D;AAAA,OACF;AAAA,EAEJ;AAEA,QAAM,wBAAwB,gBAAgB;AAC9C,QAAM,eAAe,oBAAI,IAA0B;AACnD,aAAW,SAAS,uBAAuB;AACzC,iBAAa,IAAI,gBAAgB,KAAK,GAAG,KAAK;AAAA,EAChD;AACA,QAAM,kBAAkB,gBAAgB,MAAM,OAAO,MAAM,KAAK,aAAa,KAAK,CAAC,CAAC;AACpF,QAAM,cAAc,gBAAgB;AAEpC,QAAM,cAAc,eAAe,kBAAkB,qBAAqB;AAC1E,QAAM,oBAAoB,SAAS,SAAS,KAAK;AAEjD,SACE,iCACG;AAAA;AAAA,IACD;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,cAAc,CAAC,SAAS;AACtB,cAAI,CAAC,MAAM;AACT,6BAAiB,KAAK;AACtB,6BAAiB,EAAE;AACnB,8BAAkB,IAAI;AAAA,UACxB;AAAA,QACF;AAAA,QAEA,+BAAC,iBAAc,WAAU,eACvB;AAAA,+BAAC,gBACC;AAAA,gCAAC,eACE,YAAE,+CAA+C,iBAAiB,GACrE;AAAA,YACA,oBAAC,qBACE,YAAE,qDAAqD,yEAAyE,GACnI;AAAA,aACF;AAAA,UACA,qBAAC,SAAI,WAAU,yBACb;AAAA,gCAAC,WAAM,WAAU,yEACd,YAAE,iDAAiD,cAAc,GACpE;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAS;AAAA,gBACT,OAAO;AAAA,gBACP,UAAU,CAAC,UAAU;AACnB,mCAAiB,MAAM,OAAO,KAAK;AACnC,sBAAI,eAAgB,mBAAkB,IAAI;AAAA,gBAC5C;AAAA,gBACA,WAAW,CAAC,UAAU;AACpB,sBAAI,MAAM,QAAQ,WAAW,CAAC,MAAM,UAAU;AAC5C,0BAAM,eAAe;AACrB,yBAAK,gBAAgB;AAAA,kBACvB;AAAA,gBACF;AAAA,gBACA,aAAa,EAAE,uDAAuD,gBAAgB;AAAA,gBACtF,UAAU;AAAA,gBACV,gBAAc,iBAAiB,OAAO;AAAA,gBACtC,oBAAkB,iBAAiB,8BAA8B;AAAA;AAAA,YACnE;AAAA,YACC,iBACC;AAAA,cAAC;AAAA;AAAA,gBACC,IAAG;AAAA,gBACH,MAAK;AAAA,gBACL,WAAU;AAAA,gBAET;AAAA;AAAA,YACH,IACE;AAAA,aACN;AAAA,UACA,qBAAC,gBAAa,WAAU,QACtB;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,SAAS,MAAM;AACb,mCAAiB,KAAK;AACtB,mCAAiB,EAAE;AACnB,oCAAkB,IAAI;AAAA,gBACxB;AAAA,gBACA,UAAU;AAAA,gBAET,YAAE,qCAAqC;AAAA;AAAA,YAC1C;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,MAAM;AAAE,uBAAK,gBAAgB;AAAA,gBAAE;AAAA,gBACxC,UAAU;AAAA,gBAET,mBACG,EAAE,yCAAyC,gBAAW,IACtD,EAAE,8CAA8C,gBAAgB;AAAA;AAAA,YACtE;AAAA,aACF;AAAA,WACF;AAAA;AAAA,IACF;AAAA,IACA,qBAAC,QACC;AAAA,2BAAC,YAAO,WAAU,aAChB;AAAA,4BAAC,QAAG,WAAU,mDACX,YAAE,sCAAsC,GAC3C;AAAA,QACA,oBAAC,OAAE,WAAU,iCACV,YAAE,qCAAqC,EAAE,QAAQ,YAAY,CAAC,GACjE;AAAA,SACF;AAAA,MAEC,QACC,oBAAC,SAAI,WAAU,+FACZ,iBACH,IACE;AAAA,MAGJ,qBAAC,YAAS,WAAU,uEAClB;AAAA,6BAAC,SAAI,WAAU,aACX;AAAA,iBAAM;AACN,kBAAM,gBAAgB,mBAAmB,qBAAqB,SAAS;AACvE,gBAAI,CAAC,qBAAqB,CAAC,cAAe,QAAO;AACjD,mBACE,oBAAC,QACC,+BAAC,eAAY,WAAU,uBACpB;AAAA,kCACC,qBAAC,SAAI,WAAU,uBAEb;AAAA,qCAAC,SAAI,WAAU,yBACb;AAAA,sCAAC,WAAM,WAAU,yEACd,YAAE,iDAAiD,cAAc,GACpE;AAAA,kBACA,qBAAC,SAAI,WAAU,4BAEb;AAAA,yCAAC,SAAI,WAAU,sCACb;AAAA;AAAA,wBAAC;AAAA;AAAA,0BACC,OAAO;AAAA,0BACP,UAAU,CAAC,UAAU;AACnB,2CAAe,MAAM,OAAO,KAAK;AACjC,qCAAS,IAAI;AAAA,0BACf;AAAA,0BACA,aAAa,EAAE,uDAAuD,gBAAgB;AAAA,0BACtF,UAAU;AAAA,0BACV,WAAU;AAAA;AAAA,sBACZ;AAAA,sBACA;AAAA,wBAAC;AAAA;AAAA,0BACC,OAAO;AAAA,0BACP,eAAe,CAAC,UAAU;AAAE,iCAAK,oBAAoB,KAAK;AAAA,0BAAE;AAAA,0BAC5D,UAAU,UAAU;AAAA,0BAEpB;AAAA;AAAA,8BAAC;AAAA;AAAA,gCACC,WAAU;AAAA,gCACV,cAAY,EAAE,mDAAmD,cAAc;AAAA,gCAE/E,8BAAC,eAAY;AAAA;AAAA,4BACf;AAAA,4BACA,oBAAC,iBACE,mBAAS,SAAS,IACjB,SAAS,IAAI,CAAC,YACZ,oBAAC,cAA4B,OAAO,QAAQ,IACzC,kBAAQ,QADM,QAAQ,EAEzB,CACD,IAED,oBAAC,cAAW,OAAO,iBAAiB,UAAQ,MACzC,YAAE,8CAA8C,uBAAuB,GAC1E,GAEJ;AAAA;AAAA;AAAA,sBACF;AAAA,uBACF;AAAA,oBACA;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAK;AAAA,wBACL,SAAS,MAAM;AACb,2CAAiB,EAAE;AACnB,4CAAkB,IAAI;AACtB,2CAAiB,IAAI;AAAA,wBACvB;AAAA,wBACA,UAAU;AAAA,wBACV,OAAO,EAAE,2CAA2C,iBAAiB;AAAA,wBAErE;AAAA,8CAAC,QAAK,WAAU,UAAS;AAAA,0BACxB,EAAE,0CAA0C,YAAY;AAAA;AAAA;AAAA,oBAC3D;AAAA,qBACF;AAAA,mBACF;AAAA,gBAEC,eACC,oBAAC,OAAE,WAAU,iCACV,YAAE,+CAA+C,uFAAuF,GAC3I,IACE;AAAA,gBAGJ,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,SAAS,iBAAiB,YAAY;AAAA,sBACtC,iBAAiB,CAAC,SAAS;AACzB,4BAAI,aAAc;AAClB,6BAAK,aAAa,SAAS,IAAI;AAAA,sBACjC;AAAA,sBACA,UAAU,UAAU;AAAA,sBACpB,cAAY,EAAE,mDAAmD,QAAQ;AAAA;AAAA,kBAC3E;AAAA,kBACA,oBAAC,UAAK,WAAU,yEACb,YAAE,mDAAmD,QAAQ,GAChE;AAAA,mBACF;AAAA,iBACF,IACE;AAAA,cAEH,qBAAqB,gBACpB,oBAAC,SAAI,WAAU,kBAAiB,eAAW,MAAC,IAC1C;AAAA,cAEH,gBACC,qBAAC,SAAI,WAAU,uBACb;AAAA,qCAAC,SAAI,WAAU,aACb;AAAA,sCAAC,QAAG,WAAU,wDACX,YAAE,mCAAmC,GACxC;AAAA,kBACA,oBAAC,OAAE,WAAU,iCAAiC,YAAE,yCAAyC,GAAE;AAAA,mBAC7F;AAAA,gBACA,oBAAC,SAAI,WAAU,kCACZ,+BAAqB,IAAI,CAAC,SAAS;AAClC,wBAAM,UAAU,gBAAgB,SAAS,KAAK,EAAE;AAChD,wBAAM,YAAY,KAAK,iBAAiB,CAAC;AACzC,yBACE;AAAA,oBAAC;AAAA;AAAA,sBAEC,WAAU;AAAA,sBAEV;AAAA;AAAA,0BAAC;AAAA;AAAA,4BACC;AAAA,4BACA,iBAAiB,MAAM,oBAAoB,KAAK,EAAE;AAAA,4BAClD,UAAU;AAAA;AAAA,wBACZ;AAAA,wBACA,oBAAC,UAAK,WAAU,+CAA+C,eAAK,MAAK;AAAA,wBACxE,KAAK,gBACJ,qBAAC,OAAI,SAAS,YAAY,UAAU,QAAQ,KAAK,CAAC,WAC/C;AAAA,sCAAY,oBAAC,iBAAc,WAAU,UAAS,eAAW,MAAC,IAAK;AAAA,0BAC/D,YAAY,EAAE,+BAA+B,IAAI,EAAE,+BAA+B;AAAA,2BACrF,IACE;AAAA;AAAA;AAAA,oBAdC,KAAK;AAAA,kBAeZ;AAAA,gBAEJ,CAAC,GACH;AAAA,iBACF,IACE;AAAA,cAIJ,oBAAC,SAAI,WAAU,kBAAiB,eAAW,MAAC;AAAA,cAC5C,qBAAC,SAAI,WAAU,qDACZ;AAAA,kCACC;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAQ;AAAA,oBACR,SAAS,MAAM;AAAE,2BAAK,cAAc;AAAA,oBAAE;AAAA,oBACtC,UAAU;AAAA,oBACV,WAAU;AAAA,oBAEV;AAAA,0CAAC,UAAO,WAAU,UAAS;AAAA,sBAC1B,WACG,EAAE,wDAAwD,gBAAW,IACrE,EAAE,8CAA8C,gBAAgB;AAAA;AAAA;AAAA,gBACtE,IACE,oBAAC,UAAK;AAAA,gBACV,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,SAAQ;AAAA,sBACR,SAAS;AAAA,sBACT,UAAU,UAAU,CAAC;AAAA,sBAEpB,YAAE,oCAAoC;AAAA;AAAA,kBACzC;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,SAAQ;AAAA,sBACR,SAAS;AAAA,sBACT,UAAU,UAAU,CAAC;AAAA,sBAEpB,YAAE,qCAAqC;AAAA;AAAA,kBAC1C;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,SAAS;AAAA,sBACT,UAAU,UAAW,CAAC,gBAAgB,CAAC;AAAA,sBAEtC,mBACI,eACG,EAAE,yCAAyC,gBAAW,IACtD,EAAE,qCAAqC,IAC1C,eACG,EAAE,8CAA8C,gBAAgB,IAChE,EAAE,mCAAmC;AAAA;AAAA,kBAC/C;AAAA,mBACF;AAAA,iBACF;AAAA,eACF,GACF;AAAA,UAEJ,GAAG;AAAA,UACH,qBAAC,QACC;AAAA,iCAAC,cACC;AAAA,kCAAC,aAAU,WAAU,aAClB,YAAE,6CAA6C,oBAAoB,GACtE;AAAA,cACA,oBAAC,OAAE,WAAU,iCACV,YAAE,iDAAiD,qEAAqE,GAC3H;AAAA,eACF;AAAA,YACA,oBAAC,eAAY,WAAU,aACpB,0BAAgB,IAAI,CAAC,SAAS,UAAU;AACvC,oBAAM,YAAY,aAAa,IAAI,OAAO;AAC1C,kBAAI,CAAC,UAAW,QAAO;AACvB,oBAAM,cAAc,UAAU,eAAe,UAAU;AACvD,oBAAM,QAAQ,MAAM,YAAY,OAAO,KAAK;AAC5C,oBAAM,eAAe,MAAM,KAAK;AAChC,oBAAM,kBAAkB,aAAa,SAAS,KAAK,iBAAiB;AACpE,qBACE,qBAAC,SAAkB,WAAU,mCAC3B;AAAA,qCAAC,SAAI,WAAU,6CACb;AAAA,uCAAC,SAAI,WAAU,gCACb;AAAA,wCAAC,WAAM,WAAU,yEACd,YAAE,yCAAyC,GAC9C;AAAA,oBACA,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,wBAAC;AAAA;AAAA,0BACC;AAAA,0BACA,UAAU,CAAC,UAAU,cAAc,SAAS,MAAM,OAAO,KAAK;AAAA,0BAC9D;AAAA,0BACA,UAAU;AAAA,0BACV,WAAU;AAAA;AAAA,sBACZ;AAAA,sBACC,kBACC;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAK;AAAA,0BACL,SAAQ;AAAA,0BACR,MAAK;AAAA,0BACL,SAAS,MAAM,cAAc,SAAS,EAAE;AAAA,0BACxC,UAAU;AAAA,0BACV,cAAY,EAAE,2CAA2C,kBAAkB;AAAA,0BAC3E,OAAO,EAAE,2CAA2C,kBAAkB;AAAA,0BAEtE,8BAAC,aAAU,WAAU,YAAW;AAAA;AAAA,sBAClC,IACE;AAAA,uBACN;AAAA,oBACC,kBACC,qBAAC,OAAE,WAAU,iCACV;AAAA,wBAAE,wCAAwC,UAAU;AAAA,sBAAG;AAAA,sBACxD,oBAAC,UAAK,WAAU,kCAAkC,uBAAY;AAAA,uBAChE,IACE;AAAA,qBACN;AAAA,kBACA,qBAAC,SAAI,WAAU,8CACb;AAAA;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAK;AAAA,wBACL,SAAQ;AAAA,wBACR,MAAK;AAAA,wBACL,WAAU;AAAA,wBACV,SAAS,MAAM,UAAU,SAAS,EAAE;AAAA,wBACpC,UAAU,UAAU,KAAK;AAAA,wBACzB,cAAY,EAAE,qCAAqC;AAAA,wBACnD,OAAO,EAAE,qCAAqC;AAAA,wBAE9C,8BAAC,aAAU,WAAU,UAAS;AAAA;AAAA,oBAChC;AAAA,oBACA;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAK;AAAA,wBACL,SAAQ;AAAA,wBACR,MAAK;AAAA,wBACL,WAAU;AAAA,wBACV,SAAS,MAAM,UAAU,SAAS,CAAC;AAAA,wBACnC,UAAU,UAAU,cAAc,KAAK;AAAA,wBACvC,cAAY,EAAE,uCAAuC;AAAA,wBACrD,OAAO,EAAE,uCAAuC;AAAA,wBAEhD,8BAAC,eAAY,WAAU,UAAS;AAAA;AAAA,oBAClC;AAAA,qBACF;AAAA,mBACF;AAAA,gBACA,oBAAC,SAAI,WAAU,0BACb;AAAA,kBAAC;AAAA;AAAA,oBACC,OAAO,UAAU;AAAA,oBACjB;AAAA,oBACA,QAAQ;AAAA,oBACR,eAAe;AAAA,oBACf,gBAAgB;AAAA,oBAChB;AAAA,oBACA,UAAU;AAAA,oBACV,SAAS;AAAA,oBACT,WAAW,kBAAkB,SAAS,UAAU,MAAM,IAAI,CAAC,SAAS,eAAe,IAAI,CAAC,CAAC;AAAA;AAAA,gBAC3F,GACF;AAAA,mBA1EQ,OA2EV;AAAA,YAEJ,CAAC,GACH;AAAA,aACF;AAAA,WACF;AAAA,QAEA,oBAAC,WAAM,WAAU,mBACf,8BAAC,SAAI,WAAU,gBACb,+BAAC,SAAI,WAAU,YACb;AAAA,8BAAC,UAAK,WAAU,+LACb,YAAE,wCAAwC,SAAS,GACtD;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,QAAQ;AAAA,cACR,aAAa,EAAE,wBAAwB,cAAc;AAAA,cACrD,iBAAe;AAAA;AAAA,UACjB;AAAA,WACF,GACF,GACF;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAEJ;AAeA,SAAS,QAAQ,EAAE,MAAM,OAAO,QAAQ,eAAe,gBAAgB,GAAG,OAAO,YAAY,iBAAiB,MAAM,GAAiB;AACnI,QAAM,UAAU,eAAe,IAAI;AACnC,QAAM,cAAc,KAAK,gBAAgB,KAAK;AAC9C,QAAM,QAAQ,MAAM,WAAW,OAAO,KAAK;AAC3C,QAAM,eAAe,MAAM,KAAK;AAChC,QAAM,aAAa,aAAa,SAAS,KAAK,iBAAiB;AAC/D,QAAM,SAAS,MAAM,cAAc,OAAO,MAAM;AAChD,QAAM,oBAAoB,UAAU;AACpC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO,QAAQ,EAAE,aAAa,KAAK,QAAQ,GAAG,IAAI;AAAA,MAEjD;AAAA,uBAAe,QAAQ,IAAI,oBAAC,UAAK,WAAU,gBAAe,eAAW,MAAC,IAAK;AAAA,QAC5E,qBAAC,SAAI,WAAW,wCAAwC,oBAAoB,eAAe,EAAE,IAC3F;AAAA,+BAAC,SAAI,WAAU,2BACb;AAAA,gCAAC,SAAI,WAAU,kBACb;AAAA,cAAC;AAAA;AAAA,gBACC;AAAA,gBACA,UAAU,CAAC,UAAU,cAAc,SAAS,MAAM,OAAO,KAAK;AAAA,gBAC9D;AAAA,gBACA,UAAU;AAAA;AAAA,YACZ,GACF;AAAA,YACC,aACC;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,SAAS,MAAM,cAAc,SAAS,EAAE;AAAA,gBACxC,UAAU;AAAA,gBACV,cAAY,EAAE,2CAA2C,kBAAkB;AAAA,gBAC3E,OAAO,EAAE,2CAA2C,kBAAkB;AAAA,gBAEtE,8BAAC,aAAU,WAAU,YAAW;AAAA;AAAA,YAClC,IACE;AAAA,aACN;AAAA,UACC,aACC,qBAAC,OAAE,WAAU,iCACV;AAAA,cAAE,wCAAwC,UAAU;AAAA,YAAG;AAAA,YACxD,oBAAC,UAAK,WAAU,kCAAkC,uBAAY;AAAA,aAChE,IACE;AAAA,WACN;AAAA,QACA,qBAAC,SAAI,WAAU,2CACZ;AAAA,mBACC,oBAAC,UAAK,WAAU,4HACb,YAAE,4CAA4C,QAAQ,GACzD,IACE;AAAA,UACJ;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,CAAC;AAAA,cACV,iBAAiB,CAAC,SAAS,eAAe,SAAS,SAAS,IAAI;AAAA,cAChE,UAAU,UAAU;AAAA,cACpB,cAAY,EAAE,uCAAuC;AAAA,cACrD,OAAO,iBAAiB,EAAE,iDAAiD,4CAAuC,IAAI;AAAA;AAAA,UACxH;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;AAIA,SAAS,gBAAgB,EAAE,IAAI,GAAG,SAAS,GAAyB;AAClE,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,YAAY,WAAW,YAAY,WAAW,YAAY,YAAY,oBAAoB,IAAI,YAAY,EAAE,GAAG,CAAC;AACxH,QAAM,QAA6B;AAAA,IACjC,WAAW,IAAI,UAAU,SAAS,SAAS;AAAA,IAC3C;AAAA,IACA,SAAS,aAAa,MAAM;AAAA,EAC9B;AACA,QAAM,aACJ;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,MAAK;AAAA,MACL,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,WAAU;AAAA,MACV,cAAY,EAAE,8CAA8C,iBAAiB;AAAA,MAC7E,UAAU,SAAS;AAAA,MAClB,GAAG;AAAA,MACH,GAAG;AAAA,MAEJ,8BAAC,gBAAa,WAAU,UAAS;AAAA;AAAA,EACnC;AAEF,SACE,oBAAC,SAAI,KAAK,YAAY,OACpB,8BAAC,WAAS,GAAG,UAAU,YAAwB,GACjD;AAEJ;AAgBA,SAAS,SAAS;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AACnB,GAAkB;AAChB,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,0BAA0B,CAAC,MAAmB,iBAClD,KAAK,YAAY,KAAK,SAAS,SAAS,IACtC;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,gBAAgB;AAAA;AAAA,EAClB,IACE;AAGN,MAAI,UAAU,KAAK,YAAY,WAAW,WAAW;AACnD,UAAM,UAAU,eAAe,OAAO,gBAAgB,MAAM,YAAY,QAAQ,CAAC;AACjF,UAAM,MAAM,QAAQ,IAAI,CAAC,SAAS,eAAe,IAAI,CAAC;AACtD,WACE,oBAAC,cAAW,SAAkB,oBAAoB,eAAe,WAC/D,8BAAC,mBAAgB,OAAO,KAAK,UAAU,6BACpC,kBAAQ,IAAI,CAAC,SAAS;AACrB,YAAM,UAAU,eAAe,IAAI;AACnC,YAAM,YAAY,MAAM,cAAc,OAAO,MAAM;AACnD,aACE,qBAAC,MAAM,UAAN,EACC;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF;AAAA,QACC,wBAAwB,MAAM,kBAAkB,SAAS;AAAA,WAZvC,OAarB;AAAA,IAEJ,CAAC,GACH,GACF;AAAA,EAEJ;AAGA,SACE,gCACG,gBAAM,IAAI,CAAC,SAAS;AACnB,UAAM,UAAU,eAAe,IAAI;AACnC,UAAM,YAAY,MAAM,cAAc,OAAO,MAAM;AACnD,WACE,qBAAC,MAAM,UAAN,EACC;AAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF;AAAA,MACC,wBAAwB,MAAM,kBAAkB,SAAS;AAAA,SAXvC,OAYrB;AAAA,EAEJ,CAAC,GACH;AAEJ;AAEA,SAAS,mBAAmB,EAAE,KAAK,GAA0B;AAC3D,MAAI,KAAK,KAAM,QAAO,gCAAG,eAAK,MAAK;AACnC,MAAI,KAAK,UAAU;AACjB,UAAM,WAAW,oBAAoB,KAAK,QAAQ;AAClD,QAAI,SAAU,QAAO,gCAAG,oBAAS;AAAA,EACnC;AACA,MAAI,KAAK,YAAY;AACnB,WAAO,oBAAC,UAAK,eAAY,QAAO,yBAAyB,EAAE,QAAQ,KAAK,WAAW,GAAG;AAAA,EACxF;AAEA,SACE,oBAAC,SAAI,OAAM,MAAK,QAAO,MAAK,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAC5F,8BAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI,GAChC;AAEJ;AAEA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,IAAI,KAAK;AAGf,QAAM,YAAY,MAAM,QAAuB,MAAM;AACnD,QAAI,CAAC,gBAAiB,QAAO;AAC7B,eAAW,SAAS,QAAQ;AAC1B,iBAAW,QAAQ,MAAM,OAAO;AAC9B,YAAI,KAAK,WAAW,KAAM;AAC1B,eAAO,eAAe,IAAI;AAAA,MAC5B;AAAA,IACF;AACA,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,eAAe,CAAC;AAE5B,SACE,oBAAC,SAAI,WAAU,gFAGb,+BAAC,SAAI,WAAU,iCAEb;AAAA,wBAAC,SAAI,WAAU,QACb,+BAAC,SAAI,WAAU,0CACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,KAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,WAAU;AAAA;AAAA,MACZ;AAAA,MACA,oBAAC,UAAK,WAAU,gDAAgD,uBAAY;AAAA,OAC9E,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,yGACb;AAAA,0BAAC,UAAO,WAAU,yCAAwC,eAAW,MAAC;AAAA,MACtE,oBAAC,UAAK,WAAU,4DACb,YAAE,yDAAyD,WAAW,GACzE;AAAA,OACF;AAAA,IACC,OAAO,WAAW,IACjB,oBAAC,OAAE,WAAU,sCACV,YAAE,6CAA6C,uBAAuB,GACzE,IAEA,oBAAC,SAAI,WAAU,uBACZ,iBAAO,IAAI,CAAC,OAAO,OAAO;AACzB,YAAM,eAAe,MAAM,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,IAAI;AACtE,UAAI,aAAa,WAAW,EAAG,QAAO;AACtC,aACE,qBAAC,SACC;AAAA,4BAAC,SAAI,WAAU,+GACb,8BAAC,UAAM,gBAAM,MAAK,GACpB;AAAA,QACA,oBAAC,SAAI,WAAU,uBACZ,uBAAa,IAAI,CAAC,SAAS;AAC1B,gBAAM,UAAU,eAAe,IAAI;AACnC,gBAAM,WAAW,cAAc;AAC/B,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,WAAW,2FACT,WAAW,6BAA6B,uBAC1C;AAAA,cAEC;AAAA,2BACC;AAAA,kBAAC;AAAA;AAAA,oBACC,eAAW;AAAA,oBACX,WAAU;AAAA;AAAA,gBACZ,IACE;AAAA,gBACJ,oBAAC,UAAK,WAAU,6CACd,8BAAC,sBAAmB,MAAY,GAClC;AAAA,gBACA,oBAAC,UAAK,WAAU,YAAY,eAAK,OAAM;AAAA;AAAA;AAAA,YAdlC;AAAA,UAeP;AAAA,QAEJ,CAAC,GACH;AAAA,QACC,KAAK,OAAO,SAAS,IAAI,oBAAC,SAAI,WAAU,6BAA4B,IAAK;AAAA,WA7BlE,gBAAgB,KAAK,CA8B/B;AAAA,IAEJ,CAAC,GACH;AAAA,KAEJ,GACF;AAEJ;AAEA,IAAO,qCAAQ;",
|
|
6
6
|
"names": ["draft"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.6.1-develop.
|
|
3
|
+
"version": "0.6.1-develop.3060.d6cca7ade1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -140,12 +140,12 @@
|
|
|
140
140
|
"remark-gfm": "^4.0.1"
|
|
141
141
|
},
|
|
142
142
|
"peerDependencies": {
|
|
143
|
-
"@open-mercato/shared": "0.6.1-develop.
|
|
143
|
+
"@open-mercato/shared": "0.6.1-develop.3060.d6cca7ade1",
|
|
144
144
|
"react": ">=18.0.0",
|
|
145
145
|
"react-dom": ">=18.0.0"
|
|
146
146
|
},
|
|
147
147
|
"devDependencies": {
|
|
148
|
-
"@open-mercato/shared": "0.6.1-develop.
|
|
148
|
+
"@open-mercato/shared": "0.6.1-develop.3060.d6cca7ade1",
|
|
149
149
|
"@testing-library/dom": "^10.4.1",
|
|
150
150
|
"@testing-library/jest-dom": "^6.9.1",
|
|
151
151
|
"@testing-library/react": "^16.3.1",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import * as React from 'react'
|
|
6
|
-
import { screen, waitFor } from '@testing-library/react'
|
|
6
|
+
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
|
|
7
7
|
import { SidebarCustomizationEditor } from '../sidebar/SidebarCustomizationEditor'
|
|
8
8
|
import { renderWithProviders } from '@open-mercato/shared/lib/testing/renderWithProviders'
|
|
9
9
|
|
|
@@ -178,6 +178,86 @@ describe('SidebarCustomizationEditor', () => {
|
|
|
178
178
|
expect(screen.getByText('Admin')).toBeInTheDocument()
|
|
179
179
|
})
|
|
180
180
|
|
|
181
|
+
it('shows duplicate-name error inline inside the add-variant dialog (not on the page behind it)', async () => {
|
|
182
|
+
const duplicateMessage = 'A variant with this name already exists. Choose a different name.'
|
|
183
|
+
|
|
184
|
+
apiCallMock.mockImplementation((url: string, init?: RequestInit) => {
|
|
185
|
+
if (init?.method === 'POST' && /\/api\/auth\/sidebar\/variants$/.test(url)) {
|
|
186
|
+
return Promise.resolve({
|
|
187
|
+
ok: false,
|
|
188
|
+
status: 409,
|
|
189
|
+
result: { error: duplicateMessage, code: 'duplicate_name' },
|
|
190
|
+
response: {},
|
|
191
|
+
cacheStatus: null,
|
|
192
|
+
} as ApiCallResult<unknown>)
|
|
193
|
+
}
|
|
194
|
+
if (/\/api\/auth\/sidebar\/variants/.test(url)) {
|
|
195
|
+
return Promise.resolve(okResult({ locale: 'en', variants: [] }))
|
|
196
|
+
}
|
|
197
|
+
if (/\/api\/auth\/sidebar\/preferences/.test(url)) {
|
|
198
|
+
return Promise.resolve(okResult({ canApplyToRoles: false, roles: [] }))
|
|
199
|
+
}
|
|
200
|
+
throw new Error(`apiCall mock: no response configured for ${url}`)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
renderWithProviders(<SidebarCustomizationEditor groups={fakeGroups} />, { dict })
|
|
204
|
+
|
|
205
|
+
const openDialogButton = await screen.findByRole('button', { name: /Create new/ })
|
|
206
|
+
fireEvent.click(openDialogButton)
|
|
207
|
+
|
|
208
|
+
const dialog = await screen.findByRole('dialog')
|
|
209
|
+
const nameInput = within(dialog).getByPlaceholderText('My preferences')
|
|
210
|
+
fireEvent.change(nameInput, { target: { value: 'My Variant' } })
|
|
211
|
+
|
|
212
|
+
fireEvent.click(within(dialog).getByRole('button', { name: /Create variant/ }))
|
|
213
|
+
|
|
214
|
+
const dialogAlert = await within(dialog).findByRole('alert')
|
|
215
|
+
expect(dialogAlert).toHaveTextContent(duplicateMessage)
|
|
216
|
+
expect(within(dialog).getByRole('textbox')).toHaveAttribute('aria-invalid', 'true')
|
|
217
|
+
|
|
218
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
|
219
|
+
|
|
220
|
+
expect(screen.getAllByText(duplicateMessage)).toHaveLength(1)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('clears the inline dialog error when the user edits the name', async () => {
|
|
224
|
+
const duplicateMessage = 'A variant with this name already exists. Choose a different name.'
|
|
225
|
+
|
|
226
|
+
apiCallMock.mockImplementation((url: string, init?: RequestInit) => {
|
|
227
|
+
if (init?.method === 'POST' && /\/api\/auth\/sidebar\/variants$/.test(url)) {
|
|
228
|
+
return Promise.resolve({
|
|
229
|
+
ok: false,
|
|
230
|
+
status: 409,
|
|
231
|
+
result: { error: duplicateMessage, code: 'duplicate_name' },
|
|
232
|
+
response: {},
|
|
233
|
+
cacheStatus: null,
|
|
234
|
+
} as ApiCallResult<unknown>)
|
|
235
|
+
}
|
|
236
|
+
if (/\/api\/auth\/sidebar\/variants/.test(url)) {
|
|
237
|
+
return Promise.resolve(okResult({ locale: 'en', variants: [] }))
|
|
238
|
+
}
|
|
239
|
+
if (/\/api\/auth\/sidebar\/preferences/.test(url)) {
|
|
240
|
+
return Promise.resolve(okResult({ canApplyToRoles: false, roles: [] }))
|
|
241
|
+
}
|
|
242
|
+
throw new Error(`apiCall mock: no response configured for ${url}`)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
renderWithProviders(<SidebarCustomizationEditor groups={fakeGroups} />, { dict })
|
|
246
|
+
|
|
247
|
+
const openDialogButton = await screen.findByRole('button', { name: /Create new/ })
|
|
248
|
+
fireEvent.click(openDialogButton)
|
|
249
|
+
const dialog = await screen.findByRole('dialog')
|
|
250
|
+
const nameInput = within(dialog).getByPlaceholderText('My preferences')
|
|
251
|
+
fireEvent.change(nameInput, { target: { value: 'My Variant' } })
|
|
252
|
+
fireEvent.click(within(dialog).getByRole('button', { name: /Create variant/ }))
|
|
253
|
+
|
|
254
|
+
await within(dialog).findByRole('alert')
|
|
255
|
+
|
|
256
|
+
fireEvent.change(nameInput, { target: { value: 'My Variant 2' } })
|
|
257
|
+
|
|
258
|
+
expect(within(dialog).queryByRole('alert')).not.toBeInTheDocument()
|
|
259
|
+
})
|
|
260
|
+
|
|
181
261
|
it('does not render the role-apply target list when canApplyToRoles is false', async () => {
|
|
182
262
|
setApiCallSequence([
|
|
183
263
|
{ url: /\/api\/auth\/sidebar\/variants/, response: okResult({ locale: 'en', variants: [] }) },
|
|
@@ -228,6 +228,7 @@ export function SidebarCustomizationEditor({
|
|
|
228
228
|
const [canApplyToRoles, setCanApplyToRoles] = React.useState(false)
|
|
229
229
|
const [addDialogOpen, setAddDialogOpen] = React.useState(false)
|
|
230
230
|
const [addDialogName, setAddDialogName] = React.useState('')
|
|
231
|
+
const [addDialogError, setAddDialogError] = React.useState<string | null>(null)
|
|
231
232
|
const baseSnapshotRef = React.useRef<SidebarGroup[] | null>(null)
|
|
232
233
|
const hasInitializedRef = React.useRef(false)
|
|
233
234
|
|
|
@@ -366,8 +367,11 @@ export function SidebarCustomizationEditor({
|
|
|
366
367
|
setDirty(true)
|
|
367
368
|
}, [])
|
|
368
369
|
|
|
369
|
-
const createNewVariant = React.useCallback(async (
|
|
370
|
-
|
|
370
|
+
const createNewVariant = React.useCallback(async (
|
|
371
|
+
proposedName?: string,
|
|
372
|
+
options: { suppressPageError?: boolean } = {},
|
|
373
|
+
): Promise<{ ok: boolean; error?: string }> => {
|
|
374
|
+
if (saving || deleting) return { ok: false }
|
|
371
375
|
if (dirty && selectedVariantId !== null) {
|
|
372
376
|
const proceed = await confirmDialog({
|
|
373
377
|
title: t('appShell.sidebarCustomizationSwitchConfirmTitle', 'Discard unsaved changes?'),
|
|
@@ -376,10 +380,10 @@ export function SidebarCustomizationEditor({
|
|
|
376
380
|
cancelText: t('common.cancel', 'Cancel'),
|
|
377
381
|
variant: 'destructive',
|
|
378
382
|
})
|
|
379
|
-
if (!proceed) return false
|
|
383
|
+
if (!proceed) return { ok: false }
|
|
380
384
|
}
|
|
381
385
|
setSaving(true)
|
|
382
|
-
setError(null)
|
|
386
|
+
if (!options.suppressPageError) setError(null)
|
|
383
387
|
try {
|
|
384
388
|
const baseSnapshot = baseSnapshotRef.current ?? buildBaseSnapshot()
|
|
385
389
|
baseSnapshotRef.current = baseSnapshot
|
|
@@ -401,8 +405,9 @@ export function SidebarCustomizationEditor({
|
|
|
401
405
|
mutationPayload: { name: trimmed.length > 0 ? trimmed : null },
|
|
402
406
|
})
|
|
403
407
|
if (!call.ok) {
|
|
404
|
-
|
|
405
|
-
|
|
408
|
+
const message = formatVariantApiError(call, t)
|
|
409
|
+
if (!options.suppressPageError) setError(message)
|
|
410
|
+
return { ok: false, error: message }
|
|
406
411
|
}
|
|
407
412
|
const created = call.result?.variant ?? null
|
|
408
413
|
// Trust POST response as authoritative; refetch in background for any side-effects
|
|
@@ -424,11 +429,12 @@ export function SidebarCustomizationEditor({
|
|
|
424
429
|
selectVariantInternal(fresh, nextList)
|
|
425
430
|
}
|
|
426
431
|
flash(t('appShell.sidebarCustomizationVariantCreated', 'Variant created.'), 'success')
|
|
427
|
-
return true
|
|
432
|
+
return { ok: true }
|
|
428
433
|
} catch (err) {
|
|
429
434
|
console.error('Failed to create sidebar variant', err)
|
|
430
|
-
|
|
431
|
-
|
|
435
|
+
const message = t('appShell.sidebarCustomizationSaveError')
|
|
436
|
+
if (!options.suppressPageError) setError(message)
|
|
437
|
+
return { ok: false, error: message }
|
|
432
438
|
} finally {
|
|
433
439
|
setSaving(false)
|
|
434
440
|
}
|
|
@@ -552,12 +558,16 @@ export function SidebarCustomizationEditor({
|
|
|
552
558
|
}, [onCanceled])
|
|
553
559
|
|
|
554
560
|
const submitAddDialog = React.useCallback(async () => {
|
|
555
|
-
|
|
556
|
-
|
|
561
|
+
setAddDialogError(null)
|
|
562
|
+
const result = await createNewVariant(addDialogName, { suppressPageError: true })
|
|
563
|
+
if (result.ok) {
|
|
557
564
|
setAddDialogOpen(false)
|
|
558
565
|
setAddDialogName('')
|
|
566
|
+
setAddDialogError(null)
|
|
567
|
+
return
|
|
559
568
|
}
|
|
560
|
-
|
|
569
|
+
setAddDialogError(result.error ?? t('appShell.sidebarCustomizationSaveError'))
|
|
570
|
+
}, [createNewVariant, addDialogName, t])
|
|
561
571
|
|
|
562
572
|
const sanitizeSettingsPayload = React.useCallback(() => {
|
|
563
573
|
if (!draft || !baseSnapshotRef.current) return null
|
|
@@ -857,6 +867,7 @@ export function SidebarCustomizationEditor({
|
|
|
857
867
|
if (!next) {
|
|
858
868
|
setAddDialogOpen(false)
|
|
859
869
|
setAddDialogName('')
|
|
870
|
+
setAddDialogError(null)
|
|
860
871
|
}
|
|
861
872
|
}}
|
|
862
873
|
>
|
|
@@ -876,7 +887,10 @@ export function SidebarCustomizationEditor({
|
|
|
876
887
|
<Input
|
|
877
888
|
autoFocus
|
|
878
889
|
value={addDialogName}
|
|
879
|
-
onChange={(event) =>
|
|
890
|
+
onChange={(event) => {
|
|
891
|
+
setAddDialogName(event.target.value)
|
|
892
|
+
if (addDialogError) setAddDialogError(null)
|
|
893
|
+
}}
|
|
880
894
|
onKeyDown={(event) => {
|
|
881
895
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
882
896
|
event.preventDefault()
|
|
@@ -885,7 +899,18 @@ export function SidebarCustomizationEditor({
|
|
|
885
899
|
}}
|
|
886
900
|
placeholder={t('appShell.sidebarCustomizationVariantNamePlaceholder', 'My preferences')}
|
|
887
901
|
disabled={saving}
|
|
902
|
+
aria-invalid={addDialogError ? true : undefined}
|
|
903
|
+
aria-describedby={addDialogError ? 'sidebar-add-variant-error' : undefined}
|
|
888
904
|
/>
|
|
905
|
+
{addDialogError ? (
|
|
906
|
+
<p
|
|
907
|
+
id="sidebar-add-variant-error"
|
|
908
|
+
role="alert"
|
|
909
|
+
className="text-xs text-destructive"
|
|
910
|
+
>
|
|
911
|
+
{addDialogError}
|
|
912
|
+
</p>
|
|
913
|
+
) : null}
|
|
889
914
|
</div>
|
|
890
915
|
<DialogFooter className="mt-2">
|
|
891
916
|
<Button
|
|
@@ -894,6 +919,7 @@ export function SidebarCustomizationEditor({
|
|
|
894
919
|
onClick={() => {
|
|
895
920
|
setAddDialogOpen(false)
|
|
896
921
|
setAddDialogName('')
|
|
922
|
+
setAddDialogError(null)
|
|
897
923
|
}}
|
|
898
924
|
disabled={saving}
|
|
899
925
|
>
|
|
@@ -986,6 +1012,7 @@ export function SidebarCustomizationEditor({
|
|
|
986
1012
|
type="button"
|
|
987
1013
|
onClick={() => {
|
|
988
1014
|
setAddDialogName('')
|
|
1015
|
+
setAddDialogError(null)
|
|
989
1016
|
setAddDialogOpen(true)
|
|
990
1017
|
}}
|
|
991
1018
|
disabled={isBusy}
|