@plumile/backoffice-react 0.1.87 → 0.1.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +6 -6
  2. package/lib/esm/AcceptInvitationScreen-dnOvRx4Z.js.map +1 -1
  3. package/lib/esm/BackofficeAcceptInvitationPage-CGht2ka0.js.map +1 -1
  4. package/lib/esm/BackofficeDashboardPage-h1OWb_rV.js.map +1 -1
  5. package/lib/esm/BackofficeEntityActionFormDialog-V4QXnvpy.js.map +1 -1
  6. package/lib/esm/BackofficeEntityDetailLayoutPage-DXjRqvcZ.js.map +1 -1
  7. package/lib/esm/BackofficeEntityDetailPage-CIyGKwVP.js.map +1 -1
  8. package/lib/esm/BackofficeEntityListPage-DmZozSNk.js.map +1 -1
  9. package/lib/esm/BackofficeLayoutPage-DtFDn_nU.js.map +1 -1
  10. package/lib/esm/BackofficeLoginPage-BvOPqbKO.js.map +1 -1
  11. package/lib/esm/BackofficePasswordResetCompletePage-ZLhghfhC.js.map +1 -1
  12. package/lib/esm/BackofficePasswordResetRequestPage-BLNHQD79.js.map +1 -1
  13. package/lib/esm/BackofficeRightPageLayout-DZQvIHnj.js.map +1 -1
  14. package/lib/esm/BackofficeVerifyEmailPage-BSTtLXdx.js.map +1 -1
  15. package/lib/esm/EntityFilterValue-B5ZGHO_u.js.map +1 -1
  16. package/lib/esm/EntityIdPickerDialog-DbTnDU4v.js.map +1 -1
  17. package/lib/esm/LazyBackofficeEntityActionFormDialog-BE3wVfU6.js.map +1 -1
  18. package/lib/esm/PasswordResetCompleteScreen-B0P_tZg2.js.map +1 -1
  19. package/lib/esm/VerifyEmailScreen--9lxOGlW.js.map +1 -1
  20. package/lib/esm/backoffice-react.js +33 -35
  21. package/lib/esm/backoffice-react.js.map +1 -1
  22. package/lib/esm/buildBreadcrumbs-CqF9Nh6x.js.map +1 -1
  23. package/lib/esm/environment-BXoBq_6e.js.map +1 -1
  24. package/lib/esm/mutationResult-CcQMY13J.js.map +1 -1
  25. package/lib/esm/pageResolution-hAQA5C6S.js.map +1 -1
  26. package/lib/esm/sidebarUtils-CuwJ_3mD.js.map +1 -1
  27. package/lib/esm/synchronizeAuthStatusQuery-By_lNCnP.js.map +1 -1
  28. package/lib/esm/useAuth-OVPPa9bO.js.map +1 -1
  29. package/lib/esm/useBackofficeAuth-BvEoEqnB.js.map +1 -1
  30. package/lib/esm/useBackofficeListUrlState-D4fx5O7u.js.map +1 -1
  31. package/lib/types/components/backoffice/scaffolds/BackofficeListFilterContext.d.ts.map +1 -1
  32. package/lib/types/hooks/useConditionalSubscription.d.ts.map +1 -1
  33. package/lib/types/pages/BackofficeEntityDetailPage.d.ts.map +1 -1
  34. package/lib/types/router/createBackofficeRoutes.d.ts.map +1 -1
  35. package/package.json +12 -12
@@ -1 +1 @@
1
- {"version":3,"file":"BackofficeEntityListPage-DmZozSNk.js","names":[],"sources":["../../src/components/backoffice/list/RowFlagsCell.css.ts","../../src/components/backoffice/list/RowFlagsCell.tsx","../../src/pages/backofficeEntityListPage.css.ts","../../src/pages/BackofficeEntityListPage.helpers.tsx","../../src/pages/BackofficeEntityListPage.tsx"],"sourcesContent":["import { style } from '@vanilla-extract/css';\n\nexport const rowFlagsColumnCell = style({\n justifyContent: 'center',\n padding: '0 4px',\n});\n\nexport const container = style({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 6,\n width: '100%',\n});\n\nexport const flag = style({\n display: 'grid',\n placeItems: 'center',\n width: 24,\n height: 24,\n});\n","import { useMemo, type JSX, type ReactNode } from 'react';\nimport type { TFunction } from 'i18next';\n\nimport type { BackofficeRowFlagSpec } from '@plumile/backoffice-core/types.js';\n\nimport * as styles from './RowFlagsCell.css.js';\n\nexport type RowFlagsCellProps<Row> = {\n row: Row;\n flags: readonly BackofficeRowFlagSpec<Row>[];\n tApp: TFunction;\n};\n\n/**\n * Renders row-level flags (icons) in a dedicated compact column.\n */\nexport const RowFlagsCell = <Row,>({\n row,\n flags,\n tApp,\n}: RowFlagsCellProps<Row>): JSX.Element => {\n const resolved = useMemo(() => {\n const out: { id: string; node: ReactNode; label: string }[] = [];\n for (const flag of flags) {\n const node = flag.render(row) as ReactNode;\n if (node != null && node !== '' && node !== false) {\n out.push({ id: flag.id, node, label: flag.label(row)(tApp) });\n }\n }\n return out;\n }, [flags, row, tApp]);\n\n const ariaLabel = resolved\n .map((entry) => {\n return entry.label.trim();\n })\n .filter((label) => {\n return label !== '';\n })\n .join(', ');\n\n let ariaLabelProp: string | undefined;\n if (ariaLabel !== '') {\n ariaLabelProp = ariaLabel;\n }\n\n return (\n <div className={styles.container} aria-label={ariaLabelProp}>\n {resolved.map((entry) => {\n return (\n <span\n key={entry.id}\n className={styles.flag}\n title={entry.label}\n role=\"img\"\n aria-label={entry.label}\n >\n {entry.node}\n </span>\n );\n })}\n </div>\n );\n};\n","import { style } from '@vanilla-extract/css';\nimport { sprinkles } from '@plumile/ui';\n\nexport const headerActions = sprinkles({\n display: 'flex',\n alignItems: 'center',\n gap: 2,\n flexWrap: 'wrap',\n});\n\nexport const actionsColumnCell = style({\n justifyContent: 'flex-end',\n});\n\nexport const actionTrigger = style({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '2rem',\n height: '2rem',\n flexShrink: 0,\n});\n","import type { ComponentProps, JSX } from 'react';\n\nimport type {\n BackofficeEntityFormMutationActionSpec,\n BackofficeEntityRouteActionSpec,\n BackofficeListActionSpec,\n BackofficeRecordListConfig,\n BackofficeResolvedListFacetConfig,\n I18nLabel,\n} from '@plumile/backoffice-core/types.js';\nimport type { Button, DataTableColumn } from '@plumile/ui';\nimport type { TFunction } from 'i18next';\n\nimport type { BackofficeSizedDataTableColumn } from '../components/backoffice/columns/buildDataTableColumns.js';\n\ntype ButtonVariant = ComponentProps<typeof Button>['variant'];\n\nconst ROW_FLAGS_SLOT_PX = 24;\nconst ROW_FLAGS_GAP_PX = 6;\nconst ROW_FLAGS_PADDING_X_PX = 4;\n\nconst ACTIONS_SLOT_PX = 32;\nconst ACTIONS_GAP_PX = 8;\nconst ACTIONS_PADDING_X_PX = 12;\nconst ACTIONS_SAFETY_PX = 4;\n\nconst listColumnTrackBySize: Record<\n BackofficeSizedDataTableColumn<unknown>['size'],\n string\n> = {\n xs: 'minmax(56px, 76px)',\n s: 'minmax(88px, 120px)',\n m: 'minmax(120px, 168px)',\n l: 'minmax(160px, 220px)',\n xl: 'minmax(220px, 300px)',\n fluid: 'minmax(0, 1fr)',\n};\n\nexport const computeRowFlagsColumnWidthPx = (flagCount: number): number => {\n if (flagCount <= 0) {\n return 0;\n }\n return (\n ROW_FLAGS_PADDING_X_PX * 2 +\n ROW_FLAGS_SLOT_PX * flagCount +\n ROW_FLAGS_GAP_PX * Math.max(0, flagCount - 1)\n );\n};\n\nexport const computeActionsColumnWidthPx = (actionCount: number): number => {\n if (actionCount <= 0) {\n return 0;\n }\n return (\n ACTIONS_SAFETY_PX +\n ACTIONS_PADDING_X_PX * 2 +\n ACTIONS_SLOT_PX * actionCount +\n ACTIONS_GAP_PX * Math.max(0, actionCount - 1)\n );\n};\n\nexport const resolveTrackBySize = (\n column: DataTableColumn<unknown>,\n fallback: string,\n): string => {\n if ('size' in column && typeof column.size === 'string') {\n return listColumnTrackBySize[\n column.size as BackofficeSizedDataTableColumn<unknown>['size']\n ];\n }\n return fallback;\n};\n\nexport const isRouteAction = (\n action: BackofficeListActionSpec,\n): action is BackofficeEntityRouteActionSpec<null> => {\n return action.kind === 'route';\n};\n\nexport const isFormMutationAction = (\n action: BackofficeListActionSpec,\n): action is BackofficeEntityFormMutationActionSpec<null> => {\n return action.kind === 'formMutation';\n};\n\nexport const resolveActionVariant = (\n actionVariant: ButtonVariant | undefined,\n index: number,\n): Exclude<ButtonVariant, undefined> => {\n if (actionVariant != null) {\n return actionVariant;\n }\n if (index === 0) {\n return 'primary';\n }\n return 'secondary';\n};\n\nexport const resolveRowId = (row: unknown): string | null => {\n if (row == null || typeof row !== 'object') {\n return null;\n }\n if (!('id' in row)) {\n return null;\n }\n const value = (row as { id?: unknown }).id;\n if (typeof value !== 'string') {\n return null;\n }\n const trimmed = value.trim();\n if (trimmed === '') {\n return null;\n }\n return trimmed;\n};\n\ntype RecordListConfig = BackofficeResolvedListFacetConfig & {\n list: BackofficeRecordListConfig<\n Record<string, unknown>,\n string,\n unknown,\n unknown,\n unknown,\n unknown,\n Record<string, unknown>\n >;\n};\n\nexport const isRecordListConfig = (\n config: BackofficeResolvedListFacetConfig,\n): config is RecordListConfig => {\n return config.list.kind === 'records';\n};\n\nexport const resolveLabel = (label: I18nLabel, tApp: TFunction): string => {\n return label(tApp);\n};\n\nexport const buildActionsColumn = (options: {\n ariaLabel: string;\n fallback: string;\n resolveDetailHref: (id: string) => string | null;\n className?: string;\n renderAction: (input: { href: string; ariaLabel: string }) => JSX.Element;\n}): DataTableColumn<unknown> => {\n const { ariaLabel, fallback, resolveDetailHref, className, renderAction } =\n options;\n\n return {\n id: 'actions',\n header: '',\n className,\n cell: (row) => {\n const id = resolveRowId(row);\n if (id == null) {\n return fallback;\n }\n const href = resolveDetailHref(id);\n if (href == null) {\n return fallback;\n }\n return renderAction({ href, ariaLabel });\n },\n };\n};\n","/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n type JSX,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type { TFunction } from 'i18next';\nimport { useTranslation } from 'react-i18next';\nimport {\n useFragment,\n usePaginationFragment,\n usePreloadedQuery,\n useRelayEnvironment,\n} from 'react-relay';\nimport { fetchQuery } from 'relay-runtime';\nimport {\n BACKOFFICE_LIST_DEFAULTS,\n BACKOFFICE_LIST_REFETCH_POLICY,\n} from '@plumile/backoffice-core/constants.js';\nimport { stableListVariablesKey } from '@plumile/backoffice-core/state/stableKey.js';\nimport type {\n BackofficeEntityManifestItem,\n BackofficePreparedListRoute,\n BackofficeResolvedListFacetConfig,\n BackofficeRowFlagSpec,\n} from '@plumile/backoffice-core/types.js';\nimport {\n Button,\n EyeSvg,\n LinkButton,\n TableCell,\n type DataTableColumn,\n type GetRowId,\n} from '@plumile/ui';\nimport { BackofficeEntityListScaffold } from '../components/backoffice/scaffolds/BackofficeEntityListScaffold.js';\nimport { LazyBackofficeEntityActionFormDialog } from '../components/backoffice/actions/LazyBackofficeEntityActionFormDialog.js';\nimport { buildDataTableColumns } from '../components/backoffice/columns/buildDataTableColumns.js';\nimport { RowFlagsCell } from '../components/backoffice/list/RowFlagsCell.js';\nimport { useBackofficeListUrlState } from '../hooks/useBackofficeListUrlState.js';\nimport { useBackofficeLoadMore } from '../hooks/useBackofficeLoadMore.js';\nimport { useBackofficeListRefetch } from '../hooks/useBackofficeListRefetch.js';\nimport { useBackofficeReactTranslation } from '../i18n/useBackofficeReactTranslation.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport * as pageStyles from './backofficeEntityListPage.css.js';\nimport { rowFlagsColumnCell } from '../components/backoffice/list/RowFlagsCell.css.js';\nimport { BackofficeRightPageLayout } from '../components/backoffice/layout/breadcrumb/BackofficeRightPageLayout.js';\nimport { buildEntityListBreadcrumb } from '../components/backoffice/layout/breadcrumb/buildBreadcrumbs.js';\nimport {\n buildActionsColumn,\n computeActionsColumnWidthPx,\n computeRowFlagsColumnWidthPx,\n isFormMutationAction,\n isRecordListConfig,\n isRouteAction,\n resolveLabel,\n resolveActionVariant,\n resolveTrackBySize,\n} from './BackofficeEntityListPage.helpers.js';\n\nexport type BackofficeEntityListPageProps = {\n entityManifest: BackofficeEntityManifestItem;\n config: BackofficeResolvedListFacetConfig;\n prepared: BackofficePreparedListRoute;\n};\n\ntype RecordFetchMode = 'append' | 'reset';\ntype RecordFetchInput = {\n where: Record<string, unknown> | null;\n sort: string | null;\n count: number;\n cursor: string | null;\n mode: RecordFetchMode;\n};\n\nconst RECORD_FETCH_POLICY = 'store-or-network' as const;\n\nconst applyListEdgeColumns = <Row,>(\n inputColumns: readonly DataTableColumn<Row>[],\n rowFlags: readonly BackofficeRowFlagSpec<Row>[] | undefined,\n actionCount: number,\n tApp: TFunction,\n): {\n columns: readonly DataTableColumn<Row>[];\n gridTemplateColumns?: string;\n} => {\n const hasFlags = rowFlags != null && rowFlags.length > 0;\n\n let columns = inputColumns;\n if (hasFlags) {\n const flagsColumn: DataTableColumn<Row> = {\n id: '__rowFlags',\n header: '',\n className: rowFlagsColumnCell,\n cell: (row) => {\n return <RowFlagsCell row={row} flags={rowFlags} tApp={tApp} />;\n },\n };\n\n // Ensure we never pick the flags column as \"primary\".\n const withFlags = [flagsColumn, ...inputColumns];\n const hasPrimary = withFlags.some((col) => {\n return col.isPrimary === true;\n });\n\n columns = withFlags;\n if (!hasPrimary) {\n columns = withFlags.map((col, index) => {\n if (index === 1) {\n return { ...col, isPrimary: true };\n }\n return col;\n });\n }\n }\n\n let flagCount = 0;\n if (hasFlags) {\n flagCount = rowFlags.length;\n }\n const flagsWidthPx = computeRowFlagsColumnWidthPx(flagCount);\n const actionsWidthPx = computeActionsColumnWidthPx(actionCount);\n\n // We always include the right-side \"actions\" column in list pages.\n let leftColumnCount = 0;\n if (hasFlags) {\n leftColumnCount = 1;\n }\n const middleCount = columns.length - leftColumnCount - 1;\n\n const middleTracks = columns\n .slice(leftColumnCount, leftColumnCount + Math.max(0, middleCount))\n .map((column) => {\n return resolveTrackBySize(column as DataTableColumn<unknown>, '1fr');\n })\n .join(' ');\n\n let gridTemplateColumns = '';\n if (hasFlags) {\n gridTemplateColumns = `${flagsWidthPx}px ${middleTracks} ${actionsWidthPx}px`;\n } else {\n gridTemplateColumns = `${middleTracks} ${actionsWidthPx}px`;\n }\n\n return { columns, gridTemplateColumns };\n};\n\nconst BackofficeEntityRecordListPage = ({\n config,\n prepared,\n}: BackofficeEntityListPageProps): JSX.Element | null => {\n const { t: tApp } = useTranslation();\n const { t } = useBackofficeReactTranslation();\n const { entities } = useBackofficeConfig();\n const [activeFormActionId, setActiveFormActionId] = useState<string | null>(\n null,\n );\n const environment = useRelayEnvironment();\n\n if (!isRecordListConfig(config)) {\n return null;\n }\n\n const listConfig = config.list;\n const listDefaults = config.listDefaults ??\n listConfig.defaultState ?? { where: null, sort: null };\n const queryData = usePreloadedQuery(listConfig.query, prepared.query);\n const fragmentData = useFragment(listConfig.fragment, queryData as never);\n\n const initialRows = useMemo(() => {\n return listConfig.getRows(fragmentData).map((row) => {\n return listConfig.toRow(row);\n });\n }, [fragmentData, listConfig]);\n\n const initialCursor = useMemo(() => {\n return listConfig.getNextCursor?.(queryData as never) ?? null;\n }, [listConfig, queryData]);\n\n const [rows, setRows] = useState(initialRows);\n const [nextCursor, setNextCursor] = useState(initialCursor);\n const [isLoadingMore, setIsLoadingMore] = useState(false);\n const [isRefreshing, setIsRefreshing] = useState(false);\n\n useEffect(() => {\n setRows(initialRows);\n setNextCursor(initialCursor);\n }, [initialCursor, initialRows]);\n\n const { columns, gridTemplateColumns } = useMemo((): {\n columns: readonly DataTableColumn<unknown>[];\n gridTemplateColumns?: string;\n } => {\n const baseColumns = buildDataTableColumns(listConfig.columns, {\n tApp,\n t,\n resolveEntityHref: (entityId, refId) => {\n const entityManifest = entities[entityId];\n if (entityManifest == null) {\n return null;\n }\n return entityManifest.routes.detail(refId);\n },\n });\n const actionsColumn = buildActionsColumn({\n ariaLabel: t('actions.view'),\n fallback: t('common.notAvailable'),\n className: pageStyles.actionsColumnCell,\n resolveDetailHref: (id) => {\n return config.routes.detail(id);\n },\n renderAction: ({ href, ariaLabel }) => {\n return (\n <TableCell.Actions>\n <span className={pageStyles.actionTrigger} title={ariaLabel}>\n <LinkButton\n to={href}\n variant=\"icon\"\n size=\"small\"\n aria-label={ariaLabel}\n >\n <EyeSvg width={16} height={16} />\n </LinkButton>\n </span>\n </TableCell.Actions>\n );\n },\n });\n const allColumns = [...baseColumns, actionsColumn];\n return applyListEdgeColumns(allColumns, listConfig.rowFlags, 1, tApp);\n }, [\n config.routes,\n listConfig.columns,\n listConfig.rowFlags,\n entities,\n t,\n tApp,\n ]);\n\n const getRowId = useCallback<GetRowId<unknown>>(\n (row) => {\n return listConfig.getRowId(row);\n },\n [listConfig],\n );\n\n const { state, pushState } = useBackofficeListUrlState(config);\n const resolvedSort = state.sort ?? listDefaults.sort;\n const { pageSize } = BACKOFFICE_LIST_DEFAULTS;\n\n const buildVariables = useCallback(\n (input: {\n where: Record<string, unknown> | null;\n sort: string | null;\n count: number;\n cursor: string | null;\n }) => {\n if (listConfig.buildVariables != null) {\n return listConfig.buildVariables(input as never);\n }\n return input;\n },\n [listConfig],\n );\n\n const requestIdRef = useRef(0);\n const lastRefetchKeyRef = useRef(\n stableListVariablesKey({\n where: listDefaults.where,\n sort: listDefaults.sort,\n count: pageSize,\n }),\n );\n\n const runFetch = useCallback(\n async (input: RecordFetchInput) => {\n const requestId = requestIdRef.current + 1;\n requestIdRef.current = requestId;\n\n if (input.mode === 'append') {\n setIsLoadingMore(true);\n } else {\n setIsRefreshing(true);\n }\n\n try {\n const variables = buildVariables({\n where: input.where,\n sort: input.sort,\n count: input.count,\n cursor: input.cursor,\n });\n\n const response = await fetchQuery(\n environment,\n listConfig.query,\n variables as never,\n { fetchPolicy: RECORD_FETCH_POLICY },\n ).toPromise();\n\n if (response == null || requestIdRef.current !== requestId) {\n return;\n }\n\n const nextRows = listConfig.getRows(response as never).map((row) => {\n return listConfig.toRow(row);\n });\n setRows((prev) => {\n if (input.mode === 'append') {\n return [...prev, ...nextRows];\n }\n return nextRows;\n });\n const cursor = listConfig.getNextCursor?.(response as never) ?? null;\n setNextCursor(cursor);\n } finally {\n if (input.mode === 'append') {\n setIsLoadingMore(false);\n } else {\n setIsRefreshing(false);\n }\n }\n },\n [buildVariables, environment, listConfig],\n );\n\n useEffect(() => {\n const key = stableListVariablesKey({\n where: state.where,\n sort: resolvedSort,\n count: pageSize,\n });\n if (lastRefetchKeyRef.current === key) {\n return;\n }\n lastRefetchKeyRef.current = key;\n runFetch({\n where: state.where,\n sort: resolvedSort,\n count: pageSize,\n cursor: null,\n mode: 'reset',\n }).catch(() => {});\n }, [pageSize, resolvedSort, runFetch, state.where]);\n\n const handleRefresh = useCallback(() => {\n if (isRefreshing) {\n return;\n }\n runFetch({\n where: state.where,\n sort: resolvedSort,\n count: pageSize,\n cursor: null,\n mode: 'reset',\n }).catch(() => {});\n }, [isRefreshing, pageSize, resolvedSort, runFetch, state.where]);\n\n const handleLoadMore = useBackofficeLoadMore({\n hasNext: nextCursor != null,\n isLoadingNext: isLoadingMore,\n loadNext: (count) => {\n if (nextCursor == null) {\n return;\n }\n runFetch({\n where: state.where,\n sort: resolvedSort,\n count,\n cursor: nextCursor,\n mode: 'append',\n }).catch(() => {});\n },\n count: pageSize,\n });\n\n const listActions = useMemo(() => {\n return config.listActions ?? [];\n }, [config.listActions]);\n const visibleActions = useMemo(() => {\n return listActions.filter((action) => {\n if (action.isVisible == null) {\n return true;\n }\n return action.isVisible(null);\n });\n }, [listActions]);\n\n const headerActions = useMemo(() => {\n if (visibleActions.length === 0) {\n return undefined;\n }\n return (\n <div className={pageStyles.headerActions}>\n {visibleActions.map((action, index) => {\n const { variant: actionVariant } = action;\n const label = resolveLabel(action.label, tApp);\n let ariaLabel = label;\n if (action.ariaLabel != null) {\n ariaLabel = resolveLabel(action.ariaLabel, tApp);\n }\n const variant = resolveActionVariant(actionVariant, index);\n const size = action.size ?? 'small';\n const isDisabled = action.isDisabled?.(null) === true;\n\n if (isRouteAction(action)) {\n const href = action.to(null);\n return (\n <LinkButton\n key={action.id}\n to={href}\n variant={variant}\n size={size}\n isDisabled={isDisabled}\n aria-label={ariaLabel}\n >\n {label}\n </LinkButton>\n );\n }\n\n if (isFormMutationAction(action)) {\n return (\n <Button\n key={action.id}\n type=\"button\"\n variant={variant}\n size={size}\n disabled={isDisabled}\n onClick={() => {\n setActiveFormActionId(action.id);\n }}\n aria-label={ariaLabel}\n >\n {label}\n </Button>\n );\n }\n\n return null;\n })}\n </div>\n );\n }, [tApp, visibleActions]);\n\n const activeFormAction = listActions.find((action) => {\n return action.id === activeFormActionId;\n });\n\n return (\n <>\n <BackofficeEntityListScaffold\n config={config}\n state={state}\n pushState={pushState}\n headerActions={headerActions}\n rows={rows}\n columns={columns}\n gridTemplateColumns={gridTemplateColumns}\n getRowId={getRowId}\n hasNextPage={nextCursor != null}\n isLoadingMore={isLoadingMore}\n onLoadMore={handleLoadMore}\n onRefresh={handleRefresh}\n totalCount={null}\n />\n {activeFormAction != null && isFormMutationAction(activeFormAction) && (\n <LazyBackofficeEntityActionFormDialog\n isOpen\n action={activeFormAction}\n node={null}\n onClose={() => {\n setActiveFormActionId(null);\n }}\n onSuccess={handleRefresh}\n />\n )}\n </>\n );\n};\n\nexport const BackofficeEntityListPage = ({\n entityManifest,\n config,\n prepared,\n}: BackofficeEntityListPageProps): JSX.Element | null => {\n const { t: tApp } = useTranslation();\n const breadcrumb = buildEntityListBreadcrumb(config, tApp);\n\n if (isRecordListConfig(config)) {\n return (\n <BackofficeRightPageLayout breadcrumb={breadcrumb}>\n <BackofficeEntityRecordListPage\n entityManifest={entityManifest}\n config={config}\n prepared={prepared}\n />\n </BackofficeRightPageLayout>\n );\n }\n const listConfig = config.list;\n if (listConfig.kind === 'records') {\n return null;\n }\n\n const { t } = useBackofficeReactTranslation();\n const { entities } = useBackofficeConfig();\n const [activeFormActionId, setActiveFormActionId] = useState<string | null>(\n null,\n );\n\n const queryData = usePreloadedQuery(listConfig.query, prepared.query);\n const {\n data: fragmentData,\n loadNext,\n hasNext,\n isLoadingNext,\n refetch,\n } = usePaginationFragment(listConfig.fragment, queryData as never);\n\n const connection = listConfig.getConnection(fragmentData);\n\n const rows = useMemo(() => {\n return connection.edges.map((edge) => {\n return listConfig.toRow(edge.node);\n });\n }, [connection.edges, listConfig]);\n\n const { columns, gridTemplateColumns } = useMemo((): {\n columns: readonly DataTableColumn<unknown>[];\n gridTemplateColumns?: string;\n } => {\n const baseColumns = buildDataTableColumns(listConfig.columns, {\n tApp,\n t,\n resolveEntityHref: (entityId, refId) => {\n const entityManifest = entities[entityId];\n if (entityManifest == null) {\n return null;\n }\n return entityManifest.routes.detail(refId);\n },\n });\n const actionsColumn = buildActionsColumn({\n ariaLabel: t('actions.view'),\n fallback: t('common.notAvailable'),\n className: pageStyles.actionsColumnCell,\n resolveDetailHref: (id) => {\n return config.routes.detail(id);\n },\n renderAction: ({ href, ariaLabel }) => {\n return (\n <TableCell.Actions>\n <span className={pageStyles.actionTrigger} title={ariaLabel}>\n <LinkButton\n to={href}\n variant=\"icon\"\n size=\"small\"\n aria-label={ariaLabel}\n >\n <EyeSvg width={16} height={16} />\n </LinkButton>\n </span>\n </TableCell.Actions>\n );\n },\n });\n const allColumns = [...baseColumns, actionsColumn];\n return applyListEdgeColumns(allColumns, listConfig.rowFlags, 1, tApp);\n }, [\n config.routes,\n listConfig.columns,\n listConfig.rowFlags,\n entities,\n t,\n tApp,\n ]);\n\n const getRowId = useCallback<GetRowId<unknown>>(\n (row) => {\n return listConfig.getRowId(row as never);\n },\n [listConfig],\n );\n\n const { state, pushState } = useBackofficeListUrlState(config);\n const listDefaults = config.listDefaults ??\n listConfig.defaultState ?? { where: null, sort: null };\n const resolvedSort = state.sort ?? listDefaults.sort;\n const { pageSize } = BACKOFFICE_LIST_DEFAULTS;\n\n const baseVariables = useMemo(() => {\n return {\n where: state.where,\n sort: resolvedSort,\n count: pageSize,\n cursor: null,\n };\n }, [pageSize, resolvedSort, state.where]);\n\n const { onRefresh } = useBackofficeListRefetch({\n refetch: refetch as never,\n variables: baseVariables,\n defaults: {\n where: listDefaults.where,\n sort: listDefaults.sort,\n count: pageSize,\n cursor: null,\n },\n fetchPolicy: BACKOFFICE_LIST_REFETCH_POLICY,\n buildVariables: listConfig.buildVariables,\n });\n\n const handleLoadMore = useBackofficeLoadMore({\n hasNext,\n isLoadingNext,\n loadNext,\n count: pageSize,\n });\n\n const listActions = useMemo(() => {\n return config.listActions ?? [];\n }, [config.listActions]);\n const visibleActions = useMemo(() => {\n return listActions.filter((action) => {\n if (action.isVisible == null) {\n return true;\n }\n return action.isVisible(null);\n });\n }, [listActions]);\n\n const headerActions = useMemo(() => {\n if (visibleActions.length === 0) {\n return undefined;\n }\n return (\n <div className={pageStyles.headerActions}>\n {visibleActions.map((action, index) => {\n const { variant: actionVariant } = action;\n const label = resolveLabel(action.label, tApp);\n let ariaLabel = label;\n if (action.ariaLabel != null) {\n ariaLabel = resolveLabel(action.ariaLabel, tApp);\n }\n const variant = resolveActionVariant(actionVariant, index);\n const size = action.size ?? 'small';\n const isDisabled = action.isDisabled?.(null) === true;\n\n if (isRouteAction(action)) {\n const href = action.to(null);\n return (\n <LinkButton\n key={action.id}\n to={href}\n variant={variant}\n size={size}\n isDisabled={isDisabled}\n aria-label={ariaLabel}\n >\n {label}\n </LinkButton>\n );\n }\n\n if (isFormMutationAction(action)) {\n return (\n <Button\n key={action.id}\n type=\"button\"\n variant={variant}\n size={size}\n disabled={isDisabled}\n onClick={() => {\n setActiveFormActionId(action.id);\n }}\n aria-label={ariaLabel}\n >\n {label}\n </Button>\n );\n }\n\n return null;\n })}\n </div>\n );\n }, [tApp, visibleActions]);\n\n const activeFormAction = listActions.find((action) => {\n return action.id === activeFormActionId;\n });\n\n return (\n <BackofficeRightPageLayout breadcrumb={breadcrumb}>\n <BackofficeEntityListScaffold\n config={config}\n state={state}\n pushState={pushState}\n headerActions={headerActions}\n rows={rows}\n columns={columns}\n gridTemplateColumns={gridTemplateColumns}\n getRowId={getRowId}\n hasNextPage={hasNext}\n isLoadingMore={isLoadingNext}\n onLoadMore={handleLoadMore}\n onRefresh={onRefresh}\n totalCount={connection.totalCount ?? null}\n />\n {activeFormAction != null && isFormMutationAction(activeFormAction) && (\n <LazyBackofficeEntityActionFormDialog\n isOpen\n action={activeFormAction}\n node={null}\n onClose={() => {\n setActiveFormActionId(null);\n }}\n onSuccess={onRefresh}\n />\n )}\n </BackofficeRightPageLayout>\n );\n};\n\nexport default BackofficeEntityListPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"BackofficeEntityListPage-DmZozSNk.js","names":[],"sources":["../../src/components/backoffice/list/RowFlagsCell.css.ts","../../src/components/backoffice/list/RowFlagsCell.tsx","../../src/pages/backofficeEntityListPage.css.ts","../../src/pages/BackofficeEntityListPage.helpers.tsx","../../src/pages/BackofficeEntityListPage.tsx"],"sourcesContent":["import { style } from '@vanilla-extract/css';\n\nexport const rowFlagsColumnCell = style({\n justifyContent: 'center',\n padding: '0 4px',\n});\n\nexport const container = style({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 6,\n width: '100%',\n});\n\nexport const flag = style({\n display: 'grid',\n placeItems: 'center',\n width: 24,\n height: 24,\n});\n","import { useMemo, type JSX, type ReactNode } from 'react';\nimport type { TFunction } from 'i18next';\n\nimport type { BackofficeRowFlagSpec } from '@plumile/backoffice-core/types.js';\n\nimport * as styles from './RowFlagsCell.css.js';\n\nexport type RowFlagsCellProps<Row> = {\n row: Row;\n flags: readonly BackofficeRowFlagSpec<Row>[];\n tApp: TFunction;\n};\n\n/**\n * Renders row-level flags (icons) in a dedicated compact column.\n */\nexport const RowFlagsCell = <Row,>({\n row,\n flags,\n tApp,\n}: RowFlagsCellProps<Row>): JSX.Element => {\n const resolved = useMemo(() => {\n const out: { id: string; node: ReactNode; label: string }[] = [];\n for (const flag of flags) {\n const node = flag.render(row) as ReactNode;\n if (node != null && node !== '' && node !== false) {\n out.push({ id: flag.id, node, label: flag.label(row)(tApp) });\n }\n }\n return out;\n }, [flags, row, tApp]);\n\n const ariaLabel = resolved\n .map((entry) => {\n return entry.label.trim();\n })\n .filter((label) => {\n return label !== '';\n })\n .join(', ');\n\n let ariaLabelProp: string | undefined;\n if (ariaLabel !== '') {\n ariaLabelProp = ariaLabel;\n }\n\n return (\n <div className={styles.container} aria-label={ariaLabelProp}>\n {resolved.map((entry) => {\n return (\n <span\n key={entry.id}\n className={styles.flag}\n title={entry.label}\n role=\"img\"\n aria-label={entry.label}\n >\n {entry.node}\n </span>\n );\n })}\n </div>\n );\n};\n","import { style } from '@vanilla-extract/css';\nimport { sprinkles } from '@plumile/ui';\n\nexport const headerActions = sprinkles({\n display: 'flex',\n alignItems: 'center',\n gap: 2,\n flexWrap: 'wrap',\n});\n\nexport const actionsColumnCell = style({\n justifyContent: 'flex-end',\n});\n\nexport const actionTrigger = style({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '2rem',\n height: '2rem',\n flexShrink: 0,\n});\n","import type { ComponentProps, JSX } from 'react';\n\nimport type {\n BackofficeEntityFormMutationActionSpec,\n BackofficeEntityRouteActionSpec,\n BackofficeListActionSpec,\n BackofficeRecordListConfig,\n BackofficeResolvedListFacetConfig,\n I18nLabel,\n} from '@plumile/backoffice-core/types.js';\nimport type { Button, DataTableColumn } from '@plumile/ui';\nimport type { TFunction } from 'i18next';\n\nimport type { BackofficeSizedDataTableColumn } from '../components/backoffice/columns/buildDataTableColumns.js';\n\ntype ButtonVariant = ComponentProps<typeof Button>['variant'];\n\nconst ROW_FLAGS_SLOT_PX = 24;\nconst ROW_FLAGS_GAP_PX = 6;\nconst ROW_FLAGS_PADDING_X_PX = 4;\n\nconst ACTIONS_SLOT_PX = 32;\nconst ACTIONS_GAP_PX = 8;\nconst ACTIONS_PADDING_X_PX = 12;\nconst ACTIONS_SAFETY_PX = 4;\n\nconst listColumnTrackBySize: Record<\n BackofficeSizedDataTableColumn<unknown>['size'],\n string\n> = {\n xs: 'minmax(56px, 76px)',\n s: 'minmax(88px, 120px)',\n m: 'minmax(120px, 168px)',\n l: 'minmax(160px, 220px)',\n xl: 'minmax(220px, 300px)',\n fluid: 'minmax(0, 1fr)',\n};\n\nexport const computeRowFlagsColumnWidthPx = (flagCount: number): number => {\n if (flagCount <= 0) {\n return 0;\n }\n return (\n ROW_FLAGS_PADDING_X_PX * 2 +\n ROW_FLAGS_SLOT_PX * flagCount +\n ROW_FLAGS_GAP_PX * Math.max(0, flagCount - 1)\n );\n};\n\nexport const computeActionsColumnWidthPx = (actionCount: number): number => {\n if (actionCount <= 0) {\n return 0;\n }\n return (\n ACTIONS_SAFETY_PX +\n ACTIONS_PADDING_X_PX * 2 +\n ACTIONS_SLOT_PX * actionCount +\n ACTIONS_GAP_PX * Math.max(0, actionCount - 1)\n );\n};\n\nexport const resolveTrackBySize = (\n column: DataTableColumn<unknown>,\n fallback: string,\n): string => {\n if ('size' in column && typeof column.size === 'string') {\n return listColumnTrackBySize[\n column.size as BackofficeSizedDataTableColumn<unknown>['size']\n ];\n }\n return fallback;\n};\n\nexport const isRouteAction = (\n action: BackofficeListActionSpec,\n): action is BackofficeEntityRouteActionSpec<null> => {\n return action.kind === 'route';\n};\n\nexport const isFormMutationAction = (\n action: BackofficeListActionSpec,\n): action is BackofficeEntityFormMutationActionSpec<null> => {\n return action.kind === 'formMutation';\n};\n\nexport const resolveActionVariant = (\n actionVariant: ButtonVariant | undefined,\n index: number,\n): Exclude<ButtonVariant, undefined> => {\n if (actionVariant != null) {\n return actionVariant;\n }\n if (index === 0) {\n return 'primary';\n }\n return 'secondary';\n};\n\nexport const resolveRowId = (row: unknown): string | null => {\n if (row == null || typeof row !== 'object') {\n return null;\n }\n if (!('id' in row)) {\n return null;\n }\n const value = (row as { id?: unknown }).id;\n if (typeof value !== 'string') {\n return null;\n }\n const trimmed = value.trim();\n if (trimmed === '') {\n return null;\n }\n return trimmed;\n};\n\ntype RecordListConfig = BackofficeResolvedListFacetConfig & {\n list: BackofficeRecordListConfig<\n Record<string, unknown>,\n string,\n unknown,\n unknown,\n unknown,\n unknown,\n Record<string, unknown>\n >;\n};\n\nexport const isRecordListConfig = (\n config: BackofficeResolvedListFacetConfig,\n): config is RecordListConfig => {\n return config.list.kind === 'records';\n};\n\nexport const resolveLabel = (label: I18nLabel, tApp: TFunction): string => {\n return label(tApp);\n};\n\nexport const buildActionsColumn = (options: {\n ariaLabel: string;\n fallback: string;\n resolveDetailHref: (id: string) => string | null;\n className?: string;\n renderAction: (input: { href: string; ariaLabel: string }) => JSX.Element;\n}): DataTableColumn<unknown> => {\n const { ariaLabel, fallback, resolveDetailHref, className, renderAction } =\n options;\n\n return {\n id: 'actions',\n header: '',\n className,\n cell: (row) => {\n const id = resolveRowId(row);\n if (id == null) {\n return fallback;\n }\n const href = resolveDetailHref(id);\n if (href == null) {\n return fallback;\n }\n return renderAction({ href, ariaLabel });\n },\n };\n};\n","/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n type JSX,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type { TFunction } from 'i18next';\nimport { useTranslation } from 'react-i18next';\nimport {\n useFragment,\n usePaginationFragment,\n usePreloadedQuery,\n useRelayEnvironment,\n} from 'react-relay';\nimport { fetchQuery } from 'relay-runtime';\nimport {\n BACKOFFICE_LIST_DEFAULTS,\n BACKOFFICE_LIST_REFETCH_POLICY,\n} from '@plumile/backoffice-core/constants.js';\nimport { stableListVariablesKey } from '@plumile/backoffice-core/state/stableKey.js';\nimport type {\n BackofficeEntityManifestItem,\n BackofficePreparedListRoute,\n BackofficeResolvedListFacetConfig,\n BackofficeRowFlagSpec,\n} from '@plumile/backoffice-core/types.js';\nimport {\n Button,\n EyeSvg,\n LinkButton,\n TableCell,\n type DataTableColumn,\n type GetRowId,\n} from '@plumile/ui';\nimport { BackofficeEntityListScaffold } from '../components/backoffice/scaffolds/BackofficeEntityListScaffold.js';\nimport { LazyBackofficeEntityActionFormDialog } from '../components/backoffice/actions/LazyBackofficeEntityActionFormDialog.js';\nimport { buildDataTableColumns } from '../components/backoffice/columns/buildDataTableColumns.js';\nimport { RowFlagsCell } from '../components/backoffice/list/RowFlagsCell.js';\nimport { useBackofficeListUrlState } from '../hooks/useBackofficeListUrlState.js';\nimport { useBackofficeLoadMore } from '../hooks/useBackofficeLoadMore.js';\nimport { useBackofficeListRefetch } from '../hooks/useBackofficeListRefetch.js';\nimport { useBackofficeReactTranslation } from '../i18n/useBackofficeReactTranslation.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport * as pageStyles from './backofficeEntityListPage.css.js';\nimport { rowFlagsColumnCell } from '../components/backoffice/list/RowFlagsCell.css.js';\nimport { BackofficeRightPageLayout } from '../components/backoffice/layout/breadcrumb/BackofficeRightPageLayout.js';\nimport { buildEntityListBreadcrumb } from '../components/backoffice/layout/breadcrumb/buildBreadcrumbs.js';\nimport {\n buildActionsColumn,\n computeActionsColumnWidthPx,\n computeRowFlagsColumnWidthPx,\n isFormMutationAction,\n isRecordListConfig,\n isRouteAction,\n resolveLabel,\n resolveActionVariant,\n resolveTrackBySize,\n} from './BackofficeEntityListPage.helpers.js';\n\nexport type BackofficeEntityListPageProps = {\n entityManifest: BackofficeEntityManifestItem;\n config: BackofficeResolvedListFacetConfig;\n prepared: BackofficePreparedListRoute;\n};\n\ntype RecordFetchMode = 'append' | 'reset';\ntype RecordFetchInput = {\n where: Record<string, unknown> | null;\n sort: string | null;\n count: number;\n cursor: string | null;\n mode: RecordFetchMode;\n};\n\nconst RECORD_FETCH_POLICY = 'store-or-network' as const;\n\nconst applyListEdgeColumns = <Row,>(\n inputColumns: readonly DataTableColumn<Row>[],\n rowFlags: readonly BackofficeRowFlagSpec<Row>[] | undefined,\n actionCount: number,\n tApp: TFunction,\n): {\n columns: readonly DataTableColumn<Row>[];\n gridTemplateColumns?: string;\n} => {\n const hasFlags = rowFlags != null && rowFlags.length > 0;\n\n let columns = inputColumns;\n if (hasFlags) {\n const flagsColumn: DataTableColumn<Row> = {\n id: '__rowFlags',\n header: '',\n className: rowFlagsColumnCell,\n cell: (row) => {\n return <RowFlagsCell row={row} flags={rowFlags} tApp={tApp} />;\n },\n };\n\n // Ensure we never pick the flags column as \"primary\".\n const withFlags = [flagsColumn, ...inputColumns];\n const hasPrimary = withFlags.some((col) => {\n return col.isPrimary === true;\n });\n\n columns = withFlags;\n if (!hasPrimary) {\n columns = withFlags.map((col, index) => {\n if (index === 1) {\n return { ...col, isPrimary: true };\n }\n return col;\n });\n }\n }\n\n let flagCount = 0;\n if (hasFlags) {\n flagCount = rowFlags.length;\n }\n const flagsWidthPx = computeRowFlagsColumnWidthPx(flagCount);\n const actionsWidthPx = computeActionsColumnWidthPx(actionCount);\n\n // We always include the right-side \"actions\" column in list pages.\n let leftColumnCount = 0;\n if (hasFlags) {\n leftColumnCount = 1;\n }\n const middleCount = columns.length - leftColumnCount - 1;\n\n const middleTracks = columns\n .slice(leftColumnCount, leftColumnCount + Math.max(0, middleCount))\n .map((column) => {\n return resolveTrackBySize(column as DataTableColumn<unknown>, '1fr');\n })\n .join(' ');\n\n let gridTemplateColumns = '';\n if (hasFlags) {\n gridTemplateColumns = `${flagsWidthPx}px ${middleTracks} ${actionsWidthPx}px`;\n } else {\n gridTemplateColumns = `${middleTracks} ${actionsWidthPx}px`;\n }\n\n return { columns, gridTemplateColumns };\n};\n\nconst BackofficeEntityRecordListPage = ({\n config,\n prepared,\n}: BackofficeEntityListPageProps): JSX.Element | null => {\n const { t: tApp } = useTranslation();\n const { t } = useBackofficeReactTranslation();\n const { entities } = useBackofficeConfig();\n const [activeFormActionId, setActiveFormActionId] = useState<string | null>(\n null,\n );\n const environment = useRelayEnvironment();\n\n if (!isRecordListConfig(config)) {\n return null;\n }\n\n const listConfig = config.list;\n const listDefaults = config.listDefaults ??\n listConfig.defaultState ?? { where: null, sort: null };\n const queryData = usePreloadedQuery(listConfig.query, prepared.query);\n const fragmentData = useFragment(listConfig.fragment, queryData as never);\n\n const initialRows = useMemo(() => {\n return listConfig.getRows(fragmentData).map((row) => {\n return listConfig.toRow(row);\n });\n }, [fragmentData, listConfig]);\n\n const initialCursor = useMemo(() => {\n return listConfig.getNextCursor?.(queryData) ?? null;\n }, [listConfig, queryData]);\n\n const [rows, setRows] = useState(initialRows);\n const [nextCursor, setNextCursor] = useState(initialCursor);\n const [isLoadingMore, setIsLoadingMore] = useState(false);\n const [isRefreshing, setIsRefreshing] = useState(false);\n\n useEffect(() => {\n setRows(initialRows);\n setNextCursor(initialCursor);\n }, [initialCursor, initialRows]);\n\n const { columns, gridTemplateColumns } = useMemo((): {\n columns: readonly DataTableColumn<unknown>[];\n gridTemplateColumns?: string;\n } => {\n const baseColumns = buildDataTableColumns(listConfig.columns, {\n tApp,\n t,\n resolveEntityHref: (entityId, refId) => {\n const entityManifest = entities[entityId];\n if (entityManifest == null) {\n return null;\n }\n return entityManifest.routes.detail(refId);\n },\n });\n const actionsColumn = buildActionsColumn({\n ariaLabel: t('actions.view'),\n fallback: t('common.notAvailable'),\n className: pageStyles.actionsColumnCell,\n resolveDetailHref: (id) => {\n return config.routes.detail(id);\n },\n renderAction: ({ href, ariaLabel }) => {\n return (\n <TableCell.Actions>\n <span className={pageStyles.actionTrigger} title={ariaLabel}>\n <LinkButton\n to={href}\n variant=\"icon\"\n size=\"small\"\n aria-label={ariaLabel}\n >\n <EyeSvg width={16} height={16} />\n </LinkButton>\n </span>\n </TableCell.Actions>\n );\n },\n });\n const allColumns = [...baseColumns, actionsColumn];\n return applyListEdgeColumns(allColumns, listConfig.rowFlags, 1, tApp);\n }, [\n config.routes,\n listConfig.columns,\n listConfig.rowFlags,\n entities,\n t,\n tApp,\n ]);\n\n const getRowId = useCallback<GetRowId<unknown>>(\n (row) => {\n return listConfig.getRowId(row);\n },\n [listConfig],\n );\n\n const { state, pushState } = useBackofficeListUrlState(config);\n const resolvedSort = state.sort ?? listDefaults.sort;\n const { pageSize } = BACKOFFICE_LIST_DEFAULTS;\n\n const buildVariables = useCallback(\n (input: {\n where: Record<string, unknown> | null;\n sort: string | null;\n count: number;\n cursor: string | null;\n }) => {\n if (listConfig.buildVariables != null) {\n return listConfig.buildVariables(input);\n }\n return input;\n },\n [listConfig],\n );\n\n const requestIdRef = useRef(0);\n const lastRefetchKeyRef = useRef(\n stableListVariablesKey({\n where: listDefaults.where,\n sort: listDefaults.sort,\n count: pageSize,\n }),\n );\n\n const runFetch = useCallback(\n async (input: RecordFetchInput) => {\n const requestId = requestIdRef.current + 1;\n requestIdRef.current = requestId;\n\n if (input.mode === 'append') {\n setIsLoadingMore(true);\n } else {\n setIsRefreshing(true);\n }\n\n try {\n const variables = buildVariables({\n where: input.where,\n sort: input.sort,\n count: input.count,\n cursor: input.cursor,\n });\n\n const response = await fetchQuery(\n environment,\n listConfig.query,\n variables as never,\n { fetchPolicy: RECORD_FETCH_POLICY },\n ).toPromise();\n\n if (response == null || requestIdRef.current !== requestId) {\n return;\n }\n\n const nextRows = listConfig.getRows(response as never).map((row) => {\n return listConfig.toRow(row);\n });\n setRows((prev) => {\n if (input.mode === 'append') {\n return [...prev, ...nextRows];\n }\n return nextRows;\n });\n const cursor = listConfig.getNextCursor?.(response) ?? null;\n setNextCursor(cursor);\n } finally {\n if (input.mode === 'append') {\n setIsLoadingMore(false);\n } else {\n setIsRefreshing(false);\n }\n }\n },\n [buildVariables, environment, listConfig],\n );\n\n useEffect(() => {\n const key = stableListVariablesKey({\n where: state.where,\n sort: resolvedSort,\n count: pageSize,\n });\n if (lastRefetchKeyRef.current === key) {\n return;\n }\n lastRefetchKeyRef.current = key;\n runFetch({\n where: state.where,\n sort: resolvedSort,\n count: pageSize,\n cursor: null,\n mode: 'reset',\n }).catch(() => {});\n }, [pageSize, resolvedSort, runFetch, state.where]);\n\n const handleRefresh = useCallback(() => {\n if (isRefreshing) {\n return;\n }\n runFetch({\n where: state.where,\n sort: resolvedSort,\n count: pageSize,\n cursor: null,\n mode: 'reset',\n }).catch(() => {});\n }, [isRefreshing, pageSize, resolvedSort, runFetch, state.where]);\n\n const handleLoadMore = useBackofficeLoadMore({\n hasNext: nextCursor != null,\n isLoadingNext: isLoadingMore,\n loadNext: (count) => {\n if (nextCursor == null) {\n return;\n }\n runFetch({\n where: state.where,\n sort: resolvedSort,\n count,\n cursor: nextCursor,\n mode: 'append',\n }).catch(() => {});\n },\n count: pageSize,\n });\n\n const listActions = useMemo(() => {\n return config.listActions ?? [];\n }, [config.listActions]);\n const visibleActions = useMemo(() => {\n return listActions.filter((action) => {\n if (action.isVisible == null) {\n return true;\n }\n return action.isVisible(null);\n });\n }, [listActions]);\n\n const headerActions = useMemo(() => {\n if (visibleActions.length === 0) {\n return undefined;\n }\n return (\n <div className={pageStyles.headerActions}>\n {visibleActions.map((action, index) => {\n const { variant: actionVariant } = action;\n const label = resolveLabel(action.label, tApp);\n let ariaLabel = label;\n if (action.ariaLabel != null) {\n ariaLabel = resolveLabel(action.ariaLabel, tApp);\n }\n const variant = resolveActionVariant(actionVariant, index);\n const size = action.size ?? 'small';\n const isDisabled = action.isDisabled?.(null) === true;\n\n if (isRouteAction(action)) {\n const href = action.to(null);\n return (\n <LinkButton\n key={action.id}\n to={href}\n variant={variant}\n size={size}\n isDisabled={isDisabled}\n aria-label={ariaLabel}\n >\n {label}\n </LinkButton>\n );\n }\n\n if (isFormMutationAction(action)) {\n return (\n <Button\n key={action.id}\n type=\"button\"\n variant={variant}\n size={size}\n disabled={isDisabled}\n onClick={() => {\n setActiveFormActionId(action.id);\n }}\n aria-label={ariaLabel}\n >\n {label}\n </Button>\n );\n }\n\n return null;\n })}\n </div>\n );\n }, [tApp, visibleActions]);\n\n const activeFormAction = listActions.find((action) => {\n return action.id === activeFormActionId;\n });\n\n return (\n <>\n <BackofficeEntityListScaffold\n config={config}\n state={state}\n pushState={pushState}\n headerActions={headerActions}\n rows={rows}\n columns={columns}\n gridTemplateColumns={gridTemplateColumns}\n getRowId={getRowId}\n hasNextPage={nextCursor != null}\n isLoadingMore={isLoadingMore}\n onLoadMore={handleLoadMore}\n onRefresh={handleRefresh}\n totalCount={null}\n />\n {activeFormAction != null && isFormMutationAction(activeFormAction) && (\n <LazyBackofficeEntityActionFormDialog\n isOpen\n action={activeFormAction}\n node={null}\n onClose={() => {\n setActiveFormActionId(null);\n }}\n onSuccess={handleRefresh}\n />\n )}\n </>\n );\n};\n\nexport const BackofficeEntityListPage = ({\n entityManifest,\n config,\n prepared,\n}: BackofficeEntityListPageProps): JSX.Element | null => {\n const { t: tApp } = useTranslation();\n const breadcrumb = buildEntityListBreadcrumb(config, tApp);\n\n if (isRecordListConfig(config)) {\n return (\n <BackofficeRightPageLayout breadcrumb={breadcrumb}>\n <BackofficeEntityRecordListPage\n entityManifest={entityManifest}\n config={config}\n prepared={prepared}\n />\n </BackofficeRightPageLayout>\n );\n }\n const listConfig = config.list;\n if (listConfig.kind === 'records') {\n return null;\n }\n\n const { t } = useBackofficeReactTranslation();\n const { entities } = useBackofficeConfig();\n const [activeFormActionId, setActiveFormActionId] = useState<string | null>(\n null,\n );\n\n const queryData = usePreloadedQuery(listConfig.query, prepared.query);\n const {\n data: fragmentData,\n loadNext,\n hasNext,\n isLoadingNext,\n refetch,\n } = usePaginationFragment(listConfig.fragment, queryData as never);\n\n const connection = listConfig.getConnection(fragmentData);\n\n const rows = useMemo(() => {\n return connection.edges.map((edge) => {\n return listConfig.toRow(edge.node);\n });\n }, [connection.edges, listConfig]);\n\n const { columns, gridTemplateColumns } = useMemo((): {\n columns: readonly DataTableColumn<unknown>[];\n gridTemplateColumns?: string;\n } => {\n const baseColumns = buildDataTableColumns(listConfig.columns, {\n tApp,\n t,\n resolveEntityHref: (entityId, refId) => {\n const entityManifest = entities[entityId];\n if (entityManifest == null) {\n return null;\n }\n return entityManifest.routes.detail(refId);\n },\n });\n const actionsColumn = buildActionsColumn({\n ariaLabel: t('actions.view'),\n fallback: t('common.notAvailable'),\n className: pageStyles.actionsColumnCell,\n resolveDetailHref: (id) => {\n return config.routes.detail(id);\n },\n renderAction: ({ href, ariaLabel }) => {\n return (\n <TableCell.Actions>\n <span className={pageStyles.actionTrigger} title={ariaLabel}>\n <LinkButton\n to={href}\n variant=\"icon\"\n size=\"small\"\n aria-label={ariaLabel}\n >\n <EyeSvg width={16} height={16} />\n </LinkButton>\n </span>\n </TableCell.Actions>\n );\n },\n });\n const allColumns = [...baseColumns, actionsColumn];\n return applyListEdgeColumns(allColumns, listConfig.rowFlags, 1, tApp);\n }, [\n config.routes,\n listConfig.columns,\n listConfig.rowFlags,\n entities,\n t,\n tApp,\n ]);\n\n const getRowId = useCallback<GetRowId<unknown>>(\n (row) => {\n return listConfig.getRowId(row);\n },\n [listConfig],\n );\n\n const { state, pushState } = useBackofficeListUrlState(config);\n const listDefaults = config.listDefaults ??\n listConfig.defaultState ?? { where: null, sort: null };\n const resolvedSort = state.sort ?? listDefaults.sort;\n const { pageSize } = BACKOFFICE_LIST_DEFAULTS;\n\n const baseVariables = useMemo(() => {\n return {\n where: state.where,\n sort: resolvedSort,\n count: pageSize,\n cursor: null,\n };\n }, [pageSize, resolvedSort, state.where]);\n\n const { onRefresh } = useBackofficeListRefetch({\n refetch,\n variables: baseVariables,\n defaults: {\n where: listDefaults.where,\n sort: listDefaults.sort,\n count: pageSize,\n cursor: null,\n },\n fetchPolicy: BACKOFFICE_LIST_REFETCH_POLICY,\n buildVariables: listConfig.buildVariables,\n });\n\n const handleLoadMore = useBackofficeLoadMore({\n hasNext,\n isLoadingNext,\n loadNext,\n count: pageSize,\n });\n\n const listActions = useMemo(() => {\n return config.listActions ?? [];\n }, [config.listActions]);\n const visibleActions = useMemo(() => {\n return listActions.filter((action) => {\n if (action.isVisible == null) {\n return true;\n }\n return action.isVisible(null);\n });\n }, [listActions]);\n\n const headerActions = useMemo(() => {\n if (visibleActions.length === 0) {\n return undefined;\n }\n return (\n <div className={pageStyles.headerActions}>\n {visibleActions.map((action, index) => {\n const { variant: actionVariant } = action;\n const label = resolveLabel(action.label, tApp);\n let ariaLabel = label;\n if (action.ariaLabel != null) {\n ariaLabel = resolveLabel(action.ariaLabel, tApp);\n }\n const variant = resolveActionVariant(actionVariant, index);\n const size = action.size ?? 'small';\n const isDisabled = action.isDisabled?.(null) === true;\n\n if (isRouteAction(action)) {\n const href = action.to(null);\n return (\n <LinkButton\n key={action.id}\n to={href}\n variant={variant}\n size={size}\n isDisabled={isDisabled}\n aria-label={ariaLabel}\n >\n {label}\n </LinkButton>\n );\n }\n\n if (isFormMutationAction(action)) {\n return (\n <Button\n key={action.id}\n type=\"button\"\n variant={variant}\n size={size}\n disabled={isDisabled}\n onClick={() => {\n setActiveFormActionId(action.id);\n }}\n aria-label={ariaLabel}\n >\n {label}\n </Button>\n );\n }\n\n return null;\n })}\n </div>\n );\n }, [tApp, visibleActions]);\n\n const activeFormAction = listActions.find((action) => {\n return action.id === activeFormActionId;\n });\n\n return (\n <BackofficeRightPageLayout breadcrumb={breadcrumb}>\n <BackofficeEntityListScaffold\n config={config}\n state={state}\n pushState={pushState}\n headerActions={headerActions}\n rows={rows}\n columns={columns}\n gridTemplateColumns={gridTemplateColumns}\n getRowId={getRowId}\n hasNextPage={hasNext}\n isLoadingMore={isLoadingNext}\n onLoadMore={handleLoadMore}\n onRefresh={onRefresh}\n totalCount={connection.totalCount ?? null}\n />\n {activeFormAction != null && isFormMutationAction(activeFormAction) && (\n <LazyBackofficeEntityActionFormDialog\n isOpen\n action={activeFormAction}\n node={null}\n onClose={() => {\n setActiveFormActionId(null);\n }}\n onSuccess={onRefresh}\n />\n )}\n </BackofficeRightPageLayout>\n );\n};\n\nexport default BackofficeEntityListPage;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BackofficeLayoutPage-DtFDn_nU.js","names":[],"sources":["../../src/hooks/useBackofficeSidebarPins.ts","../../src/hooks/useSidebarGroupCollapse.ts","../../src/components/backoffice/layout/backofficeSidebarActions.css.ts","../../src/components/backoffice/layout/buildSidebarSections.tsx","../../src/components/backoffice/routing/backofficeContentError.css.ts","../../src/components/backoffice/routing/BackofficeContentError.tsx","../../src/components/backoffice/routing/backofficeContentFallback.css.ts","../../src/components/backoffice/routing/BackofficeContentFallback.tsx","../../src/components/backoffice/routing/backofficeContentBoundary.css.ts","../../src/components/backoffice/routing/BackofficeContentBoundary.tsx","../../src/components/backoffice/layout/mapViewerToSidebarProfileView.ts","../../src/pages/BackofficeLayoutPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\n\nconst DEFAULT_STORAGE_KEY = 'backoffice.sidebar.pins.v1';\n\ntype PinsPayload = unknown;\n\nconst readPinsFromStorage = (storageKey: string): string[] => {\n if (typeof window === 'undefined') {\n return [];\n }\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (raw == null) {\n return [];\n }\n const parsed = JSON.parse(raw) as PinsPayload;\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter((entry): entry is string => {\n return typeof entry === 'string';\n });\n } catch {\n return [];\n }\n};\n\nconst normalizePins = (\n pins: readonly string[],\n validIds: Set<string>,\n): string[] => {\n const output: string[] = [];\n const seen = new Set<string>();\n pins.forEach((entry) => {\n if (!validIds.has(entry)) {\n return;\n }\n if (seen.has(entry)) {\n return;\n }\n seen.add(entry);\n output.push(entry);\n });\n return output;\n};\n\nexport type SidebarPinsState = {\n pins: readonly string[];\n isPinned: (id: string) => boolean;\n pin: (id: string) => void;\n unpin: (id: string) => void;\n toggle: (id: string) => void;\n reorder: (fromId: string, toId: string) => void;\n};\n\nexport type UseBackofficeSidebarPinsInput = {\n storageKey?: string;\n visibleEntityIds: readonly string[];\n};\n\nexport const useBackofficeSidebarPins = (\n input: UseBackofficeSidebarPinsInput,\n): SidebarPinsState => {\n const { storageKey = DEFAULT_STORAGE_KEY, visibleEntityIds } = input;\n const validIds = useMemo(() => {\n return new Set(visibleEntityIds);\n }, [visibleEntityIds]);\n\n const [pins, setPins] = useState<string[]>(() => {\n const stored = readPinsFromStorage(storageKey);\n return normalizePins(stored, validIds);\n });\n\n useEffect(() => {\n setPins((prev) => {\n const normalized = normalizePins(prev, validIds);\n if (normalized.length === prev.length) {\n let unchanged = true;\n for (let index = 0; index < normalized.length; index += 1) {\n if (normalized[index] !== prev[index]) {\n unchanged = false;\n break;\n }\n }\n if (unchanged) {\n return prev;\n }\n }\n return normalized;\n });\n }, [validIds]);\n\n useEffect(() => {\n const stored = readPinsFromStorage(storageKey);\n setPins(normalizePins(stored, validIds));\n }, [storageKey, validIds]);\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(pins));\n } catch {\n // Ignore storage errors in non-browser or restricted environments.\n }\n }, [pins, storageKey]);\n\n const pinsSet = useMemo(() => {\n return new Set(pins);\n }, [pins]);\n\n const pin = useCallback(\n (id: string) => {\n if (!validIds.has(id)) {\n return;\n }\n setPins((prev) => {\n if (prev.includes(id)) {\n return prev;\n }\n return [...prev, id];\n });\n },\n [validIds],\n );\n\n const unpin = useCallback((id: string) => {\n setPins((prev) => {\n return prev.filter((entry) => {\n return entry !== id;\n });\n });\n }, []);\n\n const toggle = useCallback(\n (id: string) => {\n if (pinsSet.has(id)) {\n unpin(id);\n } else {\n pin(id);\n }\n },\n [pin, pinsSet, unpin],\n );\n\n const reorder = useCallback((fromId: string, toId: string) => {\n if (fromId === toId) {\n return;\n }\n setPins((prev) => {\n const fromIndex = prev.indexOf(fromId);\n const toIndex = prev.indexOf(toId);\n if (fromIndex === -1 || toIndex === -1) {\n return prev;\n }\n if (fromIndex === toIndex) {\n return prev;\n }\n const next = [...prev];\n next.splice(fromIndex, 1);\n next.splice(toIndex, 0, fromId);\n return next;\n });\n }, []);\n\n const isPinned = useCallback(\n (id: string) => {\n return pinsSet.has(id);\n },\n [pinsSet],\n );\n\n return {\n pins,\n isPinned,\n pin,\n unpin,\n toggle,\n reorder,\n };\n};\n\nexport const __test = {\n normalizePins,\n readPinsFromStorage,\n};\n\nexport default useBackofficeSidebarPins;\n","import { useCallback, useEffect, useMemo, useState } from 'react';\n\nexport type SidebarGroupCollapseState = Record<string, boolean | undefined>;\n\nexport type UseSidebarGroupCollapseInput = {\n groupIds: readonly string[];\n activeGroupId?: string | null;\n};\n\nconst buildInitialState = (\n groupIds: readonly string[],\n activeGroupId?: string | null,\n): SidebarGroupCollapseState => {\n const state: SidebarGroupCollapseState = {};\n groupIds.forEach((groupId) => {\n state[groupId] = true;\n });\n if (activeGroupId != null && groupIds.includes(activeGroupId)) {\n state[activeGroupId] = false;\n }\n return state;\n};\n\nexport const useSidebarGroupCollapse = (\n input: UseSidebarGroupCollapseInput,\n): {\n collapsedByGroupId: SidebarGroupCollapseState;\n setCollapsed: (groupId: string, collapsed: boolean) => void;\n} => {\n const { groupIds, activeGroupId } = input;\n\n const groupIdList = useMemo(() => {\n return [...groupIds];\n }, [groupIds]);\n\n const [collapsedByGroupId, setCollapsedByGroupId] =\n useState<SidebarGroupCollapseState>(() => {\n return buildInitialState(groupIdList, activeGroupId);\n });\n\n useEffect(() => {\n setCollapsedByGroupId((prev) => {\n const next: SidebarGroupCollapseState = {};\n\n groupIdList.forEach((groupId) => {\n const existing = prev[groupId];\n next[groupId] = existing ?? true;\n });\n\n if (activeGroupId != null && groupIdList.includes(activeGroupId)) {\n next[activeGroupId] = false;\n }\n\n return next;\n });\n }, [activeGroupId, groupIdList]);\n\n useEffect(() => {\n if (activeGroupId == null) {\n return;\n }\n setCollapsedByGroupId((prev) => {\n if (prev[activeGroupId] === false) {\n return prev;\n }\n return {\n ...prev,\n [activeGroupId]: false,\n };\n });\n }, [activeGroupId]);\n\n const setCollapsed = useCallback((groupId: string, collapsed: boolean) => {\n setCollapsedByGroupId((prev) => {\n if (prev[groupId] === collapsed) {\n return prev;\n }\n return {\n ...prev,\n [groupId]: collapsed,\n };\n });\n }, []);\n\n return {\n collapsedByGroupId,\n setCollapsed,\n };\n};\n\nexport default useSidebarGroupCollapse;\n","import { style } from '@vanilla-extract/css';\n\nimport { sprinkles, vars } from '@plumile/ui';\n\nexport const actionButton = style([\n sprinkles({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 6,\n height: 6,\n borderRadius: 'md',\n borderWidth: 0,\n borderStyle: 'none',\n padding: 0,\n backgroundColor: 'transparent',\n color: 'textSecondary',\n cursor: 'pointer',\n transitionProperty: 'colors',\n transitionDuration: 150,\n transitionTimingFunction: 'ease',\n }),\n {\n selectors: {\n '&:hover': {\n backgroundColor: vars.colors.surfaceMuted,\n color: vars.colors.text,\n },\n '&:focus-visible': {\n outline: `2px solid ${vars.colors['blue-500']}`,\n outlineOffset: 2,\n },\n },\n },\n]);\n","import { type DragEvent, type ReactNode } from 'react';\nimport type { TFunction } from 'i18next';\n\nimport type { BackofficeEntityManifestMap } from '@plumile/backoffice-core/types.js';\nimport type {\n BackofficeSidebarConfig,\n BackofficeSidebarItemDescriptor,\n BackofficeIconComponent,\n} from '../../../provider/types.js';\nimport {\n type AdminSidebarSection,\n type SidebarNavSectionItem,\n GripDotsSvg,\n PinFilledSvg,\n PinSvg,\n SidebarHomeSvg,\n SidebarTasksSvg,\n} from '@plumile/ui';\nimport type { SidebarGroupCollapseState } from '../../../hooks/useSidebarGroupCollapse.js';\nimport * as styles from './backofficeSidebarActions.css.js';\nimport {\n buildEntityGroupLookup,\n isActivePath,\n resolveLabel,\n resolveSidebarGroups,\n} from './sidebarUtils.js';\n\nconst renderIcon = (\n Icon?: BackofficeIconComponent,\n fallback?: ReactNode,\n): ReactNode => {\n if (Icon != null) {\n return <Icon width={18} height={18} aria-hidden=\"true\" />;\n }\n return fallback ?? null;\n};\n\nexport type BuildSidebarSectionsInput = {\n basePath: string;\n pathname: string;\n entities: BackofficeEntityManifestMap;\n sidebar?: BackofficeSidebarConfig;\n permissions: unknown;\n searchQuery?: string;\n tApp: TFunction;\n t: TFunction;\n pinnedEntityIds?: readonly string[];\n onTogglePin?: (entityId: string) => void;\n onReorderPin?: (fromId: string, toId: string) => void;\n collapsedByGroupId?: SidebarGroupCollapseState;\n onGroupCollapsedChange?: (groupId: string, collapsed: boolean) => void;\n};\n\n/**\n * Builds the sidebar sections for the backoffice layout.\n */\nexport function buildSidebarSections(\n input: BuildSidebarSectionsInput,\n): readonly AdminSidebarSection[] {\n const {\n basePath,\n pathname,\n entities,\n sidebar,\n permissions,\n searchQuery,\n tApp,\n t,\n pinnedEntityIds = [],\n onTogglePin,\n onReorderPin,\n collapsedByGroupId,\n onGroupCollapsedChange,\n } = input;\n\n const groups = resolveSidebarGroups(entities, sidebar);\n const entries = Object.entries(groups);\n const pinnedSet = new Set(pinnedEntityIds);\n const pinLabel = t('sidebar.actions.pin');\n const unpinLabel = t('sidebar.actions.unpin');\n const reorderLabel = t('sidebar.actions.reorder');\n const entityGroupLookup = buildEntityGroupLookup(groups);\n const normalizedQuery = searchQuery?.trim().toLowerCase() ?? '';\n\n const renderPinAction = (entityId: string): ReactNode | null => {\n if (onTogglePin == null) {\n return null;\n }\n const isPinned = pinnedSet.has(entityId);\n let label = pinLabel;\n let Icon = PinSvg;\n if (isPinned) {\n label = unpinLabel;\n Icon = PinFilledSvg;\n }\n\n return (\n <button\n type=\"button\"\n className={styles.actionButton}\n aria-pressed={isPinned}\n aria-label={label}\n title={label}\n onClick={(event) => {\n event.preventDefault();\n event.stopPropagation();\n onTogglePin(entityId);\n }}\n >\n <Icon width={14} height={14} aria-hidden=\"true\" />\n </button>\n );\n };\n\n const buildEntityItem = (inputItem: {\n entityId: string;\n groupId?: string;\n groupIcon?: BackofficeIconComponent;\n enableReorder?: boolean;\n }): SidebarNavSectionItem | null => {\n const { entityId, groupId, groupIcon, enableReorder } = inputItem;\n const config = entities[entityId];\n if (config == null) {\n return null;\n }\n\n let descriptor: BackofficeSidebarItemDescriptor = {\n kind: 'entity',\n id: entityId,\n };\n if (config.kind === 'tool') {\n descriptor = { kind: 'tool', id: entityId };\n }\n const isEntityVisible = sidebar?.isItemVisible?.(descriptor, permissions);\n if (isEntityVisible === false) {\n return null;\n }\n\n if (config.kind === 'tool') {\n const label = resolveLabel(config.label, tApp);\n if (\n normalizedQuery !== '' &&\n !label.toLowerCase().includes(normalizedQuery)\n ) {\n return null;\n }\n return {\n id: `tool-${entityId}`,\n data: {\n kind: 'tool',\n id: entityId,\n groupId,\n },\n label,\n href: config.routes.list,\n icon: renderIcon(\n groupIcon,\n <SidebarTasksSvg width={18} height={18} aria-hidden=\"true\" />,\n ),\n isActive: isActivePath(pathname, config.routes.list),\n ariaLabel: label,\n actionSlot: renderPinAction(entityId),\n };\n }\n\n if (!config.hasList) {\n return null;\n }\n\n const label = resolveLabel(config.label, tApp);\n if (\n normalizedQuery !== '' &&\n !label.toLowerCase().includes(normalizedQuery)\n ) {\n return null;\n }\n\n let dragHandleSlot: ReactNode | undefined;\n let onDragStart: ((event: DragEvent) => void) | undefined;\n let onDragOver: ((event: DragEvent) => void) | undefined;\n let onDrop: ((event: DragEvent) => void) | undefined;\n let draggable = false;\n\n if (enableReorder === true && onReorderPin != null) {\n draggable = true;\n dragHandleSlot = (\n <GripDotsSvg width={14} height={14} aria-hidden=\"true\" />\n );\n onDragStart = (event) => {\n const { dataTransfer } = event;\n dataTransfer.effectAllowed = 'move';\n dataTransfer.setData('text/plain', entityId);\n };\n onDragOver = (event) => {\n event.preventDefault();\n const { dataTransfer } = event;\n dataTransfer.dropEffect = 'move';\n };\n onDrop = (event) => {\n event.preventDefault();\n const fromId = event.dataTransfer.getData('text/plain');\n if (fromId === '' || fromId === entityId) {\n return;\n }\n onReorderPin(fromId, entityId);\n };\n }\n\n let dragHandleLabel: string | undefined;\n if (dragHandleSlot != null) {\n dragHandleLabel = reorderLabel;\n }\n\n return {\n id: entityId,\n data: {\n kind: 'entity',\n id: entityId,\n groupId,\n },\n label,\n href: config.routes.list,\n icon: renderIcon(\n groupIcon,\n <SidebarTasksSvg width={18} height={18} aria-hidden=\"true\" />,\n ),\n isActive: isActivePath(pathname, config.routes.list),\n ariaLabel: label,\n actionSlot: renderPinAction(entityId),\n dragHandleSlot,\n dragHandleLabel,\n draggable,\n onDragStart,\n onDragOver,\n onDrop,\n };\n };\n\n const sections: AdminSidebarSection[] = [];\n\n if (pinnedEntityIds.length > 0) {\n const pinnedItems = pinnedEntityIds\n .map((entityId) => {\n const groupMeta = entityGroupLookup.get(entityId);\n return buildEntityItem({\n entityId,\n groupId: groupMeta?.groupId,\n groupIcon: groupMeta?.icon,\n enableReorder: true,\n });\n })\n .filter((item): item is SidebarNavSectionItem => {\n return item != null;\n });\n\n if (pinnedItems.length > 0) {\n sections.push({\n id: 'pinned',\n title: t('sidebar.sections.pinned'),\n items: pinnedItems,\n collapsible: false,\n });\n }\n }\n\n entries.forEach(([groupId, group], index) => {\n if (group.isVisible != null && !group.isVisible(permissions)) {\n return;\n }\n\n const items: SidebarNavSectionItem[] = [];\n\n if (index === 0) {\n const dashboardDescriptor: BackofficeSidebarItemDescriptor = {\n kind: 'dashboard',\n id: 'dashboard',\n };\n const isDashboardVisible = sidebar?.isItemVisible?.(\n dashboardDescriptor,\n permissions,\n );\n if (isDashboardVisible !== false) {\n const dashboardLabel = t('sidebar.items.dashboard');\n if (\n normalizedQuery !== '' &&\n !dashboardLabel.toLowerCase().includes(normalizedQuery)\n ) {\n return;\n }\n items.push({\n id: 'dashboard',\n data: {\n kind: 'dashboard',\n id: 'dashboard',\n groupId,\n },\n label: dashboardLabel,\n href: basePath,\n icon: renderIcon(\n undefined,\n <SidebarHomeSvg width={18} height={18} aria-hidden=\"true\" />,\n ),\n isActive: isActivePath(pathname, basePath),\n ariaLabel: t('sidebar.items.dashboard'),\n });\n }\n }\n\n if (group.entities != null) {\n group.entities.forEach((entityId) => {\n const item = buildEntityItem({\n entityId,\n groupId,\n groupIcon: group.icon,\n });\n if (item != null) {\n items.push(item);\n }\n });\n }\n\n if (items.length === 0) {\n return;\n }\n\n let title: string | undefined;\n if (group.title != null) {\n title = resolveLabel(group.title, tApp);\n }\n\n const isCollapsed = collapsedByGroupId?.[groupId];\n let onCollapsedChange: ((collapsed: boolean) => void) | undefined;\n if (onGroupCollapsedChange != null) {\n onCollapsedChange = (collapsed: boolean) => {\n onGroupCollapsedChange(groupId, collapsed);\n };\n }\n\n sections.push({\n id: groupId,\n title,\n items,\n collapsible: true,\n defaultCollapsed: true,\n isCollapsed,\n onCollapsedChange,\n });\n });\n\n return sections;\n}\n","import { sprinkles } from '@plumile/ui';\n\nexport const root = sprinkles({\n display: 'flex',\n flexDirection: 'column',\n width: 'full',\n});\n\nexport const banner = sprinkles({\n borderColor: 'borderStrong',\n});\n","import { type JSX } from 'react';\n\nimport { Button, InlineBanner } from '@plumile/ui';\nimport { useBackofficeReactTranslation } from '../../../i18n/useBackofficeReactTranslation.js';\n\nimport * as styles from './backofficeContentError.css.js';\n\ntype BackofficeContentErrorProps = {\n error: unknown;\n onRetry: () => void;\n};\n\nconst resolveErrorMessage = (error: unknown): string | null => {\n if (error instanceof Error) {\n const message = error.message.trim();\n if (message.length > 0) {\n return message;\n }\n return null;\n }\n if (typeof error === 'string') {\n const message = error.trim();\n if (message.length > 0) {\n return message;\n }\n return null;\n }\n return null;\n};\n\nexport const BackofficeContentError = ({\n error,\n onRetry,\n}: BackofficeContentErrorProps): JSX.Element => {\n const { t } = useBackofficeReactTranslation();\n const description = resolveErrorMessage(error);\n\n return (\n <div className={styles.root} role=\"alert\">\n <InlineBanner\n tone=\"danger\"\n className={styles.banner}\n actions={\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"small\"\n onClick={onRetry}\n >\n {t('common.actions.retry')}\n </Button>\n }\n >\n {description}\n </InlineBanner>\n </div>\n );\n};\n\nexport default BackofficeContentError;\n","import { sprinkles } from '@plumile/ui';\n\nexport const container = sprinkles({\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n width: 'full',\n minHeight: 'full',\n});\n\nexport const title = sprinkles({\n maxWidth: 'md',\n});\n\nexport const grid = sprinkles({\n display: 'grid',\n gap: 4,\n gridTemplateColumns: 'autoFitMinmax240',\n});\n\nexport const card = sprinkles({\n display: 'flex',\n flexDirection: 'column',\n gap: 3,\n padding: 4,\n minHeight: 44,\n borderRadius: 'xl',\n borderWidth: 'default',\n borderStyle: 'solid',\n borderColor: 'borderSubtle',\n backgroundColor: 'surfaceMuted',\n});\n","import { type JSX } from 'react';\n\nimport { Skeleton } from '@plumile/ui';\n\nimport * as styles from './backofficeContentFallback.css.js';\n\nexport const BackofficeContentFallback = (): JSX.Element => {\n return (\n <div\n className={styles.container}\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n >\n <Skeleton variant=\"text\" width=\"38%\" className={styles.title} />\n <Skeleton variant=\"text\" width=\"62%\" />\n <div className={styles.grid}>\n {Array.from({ length: 4 }, (_, index) => {\n return (\n <div key={`content-skeleton-${index}`} className={styles.card}>\n <Skeleton variant=\"text\" width=\"46%\" />\n <Skeleton variant=\"text\" width=\"82%\" lines={2} />\n <Skeleton variant=\"block\" width=\"100%\" height={120} />\n </div>\n );\n })}\n </div>\n </div>\n );\n};\n\nexport default BackofficeContentFallback;\n","import { sprinkles } from '@plumile/ui';\n\nexport const root = sprinkles({\n display: 'flex',\n flexDirection: 'column',\n minHeight: 'full',\n width: 'full',\n minWidth: 0,\n});\n","import { Suspense, type JSX, type ReactNode } from 'react';\n\nimport { BackofficeErrorBoundary } from '../errors/BackofficeErrorBoundary.js';\n\nimport { BackofficeContentError } from './BackofficeContentError.js';\nimport { BackofficeContentFallback } from './BackofficeContentFallback.js';\nimport * as styles from './backofficeContentBoundary.css.js';\n\nexport type BackofficeContentBoundaryProps = {\n children: ReactNode;\n};\n\nexport const BackofficeContentBoundary = ({\n children,\n}: BackofficeContentBoundaryProps): JSX.Element => {\n return (\n <div className={styles.root}>\n <BackofficeErrorBoundary\n fallback={({ error, reset }) => {\n return <BackofficeContentError error={error} onRetry={reset} />;\n }}\n >\n <Suspense fallback={<BackofficeContentFallback />}>{children}</Suspense>\n </BackofficeErrorBoundary>\n </div>\n );\n};\n\nexport default BackofficeContentBoundary;\n","import type { BackofficeSidebarProfileViewer } from '@plumile/ui';\n\nexport type BackofficeViewerIdentity = {\n id: string;\n firstName: string;\n lastName: string;\n email: string;\n initials: string;\n};\n\ntype MapViewerToSidebarProfileViewInput = {\n viewer: BackofficeViewerIdentity | null | undefined;\n unknownUserLabel: string;\n};\n\nconst sanitizeToken = (value: string | null | undefined): string => {\n return value?.trim() ?? '';\n};\n\nexport const mapViewerToSidebarProfileView = ({\n viewer,\n unknownUserLabel,\n}: MapViewerToSidebarProfileViewInput): BackofficeSidebarProfileViewer => {\n const firstName = sanitizeToken(viewer?.firstName);\n const lastName = sanitizeToken(viewer?.lastName);\n const joinedDisplayName = [firstName, lastName]\n .filter((token) => {\n return token !== '';\n })\n .join(' ')\n .trim();\n let displayName = joinedDisplayName;\n if (displayName === '') {\n displayName = unknownUserLabel;\n }\n\n const email = sanitizeToken(viewer?.email);\n const initialsToken = sanitizeToken(viewer?.initials);\n let initials = initialsToken;\n if (initials === '') {\n initials = '?';\n }\n\n const ariaParts = [displayName];\n if (email !== '') {\n ariaParts.push(email);\n }\n\n return {\n displayName,\n email,\n initials,\n ariaLabel: ariaParts.join(' - '),\n };\n};\n\nexport default mapViewerToSidebarProfileView;\n","import {\n useMemo,\n type JSX,\n type ReactNode,\n useCallback,\n useContext,\n useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n commitMutation,\n usePreloadedQuery,\n type PreloadedQuery,\n} from 'react-relay';\nimport type {\n GraphQLTaggedNode,\n MutationParameters,\n OperationType,\n} from 'relay-runtime';\nimport { RoutingContext, useLocation } from '@plumile/router';\n\nimport {\n AdminShellLayout,\n BackofficeSidebarProfileMenu,\n EnvironmentBadge,\n GlobalSearchInput,\n ToastProvider,\n} from '@plumile/ui';\n\nimport { useBackofficeReactTranslation } from '../i18n/useBackofficeReactTranslation.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport type { LogoutResponse, LogoutVariables } from '../hooks/useAuth.js';\nimport { useBackofficeSidebarPins } from '../hooks/useBackofficeSidebarPins.js';\nimport { useSidebarGroupCollapse } from '../hooks/useSidebarGroupCollapse.js';\nimport { buildSidebarSections } from '../components/backoffice/layout/buildSidebarSections.js';\nimport { BackofficeContentBoundary } from '../components/backoffice/routing/BackofficeContentBoundary.js';\nimport {\n resolveSidebarGroups,\n resolveVisibleEntityIds,\n} from '../components/backoffice/layout/sidebarUtils.js';\nimport { BackofficeTopbarPortalContextProvider } from '../components/backoffice/layout/breadcrumb/BackofficeTopbarPortalContext.js';\nimport {\n mapViewerToSidebarProfileView,\n type BackofficeViewerIdentity,\n} from '../components/backoffice/layout/mapViewerToSidebarProfileView.js';\nimport { resetRelayStore } from '../relay/environment.js';\nimport { useRelayEnvironment } from '../relay/useRelayEnvironment.js';\nimport { getBackofficeLoginPath } from '../router/backofficeAuthPaths.js';\n\nexport type BackofficeLayoutPageProps = {\n children: ReactNode;\n permissionsQuery?: GraphQLTaggedNode;\n prepared?: PreloadedQuery<OperationType> | null;\n authStatus?: {\n isLoggedIn?: boolean | null;\n me?: BackofficeViewerIdentity | null;\n } | null;\n activeGroupId?: string | null;\n};\n\ntype LayoutShellProps = {\n children: ReactNode;\n permissions: unknown;\n authStatus?: {\n isLoggedIn?: boolean | null;\n me?: BackofficeViewerIdentity | null;\n } | null;\n activeGroupId?: string | null;\n};\n\nconst BackofficeLayoutShell = ({\n children,\n permissions,\n authStatus,\n activeGroupId,\n}: LayoutShellProps): JSX.Element => {\n const { t: tApp } = useTranslation();\n const { t } = useBackofficeReactTranslation();\n const { pathname } = useLocation();\n const routing = useContext(RoutingContext);\n const relayEnvironment = useRelayEnvironment();\n const {\n auth: authConfig,\n basePath,\n entities,\n sidebar,\n } = useBackofficeConfig();\n const [sidebarQuery, setSidebarQuery] = useState('');\n const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);\n const [isSigningOut, setIsSigningOut] = useState(false);\n const [topbarTarget, setTopbarTarget] = useState<HTMLDivElement | null>(null);\n\n const groups = useMemo(() => {\n return resolveSidebarGroups(entities, sidebar);\n }, [entities, sidebar]);\n\n const groupIds = useMemo(() => {\n return Object.keys(groups);\n }, [groups]);\n\n const visibleEntityIds = useMemo(() => {\n return resolveVisibleEntityIds(groups, entities, sidebar, permissions);\n }, [entities, groups, permissions, sidebar]);\n\n const {\n pins,\n toggle: togglePin,\n reorder: reorderPin,\n } = useBackofficeSidebarPins({ visibleEntityIds });\n\n const { collapsedByGroupId, setCollapsed } = useSidebarGroupCollapse({\n groupIds,\n activeGroupId,\n });\n\n const sections = useMemo(() => {\n return buildSidebarSections({\n basePath,\n pathname,\n entities,\n sidebar,\n permissions,\n searchQuery: sidebarQuery,\n tApp,\n t,\n pinnedEntityIds: pins,\n onTogglePin: togglePin,\n onReorderPin: reorderPin,\n collapsedByGroupId,\n onGroupCollapsedChange: setCollapsed,\n });\n }, [\n basePath,\n collapsedByGroupId,\n entities,\n pathname,\n permissions,\n pins,\n reorderPin,\n setCollapsed,\n sidebar,\n sidebarQuery,\n t,\n tApp,\n togglePin,\n ]);\n\n const environment = useMemo(() => {\n const meta = import.meta as unknown as { env?: Record<string, unknown> };\n if (meta.env?.DEV === true) {\n return 'dev' as const;\n }\n return 'prod' as const;\n }, []);\n\n const handleSignOut = useCallback(() => {\n if (isSigningOut) {\n return;\n }\n\n type LogoutMutation = MutationParameters & {\n response: LogoutResponse;\n variables: LogoutVariables;\n };\n\n setIsSigningOut(true);\n\n const runSignOut = async (): Promise<void> => {\n try {\n const config = await authConfig.logout.load();\n await new Promise<void>((resolve, reject) => {\n commitMutation<LogoutMutation>(relayEnvironment, {\n mutation: config.logoutMutation,\n variables: {},\n onCompleted: () => {\n resolve();\n },\n onError: (error) => {\n reject(error);\n },\n });\n });\n localStorage.removeItem('auth_token');\n localStorage.removeItem('remember_me');\n resetRelayStore();\n routing?.history.push({ pathname: getBackofficeLoginPath(basePath) });\n } finally {\n setIsSigningOut(false);\n }\n };\n\n runSignOut().catch(() => {\n /* noop */\n });\n }, [authConfig.logout, basePath, isSigningOut, relayEnvironment, routing]);\n\n const viewer = authStatus?.me ?? null;\n const sidebarProfile = useMemo(() => {\n return mapViewerToSidebarProfileView({\n viewer,\n unknownUserLabel: t('sidebar.profile.unknownUser'),\n });\n }, [t, viewer]);\n\n const sidebarFooter = (\n <BackofficeSidebarProfileMenu\n collapsed={isSidebarCollapsed}\n viewer={sidebarProfile}\n labels={{\n sectionTitle: t('sidebar.profile.title'),\n menuAriaLabel: t('sidebar.profile.menuAriaLabel'),\n signOut: t('sidebar.profile.actions.signOut'),\n }}\n onSignOut={handleSignOut}\n isSigningOut={isSigningOut}\n />\n );\n\n let contentNode: JSX.Element | null = null;\n if (topbarTarget != null) {\n contentNode = (\n <BackofficeContentBoundary>{children}</BackofficeContentBoundary>\n );\n }\n\n return (\n <ToastProvider>\n <AdminShellLayout\n sidebar={{\n sections,\n header: <EnvironmentBadge environment={environment} />,\n search: (\n <GlobalSearchInput\n value={sidebarQuery}\n onChange={setSidebarQuery}\n placeholder={t('sidebar.search.placeholder')}\n ariaLabel={t('sidebar.search.placeholder')}\n />\n ),\n footer: sidebarFooter,\n isCollapsed: isSidebarCollapsed,\n onCollapsedChange: setIsSidebarCollapsed,\n }}\n topbar={{\n breadcrumb: <div ref={setTopbarTarget} />,\n }}\n >\n <BackofficeTopbarPortalContextProvider value={{ target: topbarTarget }}>\n {contentNode}\n </BackofficeTopbarPortalContextProvider>\n </AdminShellLayout>\n </ToastProvider>\n );\n};\n\ntype LayoutWithPermissionsProps = {\n children: ReactNode;\n permissionsQuery: GraphQLTaggedNode;\n prepared: PreloadedQuery<OperationType>;\n authStatus?: {\n isLoggedIn?: boolean | null;\n me?: BackofficeViewerIdentity | null;\n } | null;\n activeGroupId?: string | null;\n};\n\nconst LayoutWithPermissions = ({\n children,\n permissionsQuery,\n prepared,\n authStatus,\n activeGroupId,\n}: LayoutWithPermissionsProps): JSX.Element => {\n const permissions = usePreloadedQuery(permissionsQuery, prepared);\n\n return (\n <BackofficeLayoutShell\n permissions={permissions}\n authStatus={authStatus}\n activeGroupId={activeGroupId}\n >\n {children}\n </BackofficeLayoutShell>\n );\n};\n\nexport const BackofficeLayoutPage = ({\n children,\n permissionsQuery,\n prepared,\n authStatus,\n activeGroupId,\n}: BackofficeLayoutPageProps): JSX.Element => {\n if (permissionsQuery != null && prepared != null) {\n return (\n <LayoutWithPermissions\n permissionsQuery={permissionsQuery}\n prepared={prepared}\n authStatus={authStatus}\n activeGroupId={activeGroupId}\n >\n {children}\n </LayoutWithPermissions>\n );\n }\n\n return (\n <BackofficeLayoutShell\n permissions={null}\n authStatus={authStatus}\n activeGroupId={activeGroupId}\n >\n {children}\n </BackofficeLayoutShell>\n );\n};\n\nexport default BackofficeLayoutPage;\n"],"mappings":";;;;;;;;;;;;;;AAEA,IAAM,IAAsB,8BAItB,KAAuB,MAAiC;AAC5D,KAAI,OAAO,SAAW,IACpB,QAAO,EAAE;AAEX,KAAI;EACF,IAAM,IAAM,OAAO,aAAa,QAAQ,EAAW;AACnD,MAAI,KAAO,KACT,QAAO,EAAE;EAEX,IAAM,IAAS,KAAK,MAAM,EAAI;AAI9B,SAHK,MAAM,QAAQ,EAAO,GAGnB,EAAO,QAAQ,MACb,OAAO,KAAU,SACxB,GAJO,EAAE;SAKL;AACN,SAAO,EAAE;;GAIP,KACJ,GACA,MACa;CACb,IAAM,IAAmB,EAAE,EACrB,oBAAO,IAAI,KAAa;AAW9B,QAVA,EAAK,SAAS,MAAU;AACjB,IAAS,IAAI,EAAM,KAGpB,EAAK,IAAI,EAAM,KAGnB,EAAK,IAAI,EAAM,EACf,EAAO,KAAK,EAAM;GAClB,EACK;GAiBI,MACX,MACqB;CACrB,IAAM,EAAE,gBAAa,GAAqB,wBAAqB,GACzD,IAAW,QACR,IAAI,IAAI,EAAiB,EAC/B,CAAC,EAAiB,CAAC,EAEhB,CAAC,GAAM,KAAW,QAEf,EADQ,EAAoB,EAAW,EACjB,EAAS,CACtC;AA0BF,CAxBA,QAAgB;AACd,KAAS,MAAS;GAChB,IAAM,IAAa,EAAc,GAAM,EAAS;AAChD,OAAI,EAAW,WAAW,EAAK,QAAQ;IACrC,IAAI,IAAY;AAChB,SAAK,IAAI,IAAQ,GAAG,IAAQ,EAAW,QAAQ,KAAS,EACtD,KAAI,EAAW,OAAW,EAAK,IAAQ;AACrC,SAAY;AACZ;;AAGJ,QAAI,EACF,QAAO;;AAGX,UAAO;IACP;IACD,CAAC,EAAS,CAAC,EAEd,QAAgB;AAEd,IAAQ,EADO,EAAoB,EAAW,EAChB,EAAS,CAAC;IACvC,CAAC,GAAY,EAAS,CAAC,EAE1B,QAAgB;AACV,eAAO,SAAW,KAGtB,KAAI;AACF,UAAO,aAAa,QAAQ,GAAY,KAAK,UAAU,EAAK,CAAC;UACvD;IAGP,CAAC,GAAM,EAAW,CAAC;CAEtB,IAAM,IAAU,QACP,IAAI,IAAI,EAAK,EACnB,CAAC,EAAK,CAAC,EAEJ,IAAM,GACT,MAAe;AACT,IAAS,IAAI,EAAG,IAGrB,GAAS,MACH,EAAK,SAAS,EAAG,GACZ,IAEF,CAAC,GAAG,GAAM,EAAG,CACpB;IAEJ,CAAC,EAAS,CACX,EAEK,IAAQ,GAAa,MAAe;AACxC,KAAS,MACA,EAAK,QAAQ,MACX,MAAU,EACjB,CACF;IACD,EAAE,CAAC,EAEA,IAAS,GACZ,MAAe;AACd,EAAI,EAAQ,IAAI,EAAG,GACjB,EAAM,EAAG,GAET,EAAI,EAAG;IAGX;EAAC;EAAK;EAAS;EAAM,CACtB,EAEK,IAAU,GAAa,GAAgB,MAAiB;AACxD,QAAW,KAGf,GAAS,MAAS;GAChB,IAAM,IAAY,EAAK,QAAQ,EAAO,EAChC,IAAU,EAAK,QAAQ,EAAK;AAIlC,OAHI,MAAc,MAAM,MAAY,MAGhC,MAAc,EAChB,QAAO;GAET,IAAM,IAAO,CAAC,GAAG,EAAK;AAGtB,UAFA,EAAK,OAAO,GAAW,EAAE,EACzB,EAAK,OAAO,GAAS,GAAG,EAAO,EACxB;IACP;IACD,EAAE,CAAC;AASN,QAAO;EACL;EACA,UATe,GACd,MACQ,EAAQ,IAAI,EAAG,EAExB,CAAC,EAAQ,CACV;EAKC;EACA;EACA;EACA;EACD;GC3KG,KACJ,GACA,MAC8B;CAC9B,IAAM,IAAmC,EAAE;AAO3C,QANA,EAAS,SAAS,MAAY;AAC5B,IAAM,KAAW;GACjB,EACE,KAAiB,QAAQ,EAAS,SAAS,EAAc,KAC3D,EAAM,KAAiB,KAElB;GAGI,MACX,MAIG;CACH,IAAM,EAAE,aAAU,qBAAkB,GAE9B,IAAc,QACX,CAAC,GAAG,EAAS,EACnB,CAAC,EAAS,CAAC,EAER,CAAC,GAAoB,KACzB,QACS,EAAkB,GAAa,EAAc,CACpD;AA8CJ,QA5CA,QAAgB;AACd,KAAuB,MAAS;GAC9B,IAAM,IAAkC,EAAE;AAW1C,UATA,EAAY,SAAS,MAAY;AAE/B,MAAK,KADY,EAAK,MACM;KAC5B,EAEE,KAAiB,QAAQ,EAAY,SAAS,EAAc,KAC9D,EAAK,KAAiB,KAGjB;IACP;IACD,CAAC,GAAe,EAAY,CAAC,EAEhC,QAAgB;AACV,OAAiB,QAGrB,GAAuB,MACjB,EAAK,OAAmB,KACnB,IAEF;GACL,GAAG;IACF,IAAgB;GAClB,CACD;IACD,CAAC,EAAc,CAAC,EAcZ;EACL;EACA,cAdmB,GAAa,GAAiB,MAAuB;AACxE,MAAuB,MACjB,EAAK,OAAa,IACb,IAEF;IACL,GAAG;KACF,IAAU;IACZ,CACD;KACD,EAAE,CAAC;EAKL;oKE5DG,KACJ,GACA,MAEI,KAAQ,OAGL,KAAY,OAFV,kBAAC,GAAD;CAAM,OAAO;CAAI,QAAQ;CAAI,eAAY;CAAS,CAAA;AAwB7D,SAAgB,GACd,GACgC;CAChC,IAAM,EACJ,aACA,aACA,aACA,YACA,gBACA,gBACA,SACA,MACA,qBAAkB,EAAE,EACpB,gBACA,iBACA,uBACA,8BACE,GAEE,IAAS,EAAqB,GAAU,EAAQ,EAChD,IAAU,OAAO,QAAQ,EAAO,EAChC,IAAY,IAAI,IAAI,EAAgB,EACpC,IAAW,EAAE,sBAAsB,EACnC,IAAa,EAAE,wBAAwB,EACvC,IAAe,EAAE,0BAA0B,EAC3C,KAAoB,EAAuB,EAAO,EAClD,IAAkB,GAAa,MAAM,CAAC,aAAa,IAAI,IAEvD,KAAmB,MAAuC;AAC9D,MAAI,KAAe,KACjB,QAAO;EAET,IAAM,IAAW,EAAU,IAAI,EAAS,EACpC,IAAQ,GACR,IAAO;AAMX,SALI,MACF,IAAQ,GACR,IAAO,IAIP,kBAAC,UAAD;GACE,MAAK;GACL,WAAW;GACX,gBAAc;GACd,cAAY;GACZ,OAAO;GACP,UAAU,MAAU;AAGlB,IAFA,EAAM,gBAAgB,EACtB,EAAM,iBAAiB,EACvB,EAAY,EAAS;;aAGvB,kBAAC,GAAD;IAAM,OAAO;IAAI,QAAQ;IAAI,eAAY;IAAS,CAAA;GAC3C,CAAA;IAIP,KAAmB,MAKW;EAClC,IAAM,EAAE,aAAU,YAAS,cAAW,qBAAkB,GAClD,IAAS,EAAS;AACxB,MAAI,KAAU,KACZ,QAAO;EAGT,IAAI,IAA8C;GAChD,MAAM;GACN,IAAI;GACL;AAKD,MAJI,EAAO,SAAS,WAClB,IAAa;GAAE,MAAM;GAAQ,IAAI;GAAU,GAErB,GAAS,gBAAgB,GAAY,EAAY,KACjD,GACtB,QAAO;AAGT,MAAI,EAAO,SAAS,QAAQ;GAC1B,IAAM,IAAQ,EAAa,EAAO,OAAO,EAAK;AAO9C,UALE,MAAoB,MACpB,CAAC,EAAM,aAAa,CAAC,SAAS,EAAgB,GAEvC,OAEF;IACL,IAAI,QAAQ;IACZ,MAAM;KACJ,MAAM;KACN,IAAI;KACJ;KACD;IACD;IACA,MAAM,EAAO,OAAO;IACpB,MAAM,EACJ,GACA,kBAAC,GAAD;KAAiB,OAAO;KAAI,QAAQ;KAAI,eAAY;KAAS,CAAA,CAC9D;IACD,UAAU,EAAa,GAAU,EAAO,OAAO,KAAK;IACpD,WAAW;IACX,YAAY,EAAgB,EAAS;IACtC;;AAGH,MAAI,CAAC,EAAO,QACV,QAAO;EAGT,IAAM,IAAQ,EAAa,EAAO,OAAO,EAAK;AAC9C,MACE,MAAoB,MACpB,CAAC,EAAM,aAAa,CAAC,SAAS,EAAgB,CAE9C,QAAO;EAGT,IAAI,GACA,GACA,GACA,GACA,IAAY;AAEhB,EAAI,MAAkB,MAAQ,KAAgB,SAC5C,IAAY,IACZ,IACE,kBAAC,GAAD;GAAa,OAAO;GAAI,QAAQ;GAAI,eAAY;GAAS,CAAA,EAE3D,KAAe,MAAU;GACvB,IAAM,EAAE,oBAAiB;AAEzB,GADA,EAAa,gBAAgB,QAC7B,EAAa,QAAQ,cAAc,EAAS;KAE9C,KAAc,MAAU;AACtB,KAAM,gBAAgB;GACtB,IAAM,EAAE,oBAAiB;AACzB,KAAa,aAAa;KAE5B,KAAU,MAAU;AAClB,KAAM,gBAAgB;GACtB,IAAM,IAAS,EAAM,aAAa,QAAQ,aAAa;AACnD,SAAW,MAAM,MAAW,KAGhC,EAAa,GAAQ,EAAS;;EAIlC,IAAI;AAKJ,SAJI,KAAkB,SACpB,IAAkB,IAGb;GACL,IAAI;GACJ,MAAM;IACJ,MAAM;IACN,IAAI;IACJ;IACD;GACD;GACA,MAAM,EAAO,OAAO;GACpB,MAAM,EACJ,GACA,kBAAC,GAAD;IAAiB,OAAO;IAAI,QAAQ;IAAI,eAAY;IAAS,CAAA,CAC9D;GACD,UAAU,EAAa,GAAU,EAAO,OAAO,KAAK;GACpD,WAAW;GACX,YAAY,EAAgB,EAAS;GACrC;GACA;GACA;GACA;GACA;GACA;GACD;IAGG,IAAkC,EAAE;AAE1C,KAAI,EAAgB,SAAS,GAAG;EAC9B,IAAM,IAAc,EACjB,KAAK,MAAa;GACjB,IAAM,IAAY,GAAkB,IAAI,EAAS;AACjD,UAAO,EAAgB;IACrB;IACA,SAAS,GAAW;IACpB,WAAW,GAAW;IACtB,eAAe;IAChB,CAAC;IACF,CACD,QAAQ,MACA,KAAQ,KACf;AAEJ,EAAI,EAAY,SAAS,KACvB,EAAS,KAAK;GACZ,IAAI;GACJ,OAAO,EAAE,0BAA0B;GACnC,OAAO;GACP,aAAa;GACd,CAAC;;AAwFN,QApFA,EAAQ,SAAS,CAAC,GAAS,IAAQ,MAAU;AAC3C,MAAI,EAAM,aAAa,QAAQ,CAAC,EAAM,UAAU,EAAY,CAC1D;EAGF,IAAM,IAAiC,EAAE;AAEzC,MAAI,MAAU,KAKe,GAAS,gBAJyB;GAC3D,MAAM;GACN,IAAI;GACL,EAGC,EACD,KAC0B,IAAO;GAChC,IAAM,IAAiB,EAAE,0BAA0B;AACnD,OACE,MAAoB,MACpB,CAAC,EAAe,aAAa,CAAC,SAAS,EAAgB,CAEvD;AAEF,KAAM,KAAK;IACT,IAAI;IACJ,MAAM;KACJ,MAAM;KACN,IAAI;KACJ;KACD;IACD,OAAO;IACP,MAAM;IACN,MAAM,EACJ,KAAA,GACA,kBAAC,GAAD;KAAgB,OAAO;KAAI,QAAQ;KAAI,eAAY;KAAS,CAAA,CAC7D;IACD,UAAU,EAAa,GAAU,EAAS;IAC1C,WAAW,EAAE,0BAA0B;IACxC,CAAC;;AAiBN,MAbI,EAAM,YAAY,QACpB,EAAM,SAAS,SAAS,MAAa;GACnC,IAAM,IAAO,EAAgB;IAC3B;IACA;IACA,WAAW,EAAM;IAClB,CAAC;AACF,GAAI,KAAQ,QACV,EAAM,KAAK,EAAK;IAElB,EAGA,EAAM,WAAW,EACnB;EAGF,IAAI;AACJ,EAAI,EAAM,SAAS,SACjB,IAAQ,EAAa,EAAM,OAAO,EAAK;EAGzC,IAAM,IAAc,IAAqB,IACrC;AAOJ,EANI,KAA0B,SAC5B,KAAqB,MAAuB;AAC1C,KAAuB,GAAS,EAAU;MAI9C,EAAS,KAAK;GACZ,IAAI;GACJ;GACA;GACA,aAAa;GACb,kBAAkB;GAClB;GACA;GACD,CAAC;GACF,EAEK;;;;yDEjVH,KAAuB,MAAkC;AAC7D,KAAI,aAAiB,OAAO;EAC1B,IAAM,IAAU,EAAM,QAAQ,MAAM;AAIpC,SAHI,EAAQ,SAAS,IACZ,IAEF;;AAET,KAAI,OAAO,KAAU,UAAU;EAC7B,IAAM,IAAU,EAAM,MAAM;AAI5B,SAHI,EAAQ,SAAS,IACZ,IAEF;;AAET,QAAO;GAGI,KAA0B,EACrC,UACA,iBAC8C;CAC9C,IAAM,EAAE,SAAM,GAA+B,EACvC,IAAc,EAAoB,EAAM;AAE9C,QACE,kBAAC,OAAD;EAAK,WAAW;EAAa,MAAK;YAChC,kBAAC,GAAD;GACE,MAAK;GACL,WAAW;GACX,SACE,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,MAAK;IACL,SAAS;cAER,EAAE,uBAAuB;IACnB,CAAA;aAGV;GACY,CAAA;EACX,CAAA;4NEjDG,UAET,kBAAC,OAAD;CACE,WAAW;CACX,MAAK;CACL,aAAU;CACV,aAAU;WAJZ;EAME,kBAAC,GAAD;GAAU,SAAQ;GAAO,OAAM;GAAM,WAAW;GAAgB,CAAA;EAChE,kBAAC,GAAD;GAAU,SAAQ;GAAO,OAAM;GAAQ,CAAA;EACvC,kBAAC,OAAD;GAAK,WAAW;aACb,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,GAAG,MAE3B,kBAAC,OAAD;IAAuC,WAAW;cAAlD;KACE,kBAAC,GAAD;MAAU,SAAQ;MAAO,OAAM;MAAQ,CAAA;KACvC,kBAAC,GAAD;MAAU,SAAQ;MAAO,OAAM;MAAM,OAAO;MAAK,CAAA;KACjD,kBAAC,GAAD;MAAU,SAAQ;MAAQ,OAAM;MAAO,QAAQ;MAAO,CAAA;KAClD;MAJI,oBAAoB,IAIxB,CAER;GACE,CAAA;EACF;6DEfG,MAA6B,EACxC,kBAGE,kBAAC,OAAD;CAAK,WAAW;WACd,kBAAC,GAAD;EACE,WAAW,EAAE,UAAO,eACX,kBAAC,GAAD;GAA+B;GAAO,SAAS;GAAS,CAAA;YAGjE,kBAAC,GAAD;GAAU,UAAU,kBAAC,GAAD,EAA6B,CAAA;GAAG;GAAoB,CAAA;EAChD,CAAA;CACtB,CAAA,ECTJ,KAAiB,MACd,GAAO,MAAM,IAAI,IAGb,MAAiC,EAC5C,WACA,0BACwE;CASxE,IAAI,IANsB,CAFR,EAAc,GAAQ,UAAU,EACjC,EAAc,GAAQ,SAAS,CACD,CAC5C,QAAQ,MACA,MAAU,GACjB,CACD,KAAK,IAAI,CACT,MAAM;AAET,CAAI,MAAgB,OAClB,IAAc;CAGhB,IAAM,IAAQ,EAAc,GAAQ,MAAM,EAEtC,IADkB,EAAc,GAAQ,SAAS;AAErD,CAAI,MAAa,OACf,IAAW;CAGb,IAAM,IAAY,CAAC,EAAY;AAK/B,QAJI,MAAU,MACZ,EAAU,KAAK,EAAM,EAGhB;EACL;EACA;EACA;EACA,WAAW,EAAU,KAAK,MAAM;EACjC;GCiBG,KAAyB,EAC7B,aACA,gBACA,eACA,uBACmC;CACnC,IAAM,EAAE,GAAG,MAAS,GAAgB,EAC9B,EAAE,SAAM,GAA+B,EACvC,EAAE,gBAAa,GAAa,EAC5B,IAAU,EAAW,EAAe,EACpC,IAAmB,GAAqB,EACxC,EACJ,MAAM,GACN,aACA,aACA,eACE,GAAqB,EACnB,CAAC,GAAc,KAAmB,EAAS,GAAG,EAC9C,CAAC,GAAoB,KAAyB,EAAS,GAAM,EAC7D,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAc,KAAmB,EAAgC,KAAK,EAEvE,IAAS,QACN,EAAqB,GAAU,EAAQ,EAC7C,CAAC,GAAU,EAAQ,CAAC,EAEjB,IAAW,QACR,OAAO,KAAK,EAAO,EACzB,CAAC,EAAO,CAAC,EAMN,EACJ,SACA,QAAQ,GACR,SAAS,MACP,GAAyB,EAAE,kBARN,QAChB,EAAwB,GAAQ,GAAU,GAAS,EAAY,EACrE;EAAC;EAAU;EAAQ;EAAa;EAAQ,CAAC,EAMK,CAAC,EAE5C,EAAE,uBAAoB,oBAAiB,GAAwB;EACnE;EACA;EACD,CAAC,EAEI,IAAW,QACR,GAAqB;EAC1B;EACA;EACA;EACA;EACA;EACA,aAAa;EACb;EACA;EACA,iBAAiB;EACjB,aAAa;EACb,cAAc;EACd;EACA,wBAAwB;EACzB,CAAC,EACD;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,EAEI,IAAc,QACL,OAAO,KACX,KAAK,QAAQ,KACb,QAEF,QACN,EAAE,CAAC,EAEA,IAAgB,QAAkB;AAClC,QASJ,EAAgB,GAAK,GAEF,YAA2B;AAC5C,OAAI;IACF,IAAM,IAAS,MAAM,EAAW,OAAO,MAAM;AAgB7C,IAfA,MAAM,IAAI,SAAe,GAAS,MAAW;AAC3C,QAA+B,GAAkB;MAC/C,UAAU,EAAO;MACjB,WAAW,EAAE;MACb,mBAAmB;AACjB,UAAS;;MAEX,UAAU,MAAU;AAClB,SAAO,EAAM;;MAEhB,CAAC;MACF,EACF,aAAa,WAAW,aAAa,EACrC,aAAa,WAAW,cAAc,EACtC,GAAiB,EACjB,GAAS,QAAQ,KAAK,EAAE,UAAU,EAAuB,EAAS,EAAE,CAAC;aAC7D;AACR,MAAgB,GAAM;;MAId,CAAC,YAAY,GAEvB;IACD;EAAC,EAAW;EAAQ;EAAU;EAAc;EAAkB;EAAQ,CAAC,EAEpE,IAAS,GAAY,MAAM,MAQ3B,IACJ,kBAAC,GAAD;EACE,WAAW;EACX,QAVmB,QACd,GAA8B;GACnC;GACA,kBAAkB,EAAE,8BAA8B;GACnD,CAAC,EACD,CAAC,GAAG,EAAO,CAAC;EAMX,QAAQ;GACN,cAAc,EAAE,wBAAwB;GACxC,eAAe,EAAE,gCAAgC;GACjD,SAAS,EAAE,kCAAkC;GAC9C;EACD,WAAW;EACG;EACd,CAAA,EAGA,IAAkC;AAOtC,QANI,KAAgB,SAClB,IACE,kBAAC,IAAD,EAA4B,aAAqC,CAAA,GAKnE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EACE,SAAS;GACP;GACA,QAAQ,kBAAC,IAAD,EAA+B,gBAAe,CAAA;GACtD,QACE,kBAAC,GAAD;IACE,OAAO;IACP,UAAU;IACV,aAAa,EAAE,6BAA6B;IAC5C,WAAW,EAAE,6BAA6B;IAC1C,CAAA;GAEJ,QAAQ;GACR,aAAa;GACb,mBAAmB;GACpB;EACD,QAAQ,EACN,YAAY,kBAAC,OAAD,EAAK,KAAK,GAAmB,CAAA,EAC1C;YAED,kBAAC,GAAD;GAAuC,OAAO,EAAE,QAAQ,GAAc;aACnE;GACqC,CAAA;EACvB,CAAA,EACL,CAAA;GAed,MAAyB,EAC7B,aACA,qBACA,aACA,eACA,uBAKE,kBAAC,GAAD;CACe,aAJG,EAAkB,GAAkB,EAAS;CAKjD;CACG;CAEd;CACqB,CAAA,EAIf,MAAwB,EACnC,aACA,qBACA,aACA,eACA,uBAEI,KAAoB,QAAQ,KAAY,OAExC,kBAAC,IAAD;CACoB;CACR;CACE;CACG;CAEd;CACqB,CAAA,GAK1B,kBAAC,GAAD;CACE,aAAa;CACD;CACG;CAEd;CACqB,CAAA"}
1
+ {"version":3,"file":"BackofficeLayoutPage-DtFDn_nU.js","names":[],"sources":["../../src/hooks/useBackofficeSidebarPins.ts","../../src/hooks/useSidebarGroupCollapse.ts","../../src/components/backoffice/layout/backofficeSidebarActions.css.ts","../../src/components/backoffice/layout/buildSidebarSections.tsx","../../src/components/backoffice/routing/backofficeContentError.css.ts","../../src/components/backoffice/routing/BackofficeContentError.tsx","../../src/components/backoffice/routing/backofficeContentFallback.css.ts","../../src/components/backoffice/routing/BackofficeContentFallback.tsx","../../src/components/backoffice/routing/backofficeContentBoundary.css.ts","../../src/components/backoffice/routing/BackofficeContentBoundary.tsx","../../src/components/backoffice/layout/mapViewerToSidebarProfileView.ts","../../src/pages/BackofficeLayoutPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\n\nconst DEFAULT_STORAGE_KEY = 'backoffice.sidebar.pins.v1';\n\ntype PinsPayload = unknown;\n\nconst readPinsFromStorage = (storageKey: string): string[] => {\n if (typeof window === 'undefined') {\n return [];\n }\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (raw == null) {\n return [];\n }\n const parsed = JSON.parse(raw) as PinsPayload;\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter((entry): entry is string => {\n return typeof entry === 'string';\n });\n } catch {\n return [];\n }\n};\n\nconst normalizePins = (\n pins: readonly string[],\n validIds: Set<string>,\n): string[] => {\n const output: string[] = [];\n const seen = new Set<string>();\n pins.forEach((entry) => {\n if (!validIds.has(entry)) {\n return;\n }\n if (seen.has(entry)) {\n return;\n }\n seen.add(entry);\n output.push(entry);\n });\n return output;\n};\n\nexport type SidebarPinsState = {\n pins: readonly string[];\n isPinned: (id: string) => boolean;\n pin: (id: string) => void;\n unpin: (id: string) => void;\n toggle: (id: string) => void;\n reorder: (fromId: string, toId: string) => void;\n};\n\nexport type UseBackofficeSidebarPinsInput = {\n storageKey?: string;\n visibleEntityIds: readonly string[];\n};\n\nexport const useBackofficeSidebarPins = (\n input: UseBackofficeSidebarPinsInput,\n): SidebarPinsState => {\n const { storageKey = DEFAULT_STORAGE_KEY, visibleEntityIds } = input;\n const validIds = useMemo(() => {\n return new Set(visibleEntityIds);\n }, [visibleEntityIds]);\n\n const [pins, setPins] = useState<string[]>(() => {\n const stored = readPinsFromStorage(storageKey);\n return normalizePins(stored, validIds);\n });\n\n useEffect(() => {\n setPins((prev) => {\n const normalized = normalizePins(prev, validIds);\n if (normalized.length === prev.length) {\n let unchanged = true;\n for (let index = 0; index < normalized.length; index += 1) {\n if (normalized[index] !== prev[index]) {\n unchanged = false;\n break;\n }\n }\n if (unchanged) {\n return prev;\n }\n }\n return normalized;\n });\n }, [validIds]);\n\n useEffect(() => {\n const stored = readPinsFromStorage(storageKey);\n setPins(normalizePins(stored, validIds));\n }, [storageKey, validIds]);\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(pins));\n } catch {\n // Ignore storage errors in non-browser or restricted environments.\n }\n }, [pins, storageKey]);\n\n const pinsSet = useMemo(() => {\n return new Set(pins);\n }, [pins]);\n\n const pin = useCallback(\n (id: string) => {\n if (!validIds.has(id)) {\n return;\n }\n setPins((prev) => {\n if (prev.includes(id)) {\n return prev;\n }\n return [...prev, id];\n });\n },\n [validIds],\n );\n\n const unpin = useCallback((id: string) => {\n setPins((prev) => {\n return prev.filter((entry) => {\n return entry !== id;\n });\n });\n }, []);\n\n const toggle = useCallback(\n (id: string) => {\n if (pinsSet.has(id)) {\n unpin(id);\n } else {\n pin(id);\n }\n },\n [pin, pinsSet, unpin],\n );\n\n const reorder = useCallback((fromId: string, toId: string) => {\n if (fromId === toId) {\n return;\n }\n setPins((prev) => {\n const fromIndex = prev.indexOf(fromId);\n const toIndex = prev.indexOf(toId);\n if (fromIndex === -1 || toIndex === -1) {\n return prev;\n }\n if (fromIndex === toIndex) {\n return prev;\n }\n const next = [...prev];\n next.splice(fromIndex, 1);\n next.splice(toIndex, 0, fromId);\n return next;\n });\n }, []);\n\n const isPinned = useCallback(\n (id: string) => {\n return pinsSet.has(id);\n },\n [pinsSet],\n );\n\n return {\n pins,\n isPinned,\n pin,\n unpin,\n toggle,\n reorder,\n };\n};\n\nexport const __test = {\n normalizePins,\n readPinsFromStorage,\n};\n\nexport default useBackofficeSidebarPins;\n","import { useCallback, useEffect, useMemo, useState } from 'react';\n\nexport type SidebarGroupCollapseState = Record<string, boolean | undefined>;\n\nexport type UseSidebarGroupCollapseInput = {\n groupIds: readonly string[];\n activeGroupId?: string | null;\n};\n\nconst buildInitialState = (\n groupIds: readonly string[],\n activeGroupId?: string | null,\n): SidebarGroupCollapseState => {\n const state: SidebarGroupCollapseState = {};\n groupIds.forEach((groupId) => {\n state[groupId] = true;\n });\n if (activeGroupId != null && groupIds.includes(activeGroupId)) {\n state[activeGroupId] = false;\n }\n return state;\n};\n\nexport const useSidebarGroupCollapse = (\n input: UseSidebarGroupCollapseInput,\n): {\n collapsedByGroupId: SidebarGroupCollapseState;\n setCollapsed: (groupId: string, collapsed: boolean) => void;\n} => {\n const { groupIds, activeGroupId } = input;\n\n const groupIdList = useMemo(() => {\n return [...groupIds];\n }, [groupIds]);\n\n const [collapsedByGroupId, setCollapsedByGroupId] =\n useState<SidebarGroupCollapseState>(() => {\n return buildInitialState(groupIdList, activeGroupId);\n });\n\n useEffect(() => {\n setCollapsedByGroupId((prev) => {\n const next: SidebarGroupCollapseState = {};\n\n groupIdList.forEach((groupId) => {\n const existing = prev[groupId];\n next[groupId] = existing ?? true;\n });\n\n if (activeGroupId != null && groupIdList.includes(activeGroupId)) {\n next[activeGroupId] = false;\n }\n\n return next;\n });\n }, [activeGroupId, groupIdList]);\n\n useEffect(() => {\n if (activeGroupId == null) {\n return;\n }\n setCollapsedByGroupId((prev) => {\n if (prev[activeGroupId] === false) {\n return prev;\n }\n return {\n ...prev,\n [activeGroupId]: false,\n };\n });\n }, [activeGroupId]);\n\n const setCollapsed = useCallback((groupId: string, collapsed: boolean) => {\n setCollapsedByGroupId((prev) => {\n if (prev[groupId] === collapsed) {\n return prev;\n }\n return {\n ...prev,\n [groupId]: collapsed,\n };\n });\n }, []);\n\n return {\n collapsedByGroupId,\n setCollapsed,\n };\n};\n\nexport default useSidebarGroupCollapse;\n","import { style } from '@vanilla-extract/css';\n\nimport { sprinkles, vars } from '@plumile/ui';\n\nexport const actionButton = style([\n sprinkles({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 6,\n height: 6,\n borderRadius: 'md',\n borderWidth: 0,\n borderStyle: 'none',\n padding: 0,\n backgroundColor: 'transparent',\n color: 'textSecondary',\n cursor: 'pointer',\n transitionProperty: 'colors',\n transitionDuration: 150,\n transitionTimingFunction: 'ease',\n }),\n {\n selectors: {\n '&:hover': {\n backgroundColor: vars.colors.surfaceMuted,\n color: vars.colors.text,\n },\n '&:focus-visible': {\n outline: `2px solid ${vars.colors['blue-500']}`,\n outlineOffset: 2,\n },\n },\n },\n]);\n","import { type DragEvent, type ReactNode } from 'react';\nimport type { TFunction } from 'i18next';\n\nimport type { BackofficeEntityManifestMap } from '@plumile/backoffice-core/types.js';\nimport type {\n BackofficeSidebarConfig,\n BackofficeSidebarItemDescriptor,\n BackofficeIconComponent,\n} from '../../../provider/types.js';\nimport {\n type AdminSidebarSection,\n type SidebarNavSectionItem,\n GripDotsSvg,\n PinFilledSvg,\n PinSvg,\n SidebarHomeSvg,\n SidebarTasksSvg,\n} from '@plumile/ui';\nimport type { SidebarGroupCollapseState } from '../../../hooks/useSidebarGroupCollapse.js';\nimport * as styles from './backofficeSidebarActions.css.js';\nimport {\n buildEntityGroupLookup,\n isActivePath,\n resolveLabel,\n resolveSidebarGroups,\n} from './sidebarUtils.js';\n\nconst renderIcon = (\n Icon?: BackofficeIconComponent,\n fallback?: ReactNode,\n): ReactNode => {\n if (Icon != null) {\n return <Icon width={18} height={18} aria-hidden=\"true\" />;\n }\n return fallback ?? null;\n};\n\nexport type BuildSidebarSectionsInput = {\n basePath: string;\n pathname: string;\n entities: BackofficeEntityManifestMap;\n sidebar?: BackofficeSidebarConfig;\n permissions: unknown;\n searchQuery?: string;\n tApp: TFunction;\n t: TFunction;\n pinnedEntityIds?: readonly string[];\n onTogglePin?: (entityId: string) => void;\n onReorderPin?: (fromId: string, toId: string) => void;\n collapsedByGroupId?: SidebarGroupCollapseState;\n onGroupCollapsedChange?: (groupId: string, collapsed: boolean) => void;\n};\n\n/**\n * Builds the sidebar sections for the backoffice layout.\n */\nexport function buildSidebarSections(\n input: BuildSidebarSectionsInput,\n): readonly AdminSidebarSection[] {\n const {\n basePath,\n pathname,\n entities,\n sidebar,\n permissions,\n searchQuery,\n tApp,\n t,\n pinnedEntityIds = [],\n onTogglePin,\n onReorderPin,\n collapsedByGroupId,\n onGroupCollapsedChange,\n } = input;\n\n const groups = resolveSidebarGroups(entities, sidebar);\n const entries = Object.entries(groups);\n const pinnedSet = new Set(pinnedEntityIds);\n const pinLabel = t('sidebar.actions.pin');\n const unpinLabel = t('sidebar.actions.unpin');\n const reorderLabel = t('sidebar.actions.reorder');\n const entityGroupLookup = buildEntityGroupLookup(groups);\n const normalizedQuery = searchQuery?.trim().toLowerCase() ?? '';\n\n const renderPinAction = (entityId: string): ReactNode | null => {\n if (onTogglePin == null) {\n return null;\n }\n const isPinned = pinnedSet.has(entityId);\n let label = pinLabel;\n let Icon = PinSvg;\n if (isPinned) {\n label = unpinLabel;\n Icon = PinFilledSvg;\n }\n\n return (\n <button\n type=\"button\"\n className={styles.actionButton}\n aria-pressed={isPinned}\n aria-label={label}\n title={label}\n onClick={(event) => {\n event.preventDefault();\n event.stopPropagation();\n onTogglePin(entityId);\n }}\n >\n <Icon width={14} height={14} aria-hidden=\"true\" />\n </button>\n );\n };\n\n const buildEntityItem = (inputItem: {\n entityId: string;\n groupId?: string;\n groupIcon?: BackofficeIconComponent;\n enableReorder?: boolean;\n }): SidebarNavSectionItem | null => {\n const { entityId, groupId, groupIcon, enableReorder } = inputItem;\n const config = entities[entityId];\n if (config == null) {\n return null;\n }\n\n let descriptor: BackofficeSidebarItemDescriptor = {\n kind: 'entity',\n id: entityId,\n };\n if (config.kind === 'tool') {\n descriptor = { kind: 'tool', id: entityId };\n }\n const isEntityVisible = sidebar?.isItemVisible?.(descriptor, permissions);\n if (isEntityVisible === false) {\n return null;\n }\n\n if (config.kind === 'tool') {\n const label = resolveLabel(config.label, tApp);\n if (\n normalizedQuery !== '' &&\n !label.toLowerCase().includes(normalizedQuery)\n ) {\n return null;\n }\n return {\n id: `tool-${entityId}`,\n data: {\n kind: 'tool',\n id: entityId,\n groupId,\n },\n label,\n href: config.routes.list,\n icon: renderIcon(\n groupIcon,\n <SidebarTasksSvg width={18} height={18} aria-hidden=\"true\" />,\n ),\n isActive: isActivePath(pathname, config.routes.list),\n ariaLabel: label,\n actionSlot: renderPinAction(entityId),\n };\n }\n\n if (!config.hasList) {\n return null;\n }\n\n const label = resolveLabel(config.label, tApp);\n if (\n normalizedQuery !== '' &&\n !label.toLowerCase().includes(normalizedQuery)\n ) {\n return null;\n }\n\n let dragHandleSlot: ReactNode | undefined;\n let onDragStart: ((event: DragEvent) => void) | undefined;\n let onDragOver: ((event: DragEvent) => void) | undefined;\n let onDrop: ((event: DragEvent) => void) | undefined;\n let draggable = false;\n\n if (enableReorder === true && onReorderPin != null) {\n draggable = true;\n dragHandleSlot = (\n <GripDotsSvg width={14} height={14} aria-hidden=\"true\" />\n );\n onDragStart = (event) => {\n const { dataTransfer } = event;\n dataTransfer.effectAllowed = 'move';\n dataTransfer.setData('text/plain', entityId);\n };\n onDragOver = (event) => {\n event.preventDefault();\n const { dataTransfer } = event;\n dataTransfer.dropEffect = 'move';\n };\n onDrop = (event) => {\n event.preventDefault();\n const fromId = event.dataTransfer.getData('text/plain');\n if (fromId === '' || fromId === entityId) {\n return;\n }\n onReorderPin(fromId, entityId);\n };\n }\n\n let dragHandleLabel: string | undefined;\n if (dragHandleSlot != null) {\n dragHandleLabel = reorderLabel;\n }\n\n return {\n id: entityId,\n data: {\n kind: 'entity',\n id: entityId,\n groupId,\n },\n label,\n href: config.routes.list,\n icon: renderIcon(\n groupIcon,\n <SidebarTasksSvg width={18} height={18} aria-hidden=\"true\" />,\n ),\n isActive: isActivePath(pathname, config.routes.list),\n ariaLabel: label,\n actionSlot: renderPinAction(entityId),\n dragHandleSlot,\n dragHandleLabel,\n draggable,\n onDragStart,\n onDragOver,\n onDrop,\n };\n };\n\n const sections: AdminSidebarSection[] = [];\n\n if (pinnedEntityIds.length > 0) {\n const pinnedItems = pinnedEntityIds\n .map((entityId) => {\n const groupMeta = entityGroupLookup.get(entityId);\n return buildEntityItem({\n entityId,\n groupId: groupMeta?.groupId,\n groupIcon: groupMeta?.icon,\n enableReorder: true,\n });\n })\n .filter((item): item is SidebarNavSectionItem => {\n return item != null;\n });\n\n if (pinnedItems.length > 0) {\n sections.push({\n id: 'pinned',\n title: t('sidebar.sections.pinned'),\n items: pinnedItems,\n collapsible: false,\n });\n }\n }\n\n entries.forEach(([groupId, group], index) => {\n if (group.isVisible != null && !group.isVisible(permissions)) {\n return;\n }\n\n const items: SidebarNavSectionItem[] = [];\n\n if (index === 0) {\n const dashboardDescriptor: BackofficeSidebarItemDescriptor = {\n kind: 'dashboard',\n id: 'dashboard',\n };\n const isDashboardVisible = sidebar?.isItemVisible?.(\n dashboardDescriptor,\n permissions,\n );\n if (isDashboardVisible !== false) {\n const dashboardLabel = t('sidebar.items.dashboard');\n if (\n normalizedQuery !== '' &&\n !dashboardLabel.toLowerCase().includes(normalizedQuery)\n ) {\n return;\n }\n items.push({\n id: 'dashboard',\n data: {\n kind: 'dashboard',\n id: 'dashboard',\n groupId,\n },\n label: dashboardLabel,\n href: basePath,\n icon: renderIcon(\n undefined,\n <SidebarHomeSvg width={18} height={18} aria-hidden=\"true\" />,\n ),\n isActive: isActivePath(pathname, basePath),\n ariaLabel: t('sidebar.items.dashboard'),\n });\n }\n }\n\n if (group.entities != null) {\n group.entities.forEach((entityId) => {\n const item = buildEntityItem({\n entityId,\n groupId,\n groupIcon: group.icon,\n });\n if (item != null) {\n items.push(item);\n }\n });\n }\n\n if (items.length === 0) {\n return;\n }\n\n let title: string | undefined;\n if (group.title != null) {\n title = resolveLabel(group.title, tApp);\n }\n\n const isCollapsed = collapsedByGroupId?.[groupId];\n let onCollapsedChange: ((collapsed: boolean) => void) | undefined;\n if (onGroupCollapsedChange != null) {\n onCollapsedChange = (collapsed: boolean) => {\n onGroupCollapsedChange(groupId, collapsed);\n };\n }\n\n sections.push({\n id: groupId,\n title,\n items,\n collapsible: true,\n defaultCollapsed: true,\n isCollapsed,\n onCollapsedChange,\n });\n });\n\n return sections;\n}\n","import { sprinkles } from '@plumile/ui';\n\nexport const root = sprinkles({\n display: 'flex',\n flexDirection: 'column',\n width: 'full',\n});\n\nexport const banner = sprinkles({\n borderColor: 'borderStrong',\n});\n","import { type JSX } from 'react';\n\nimport { Button, InlineBanner } from '@plumile/ui';\nimport { useBackofficeReactTranslation } from '../../../i18n/useBackofficeReactTranslation.js';\n\nimport * as styles from './backofficeContentError.css.js';\n\ntype BackofficeContentErrorProps = {\n error: unknown;\n onRetry: () => void;\n};\n\nconst resolveErrorMessage = (error: unknown): string | null => {\n if (error instanceof Error) {\n const message = error.message.trim();\n if (message.length > 0) {\n return message;\n }\n return null;\n }\n if (typeof error === 'string') {\n const message = error.trim();\n if (message.length > 0) {\n return message;\n }\n return null;\n }\n return null;\n};\n\nexport const BackofficeContentError = ({\n error,\n onRetry,\n}: BackofficeContentErrorProps): JSX.Element => {\n const { t } = useBackofficeReactTranslation();\n const description = resolveErrorMessage(error);\n\n return (\n <div className={styles.root} role=\"alert\">\n <InlineBanner\n tone=\"danger\"\n className={styles.banner}\n actions={\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"small\"\n onClick={onRetry}\n >\n {t('common.actions.retry')}\n </Button>\n }\n >\n {description}\n </InlineBanner>\n </div>\n );\n};\n\nexport default BackofficeContentError;\n","import { sprinkles } from '@plumile/ui';\n\nexport const container = sprinkles({\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n width: 'full',\n minHeight: 'full',\n});\n\nexport const title = sprinkles({\n maxWidth: 'md',\n});\n\nexport const grid = sprinkles({\n display: 'grid',\n gap: 4,\n gridTemplateColumns: 'autoFitMinmax240',\n});\n\nexport const card = sprinkles({\n display: 'flex',\n flexDirection: 'column',\n gap: 3,\n padding: 4,\n minHeight: 44,\n borderRadius: 'xl',\n borderWidth: 'default',\n borderStyle: 'solid',\n borderColor: 'borderSubtle',\n backgroundColor: 'surfaceMuted',\n});\n","import { type JSX } from 'react';\n\nimport { Skeleton } from '@plumile/ui';\n\nimport * as styles from './backofficeContentFallback.css.js';\n\nexport const BackofficeContentFallback = (): JSX.Element => {\n return (\n <div\n className={styles.container}\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n >\n <Skeleton variant=\"text\" width=\"38%\" className={styles.title} />\n <Skeleton variant=\"text\" width=\"62%\" />\n <div className={styles.grid}>\n {Array.from({ length: 4 }, (_, index) => {\n return (\n <div key={`content-skeleton-${index}`} className={styles.card}>\n <Skeleton variant=\"text\" width=\"46%\" />\n <Skeleton variant=\"text\" width=\"82%\" lines={2} />\n <Skeleton variant=\"block\" width=\"100%\" height={120} />\n </div>\n );\n })}\n </div>\n </div>\n );\n};\n\nexport default BackofficeContentFallback;\n","import { sprinkles } from '@plumile/ui';\n\nexport const root = sprinkles({\n display: 'flex',\n flexDirection: 'column',\n minHeight: 'full',\n width: 'full',\n minWidth: 0,\n});\n","import { Suspense, type JSX, type ReactNode } from 'react';\n\nimport { BackofficeErrorBoundary } from '../errors/BackofficeErrorBoundary.js';\n\nimport { BackofficeContentError } from './BackofficeContentError.js';\nimport { BackofficeContentFallback } from './BackofficeContentFallback.js';\nimport * as styles from './backofficeContentBoundary.css.js';\n\nexport type BackofficeContentBoundaryProps = {\n children: ReactNode;\n};\n\nexport const BackofficeContentBoundary = ({\n children,\n}: BackofficeContentBoundaryProps): JSX.Element => {\n return (\n <div className={styles.root}>\n <BackofficeErrorBoundary\n fallback={({ error, reset }) => {\n return <BackofficeContentError error={error} onRetry={reset} />;\n }}\n >\n <Suspense fallback={<BackofficeContentFallback />}>{children}</Suspense>\n </BackofficeErrorBoundary>\n </div>\n );\n};\n\nexport default BackofficeContentBoundary;\n","import type { BackofficeSidebarProfileViewer } from '@plumile/ui';\n\nexport type BackofficeViewerIdentity = {\n id: string;\n firstName: string;\n lastName: string;\n email: string;\n initials: string;\n};\n\ntype MapViewerToSidebarProfileViewInput = {\n viewer: BackofficeViewerIdentity | null | undefined;\n unknownUserLabel: string;\n};\n\nconst sanitizeToken = (value: string | null | undefined): string => {\n return value?.trim() ?? '';\n};\n\nexport const mapViewerToSidebarProfileView = ({\n viewer,\n unknownUserLabel,\n}: MapViewerToSidebarProfileViewInput): BackofficeSidebarProfileViewer => {\n const firstName = sanitizeToken(viewer?.firstName);\n const lastName = sanitizeToken(viewer?.lastName);\n const joinedDisplayName = [firstName, lastName]\n .filter((token) => {\n return token !== '';\n })\n .join(' ')\n .trim();\n let displayName = joinedDisplayName;\n if (displayName === '') {\n displayName = unknownUserLabel;\n }\n\n const email = sanitizeToken(viewer?.email);\n const initialsToken = sanitizeToken(viewer?.initials);\n let initials = initialsToken;\n if (initials === '') {\n initials = '?';\n }\n\n const ariaParts = [displayName];\n if (email !== '') {\n ariaParts.push(email);\n }\n\n return {\n displayName,\n email,\n initials,\n ariaLabel: ariaParts.join(' - '),\n };\n};\n\nexport default mapViewerToSidebarProfileView;\n","import {\n useMemo,\n type JSX,\n type ReactNode,\n useCallback,\n useContext,\n useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n commitMutation,\n usePreloadedQuery,\n type PreloadedQuery,\n} from 'react-relay';\nimport type {\n GraphQLTaggedNode,\n MutationParameters,\n OperationType,\n} from 'relay-runtime';\nimport { RoutingContext, useLocation } from '@plumile/router';\n\nimport {\n AdminShellLayout,\n BackofficeSidebarProfileMenu,\n EnvironmentBadge,\n GlobalSearchInput,\n ToastProvider,\n} from '@plumile/ui';\n\nimport { useBackofficeReactTranslation } from '../i18n/useBackofficeReactTranslation.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport type { LogoutResponse, LogoutVariables } from '../hooks/useAuth.js';\nimport { useBackofficeSidebarPins } from '../hooks/useBackofficeSidebarPins.js';\nimport { useSidebarGroupCollapse } from '../hooks/useSidebarGroupCollapse.js';\nimport { buildSidebarSections } from '../components/backoffice/layout/buildSidebarSections.js';\nimport { BackofficeContentBoundary } from '../components/backoffice/routing/BackofficeContentBoundary.js';\nimport {\n resolveSidebarGroups,\n resolveVisibleEntityIds,\n} from '../components/backoffice/layout/sidebarUtils.js';\nimport { BackofficeTopbarPortalContextProvider } from '../components/backoffice/layout/breadcrumb/BackofficeTopbarPortalContext.js';\nimport {\n mapViewerToSidebarProfileView,\n type BackofficeViewerIdentity,\n} from '../components/backoffice/layout/mapViewerToSidebarProfileView.js';\nimport { resetRelayStore } from '../relay/environment.js';\nimport { useRelayEnvironment } from '../relay/useRelayEnvironment.js';\nimport { getBackofficeLoginPath } from '../router/backofficeAuthPaths.js';\n\nexport type BackofficeLayoutPageProps = {\n children: ReactNode;\n permissionsQuery?: GraphQLTaggedNode;\n prepared?: PreloadedQuery<OperationType> | null;\n authStatus?: {\n isLoggedIn?: boolean | null;\n me?: BackofficeViewerIdentity | null;\n } | null;\n activeGroupId?: string | null;\n};\n\ntype LayoutShellProps = {\n children: ReactNode;\n permissions: unknown;\n authStatus?: {\n isLoggedIn?: boolean | null;\n me?: BackofficeViewerIdentity | null;\n } | null;\n activeGroupId?: string | null;\n};\n\nconst BackofficeLayoutShell = ({\n children,\n permissions,\n authStatus,\n activeGroupId,\n}: LayoutShellProps): JSX.Element => {\n const { t: tApp } = useTranslation();\n const { t } = useBackofficeReactTranslation();\n const { pathname } = useLocation();\n const routing = useContext(RoutingContext);\n const relayEnvironment = useRelayEnvironment();\n const {\n auth: authConfig,\n basePath,\n entities,\n sidebar,\n } = useBackofficeConfig();\n const [sidebarQuery, setSidebarQuery] = useState('');\n const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);\n const [isSigningOut, setIsSigningOut] = useState(false);\n const [topbarTarget, setTopbarTarget] = useState<HTMLDivElement | null>(null);\n\n const groups = useMemo(() => {\n return resolveSidebarGroups(entities, sidebar);\n }, [entities, sidebar]);\n\n const groupIds = useMemo(() => {\n return Object.keys(groups);\n }, [groups]);\n\n const visibleEntityIds = useMemo(() => {\n return resolveVisibleEntityIds(groups, entities, sidebar, permissions);\n }, [entities, groups, permissions, sidebar]);\n\n const {\n pins,\n toggle: togglePin,\n reorder: reorderPin,\n } = useBackofficeSidebarPins({ visibleEntityIds });\n\n const { collapsedByGroupId, setCollapsed } = useSidebarGroupCollapse({\n groupIds,\n activeGroupId,\n });\n\n const sections = useMemo(() => {\n return buildSidebarSections({\n basePath,\n pathname,\n entities,\n sidebar,\n permissions,\n searchQuery: sidebarQuery,\n tApp,\n t,\n pinnedEntityIds: pins,\n onTogglePin: togglePin,\n onReorderPin: reorderPin,\n collapsedByGroupId,\n onGroupCollapsedChange: setCollapsed,\n });\n }, [\n basePath,\n collapsedByGroupId,\n entities,\n pathname,\n permissions,\n pins,\n reorderPin,\n setCollapsed,\n sidebar,\n sidebarQuery,\n t,\n tApp,\n togglePin,\n ]);\n\n const environment = useMemo(() => {\n const meta = import.meta as unknown as { env?: Record<string, unknown> };\n if (meta.env?.DEV === true) {\n return 'dev' as const;\n }\n return 'prod' as const;\n }, []);\n\n const handleSignOut = useCallback(() => {\n if (isSigningOut) {\n return;\n }\n\n type LogoutMutation = MutationParameters & {\n response: LogoutResponse;\n variables: LogoutVariables;\n };\n\n setIsSigningOut(true);\n\n const runSignOut = async (): Promise<void> => {\n try {\n const config = await authConfig.logout.load();\n await new Promise<void>((resolve, reject) => {\n commitMutation<LogoutMutation>(relayEnvironment, {\n mutation: config.logoutMutation,\n variables: {},\n onCompleted: () => {\n resolve();\n },\n onError: (error) => {\n reject(error);\n },\n });\n });\n localStorage.removeItem('auth_token');\n localStorage.removeItem('remember_me');\n resetRelayStore();\n routing?.history.push({ pathname: getBackofficeLoginPath(basePath) });\n } finally {\n setIsSigningOut(false);\n }\n };\n\n runSignOut().catch(() => {\n /* noop */\n });\n }, [authConfig.logout, basePath, isSigningOut, relayEnvironment, routing]);\n\n const viewer = authStatus?.me ?? null;\n const sidebarProfile = useMemo(() => {\n return mapViewerToSidebarProfileView({\n viewer,\n unknownUserLabel: t('sidebar.profile.unknownUser'),\n });\n }, [t, viewer]);\n\n const sidebarFooter = (\n <BackofficeSidebarProfileMenu\n collapsed={isSidebarCollapsed}\n viewer={sidebarProfile}\n labels={{\n sectionTitle: t('sidebar.profile.title'),\n menuAriaLabel: t('sidebar.profile.menuAriaLabel'),\n signOut: t('sidebar.profile.actions.signOut'),\n }}\n onSignOut={handleSignOut}\n isSigningOut={isSigningOut}\n />\n );\n\n let contentNode: JSX.Element | null = null;\n if (topbarTarget != null) {\n contentNode = (\n <BackofficeContentBoundary>{children}</BackofficeContentBoundary>\n );\n }\n\n return (\n <ToastProvider>\n <AdminShellLayout\n sidebar={{\n sections,\n header: <EnvironmentBadge environment={environment} />,\n search: (\n <GlobalSearchInput\n value={sidebarQuery}\n onChange={setSidebarQuery}\n placeholder={t('sidebar.search.placeholder')}\n ariaLabel={t('sidebar.search.placeholder')}\n />\n ),\n footer: sidebarFooter,\n isCollapsed: isSidebarCollapsed,\n onCollapsedChange: setIsSidebarCollapsed,\n }}\n topbar={{\n breadcrumb: <div ref={setTopbarTarget} />,\n }}\n >\n <BackofficeTopbarPortalContextProvider value={{ target: topbarTarget }}>\n {contentNode}\n </BackofficeTopbarPortalContextProvider>\n </AdminShellLayout>\n </ToastProvider>\n );\n};\n\ntype LayoutWithPermissionsProps = {\n children: ReactNode;\n permissionsQuery: GraphQLTaggedNode;\n prepared: PreloadedQuery<OperationType>;\n authStatus?: {\n isLoggedIn?: boolean | null;\n me?: BackofficeViewerIdentity | null;\n } | null;\n activeGroupId?: string | null;\n};\n\nconst LayoutWithPermissions = ({\n children,\n permissionsQuery,\n prepared,\n authStatus,\n activeGroupId,\n}: LayoutWithPermissionsProps): JSX.Element => {\n const permissions = usePreloadedQuery(permissionsQuery, prepared);\n\n return (\n <BackofficeLayoutShell\n permissions={permissions}\n authStatus={authStatus}\n activeGroupId={activeGroupId}\n >\n {children}\n </BackofficeLayoutShell>\n );\n};\n\nexport const BackofficeLayoutPage = ({\n children,\n permissionsQuery,\n prepared,\n authStatus,\n activeGroupId,\n}: BackofficeLayoutPageProps): JSX.Element => {\n if (permissionsQuery != null && prepared != null) {\n return (\n <LayoutWithPermissions\n permissionsQuery={permissionsQuery}\n prepared={prepared}\n authStatus={authStatus}\n activeGroupId={activeGroupId}\n >\n {children}\n </LayoutWithPermissions>\n );\n }\n\n return (\n <BackofficeLayoutShell\n permissions={null}\n authStatus={authStatus}\n activeGroupId={activeGroupId}\n >\n {children}\n </BackofficeLayoutShell>\n );\n};\n\nexport default BackofficeLayoutPage;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BackofficeLoginPage-BvOPqbKO.js","names":[],"sources":["../../src/pages/BackofficeLoginPage.tsx"],"sourcesContent":["import { useCallback, useContext, useRef, type JSX } from 'react';\nimport * as ReactRelay from 'react-relay';\nimport type { PreloadedQuery } from 'react-relay';\nimport type { OperationType } from 'relay-runtime';\nimport { useRelayEnvironment } from '../relay/useRelayEnvironment.js';\nimport { RoutingContext } from '@plumile/router';\n\nimport type { OidcProviderKind } from '../modules/sharedSchemaTypes.js';\nimport { LoginFlow } from '../auth/login/LoginFlow.js';\nimport { synchronizeAuthStatusQuery } from '../auth/login/synchronizeAuthStatusQuery.js';\nimport { useBackofficeAuth } from '../hooks/useBackofficeAuth.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport { useBackofficeAuthLoginConfig } from '../provider/useBackofficeLazyValue.js';\nimport { getBackofficePasswordResetPath } from '../router/backofficeAuthPaths.js';\n\nconst { usePreloadedQuery } = ReactRelay;\n\nexport type BackofficeLoginPageProps = {\n prepared: { query: PreloadedQuery<OperationType> };\n};\n\nexport const BackofficeLoginPage = ({\n prepared,\n}: BackofficeLoginPageProps): JSX.Element => {\n const routerContext = useContext(RoutingContext);\n const { auth: authConfig, basePath } = useBackofficeConfig();\n const auth = useBackofficeAuthLoginConfig();\n const authState = useBackofficeAuth();\n const relayEnvironment = useRelayEnvironment();\n const isPostLoginSyncInFlightRef = useRef(false);\n\n const data = usePreloadedQuery(auth.loginQuery, prepared.query);\n const oidcProviders =\n (data as { oidcProviders?: readonly OidcProviderKind[] }).oidcProviders ??\n [];\n\n const handleLoginSuccess = useCallback((): void => {\n if (isPostLoginSyncInFlightRef.current) {\n return;\n }\n\n isPostLoginSyncInFlightRef.current = true;\n const runPostLoginSync = async (): Promise<void> => {\n try {\n const sessionAuth = await authConfig.session.load();\n if (sessionAuth.authStatusQuery == null) {\n routerContext?.history.push({\n pathname: basePath,\n });\n return;\n }\n\n const isLoggedIn = await synchronizeAuthStatusQuery<OperationType>(\n relayEnvironment,\n sessionAuth.authStatusQuery,\n );\n if (!isLoggedIn) {\n return;\n }\n routerContext?.history.push({\n pathname: basePath,\n });\n } catch {\n // Keep user on login page if post-login auth sync fails.\n } finally {\n isPostLoginSyncInFlightRef.current = false;\n }\n };\n\n runPostLoginSync().catch(() => {});\n }, [authConfig.session, basePath, relayEnvironment, routerContext?.history]);\n\n const handleForgotPassword = useCallback(() => {\n routerContext?.history.push({\n pathname: getBackofficePasswordResetPath(basePath),\n });\n }, [basePath, routerContext?.history]);\n\n return (\n <LoginFlow\n auth={authState}\n oidcProviders={oidcProviders}\n onLoginSuccess={handleLoginSuccess}\n onForgotPassword={handleForgotPassword}\n />\n );\n};\n\nexport default BackofficeLoginPage;\n"],"mappings":";;;;;;;;;;;AAeA,IAAM,EAAE,mBAAA,MAAsB,GAMjB,KAAuB,EAClC,kBAC2C;CAC3C,IAAM,IAAgB,EAAW,EAAe,EAC1C,EAAE,MAAM,GAAY,gBAAa,GAAqB,EACtD,IAAO,GAA8B,EACrC,IAAY,GAAmB,EAC/B,IAAmB,GAAqB,EACxC,IAA6B,EAAO,GAAM;AAiDhD,QACE,kBAAC,GAAD;EACE,MAAM;EACS,eAlDN,EAAkB,EAAK,YAAY,EAAS,MAAM,CAEH,iBAC1D,EAAE;EAgDA,gBA9CuB,QAAwB;AAC7C,KAA2B,YAI/B,EAA2B,UAAU,KACZ,YAA2B;AAClD,QAAI;KACF,IAAM,IAAc,MAAM,EAAW,QAAQ,MAAM;AACnD,SAAI,EAAY,mBAAmB,MAAM;AACvC,SAAe,QAAQ,KAAK,EAC1B,UAAU,GACX,CAAC;AACF;;AAOF,SAAI,CAJe,MAAM,EACvB,GACA,EAAY,gBACb,CAEC;AAEF,QAAe,QAAQ,KAAK,EAC1B,UAAU,GACX,CAAC;YACI,WAEE;AACR,OAA2B,UAAU;;OAIvB,CAAC,YAAY,GAAG;KACjC;GAAC,EAAW;GAAS;GAAU;GAAkB,GAAe;GAAQ,CAAC;EAaxE,kBAXyB,QAAkB;AAC7C,MAAe,QAAQ,KAAK,EAC1B,UAAU,EAA+B,EAAS,EACnD,CAAC;KACD,CAAC,GAAU,GAAe,QAAQ,CAAC;EAQlC,CAAA"}
1
+ {"version":3,"file":"BackofficeLoginPage-BvOPqbKO.js","names":[],"sources":["../../src/pages/BackofficeLoginPage.tsx"],"sourcesContent":["import { useCallback, useContext, useRef, type JSX } from 'react';\nimport * as ReactRelay from 'react-relay';\nimport type { PreloadedQuery } from 'react-relay';\nimport type { OperationType } from 'relay-runtime';\nimport { useRelayEnvironment } from '../relay/useRelayEnvironment.js';\nimport { RoutingContext } from '@plumile/router';\n\nimport type { OidcProviderKind } from '../modules/sharedSchemaTypes.js';\nimport { LoginFlow } from '../auth/login/LoginFlow.js';\nimport { synchronizeAuthStatusQuery } from '../auth/login/synchronizeAuthStatusQuery.js';\nimport { useBackofficeAuth } from '../hooks/useBackofficeAuth.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport { useBackofficeAuthLoginConfig } from '../provider/useBackofficeLazyValue.js';\nimport { getBackofficePasswordResetPath } from '../router/backofficeAuthPaths.js';\n\nconst { usePreloadedQuery } = ReactRelay;\n\nexport type BackofficeLoginPageProps = {\n prepared: { query: PreloadedQuery<OperationType> };\n};\n\nexport const BackofficeLoginPage = ({\n prepared,\n}: BackofficeLoginPageProps): JSX.Element => {\n const routerContext = useContext(RoutingContext);\n const { auth: authConfig, basePath } = useBackofficeConfig();\n const auth = useBackofficeAuthLoginConfig();\n const authState = useBackofficeAuth();\n const relayEnvironment = useRelayEnvironment();\n const isPostLoginSyncInFlightRef = useRef(false);\n\n const data = usePreloadedQuery(auth.loginQuery, prepared.query);\n const oidcProviders =\n (data as { oidcProviders?: readonly OidcProviderKind[] }).oidcProviders ??\n [];\n\n const handleLoginSuccess = useCallback((): void => {\n if (isPostLoginSyncInFlightRef.current) {\n return;\n }\n\n isPostLoginSyncInFlightRef.current = true;\n const runPostLoginSync = async (): Promise<void> => {\n try {\n const sessionAuth = await authConfig.session.load();\n if (sessionAuth.authStatusQuery == null) {\n routerContext?.history.push({\n pathname: basePath,\n });\n return;\n }\n\n const isLoggedIn = await synchronizeAuthStatusQuery<OperationType>(\n relayEnvironment,\n sessionAuth.authStatusQuery,\n );\n if (!isLoggedIn) {\n return;\n }\n routerContext?.history.push({\n pathname: basePath,\n });\n } catch {\n // Keep user on login page if post-login auth sync fails.\n } finally {\n isPostLoginSyncInFlightRef.current = false;\n }\n };\n\n runPostLoginSync().catch(() => {});\n }, [authConfig.session, basePath, relayEnvironment, routerContext?.history]);\n\n const handleForgotPassword = useCallback(() => {\n routerContext?.history.push({\n pathname: getBackofficePasswordResetPath(basePath),\n });\n }, [basePath, routerContext?.history]);\n\n return (\n <LoginFlow\n auth={authState}\n oidcProviders={oidcProviders}\n onLoginSuccess={handleLoginSuccess}\n onForgotPassword={handleForgotPassword}\n />\n );\n};\n\nexport default BackofficeLoginPage;\n"],"mappings":";;;;;;;;;;;AAeA,IAAM,EAAE,mBAAA,MAAsB,GAMjB,KAAuB,EAClC,kBAC2C;CAC3C,IAAM,IAAgB,EAAW,EAAe,EAC1C,EAAE,MAAM,GAAY,gBAAa,GAAqB,EACtD,IAAO,GAA8B,EACrC,IAAY,GAAmB,EAC/B,IAAmB,GAAqB,EACxC,IAA6B,EAAO,GAAM;AAiDhD,QACE,kBAAC,GAAD;EACE,MAAM;EACS,eAlDN,EAAkB,EAAK,YAAY,EAAS,MAEtD,CAAyD,iBAC1D,EAAE;EAgDA,gBA9CuB,QAAwB;AAC7C,KAA2B,YAI/B,EAA2B,UAAU,KA4BrC,YA3BoD;AAClD,QAAI;KACF,IAAM,IAAc,MAAM,EAAW,QAAQ,MAAM;AACnD,SAAI,EAAY,mBAAmB,MAAM;AACvC,SAAe,QAAQ,KAAK,EAC1B,UAAU,GACX,CAAC;AACF;;AAOF,SAAI,CAAC,MAJoB,EACvB,GACA,EAAY,gBACb,CAEC;AAEF,QAAe,QAAQ,KAAK,EAC1B,UAAU,GACX,CAAC;YACI,WAEE;AACR,OAA2B,UAAU;;OAIvB,CAAC,YAAY,GAAG;KACjC;GAAC,EAAW;GAAS;GAAU;GAAkB,GAAe;GAAQ,CAYvD;EAChB,kBAXyB,QAAkB;AAC7C,MAAe,QAAQ,KAAK,EAC1B,UAAU,EAA+B,EAAS,EACnD,CAAC;KACD,CAAC,GAAU,GAAe,QAAQ,CAOf;EAClB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"BackofficePasswordResetCompletePage-ZLhghfhC.js","names":[],"sources":["../../src/pages/BackofficePasswordResetCompletePage.tsx"],"sourcesContent":["import { useCallback, useContext, type JSX } from 'react';\nimport * as ReactRelay from 'react-relay';\nimport type { MutationParameters } from 'relay-runtime';\nimport { RoutingContext } from '@plumile/router';\n\nimport { PasswordResetCompleteScreen } from '../auth/pages/PasswordResetCompleteScreen.js';\nimport {\n type MutationPayloadBase,\n requireField,\n resolveMutationOutcome,\n} from '../relay/mutationResult.js';\nimport { useSharedTranslation } from '../i18n/useSharedTranslation.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport { useBackofficeAuthPasswordResetCompleteConfig } from '../provider/useBackofficeLazyValue.js';\nimport { getBackofficeLoginPath } from '../router/backofficeAuthPaths.js';\n\nconst { useMutation } = ReactRelay;\n\ntype CompletePasswordResetErrorReason =\n | 'INVALID_TOKEN'\n | 'TOKEN_EXPIRED'\n | 'PASSWORD_POLICY_VIOLATION'\n | 'INTERNAL_ERROR';\n\ntype CompletePasswordResetMutationPayload =\n MutationPayloadBase<CompletePasswordResetErrorReason> & {\n success?: boolean | null;\n };\n\nexport const BackofficePasswordResetCompletePage = (): JSX.Element => {\n const routerContext = useContext(RoutingContext);\n const { basePath } = useBackofficeConfig();\n const auth = useBackofficeAuthPasswordResetCompleteConfig();\n const { t } = useSharedTranslation();\n type CompletePasswordResetMutation = MutationParameters & {\n response: {\n completePasswordReset?: CompletePasswordResetMutationPayload | null;\n };\n variables: { input: { newPassword: string; token: string } };\n };\n const [commitReset] = useMutation<CompletePasswordResetMutation>(\n auth.completePasswordResetMutation,\n );\n\n const handleBackToLogin = useCallback(() => {\n routerContext?.history.push({ pathname: getBackofficeLoginPath(basePath) });\n }, [basePath, routerContext?.history]);\n\n const handleCompleteReset = useCallback(\n async (input: { newPassword: string; token: string }): Promise<boolean> => {\n const invalidMessage = t('auth.passwordResetComplete.errors.invalid');\n const defaultErrorMessage = invalidMessage;\n\n return new Promise((resolve, reject) => {\n commitReset({\n variables: {\n input,\n },\n onCompleted: (response) => {\n const outcome = resolveMutationOutcome(\n response.completePasswordReset ?? null,\n {\n defaultErrorMessage,\n mapReason: (reason) => {\n switch (reason) {\n case 'INVALID_TOKEN':\n return invalidMessage;\n case 'TOKEN_EXPIRED':\n return t('auth.passwordResetComplete.errors.expired');\n case 'PASSWORD_POLICY_VIOLATION':\n return t(\n 'auth.passwordResetComplete.errors.policyViolation',\n );\n case 'INTERNAL_ERROR':\n return invalidMessage;\n default:\n return null;\n }\n },\n },\n );\n if (!outcome.ok) {\n reject(new Error(outcome.message));\n return;\n }\n\n const successResult = requireField(\n outcome.payload.success ?? null,\n defaultErrorMessage,\n );\n if (!successResult.ok) {\n reject(new Error(successResult.message));\n return;\n }\n resolve(successResult.value);\n },\n onError: (mutationError) => {\n let error = new Error(defaultErrorMessage);\n if (mutationError instanceof Error) {\n error = mutationError;\n }\n reject(error);\n },\n });\n });\n },\n [commitReset, t],\n );\n\n return (\n <PasswordResetCompleteScreen\n onBackToLogin={handleBackToLogin}\n onCompletePasswordReset={handleCompleteReset}\n />\n );\n};\n\nexport default BackofficePasswordResetCompletePage;\n"],"mappings":";;;;;;;;;;;AAgBA,IAAM,EAAE,mBAAgB,GAaX,UAAyD;CACpE,IAAM,IAAgB,EAAW,EAAe,EAC1C,EAAE,gBAAa,GAAqB,EACpC,IAAO,GAA8C,EACrD,EAAE,SAAM,GAAsB,EAO9B,CAAC,KAAe,EACpB,EAAK,8BACN;AAmED,QACE,kBAAC,GAAD;EACE,eAnEsB,QAAkB;AAC1C,MAAe,QAAQ,KAAK,EAAE,UAAU,EAAuB,EAAS,EAAE,CAAC;KAC1E,CAAC,GAAU,GAAe,QAAQ,CAAC;EAkElC,yBAhEwB,EAC1B,OAAO,MAAoE;GACzE,IAAM,IAAiB,EAAE,4CAA4C,EAC/D,IAAsB;AAE5B,UAAO,IAAI,SAAS,GAAS,MAAW;AACtC,MAAY;KACV,WAAW,EACT,UACD;KACD,cAAc,MAAa;MACzB,IAAM,IAAU,EACd,EAAS,yBAAyB,MAClC;OACE;OACA,YAAY,MAAW;AACrB,gBAAQ,GAAR;SACE,KAAK,gBACH,QAAO;SACT,KAAK,gBACH,QAAO,EAAE,4CAA4C;SACvD,KAAK,4BACH,QAAO,EACL,oDACD;SACH,KAAK,iBACH,QAAO;SACT,QACE,QAAO;;;OAGd,CACF;AACD,UAAI,CAAC,EAAQ,IAAI;AACf,SAAW,MAAM,EAAQ,QAAQ,CAAC;AAClC;;MAGF,IAAM,IAAgB,EACpB,EAAQ,QAAQ,WAAW,MAC3B,EACD;AACD,UAAI,CAAC,EAAc,IAAI;AACrB,SAAW,MAAM,EAAc,QAAQ,CAAC;AACxC;;AAEF,QAAQ,EAAc,MAAM;;KAE9B,UAAU,MAAkB;MAC1B,IAAI,IAAY,MAAM,EAAoB;AAI1C,MAHI,aAAyB,UAC3B,IAAQ,IAEV,EAAO,EAAM;;KAEhB,CAAC;KACF;KAEJ,CAAC,GAAa,EAAE,CACjB;EAMG,CAAA"}
1
+ {"version":3,"file":"BackofficePasswordResetCompletePage-ZLhghfhC.js","names":[],"sources":["../../src/pages/BackofficePasswordResetCompletePage.tsx"],"sourcesContent":["import { useCallback, useContext, type JSX } from 'react';\nimport * as ReactRelay from 'react-relay';\nimport type { MutationParameters } from 'relay-runtime';\nimport { RoutingContext } from '@plumile/router';\n\nimport { PasswordResetCompleteScreen } from '../auth/pages/PasswordResetCompleteScreen.js';\nimport {\n type MutationPayloadBase,\n requireField,\n resolveMutationOutcome,\n} from '../relay/mutationResult.js';\nimport { useSharedTranslation } from '../i18n/useSharedTranslation.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport { useBackofficeAuthPasswordResetCompleteConfig } from '../provider/useBackofficeLazyValue.js';\nimport { getBackofficeLoginPath } from '../router/backofficeAuthPaths.js';\n\nconst { useMutation } = ReactRelay;\n\ntype CompletePasswordResetErrorReason =\n | 'INVALID_TOKEN'\n | 'TOKEN_EXPIRED'\n | 'PASSWORD_POLICY_VIOLATION'\n | 'INTERNAL_ERROR';\n\ntype CompletePasswordResetMutationPayload =\n MutationPayloadBase<CompletePasswordResetErrorReason> & {\n success?: boolean | null;\n };\n\nexport const BackofficePasswordResetCompletePage = (): JSX.Element => {\n const routerContext = useContext(RoutingContext);\n const { basePath } = useBackofficeConfig();\n const auth = useBackofficeAuthPasswordResetCompleteConfig();\n const { t } = useSharedTranslation();\n type CompletePasswordResetMutation = MutationParameters & {\n response: {\n completePasswordReset?: CompletePasswordResetMutationPayload | null;\n };\n variables: { input: { newPassword: string; token: string } };\n };\n const [commitReset] = useMutation<CompletePasswordResetMutation>(\n auth.completePasswordResetMutation,\n );\n\n const handleBackToLogin = useCallback(() => {\n routerContext?.history.push({ pathname: getBackofficeLoginPath(basePath) });\n }, [basePath, routerContext?.history]);\n\n const handleCompleteReset = useCallback(\n async (input: { newPassword: string; token: string }): Promise<boolean> => {\n const invalidMessage = t('auth.passwordResetComplete.errors.invalid');\n const defaultErrorMessage = invalidMessage;\n\n return new Promise((resolve, reject) => {\n commitReset({\n variables: {\n input,\n },\n onCompleted: (response) => {\n const outcome = resolveMutationOutcome(\n response.completePasswordReset ?? null,\n {\n defaultErrorMessage,\n mapReason: (reason) => {\n switch (reason) {\n case 'INVALID_TOKEN':\n return invalidMessage;\n case 'TOKEN_EXPIRED':\n return t('auth.passwordResetComplete.errors.expired');\n case 'PASSWORD_POLICY_VIOLATION':\n return t(\n 'auth.passwordResetComplete.errors.policyViolation',\n );\n case 'INTERNAL_ERROR':\n return invalidMessage;\n default:\n return null;\n }\n },\n },\n );\n if (!outcome.ok) {\n reject(new Error(outcome.message));\n return;\n }\n\n const successResult = requireField(\n outcome.payload.success ?? null,\n defaultErrorMessage,\n );\n if (!successResult.ok) {\n reject(new Error(successResult.message));\n return;\n }\n resolve(successResult.value);\n },\n onError: (mutationError) => {\n let error = new Error(defaultErrorMessage);\n if (mutationError instanceof Error) {\n error = mutationError;\n }\n reject(error);\n },\n });\n });\n },\n [commitReset, t],\n );\n\n return (\n <PasswordResetCompleteScreen\n onBackToLogin={handleBackToLogin}\n onCompletePasswordReset={handleCompleteReset}\n />\n );\n};\n\nexport default BackofficePasswordResetCompletePage;\n"],"mappings":";;;;;;;;;;;AAgBA,IAAM,EAAE,mBAAgB,GAaX,UAAyD;CACpE,IAAM,IAAgB,EAAW,EAAe,EAC1C,EAAE,gBAAa,GAAqB,EACpC,IAAO,GAA8C,EACrD,EAAE,SAAM,GAAsB,EAO9B,CAAC,KAAe,EACpB,EAAK,8BACN;AAmED,QACE,kBAAC,GAAD;EACE,eAnEsB,QAAkB;AAC1C,MAAe,QAAQ,KAAK,EAAE,UAAU,EAAuB,EAAS,EAAE,CAAC;KAC1E,CAAC,GAAU,GAAe,QAAQ,CAiElB;EACf,yBAhEwB,EAC1B,OAAO,MAAoE;GACzE,IAAM,IAAiB,EAAE,4CAA4C,EAC/D,IAAsB;AAE5B,UAAO,IAAI,SAAS,GAAS,MAAW;AACtC,MAAY;KACV,WAAW,EACT,UACD;KACD,cAAc,MAAa;MACzB,IAAM,IAAU,EACd,EAAS,yBAAyB,MAClC;OACE;OACA,YAAY,MAAW;AACrB,gBAAQ,GAAR;SACE,KAAK,gBACH,QAAO;SACT,KAAK,gBACH,QAAO,EAAE,4CAA4C;SACvD,KAAK,4BACH,QAAO,EACL,oDACD;SACH,KAAK,iBACH,QAAO;SACT,QACE,QAAO;;;OAGd,CACF;AACD,UAAI,CAAC,EAAQ,IAAI;AACf,SAAW,MAAM,EAAQ,QAAQ,CAAC;AAClC;;MAGF,IAAM,IAAgB,EACpB,EAAQ,QAAQ,WAAW,MAC3B,EACD;AACD,UAAI,CAAC,EAAc,IAAI;AACrB,SAAW,MAAM,EAAc,QAAQ,CAAC;AACxC;;AAEF,QAAQ,EAAc,MAAM;;KAE9B,UAAU,MAAkB;MAC1B,IAAI,IAAY,MAAM,EAAoB;AAI1C,MAHI,aAAyB,UAC3B,IAAQ,IAEV,EAAO,EAAM;;KAEhB,CAAC;KACF;KAEJ,CAAC,GAAa,EAAE,CAMW;EACzB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"BackofficePasswordResetRequestPage-BLNHQD79.js","names":[],"sources":["../../src/pages/BackofficePasswordResetRequestPage.tsx"],"sourcesContent":["import { useCallback, type JSX } from 'react';\nimport * as ReactRelay from 'react-relay';\nimport type { MutationParameters } from 'relay-runtime';\n\nimport { PasswordResetRequestScreen } from '../auth/pages/PasswordResetRequestScreen.js';\nimport {\n type MutationPayloadBase,\n requireField,\n resolveMutationOutcome,\n} from '../relay/mutationResult.js';\nimport { useSharedTranslation } from '../i18n/useSharedTranslation.js';\nimport { useBackofficeAuthPasswordResetRequestConfig } from '../provider/useBackofficeLazyValue.js';\n\nconst { useMutation } = ReactRelay;\n\ntype StartPasswordResetErrorReason =\n | 'INVALID_EMAIL'\n | 'RATE_LIMITED'\n | 'INTERNAL_ERROR';\n\ntype StartPasswordResetMutationPayload =\n MutationPayloadBase<StartPasswordResetErrorReason> & {\n payload?: { success?: boolean | null } | null;\n };\n\nexport const BackofficePasswordResetRequestPage = (): JSX.Element => {\n const auth = useBackofficeAuthPasswordResetRequestConfig();\n const { t } = useSharedTranslation();\n type StartPasswordResetMutation = MutationParameters & {\n response: { startPasswordReset?: StartPasswordResetMutationPayload | null };\n variables: { input: { email: string; locale?: string } };\n };\n const [commitReset] = useMutation<StartPasswordResetMutation>(\n auth.startPasswordResetMutation,\n );\n\n const mapStartResetReason = useCallback(\n (reason: StartPasswordResetErrorReason): string | null => {\n switch (reason) {\n case 'INVALID_EMAIL':\n return t('auth.passwordResetRequest.errors.invalidEmail');\n case 'RATE_LIMITED':\n return t('auth.passwordResetRequest.errors.rateLimited');\n case 'INTERNAL_ERROR':\n return t('auth.passwordResetRequest.errors.startFailed');\n default:\n return null;\n }\n },\n [t],\n );\n\n const handleStartReset = useCallback(\n async (input: { email: string; locale?: string }): Promise<boolean> => {\n const defaultErrorMessage = t(\n 'auth.passwordResetRequest.errors.startFailed',\n );\n\n return new Promise((resolve, reject) => {\n commitReset({\n variables: {\n input,\n },\n onCompleted: (response) => {\n const outcome = resolveMutationOutcome(\n response.startPasswordReset ?? null,\n {\n defaultErrorMessage,\n mapReason: mapStartResetReason,\n },\n );\n if (!outcome.ok) {\n reject(new Error(outcome.message));\n return;\n }\n\n const successResult = requireField(\n outcome.payload.payload?.success ?? null,\n defaultErrorMessage,\n );\n if (!successResult.ok) {\n reject(new Error(successResult.message));\n return;\n }\n resolve(successResult.value);\n },\n onError: (mutationError) => {\n let error = new Error(defaultErrorMessage);\n if (mutationError instanceof Error) {\n error = mutationError;\n }\n reject(error);\n },\n });\n });\n },\n [commitReset, mapStartResetReason, t],\n );\n\n return <PasswordResetRequestScreen onStartPasswordReset={handleStartReset} />;\n};\n\nexport default BackofficePasswordResetRequestPage;\n"],"mappings":";;;;;;;;AAaA,IAAM,EAAE,mBAAgB,GAYX,UAAwD;CACnE,IAAM,IAAO,GAA6C,EACpD,EAAE,SAAM,GAAsB,EAK9B,CAAC,KAAe,EACpB,EAAK,2BACN,EAEK,IAAsB,GACzB,MAAyD;AACxD,UAAQ,GAAR;GACE,KAAK,gBACH,QAAO,EAAE,gDAAgD;GAC3D,KAAK,eACH,QAAO,EAAE,+CAA+C;GAC1D,KAAK,iBACH,QAAO,EAAE,+CAA+C;GAC1D,QACE,QAAO;;IAGb,CAAC,EAAE,CACJ;AAiDD,QAAO,kBAAC,GAAD,EAA4B,sBA/CV,EACvB,OAAO,MAAgE;EACrE,IAAM,IAAsB,EAC1B,+CACD;AAED,SAAO,IAAI,SAAS,GAAS,MAAW;AACtC,KAAY;IACV,WAAW,EACT,UACD;IACD,cAAc,MAAa;KACzB,IAAM,IAAU,EACd,EAAS,sBAAsB,MAC/B;MACE;MACA,WAAW;MACZ,CACF;AACD,SAAI,CAAC,EAAQ,IAAI;AACf,QAAW,MAAM,EAAQ,QAAQ,CAAC;AAClC;;KAGF,IAAM,IAAgB,EACpB,EAAQ,QAAQ,SAAS,WAAW,MACpC,EACD;AACD,SAAI,CAAC,EAAc,IAAI;AACrB,QAAW,MAAM,EAAc,QAAQ,CAAC;AACxC;;AAEF,OAAQ,EAAc,MAAM;;IAE9B,UAAU,MAAkB;KAC1B,IAAI,IAAY,MAAM,EAAoB;AAI1C,KAHI,aAAyB,UAC3B,IAAQ,IAEV,EAAO,EAAM;;IAEhB,CAAC;IACF;IAEJ;EAAC;EAAa;EAAqB;EAAE,CACtC,EAE4E,CAAA"}
1
+ {"version":3,"file":"BackofficePasswordResetRequestPage-BLNHQD79.js","names":[],"sources":["../../src/pages/BackofficePasswordResetRequestPage.tsx"],"sourcesContent":["import { useCallback, type JSX } from 'react';\nimport * as ReactRelay from 'react-relay';\nimport type { MutationParameters } from 'relay-runtime';\n\nimport { PasswordResetRequestScreen } from '../auth/pages/PasswordResetRequestScreen.js';\nimport {\n type MutationPayloadBase,\n requireField,\n resolveMutationOutcome,\n} from '../relay/mutationResult.js';\nimport { useSharedTranslation } from '../i18n/useSharedTranslation.js';\nimport { useBackofficeAuthPasswordResetRequestConfig } from '../provider/useBackofficeLazyValue.js';\n\nconst { useMutation } = ReactRelay;\n\ntype StartPasswordResetErrorReason =\n | 'INVALID_EMAIL'\n | 'RATE_LIMITED'\n | 'INTERNAL_ERROR';\n\ntype StartPasswordResetMutationPayload =\n MutationPayloadBase<StartPasswordResetErrorReason> & {\n payload?: { success?: boolean | null } | null;\n };\n\nexport const BackofficePasswordResetRequestPage = (): JSX.Element => {\n const auth = useBackofficeAuthPasswordResetRequestConfig();\n const { t } = useSharedTranslation();\n type StartPasswordResetMutation = MutationParameters & {\n response: { startPasswordReset?: StartPasswordResetMutationPayload | null };\n variables: { input: { email: string; locale?: string } };\n };\n const [commitReset] = useMutation<StartPasswordResetMutation>(\n auth.startPasswordResetMutation,\n );\n\n const mapStartResetReason = useCallback(\n (reason: StartPasswordResetErrorReason): string | null => {\n switch (reason) {\n case 'INVALID_EMAIL':\n return t('auth.passwordResetRequest.errors.invalidEmail');\n case 'RATE_LIMITED':\n return t('auth.passwordResetRequest.errors.rateLimited');\n case 'INTERNAL_ERROR':\n return t('auth.passwordResetRequest.errors.startFailed');\n default:\n return null;\n }\n },\n [t],\n );\n\n const handleStartReset = useCallback(\n async (input: { email: string; locale?: string }): Promise<boolean> => {\n const defaultErrorMessage = t(\n 'auth.passwordResetRequest.errors.startFailed',\n );\n\n return new Promise((resolve, reject) => {\n commitReset({\n variables: {\n input,\n },\n onCompleted: (response) => {\n const outcome = resolveMutationOutcome(\n response.startPasswordReset ?? null,\n {\n defaultErrorMessage,\n mapReason: mapStartResetReason,\n },\n );\n if (!outcome.ok) {\n reject(new Error(outcome.message));\n return;\n }\n\n const successResult = requireField(\n outcome.payload.payload?.success ?? null,\n defaultErrorMessage,\n );\n if (!successResult.ok) {\n reject(new Error(successResult.message));\n return;\n }\n resolve(successResult.value);\n },\n onError: (mutationError) => {\n let error = new Error(defaultErrorMessage);\n if (mutationError instanceof Error) {\n error = mutationError;\n }\n reject(error);\n },\n });\n });\n },\n [commitReset, mapStartResetReason, t],\n );\n\n return <PasswordResetRequestScreen onStartPasswordReset={handleStartReset} />;\n};\n\nexport default BackofficePasswordResetRequestPage;\n"],"mappings":";;;;;;;;AAaA,IAAM,EAAE,mBAAgB,GAYX,UAAwD;CACnE,IAAM,IAAO,GAA6C,EACpD,EAAE,SAAM,GAAsB,EAK9B,CAAC,KAAe,EACpB,EAAK,2BACN,EAEK,IAAsB,GACzB,MAAyD;AACxD,UAAQ,GAAR;GACE,KAAK,gBACH,QAAO,EAAE,gDAAgD;GAC3D,KAAK,eACH,QAAO,EAAE,+CAA+C;GAC1D,KAAK,iBACH,QAAO,EAAE,+CAA+C;GAC1D,QACE,QAAO;;IAGb,CAAC,EAAE,CACJ;AAiDD,QAAO,kBAAC,GAAD,EAA4B,sBA/CV,EACvB,OAAO,MAAgE;EACrE,IAAM,IAAsB,EAC1B,+CACD;AAED,SAAO,IAAI,SAAS,GAAS,MAAW;AACtC,KAAY;IACV,WAAW,EACT,UACD;IACD,cAAc,MAAa;KACzB,IAAM,IAAU,EACd,EAAS,sBAAsB,MAC/B;MACE;MACA,WAAW;MACZ,CACF;AACD,SAAI,CAAC,EAAQ,IAAI;AACf,QAAW,MAAM,EAAQ,QAAQ,CAAC;AAClC;;KAGF,IAAM,IAAgB,EACpB,EAAQ,QAAQ,SAAS,WAAW,MACpC,EACD;AACD,SAAI,CAAC,EAAc,IAAI;AACrB,QAAW,MAAM,EAAc,QAAQ,CAAC;AACxC;;AAEF,OAAQ,EAAc,MAAM;;IAE9B,UAAU,MAAkB;KAC1B,IAAI,IAAY,MAAM,EAAoB;AAI1C,KAHI,aAAyB,UAC3B,IAAQ,IAEV,EAAO,EAAM;;IAEhB,CAAC;IACF;IAEJ;EAAC;EAAa;EAAqB;EAAE,CAGkB,EAAoB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"BackofficeRightPageLayout-DZQvIHnj.js","names":[],"sources":["../../src/components/backoffice/columns/buildDataTableColumns.tsx","../../src/components/backoffice/layout/breadcrumb/assertValidBreadcrumb.ts","../../src/components/backoffice/layout/breadcrumb/BackofficeTopbarPortal.tsx","../../src/components/backoffice/layout/breadcrumb/backofficeTopbarBreadcrumb.css.ts","../../src/components/backoffice/layout/breadcrumb/BackofficeTopbarBreadcrumb.tsx","../../src/components/backoffice/layout/breadcrumb/BackofficeRightPageLayout.tsx"],"sourcesContent":["import { BACKOFFICE_DATE_TIME_OPTIONS } from '@plumile/backoffice-core/constants.js';\nimport type {\n BackofficeColumnSpec,\n BackofficeFieldSize,\n I18nLabel,\n} from '@plumile/backoffice-core/types.js';\nimport type { TFunction } from 'i18next';\nimport { Link } from '@plumile/router';\nimport { FormattedDate, Tag, type DataTableColumn } from '@plumile/ui';\n\nconst resolveLabel = (label: I18nLabel, tApp: TFunction): string => {\n return label(tApp);\n};\n\nconst resolveTextValue = (\n value: string | number | null,\n fallback: string,\n): string => {\n if (value == null) {\n return fallback;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return fallback;\n }\n return String(value);\n};\n\nexport type BuildDataTableColumnsOptions = {\n tApp: TFunction;\n t: TFunction;\n resolveEntityHref?: (entityId: string, refId: string) => string | null;\n};\n\nexport type BackofficeSizedDataTableColumn<Row> = DataTableColumn<Row> & {\n size: BackofficeFieldSize;\n};\n\n/**\n *\n */\nexport function buildDataTableColumns<Row>(\n columns: readonly BackofficeColumnSpec<Row>[],\n options: BuildDataTableColumnsOptions,\n): readonly BackofficeSizedDataTableColumn<Row>[] {\n const { tApp, t, resolveEntityHref } = options;\n const fallback = t('common.notAvailable');\n const filteredColumns = columns.filter((column) => {\n return column.key !== 'id';\n });\n\n return filteredColumns.map((column) => {\n return {\n id: column.key,\n header: resolveLabel(column.header, tApp),\n size: column.size,\n cell: (row) => {\n const { cell } = column;\n switch (cell.type) {\n case 'text': {\n return resolveTextValue(cell.value(row), fallback);\n }\n case 'link': {\n const value = cell.value(row);\n if (\n value == null ||\n (typeof value === 'string' && value.trim() === '')\n ) {\n return fallback;\n }\n return <Link to={cell.to(row)}>{value}</Link>;\n }\n case 'badge': {\n const value = cell.value(row);\n if (value == null || value.trim() === '') {\n return fallback;\n }\n let { tone } = cell;\n if (typeof tone === 'function') {\n tone = tone(row);\n }\n return <Tag tone={tone}>{value}</Tag>;\n }\n case 'dateTime': {\n const value = cell.value(row);\n return (\n <FormattedDate\n value={value}\n fallback={fallback}\n options={BACKOFFICE_DATE_TIME_OPTIONS}\n />\n );\n }\n case 'entityRef': {\n const id = cell.value(row);\n if (id.trim() === '') {\n return fallback;\n }\n const href = resolveEntityHref?.(cell.entity, id) ?? null;\n if (href != null) {\n return <Link to={href}>{t('actions.view')}</Link>;\n }\n return fallback;\n }\n case 'custom': {\n const rendered = cell.render(row);\n if (rendered == null || rendered === '') {\n return fallback;\n }\n return <>{rendered}</>;\n }\n default: {\n return fallback;\n }\n }\n },\n };\n });\n}\n","import type {\n BackofficeTopbarBreadcrumbItem,\n BreadcrumbContractErrorCode,\n} from './types.js';\n\nconst formatContractError = (\n code: BreadcrumbContractErrorCode,\n details?: string,\n): Error => {\n if (details == null) {\n return new Error(`Invalid breadcrumb contract: ${code}`);\n }\n return new Error(`Invalid breadcrumb contract: ${code} (${details})`);\n};\n\nexport const assertValidBreadcrumb = (\n items: readonly BackofficeTopbarBreadcrumbItem[] | null | undefined,\n): readonly BackofficeTopbarBreadcrumbItem[] => {\n if (items == null) {\n throw formatContractError('MISSING_BREADCRUMB');\n }\n if (items.length === 0) {\n throw formatContractError('EMPTY_BREADCRUMB');\n }\n\n const seen = new Set<string>();\n items.forEach((item, index) => {\n if (typeof item.id !== 'string' || item.id.trim() === '') {\n throw formatContractError('INVALID_SEGMENT_ID', `index=${index}`);\n }\n\n if (seen.has(item.id)) {\n throw formatContractError(\n 'INVALID_SEGMENT_ID',\n `duplicate id=\"${item.id}\"`,\n );\n }\n seen.add(item.id);\n });\n\n const last = items[items.length - 1];\n if (last?.isCurrent !== true) {\n throw formatContractError('MISSING_CURRENT_SEGMENT');\n }\n\n return items;\n};\n","import { type JSX, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { useBackofficeTopbarPortalContext } from './BackofficeTopbarPortalContext.js';\n\nexport type BackofficeTopbarPortalProps = {\n children: ReactNode;\n};\n\nexport const BackofficeTopbarPortal = ({\n children,\n}: BackofficeTopbarPortalProps): JSX.Element | null => {\n const { target } = useBackofficeTopbarPortalContext();\n if (target == null) {\n throw new Error(\n 'Backoffice topbar target is missing. Ensure BackofficeLayoutPage provides a topbar breadcrumb mount target before rendering right-side pages.',\n );\n }\n return createPortal(children, target);\n};\n\nexport default BackofficeTopbarPortal;\n","import { sprinkles } from '@plumile/ui';\nimport { style } from '@vanilla-extract/css';\n\nconst INLINE_FLEX = 'inline-flex' as const;\n\nexport const nav = sprinkles({\n display: INLINE_FLEX,\n alignItems: 'center',\n});\n\nexport const list = style([\n sprinkles({\n display: INLINE_FLEX,\n alignItems: 'center',\n gap: 2,\n }),\n {\n margin: 0,\n padding: 0,\n listStyle: 'none',\n },\n]);\n\nexport const item = sprinkles({\n display: INLINE_FLEX,\n alignItems: 'center',\n gap: 2,\n});\n\nexport const separator = sprinkles({\n color: 'textSecondary',\n fontSize: 'sm',\n lineHeight: 'normal',\n});\n\nexport const link = style([\n sprinkles({\n color: 'textSecondary',\n fontSize: 'sm',\n lineHeight: 'normal',\n }),\n {\n textDecoration: 'none',\n selectors: {\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n },\n]);\n\nexport const current = sprinkles({\n color: 'text',\n fontSize: 'sm',\n lineHeight: 'normal',\n fontWeight: 'semibold',\n});\n","import { type JSX } from 'react';\nimport { Link } from '@plumile/router';\n\nimport type { BackofficeTopbarBreadcrumbItem } from './types.js';\nimport * as styles from './backofficeTopbarBreadcrumb.css.js';\n\nexport type BackofficeTopbarBreadcrumbProps = {\n items: readonly BackofficeTopbarBreadcrumbItem[];\n};\n\nexport const BackofficeTopbarBreadcrumb = ({\n items,\n}: BackofficeTopbarBreadcrumbProps): JSX.Element => {\n return (\n <nav className={styles.nav} aria-label=\"Breadcrumb\">\n <ol className={styles.list}>\n {items.map((item, index) => {\n const isFirst = index === 0;\n const isCurrent = item.isCurrent === true;\n const key = `${item.id}-${index}`;\n const shouldRenderLink = item.to != null && !isCurrent;\n let content: JSX.Element;\n\n if (shouldRenderLink) {\n content = (\n <Link to={item.to} className={styles.link}>\n {item.label}\n </Link>\n );\n } else {\n let className = styles.link;\n if (isCurrent) {\n className = styles.current;\n }\n content = <span className={className}>{item.label}</span>;\n }\n\n return (\n <li key={key} className={styles.item}>\n {!isFirst && (\n <span className={styles.separator} aria-hidden=\"true\">\n /\n </span>\n )}\n {content}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n};\n\nexport default BackofficeTopbarBreadcrumb;\n","import { type JSX, type ReactNode } from 'react';\n\nimport { assertValidBreadcrumb } from './assertValidBreadcrumb.js';\nimport { BackofficeTopbarPortal } from './BackofficeTopbarPortal.js';\nimport { BackofficeTopbarBreadcrumb } from './BackofficeTopbarBreadcrumb.js';\nimport type { BackofficeTopbarBreadcrumbItem } from './types.js';\n\nexport type BackofficeRightPageLayoutProps = {\n breadcrumb: readonly BackofficeTopbarBreadcrumbItem[];\n children: ReactNode;\n};\n\nexport const BackofficeRightPageLayout = ({\n breadcrumb,\n children,\n}: BackofficeRightPageLayoutProps): JSX.Element => {\n const items = assertValidBreadcrumb(breadcrumb);\n\n return (\n <>\n <BackofficeTopbarPortal>\n <BackofficeTopbarBreadcrumb items={items} />\n </BackofficeTopbarPortal>\n <>{children}</>\n </>\n );\n};\n\nexport default BackofficeRightPageLayout;\n"],"mappings":"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"}
1
+ {"version":3,"file":"BackofficeRightPageLayout-DZQvIHnj.js","names":[],"sources":["../../src/components/backoffice/columns/buildDataTableColumns.tsx","../../src/components/backoffice/layout/breadcrumb/assertValidBreadcrumb.ts","../../src/components/backoffice/layout/breadcrumb/BackofficeTopbarPortal.tsx","../../src/components/backoffice/layout/breadcrumb/backofficeTopbarBreadcrumb.css.ts","../../src/components/backoffice/layout/breadcrumb/BackofficeTopbarBreadcrumb.tsx","../../src/components/backoffice/layout/breadcrumb/BackofficeRightPageLayout.tsx"],"sourcesContent":["import { BACKOFFICE_DATE_TIME_OPTIONS } from '@plumile/backoffice-core/constants.js';\nimport type {\n BackofficeColumnSpec,\n BackofficeFieldSize,\n I18nLabel,\n} from '@plumile/backoffice-core/types.js';\nimport type { TFunction } from 'i18next';\nimport { Link } from '@plumile/router';\nimport { FormattedDate, Tag, type DataTableColumn } from '@plumile/ui';\n\nconst resolveLabel = (label: I18nLabel, tApp: TFunction): string => {\n return label(tApp);\n};\n\nconst resolveTextValue = (\n value: string | number | null,\n fallback: string,\n): string => {\n if (value == null) {\n return fallback;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return fallback;\n }\n return String(value);\n};\n\nexport type BuildDataTableColumnsOptions = {\n tApp: TFunction;\n t: TFunction;\n resolveEntityHref?: (entityId: string, refId: string) => string | null;\n};\n\nexport type BackofficeSizedDataTableColumn<Row> = DataTableColumn<Row> & {\n size: BackofficeFieldSize;\n};\n\n/**\n *\n */\nexport function buildDataTableColumns<Row>(\n columns: readonly BackofficeColumnSpec<Row>[],\n options: BuildDataTableColumnsOptions,\n): readonly BackofficeSizedDataTableColumn<Row>[] {\n const { tApp, t, resolveEntityHref } = options;\n const fallback = t('common.notAvailable');\n const filteredColumns = columns.filter((column) => {\n return column.key !== 'id';\n });\n\n return filteredColumns.map((column) => {\n return {\n id: column.key,\n header: resolveLabel(column.header, tApp),\n size: column.size,\n cell: (row) => {\n const { cell } = column;\n switch (cell.type) {\n case 'text': {\n return resolveTextValue(cell.value(row), fallback);\n }\n case 'link': {\n const value = cell.value(row);\n if (\n value == null ||\n (typeof value === 'string' && value.trim() === '')\n ) {\n return fallback;\n }\n return <Link to={cell.to(row)}>{value}</Link>;\n }\n case 'badge': {\n const value = cell.value(row);\n if (value == null || value.trim() === '') {\n return fallback;\n }\n let { tone } = cell;\n if (typeof tone === 'function') {\n tone = tone(row);\n }\n return <Tag tone={tone}>{value}</Tag>;\n }\n case 'dateTime': {\n const value = cell.value(row);\n return (\n <FormattedDate\n value={value}\n fallback={fallback}\n options={BACKOFFICE_DATE_TIME_OPTIONS}\n />\n );\n }\n case 'entityRef': {\n const id = cell.value(row);\n if (id.trim() === '') {\n return fallback;\n }\n const href = resolveEntityHref?.(cell.entity, id) ?? null;\n if (href != null) {\n return <Link to={href}>{t('actions.view')}</Link>;\n }\n return fallback;\n }\n case 'custom': {\n const rendered = cell.render(row);\n if (rendered == null || rendered === '') {\n return fallback;\n }\n return <>{rendered}</>;\n }\n default: {\n return fallback;\n }\n }\n },\n };\n });\n}\n","import type {\n BackofficeTopbarBreadcrumbItem,\n BreadcrumbContractErrorCode,\n} from './types.js';\n\nconst formatContractError = (\n code: BreadcrumbContractErrorCode,\n details?: string,\n): Error => {\n if (details == null) {\n return new Error(`Invalid breadcrumb contract: ${code}`);\n }\n return new Error(`Invalid breadcrumb contract: ${code} (${details})`);\n};\n\nexport const assertValidBreadcrumb = (\n items: readonly BackofficeTopbarBreadcrumbItem[] | null | undefined,\n): readonly BackofficeTopbarBreadcrumbItem[] => {\n if (items == null) {\n throw formatContractError('MISSING_BREADCRUMB');\n }\n if (items.length === 0) {\n throw formatContractError('EMPTY_BREADCRUMB');\n }\n\n const seen = new Set<string>();\n items.forEach((item, index) => {\n if (typeof item.id !== 'string' || item.id.trim() === '') {\n throw formatContractError('INVALID_SEGMENT_ID', `index=${index}`);\n }\n\n if (seen.has(item.id)) {\n throw formatContractError(\n 'INVALID_SEGMENT_ID',\n `duplicate id=\"${item.id}\"`,\n );\n }\n seen.add(item.id);\n });\n\n const last = items[items.length - 1];\n if (last?.isCurrent !== true) {\n throw formatContractError('MISSING_CURRENT_SEGMENT');\n }\n\n return items;\n};\n","import { type JSX, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { useBackofficeTopbarPortalContext } from './BackofficeTopbarPortalContext.js';\n\nexport type BackofficeTopbarPortalProps = {\n children: ReactNode;\n};\n\nexport const BackofficeTopbarPortal = ({\n children,\n}: BackofficeTopbarPortalProps): JSX.Element | null => {\n const { target } = useBackofficeTopbarPortalContext();\n if (target == null) {\n throw new Error(\n 'Backoffice topbar target is missing. Ensure BackofficeLayoutPage provides a topbar breadcrumb mount target before rendering right-side pages.',\n );\n }\n return createPortal(children, target);\n};\n\nexport default BackofficeTopbarPortal;\n","import { sprinkles } from '@plumile/ui';\nimport { style } from '@vanilla-extract/css';\n\nconst INLINE_FLEX = 'inline-flex' as const;\n\nexport const nav = sprinkles({\n display: INLINE_FLEX,\n alignItems: 'center',\n});\n\nexport const list = style([\n sprinkles({\n display: INLINE_FLEX,\n alignItems: 'center',\n gap: 2,\n }),\n {\n margin: 0,\n padding: 0,\n listStyle: 'none',\n },\n]);\n\nexport const item = sprinkles({\n display: INLINE_FLEX,\n alignItems: 'center',\n gap: 2,\n});\n\nexport const separator = sprinkles({\n color: 'textSecondary',\n fontSize: 'sm',\n lineHeight: 'normal',\n});\n\nexport const link = style([\n sprinkles({\n color: 'textSecondary',\n fontSize: 'sm',\n lineHeight: 'normal',\n }),\n {\n textDecoration: 'none',\n selectors: {\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n },\n]);\n\nexport const current = sprinkles({\n color: 'text',\n fontSize: 'sm',\n lineHeight: 'normal',\n fontWeight: 'semibold',\n});\n","import { type JSX } from 'react';\nimport { Link } from '@plumile/router';\n\nimport type { BackofficeTopbarBreadcrumbItem } from './types.js';\nimport * as styles from './backofficeTopbarBreadcrumb.css.js';\n\nexport type BackofficeTopbarBreadcrumbProps = {\n items: readonly BackofficeTopbarBreadcrumbItem[];\n};\n\nexport const BackofficeTopbarBreadcrumb = ({\n items,\n}: BackofficeTopbarBreadcrumbProps): JSX.Element => {\n return (\n <nav className={styles.nav} aria-label=\"Breadcrumb\">\n <ol className={styles.list}>\n {items.map((item, index) => {\n const isFirst = index === 0;\n const isCurrent = item.isCurrent === true;\n const key = `${item.id}-${index}`;\n const shouldRenderLink = item.to != null && !isCurrent;\n let content: JSX.Element;\n\n if (shouldRenderLink) {\n content = (\n <Link to={item.to} className={styles.link}>\n {item.label}\n </Link>\n );\n } else {\n let className = styles.link;\n if (isCurrent) {\n className = styles.current;\n }\n content = <span className={className}>{item.label}</span>;\n }\n\n return (\n <li key={key} className={styles.item}>\n {!isFirst && (\n <span className={styles.separator} aria-hidden=\"true\">\n /\n </span>\n )}\n {content}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n};\n\nexport default BackofficeTopbarBreadcrumb;\n","import { type JSX, type ReactNode } from 'react';\n\nimport { assertValidBreadcrumb } from './assertValidBreadcrumb.js';\nimport { BackofficeTopbarPortal } from './BackofficeTopbarPortal.js';\nimport { BackofficeTopbarBreadcrumb } from './BackofficeTopbarBreadcrumb.js';\nimport type { BackofficeTopbarBreadcrumbItem } from './types.js';\n\nexport type BackofficeRightPageLayoutProps = {\n breadcrumb: readonly BackofficeTopbarBreadcrumbItem[];\n children: ReactNode;\n};\n\nexport const BackofficeRightPageLayout = ({\n breadcrumb,\n children,\n}: BackofficeRightPageLayoutProps): JSX.Element => {\n const items = assertValidBreadcrumb(breadcrumb);\n\n return (\n <>\n <BackofficeTopbarPortal>\n <BackofficeTopbarBreadcrumb items={items} />\n </BackofficeTopbarPortal>\n <>{children}</>\n </>\n );\n};\n\nexport default BackofficeRightPageLayout;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BackofficeVerifyEmailPage-BSTtLXdx.js","names":[],"sources":["../../src/pages/BackofficeVerifyEmailPage.tsx"],"sourcesContent":["import { useCallback, useContext, type JSX } from 'react';\nimport * as ReactRelay from 'react-relay';\nimport type { MutationParameters } from 'relay-runtime';\nimport { RoutingContext } from '@plumile/router';\n\nimport { VerifyEmailScreen } from '../auth/pages/VerifyEmailScreen.js';\nimport {\n type MutationPayloadBase,\n requireField,\n resolveMutationOutcome,\n} from '../relay/mutationResult.js';\nimport { useSharedTranslation } from '../i18n/useSharedTranslation.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport { useBackofficeAuthVerifyEmailConfig } from '../provider/useBackofficeLazyValue.js';\nimport { getBackofficeLoginPath } from '../router/backofficeAuthPaths.js';\n\nconst { useMutation } = ReactRelay;\n\ntype VerifyEmailErrorReason =\n | 'INVALID_TOKEN'\n | 'TOKEN_EXPIRED'\n | 'ALREADY_VERIFIED'\n | 'INTERNAL_ERROR';\n\ntype VerifyEmailMutationPayload =\n MutationPayloadBase<VerifyEmailErrorReason> & {\n success?: boolean | null;\n };\n\nexport const BackofficeVerifyEmailPage = (): JSX.Element => {\n const routerContext = useContext(RoutingContext);\n const { basePath } = useBackofficeConfig();\n const auth = useBackofficeAuthVerifyEmailConfig();\n const { t } = useSharedTranslation();\n type VerifyEmailMutation = MutationParameters & {\n response: { verifyEmail?: VerifyEmailMutationPayload | null };\n variables: { input: { token: string } };\n };\n const [commitVerify] = useMutation<VerifyEmailMutation>(\n auth.verifyEmailMutation,\n );\n\n const handleBackToLogin = useCallback(() => {\n routerContext?.history.push({ pathname: getBackofficeLoginPath(basePath) });\n }, [basePath, routerContext?.history]);\n\n const handleVerifyEmail = useCallback(\n async (input: { token: string }): Promise<boolean> => {\n const invalidMessage = t('auth.verifyEmail.errors.invalid');\n const defaultErrorMessage = invalidMessage;\n\n return new Promise((resolve, reject) => {\n commitVerify({\n variables: {\n input,\n },\n onCompleted: (response) => {\n const outcome = resolveMutationOutcome(\n response.verifyEmail ?? null,\n {\n defaultErrorMessage,\n mapReason: (reason) => {\n switch (reason) {\n case 'INVALID_TOKEN':\n return invalidMessage;\n case 'TOKEN_EXPIRED':\n return t('auth.verifyEmail.errors.expired');\n case 'ALREADY_VERIFIED':\n return t('auth.verifyEmail.errors.alreadyVerified');\n case 'INTERNAL_ERROR':\n return invalidMessage;\n default:\n return null;\n }\n },\n },\n );\n if (!outcome.ok) {\n reject(new Error(outcome.message));\n return;\n }\n\n const successResult = requireField(\n outcome.payload.success ?? null,\n defaultErrorMessage,\n );\n if (!successResult.ok) {\n reject(new Error(successResult.message));\n return;\n }\n resolve(successResult.value);\n },\n onError: (mutationError) => {\n let error = new Error(defaultErrorMessage);\n if (mutationError instanceof Error) {\n error = mutationError;\n }\n reject(error);\n },\n });\n });\n },\n [commitVerify, t],\n );\n\n return (\n <VerifyEmailScreen\n onBackToLogin={handleBackToLogin}\n onVerifyEmail={handleVerifyEmail}\n />\n );\n};\n\nexport default BackofficeVerifyEmailPage;\n"],"mappings":";;;;;;;;;;;AAgBA,IAAM,EAAE,mBAAgB,GAaX,UAA+C;CAC1D,IAAM,IAAgB,EAAW,EAAe,EAC1C,EAAE,gBAAa,GAAqB,EACpC,IAAO,GAAoC,EAC3C,EAAE,SAAM,GAAsB,EAK9B,CAAC,KAAgB,EACrB,EAAK,oBACN;AAiED,QACE,kBAAC,GAAD;EACE,eAjEsB,QAAkB;AAC1C,MAAe,QAAQ,KAAK,EAAE,UAAU,EAAuB,EAAS,EAAE,CAAC;KAC1E,CAAC,GAAU,GAAe,QAAQ,CAAC;EAgElC,eA9DsB,EACxB,OAAO,MAA+C;GACpD,IAAM,IAAiB,EAAE,kCAAkC,EACrD,IAAsB;AAE5B,UAAO,IAAI,SAAS,GAAS,MAAW;AACtC,MAAa;KACX,WAAW,EACT,UACD;KACD,cAAc,MAAa;MACzB,IAAM,IAAU,EACd,EAAS,eAAe,MACxB;OACE;OACA,YAAY,MAAW;AACrB,gBAAQ,GAAR;SACE,KAAK,gBACH,QAAO;SACT,KAAK,gBACH,QAAO,EAAE,kCAAkC;SAC7C,KAAK,mBACH,QAAO,EAAE,0CAA0C;SACrD,KAAK,iBACH,QAAO;SACT,QACE,QAAO;;;OAGd,CACF;AACD,UAAI,CAAC,EAAQ,IAAI;AACf,SAAW,MAAM,EAAQ,QAAQ,CAAC;AAClC;;MAGF,IAAM,IAAgB,EACpB,EAAQ,QAAQ,WAAW,MAC3B,EACD;AACD,UAAI,CAAC,EAAc,IAAI;AACrB,SAAW,MAAM,EAAc,QAAQ,CAAC;AACxC;;AAEF,QAAQ,EAAc,MAAM;;KAE9B,UAAU,MAAkB;MAC1B,IAAI,IAAY,MAAM,EAAoB;AAI1C,MAHI,aAAyB,UAC3B,IAAQ,IAEV,EAAO,EAAM;;KAEhB,CAAC;KACF;KAEJ,CAAC,GAAc,EAAE,CAClB;EAMG,CAAA"}
1
+ {"version":3,"file":"BackofficeVerifyEmailPage-BSTtLXdx.js","names":[],"sources":["../../src/pages/BackofficeVerifyEmailPage.tsx"],"sourcesContent":["import { useCallback, useContext, type JSX } from 'react';\nimport * as ReactRelay from 'react-relay';\nimport type { MutationParameters } from 'relay-runtime';\nimport { RoutingContext } from '@plumile/router';\n\nimport { VerifyEmailScreen } from '../auth/pages/VerifyEmailScreen.js';\nimport {\n type MutationPayloadBase,\n requireField,\n resolveMutationOutcome,\n} from '../relay/mutationResult.js';\nimport { useSharedTranslation } from '../i18n/useSharedTranslation.js';\nimport { useBackofficeConfig } from '../provider/BackofficeConfigContext.js';\nimport { useBackofficeAuthVerifyEmailConfig } from '../provider/useBackofficeLazyValue.js';\nimport { getBackofficeLoginPath } from '../router/backofficeAuthPaths.js';\n\nconst { useMutation } = ReactRelay;\n\ntype VerifyEmailErrorReason =\n | 'INVALID_TOKEN'\n | 'TOKEN_EXPIRED'\n | 'ALREADY_VERIFIED'\n | 'INTERNAL_ERROR';\n\ntype VerifyEmailMutationPayload =\n MutationPayloadBase<VerifyEmailErrorReason> & {\n success?: boolean | null;\n };\n\nexport const BackofficeVerifyEmailPage = (): JSX.Element => {\n const routerContext = useContext(RoutingContext);\n const { basePath } = useBackofficeConfig();\n const auth = useBackofficeAuthVerifyEmailConfig();\n const { t } = useSharedTranslation();\n type VerifyEmailMutation = MutationParameters & {\n response: { verifyEmail?: VerifyEmailMutationPayload | null };\n variables: { input: { token: string } };\n };\n const [commitVerify] = useMutation<VerifyEmailMutation>(\n auth.verifyEmailMutation,\n );\n\n const handleBackToLogin = useCallback(() => {\n routerContext?.history.push({ pathname: getBackofficeLoginPath(basePath) });\n }, [basePath, routerContext?.history]);\n\n const handleVerifyEmail = useCallback(\n async (input: { token: string }): Promise<boolean> => {\n const invalidMessage = t('auth.verifyEmail.errors.invalid');\n const defaultErrorMessage = invalidMessage;\n\n return new Promise((resolve, reject) => {\n commitVerify({\n variables: {\n input,\n },\n onCompleted: (response) => {\n const outcome = resolveMutationOutcome(\n response.verifyEmail ?? null,\n {\n defaultErrorMessage,\n mapReason: (reason) => {\n switch (reason) {\n case 'INVALID_TOKEN':\n return invalidMessage;\n case 'TOKEN_EXPIRED':\n return t('auth.verifyEmail.errors.expired');\n case 'ALREADY_VERIFIED':\n return t('auth.verifyEmail.errors.alreadyVerified');\n case 'INTERNAL_ERROR':\n return invalidMessage;\n default:\n return null;\n }\n },\n },\n );\n if (!outcome.ok) {\n reject(new Error(outcome.message));\n return;\n }\n\n const successResult = requireField(\n outcome.payload.success ?? null,\n defaultErrorMessage,\n );\n if (!successResult.ok) {\n reject(new Error(successResult.message));\n return;\n }\n resolve(successResult.value);\n },\n onError: (mutationError) => {\n let error = new Error(defaultErrorMessage);\n if (mutationError instanceof Error) {\n error = mutationError;\n }\n reject(error);\n },\n });\n });\n },\n [commitVerify, t],\n );\n\n return (\n <VerifyEmailScreen\n onBackToLogin={handleBackToLogin}\n onVerifyEmail={handleVerifyEmail}\n />\n );\n};\n\nexport default BackofficeVerifyEmailPage;\n"],"mappings":";;;;;;;;;;;AAgBA,IAAM,EAAE,mBAAgB,GAaX,UAA+C;CAC1D,IAAM,IAAgB,EAAW,EAAe,EAC1C,EAAE,gBAAa,GAAqB,EACpC,IAAO,GAAoC,EAC3C,EAAE,SAAM,GAAsB,EAK9B,CAAC,KAAgB,EACrB,EAAK,oBACN;AAiED,QACE,kBAAC,GAAD;EACE,eAjEsB,QAAkB;AAC1C,MAAe,QAAQ,KAAK,EAAE,UAAU,EAAuB,EAAS,EAAE,CAAC;KAC1E,CAAC,GAAU,GAAe,QAAQ,CA+DlB;EACf,eA9DsB,EACxB,OAAO,MAA+C;GACpD,IAAM,IAAiB,EAAE,kCAAkC,EACrD,IAAsB;AAE5B,UAAO,IAAI,SAAS,GAAS,MAAW;AACtC,MAAa;KACX,WAAW,EACT,UACD;KACD,cAAc,MAAa;MACzB,IAAM,IAAU,EACd,EAAS,eAAe,MACxB;OACE;OACA,YAAY,MAAW;AACrB,gBAAQ,GAAR;SACE,KAAK,gBACH,QAAO;SACT,KAAK,gBACH,QAAO,EAAE,kCAAkC;SAC7C,KAAK,mBACH,QAAO,EAAE,0CAA0C;SACrD,KAAK,iBACH,QAAO;SACT,QACE,QAAO;;;OAGd,CACF;AACD,UAAI,CAAC,EAAQ,IAAI;AACf,SAAW,MAAM,EAAQ,QAAQ,CAAC;AAClC;;MAGF,IAAM,IAAgB,EACpB,EAAQ,QAAQ,WAAW,MAC3B,EACD;AACD,UAAI,CAAC,EAAc,IAAI;AACrB,SAAW,MAAM,EAAc,QAAQ,CAAC;AACxC;;AAEF,QAAQ,EAAc,MAAM;;KAE9B,UAAU,MAAkB;MAC1B,IAAI,IAAY,MAAM,EAAoB;AAI1C,MAHI,aAAyB,UAC3B,IAAQ,IAEV,EAAO,EAAM;;KAEhB,CAAC;KACF;KAEJ,CAAC,GAAc,EAAE,CAMA;EACf,CAAA"}