@yca-software/yca-react-core 0.0.1

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 (87) hide show
  1. package/README.md +129 -0
  2. package/dist/admin/index.d.ts +10 -0
  3. package/dist/admin/index.js +3 -0
  4. package/dist/admin/index.js.map +1 -0
  5. package/dist/admin-BVYdWtEy.d.ts +3 -0
  6. package/dist/api/index.d.ts +68 -0
  7. package/dist/api/index.js +3 -0
  8. package/dist/api/index.js.map +1 -0
  9. package/dist/auth/index.d.ts +49 -0
  10. package/dist/auth/index.js +3 -0
  11. package/dist/auth/index.js.map +1 -0
  12. package/dist/chunk-2HJO2ICF.js +339 -0
  13. package/dist/chunk-2HJO2ICF.js.map +1 -0
  14. package/dist/chunk-3AYG73DK.js +174 -0
  15. package/dist/chunk-3AYG73DK.js.map +1 -0
  16. package/dist/chunk-3SCZ4Z6P.js +26 -0
  17. package/dist/chunk-3SCZ4Z6P.js.map +1 -0
  18. package/dist/chunk-3ZLODBRJ.js +21 -0
  19. package/dist/chunk-3ZLODBRJ.js.map +1 -0
  20. package/dist/chunk-6LO5O4G6.js +224 -0
  21. package/dist/chunk-6LO5O4G6.js.map +1 -0
  22. package/dist/chunk-APZVW5SW.js +75 -0
  23. package/dist/chunk-APZVW5SW.js.map +1 -0
  24. package/dist/chunk-EVZBADFR.js +583 -0
  25. package/dist/chunk-EVZBADFR.js.map +1 -0
  26. package/dist/chunk-GXWVFQCK.js +52 -0
  27. package/dist/chunk-GXWVFQCK.js.map +1 -0
  28. package/dist/chunk-KG4JVAKS.js +13 -0
  29. package/dist/chunk-KG4JVAKS.js.map +1 -0
  30. package/dist/chunk-KZH56VPP.js +183 -0
  31. package/dist/chunk-KZH56VPP.js.map +1 -0
  32. package/dist/chunk-LP7Q75C6.js +96 -0
  33. package/dist/chunk-LP7Q75C6.js.map +1 -0
  34. package/dist/chunk-NL4NAHXG.js +3 -0
  35. package/dist/chunk-NL4NAHXG.js.map +1 -0
  36. package/dist/chunk-PLSNCPEC.js +2507 -0
  37. package/dist/chunk-PLSNCPEC.js.map +1 -0
  38. package/dist/chunk-QFKVPVFC.js +1391 -0
  39. package/dist/chunk-QFKVPVFC.js.map +1 -0
  40. package/dist/chunk-TSXUUD64.js +64 -0
  41. package/dist/chunk-TSXUUD64.js.map +1 -0
  42. package/dist/chunk-UCJ76BEC.js +32 -0
  43. package/dist/chunk-UCJ76BEC.js.map +1 -0
  44. package/dist/chunk-WFTIYYII.js +3 -0
  45. package/dist/chunk-WFTIYYII.js.map +1 -0
  46. package/dist/components/forms/index.d.ts +101 -0
  47. package/dist/components/forms/index.js +7 -0
  48. package/dist/components/forms/index.js.map +1 -0
  49. package/dist/components/marketing/index.d.ts +473 -0
  50. package/dist/components/marketing/index.js +5 -0
  51. package/dist/components/marketing/index.js.map +1 -0
  52. package/dist/components/spa/index.d.ts +275 -0
  53. package/dist/components/spa/index.js +8 -0
  54. package/dist/components/spa/index.js.map +1 -0
  55. package/dist/components/ui/index.d.ts +420 -0
  56. package/dist/components/ui/index.js +6 -0
  57. package/dist/components/ui/index.js.map +1 -0
  58. package/dist/constants/index.d.ts +2 -0
  59. package/dist/constants/index.js +4 -0
  60. package/dist/constants/index.js.map +1 -0
  61. package/dist/cookies-ZS_QYOR7.d.ts +8 -0
  62. package/dist/dateRangePickerTranslations-B99F2WQA.d.ts +9 -0
  63. package/dist/errors/index.d.ts +48 -0
  64. package/dist/errors/index.js +3 -0
  65. package/dist/errors/index.js.map +1 -0
  66. package/dist/forms-VpLvEFob.d.ts +18 -0
  67. package/dist/hooks/index.d.ts +18 -0
  68. package/dist/hooks/index.js +4 -0
  69. package/dist/hooks/index.js.map +1 -0
  70. package/dist/index.d.ts +42 -0
  71. package/dist/index.js +19 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/lib/index.d.ts +49 -0
  74. package/dist/lib/index.js +7 -0
  75. package/dist/lib/index.js.map +1 -0
  76. package/dist/pagination-CFqoqiAG.d.ts +10 -0
  77. package/dist/radix-overlays-BlCr-uem.d.ts +10 -0
  78. package/dist/styles.css +9 -0
  79. package/dist/theme/marketing.css +750 -0
  80. package/dist/theme/tokens.css +115 -0
  81. package/dist/types/index.d.ts +2 -0
  82. package/dist/types/index.js +3 -0
  83. package/dist/types/index.js.map +1 -0
  84. package/dist/types-BuNHOJ1h.d.ts +58 -0
  85. package/dist/types-CN73TWSY.d.ts +51 -0
  86. package/dist/useAdminListPage-C4ZTTllP.d.ts +26 -0
  87. package/package.json +152 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/spa/page-loader/index.tsx","../src/components/spa/admin-detail-page/index.tsx","../src/components/spa/admin-list-page/index.tsx","../src/components/spa/audit-log-list-filters/index.tsx","../src/components/spa/query-state/index.tsx","../src/components/spa/create-bound-query-state/index.tsx","../src/components/spa/date-range-filter/index.tsx","../src/components/spa/detail-field-list/index.tsx","../src/components/spa/entity-row/index.tsx","../src/components/spa/route-suspense/index.tsx","../src/components/spa/theme-provider/index.tsx","../src/components/spa/theme-toggle/index.tsx"],"names":["jsx","jsxs","Loader2","Search","Fragment"],"mappings":"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","file":"chunk-EVZBADFR.js","sourcesContent":["import { Loader2 } from 'lucide-react';\nimport { useTranslation } from 'react-i18next';\n\nimport { cn } from '../../../lib/utils';\n\nexport interface PageLoaderProps {\n className?: string;\n spinnerClassName?: string;\n /**\n * - `page`: full-screen route guard\n * - `route`: lazy route / admin detail suspense (`min-h-[40vh]`)\n * - `section`: in-page block (`py-12`)\n * - `inline`: compact centered spinner (auth cards, nested panels)\n */\n variant?: 'page' | 'route' | 'section' | 'inline';\n /** Accessible loading text; defaults to i18n `common.loading`. */\n loadingLabel?: string;\n}\n\nexport function PageLoader({\n className,\n spinnerClassName,\n variant = 'section',\n loadingLabel,\n}: PageLoaderProps) {\n const { t } = useTranslation('common');\n const label = loadingLabel ?? t('loading');\n const spinnerSize =\n variant === 'inline'\n ? 'h-6 w-6 animate-spin text-primary'\n : 'h-8 w-8 animate-spin text-primary';\n\n const content = (\n <>\n <Loader2 className={cn(spinnerSize, spinnerClassName)} aria-hidden />\n <span className=\"sr-only\">{label}</span>\n </>\n );\n\n if (variant === 'page') {\n return (\n <div\n className={cn('min-h-screen flex items-center justify-center bg-background', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n {content}\n </div>\n );\n }\n\n if (variant === 'route') {\n return (\n <div\n className={cn('flex min-h-[40vh] items-center justify-center', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n {content}\n </div>\n );\n }\n\n if (variant === 'inline') {\n return (\n <div\n className={cn('flex items-center justify-center', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n {content}\n </div>\n );\n }\n\n return (\n <div\n className={cn('flex items-center justify-center py-12', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n {content}\n </div>\n );\n}\n","import { ArrowLeft } from 'lucide-react';\nimport { Link } from 'react-router';\nimport type { AdminDetailErrorKind } from '../../../types/admin';\nimport { Button } from '../../ui';\nimport { PageLoader } from '../page-loader';\n\nexport type { AdminDetailErrorKind };\n\nexport interface AdminDetailPageProps {\n backHref: string;\n backLabel: string;\n isLoading: boolean;\n errorKind?: AdminDetailErrorKind;\n error?: unknown;\n notFoundMessage: string;\n forbiddenMessage: string;\n loadErrorMessage: string;\n /** App-bound API error formatter (e.g. formatApiError from SPA helpers). */\n formatError?: (error: unknown) => string;\n children: React.ReactNode;\n headerActions?: React.ReactNode;\n}\n\n/** Reusable admin detail page: back button, loading/error states, content. */\nexport function AdminDetailPage({\n backHref,\n backLabel,\n isLoading,\n errorKind,\n error,\n notFoundMessage,\n forbiddenMessage,\n loadErrorMessage,\n formatError,\n children,\n headerActions,\n}: AdminDetailPageProps) {\n if (isLoading) {\n return <PageLoader variant=\"route\" />;\n }\n\n if (errorKind) {\n const message =\n errorKind === 'forbidden'\n ? forbiddenMessage\n : errorKind === 'load'\n ? formatError?.(error) || loadErrorMessage\n : notFoundMessage;\n\n return (\n <div className=\"space-y-4 sm:space-y-6\">\n <p className=\"text-destructive\">{message}</p>\n <Link to={backHref}>\n <Button variant=\"outline\">{backLabel}</Button>\n </Link>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <Link to={backHref}>\n <Button variant=\"ghost\" size=\"sm\">\n <ArrowLeft className=\"mr-2 h-4 w-4\" aria-hidden />\n {backLabel}\n </Button>\n </Link>\n {headerActions}\n </div>\n {children}\n </div>\n );\n}\n","import { Loader2, Search, X } from 'lucide-react';\n\nimport type { AdminListSearchMode } from '../../../hooks/useAdminListPage';\nimport { cn } from '../../../lib/utils';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Input,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '../../ui';\nimport { PageLoader } from '../page-loader';\n\nexport type { AdminListSearchMode };\n\nexport interface AdminListPageColumn<T> {\n key: string;\n header: string;\n render: (item: T) => React.ReactNode;\n /** Column class for alignment/width */\n className?: string;\n}\n\nexport interface AdminListPageProps<T> {\n title: string;\n description: string;\n /** Optional actions (e.g. \"Create\" button) rendered next to the page title. */\n headerActions?: React.ReactNode;\n /** Card header title (e.g. \"All Users\") */\n cardTitle: string;\n searchPlaceholder: string;\n emptyMessage: string;\n columns: AdminListPageColumn<T>[];\n items: T[];\n isLoading: boolean;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n loadMoreRef: React.RefObject<HTMLTableRowElement | null>;\n search: string;\n onSearchChange: (value: string) => void;\n onRowClick: (item: T) => void;\n getRowKey: (item: T) => string;\n /** Accessible label for the loading spinner (app i18n). */\n loadingAriaLabel?: string;\n /** `instant` updates query on debounced typing; `submit` requires Search/Clear actions. */\n searchMode?: AdminListSearchMode;\n /** Label for the Search button when `searchMode` is `submit`. */\n searchButtonLabel?: string;\n /** Label for the Clear button when `searchMode` is `submit`. */\n clearButtonLabel?: string;\n /** Called when the user clicks Search or presses Enter in submit mode. */\n onSearchSubmit?: () => void;\n /** Called when the user clicks Clear in submit mode. */\n onSearchClear?: () => void;\n /** Enter-key handler for submit mode (typically from `useAdminListPage().onSearchKeyDown`). */\n onSearchKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;\n}\n\n/**\n * Reusable admin list page: search + card + table + infinite scroll.\n */\nexport function AdminListPage<T>({\n title,\n description,\n headerActions,\n cardTitle,\n searchPlaceholder,\n emptyMessage,\n columns,\n items,\n isLoading,\n hasNextPage,\n isFetchingNextPage,\n loadMoreRef,\n search,\n onSearchChange,\n onRowClick,\n getRowKey,\n loadingAriaLabel = 'Loading...',\n searchMode = 'instant',\n searchButtonLabel = 'Search',\n clearButtonLabel = 'Clear',\n onSearchSubmit,\n onSearchClear,\n onSearchKeyDown,\n}: AdminListPageProps<T>) {\n const safeItems = items.filter((item): item is T => item != null && typeof item === 'object');\n const isSubmitMode = searchMode === 'submit';\n const hasSearchText = search.trim().length > 0;\n\n const handleSearchInputKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {\n if (isSubmitMode && event.key === 'Enter') {\n event.preventDefault();\n onSearchSubmit?.();\n }\n onSearchKeyDown?.(event);\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold sm:text-3xl\">{title}</h1>\n <p className=\"mt-1 text-sm text-muted-foreground sm:text-base\">{description}</p>\n </div>\n {headerActions}\n </div>\n\n <Card>\n <CardHeader className=\"gap-4\">\n <div className=\"space-y-2\">\n <CardTitle>{cardTitle}</CardTitle>\n {!isSubmitMode ? <CardDescription>{searchPlaceholder}</CardDescription> : null}\n </div>\n {isSubmitMode ? (\n <div\n className={cn(\n 'flex h-9 w-full min-w-0 max-w-full overflow-hidden rounded-md border border-input bg-background shadow-xs sm:max-w-sm',\n 'transition-[border-color,box-shadow] focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/20',\n )}\n >\n <div className=\"relative flex min-w-0 flex-1 items-center\">\n <Input\n placeholder={searchPlaceholder}\n value={search}\n onChange={(e) => onSearchChange(e.target.value)}\n onKeyDown={handleSearchInputKeyDown}\n className={cn(\n 'h-9 rounded-none rounded-l-md border-0 bg-transparent px-3 shadow-none',\n 'focus-visible:border-transparent focus-visible:ring-0',\n hasSearchText && 'pr-9',\n )}\n aria-label={searchPlaceholder}\n />\n {hasSearchText ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onSearchClear}\n aria-label={clearButtonLabel}\n className=\"absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground hover:bg-muted/60 hover:text-foreground\"\n >\n <X className=\"h-4 w-4\" aria-hidden />\n </Button>\n ) : null}\n </div>\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={onSearchSubmit}\n aria-label={searchButtonLabel}\n className={cn(\n 'h-full w-10 shrink-0 rounded-none rounded-r-md border-0 border-l border-primary-foreground/15 px-0 shadow-none',\n 'hover:bg-primary/90 active:bg-primary/95',\n 'shadow-[inset_0_1px_0_0_color-mix(in_oklch,var(--primary-foreground)_20%,transparent)]',\n )}\n >\n <Search className=\"h-4 w-4\" aria-hidden />\n </Button>\n </div>\n ) : (\n <div className=\"relative max-w-full sm:max-w-sm\">\n <Search\n className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\"\n aria-hidden\n />\n <Input\n placeholder={searchPlaceholder}\n value={search}\n onChange={(e) => onSearchChange(e.target.value)}\n className=\"pl-9\"\n aria-label={searchPlaceholder}\n />\n </div>\n )}\n </CardHeader>\n <CardContent>\n {isLoading ? (\n <PageLoader loadingLabel={loadingAriaLabel} />\n ) : safeItems.length === 0 ? (\n <p className=\"py-12 text-center text-muted-foreground\">{emptyMessage}</p>\n ) : (\n <div className=\"-mx-4 overflow-x-auto sm:mx-0\">\n <Table className=\"min-w-[400px]\">\n <TableHeader>\n <TableRow className=\"bg-muted/50 hover:bg-muted/50\">\n {columns.map((col) => (\n <TableHead key={col.key} className={col.className}>\n {col.header}\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {safeItems.map((item, index) => (\n <TableRow\n key={getRowKey(item) || `row-${index}`}\n role=\"button\"\n tabIndex={0}\n onClick={() => onRowClick(item)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onRowClick(item);\n }\n }}\n className=\"cursor-pointer hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\"\n >\n {columns.map((col) => (\n <TableCell\n key={col.key}\n className={cn(col.className ?? 'text-muted-foreground')}\n >\n {col.render(item)}\n </TableCell>\n ))}\n </TableRow>\n ))}\n {hasNextPage && (\n <TableRow ref={loadMoreRef}>\n <TableCell\n colSpan={columns.length}\n className=\"text-center text-muted-foreground\"\n >\n {isFetchingNextPage ? (\n <Loader2 className=\"inline-block h-5 w-5 animate-spin\" aria-hidden />\n ) : null}\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n","import { Search } from 'lucide-react';\n\nimport { Input } from '../../ui/input';\nimport { Select } from '../../ui/select';\n\nexport type AuditLogFilterOption = {\n value: string;\n label: string;\n};\n\nexport type AuditLogListFiltersProps = {\n search: string;\n onSearchChange: (value: string) => void;\n action: string;\n onActionChange: (value: string) => void;\n resourceType: string;\n onResourceTypeChange: (value: string) => void;\n actionOptions: AuditLogFilterOption[];\n resourceOptions: AuditLogFilterOption[];\n searchPlaceholder: string;\n actionFilterLabel: string;\n resourceFilterLabel: string;\n};\n\nexport function AuditLogListFilters({\n search,\n onSearchChange,\n action,\n onActionChange,\n resourceType,\n onResourceTypeChange,\n actionOptions,\n resourceOptions,\n searchPlaceholder,\n actionFilterLabel,\n resourceFilterLabel,\n}: AuditLogListFiltersProps) {\n return (\n <div className=\"flex flex-col gap-2 lg:flex-row lg:flex-wrap lg:items-center\">\n <div className=\"relative w-full lg:max-w-md\">\n <Search\n className=\"pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground\"\n aria-hidden\n />\n <Input\n className=\"pl-9\"\n placeholder={searchPlaceholder}\n value={search}\n onChange={(event) => onSearchChange(event.target.value)}\n aria-label={searchPlaceholder}\n />\n </div>\n <Select\n className=\"w-full lg:w-44\"\n value={action}\n onValueChange={onActionChange}\n options={actionOptions}\n aria-label={actionFilterLabel}\n />\n <Select\n className=\"w-full lg:w-52\"\n value={resourceType}\n onValueChange={onResourceTypeChange}\n options={resourceOptions}\n aria-label={resourceFilterLabel}\n />\n </div>\n );\n}\n","import { Alert, AlertDescription, Button } from '../../ui';\nimport { PageLoader } from '../page-loader';\n\nexport interface QueryStateProps {\n isLoading: boolean;\n isError?: boolean;\n error?: unknown;\n onRetry?: () => void;\n loadingClassName?: string;\n children: React.ReactNode;\n /** App-bound API error formatter (e.g. formatApiError from SPA helpers). */\n formatError?: (error: unknown) => string;\n /** Fallback copy when formatError returns empty. */\n errorDescription?: string;\n retryLabel?: string;\n loadingLabel?: string;\n}\n\n/** Renders loading/error states for TanStack Query results, then children on success. */\nexport function QueryState({\n isLoading,\n isError,\n error,\n onRetry,\n loadingClassName,\n children,\n formatError,\n errorDescription = 'We could not load this data. Please try again.',\n retryLabel = 'Try again',\n loadingLabel = 'Loading...',\n}: QueryStateProps) {\n if (isLoading) {\n return (\n <PageLoader variant=\"section\" className={loadingClassName} loadingLabel={loadingLabel} />\n );\n }\n\n if (isError) {\n const message = formatError?.(error) || errorDescription;\n return (\n <Alert variant=\"destructive\" className=\"max-w-xl\">\n <AlertDescription className=\"space-y-3\">\n <p>{message}</p>\n {onRetry ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={onRetry}>\n {retryLabel}\n </Button>\n ) : null}\n </AlertDescription>\n </Alert>\n );\n }\n\n return <>{children}</>;\n}\n","import { QueryState, type QueryStateProps } from '../query-state';\n\nexport type QueryStateBindings = Pick<\n QueryStateProps,\n 'formatError' | 'errorDescription' | 'retryLabel' | 'loadingLabel'\n>;\n\nexport type BoundQueryStateProps = Omit<QueryStateProps, keyof QueryStateBindings>;\n\n/** Factory for SPA-local i18n/error bindings around shared QueryState. */\nexport function createBoundQueryState(useBindings: () => QueryStateBindings) {\n function BoundQueryState(props: BoundQueryStateProps) {\n const bindings = useBindings();\n return <QueryState {...bindings} {...props} />;\n }\n\n BoundQueryState.displayName = 'BoundQueryState';\n return BoundQueryState;\n}\n","import type { DateRange } from 'react-day-picker';\n\nimport { dateRangePickerTranslations } from '../../../lib/dateRangePickerTranslations';\nimport { cn } from '../../../lib/utils';\nimport { DateRangePicker } from '../../ui/date-range-picker';\n\nexport interface DateRangeFilterProps {\n label: string;\n value: DateRange | undefined;\n onChange: (value: DateRange | undefined) => void;\n onApply: (value: DateRange | undefined) => void;\n minDate?: Date;\n maxDate?: Date;\n className?: string;\n hint?: string;\n /** Layout variant for toolbar vs panel contexts. */\n variant?: 'panel' | 'inline' | 'toolbar';\n /** i18n translate function (e.g. from useTranslationNamespace). */\n t: (key: string) => string;\n /** Namespace prefix for dateRange keys (default: `common`). */\n translationNs?: string;\n}\n\nexport function DateRangeFilter({\n label,\n value,\n onChange,\n onApply,\n minDate,\n maxDate,\n className,\n hint,\n variant = 'panel',\n t,\n translationNs = 'common',\n}: DateRangeFilterProps) {\n const inline = variant === 'inline';\n const toolbar = variant === 'toolbar';\n\n const picker = (\n <DateRangePicker\n className={cn(\n 'w-full min-w-0 border-border/80 bg-background',\n toolbar ? 'w-full min-w-0' : inline ? 'sm:max-w-[280px]' : 'sm:max-w-[320px]',\n )}\n value={value}\n onChange={onChange}\n minDate={minDate}\n maxDate={maxDate}\n translations={dateRangePickerTranslations(t, translationNs)}\n onApply={onApply}\n />\n );\n\n if (toolbar) {\n return (\n <div className={cn('min-w-0 w-full', className)}>\n <p className=\"mb-1 text-xs font-medium text-muted-foreground\">{label}</p>\n {picker}\n </div>\n );\n }\n\n if (inline) {\n return (\n <div className={cn('w-full shrink-0 sm:w-auto', className)}>\n <p className=\"mb-1 text-xs font-medium text-muted-foreground\">{label}</p>\n {picker}\n {hint ? <p className=\"mt-1 text-[0.65rem] text-muted-foreground\">{hint}</p> : null}\n </div>\n );\n }\n\n return (\n <div className={cn('rounded-lg border border-border/80 bg-muted/25 p-3 shadow-sm', className)}>\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between\">\n <div className=\"min-w-0 space-y-1\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {label}\n </p>\n {hint ? <p className=\"text-xs text-muted-foreground\">{hint}</p> : null}\n </div>\n {picker}\n </div>\n </div>\n );\n}\n\nexport { dateRangePickerTranslations };\n","import { cn } from '../../../lib/utils';\n\nexport interface DetailField {\n label: string;\n value: React.ReactNode;\n /** Span 2 columns on sm+ */\n span?: 1 | 2;\n}\n\nexport type DetailFieldVariant = 'plain' | 'bordered';\n\nexport interface DetailFieldListProps {\n fields: DetailField[];\n className?: string;\n /** When `bordered`, each label/value group is shown in a subtle framed cell. */\n fieldVariant?: DetailFieldVariant;\n}\n\n/** Definition list for detail views. Responsive two-column grid on sm+. */\nexport function DetailFieldList({\n fields,\n className,\n fieldVariant = 'plain',\n}: DetailFieldListProps) {\n return (\n <dl className={cn('grid gap-4 sm:grid-cols-2', className)}>\n {fields.map((field, i) => (\n <div\n key={`${field.label}-${i}`}\n className={cn(\n 'space-y-1',\n fieldVariant === 'bordered' && 'rounded-lg border bg-muted/20 px-3 py-2.5',\n field.span === 2 && 'sm:col-span-2',\n )}\n >\n <dt className=\"text-sm font-medium text-muted-foreground\">{field.label}</dt>\n <dd className=\"text-sm break-words\">{field.value}</dd>\n </div>\n ))}\n </dl>\n );\n}\n","import type { LucideIcon } from 'lucide-react';\nimport { MoreVertical } from 'lucide-react';\n\nimport { cn } from '../../../lib/utils';\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Tooltip,\n} from '../../ui';\n\nexport interface EntityRowAction {\n label: string;\n icon: LucideIcon;\n onClick: () => void;\n variant?: 'default' | 'destructive';\n}\n\nexport interface EntityRowProps {\n icon: LucideIcon;\n title: React.ReactNode;\n /** Tooltip for title (when title is truncated). Omit when title is a ReactNode. */\n titleTooltip?: string;\n description?: string;\n /** Secondary line below title (e.g. created date, email) */\n subtitle?: string;\n onClick?: () => void;\n actions?: EntityRowAction[];\n /** Muted styling for archived items */\n archived?: boolean;\n /** Extra content below main block (e.g. key prefix, metadata) */\n children?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Reusable row for settings entity lists (roles, teams, members, API keys).\n * Consistent layout: icon + title + optional description/subtitle + actions dropdown.\n */\nexport function EntityRow({\n icon: Icon,\n title,\n titleTooltip = typeof title === 'string' ? title : undefined,\n description,\n subtitle,\n onClick,\n actions = [],\n archived = false,\n children,\n className,\n}: EntityRowProps) {\n const hasActions = actions.length > 0;\n const isClickable = !!onClick;\n\n return (\n <div\n role={isClickable ? 'button' : undefined}\n tabIndex={isClickable ? 0 : undefined}\n onClick={onClick}\n onKeyDown={\n isClickable\n ? (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }\n : undefined\n }\n className={cn(\n 'flex flex-col gap-2 rounded-lg border p-4 transition-colors',\n archived ? 'bg-muted/30' : 'hover:bg-muted/40',\n isClickable &&\n 'cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n className,\n )}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n <div\n className={cn(\n 'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg',\n archived ? 'bg-muted' : 'bg-primary/10',\n )}\n >\n <Icon\n className={cn('h-5 w-5', archived ? 'text-muted-foreground' : 'text-primary')}\n aria-hidden\n />\n </div>\n <div className=\"min-w-0 flex-1\">\n {titleTooltip ? (\n <Tooltip content={titleTooltip} side=\"top\" align=\"start\">\n <p className=\"truncate font-medium max-w-full sm:max-w-md\">{title}</p>\n </Tooltip>\n ) : (\n <p className=\"truncate font-medium max-w-full sm:max-w-md\">{title}</p>\n )}\n {subtitle && (\n <p className=\"mt-0.5 truncate text-xs text-muted-foreground max-w-full sm:max-w-md\">\n {subtitle}\n </p>\n )}\n </div>\n </div>\n {hasActions && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0 -mr-1\"\n onClick={(e) => e.stopPropagation()}\n aria-label=\"Open menu\"\n >\n <MoreVertical className=\"h-4 w-4\" aria-hidden />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" sideOffset={4} onClick={(e) => e.stopPropagation()}>\n {actions.map((action) => {\n const ActionIcon = action.icon;\n return (\n <DropdownMenuItem\n key={action.label}\n onClick={action.onClick}\n className={\n action.variant === 'destructive'\n ? 'text-destructive focus:text-destructive'\n : undefined\n }\n >\n <ActionIcon className=\"mr-2 h-4 w-4\" aria-hidden />\n {action.label}\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n {description && (\n <Tooltip content={description} side=\"top\" align=\"start\">\n <p className=\"text-sm text-muted-foreground overflow-hidden line-clamp-2 [display:-webkit-box] [-webkit-line-clamp:2] [-webkit-box-orient:vertical]\">\n {description}\n </p>\n </Tooltip>\n )}\n {children}\n </div>\n );\n}\n","import { type ReactNode, Suspense } from 'react';\n\nimport { PageLoader } from '../page-loader';\n\nexport interface RouteSuspenseProps {\n children: ReactNode;\n loadingLabel?: string;\n}\n\n/** Suspense boundary for lazy route modules with shared loading UX. */\nexport function RouteSuspense({ children, loadingLabel }: RouteSuspenseProps) {\n return (\n <Suspense fallback={<PageLoader variant=\"route\" loadingLabel={loadingLabel} />}>\n {children}\n </Suspense>\n );\n}\n","import React, { useEffect, useState } from 'react';\n\n/**\n * Theme store interface for theme state management.\n */\nexport interface ThemeStore {\n /** Current theme mode */\n theme: 'light' | 'dark';\n /** Function to update theme */\n setTheme: (theme: 'light' | 'dark') => void;\n}\n\n/** Default in-memory theme store when no external store is provided. */\nexport function useDefaultThemeStore(): ThemeStore {\n const [theme, setTheme] = useState<'light' | 'dark'>('light');\n return { theme, setTheme };\n}\n\nconst ThemeStoreContext = React.createContext<ThemeStore | null>(null);\n\n/** Hook to read theme store from ThemeProvider context. */\nexport function useThemeStoreContext(): ThemeStore | null {\n return React.useContext(ThemeStoreContext);\n}\n\n/**\n * Props for the ThemeProvider component.\n */\nexport interface ThemeProviderProps {\n /** Child components */\n children: React.ReactNode;\n /** Hook function that returns the theme store (e.g., from Zustand) */\n useThemeStore?: () => ThemeStore;\n}\n\n/**\n * Theme provider component that applies theme classes to the document root.\n * Works with any state management solution via the `useThemeStore` prop.\n * When `useThemeStore` is not provided, uses an internal default store so hooks are always called unconditionally.\n *\n * @example\n * ```tsx\n * // With Zustand\n * const useThemeStore = () => {\n * const theme = useStore((state) => state.theme);\n * const setTheme = useStore((state) => state.setTheme);\n * return { theme, setTheme };\n * };\n *\n * <ThemeProvider useThemeStore={useThemeStore}>\n * <App />\n * </ThemeProvider>\n * ```\n */\nexport function ThemeProvider({ children, useThemeStore: useThemeStoreProp }: ThemeProviderProps) {\n const useResolvedStore = useThemeStoreProp ?? useDefaultThemeStore;\n const store = useResolvedStore();\n\n useEffect(() => {\n const root = window.document.documentElement;\n root.classList.remove('light', 'dark');\n root.classList.add(store.theme);\n }, [store.theme]);\n\n return <ThemeStoreContext.Provider value={store}>{children}</ThemeStoreContext.Provider>;\n}\n","import { Moon, Sun } from 'lucide-react';\nimport { Button } from '../../ui/button';\nimport type { ThemeStore } from '../theme-provider';\nimport { useDefaultThemeStore, useThemeStoreContext } from '../theme-provider';\n\n/**\n * Props for the ThemeToggle component.\n */\nexport interface ThemeToggleProps {\n /** Hook function that returns the theme store (e.g., from Zustand) */\n useThemeStore?: () => ThemeStore;\n /** Accessible label for the toggle button */\n ariaLabel?: string;\n}\n\nfunction ThemeToggleWithStore({\n useThemeStore,\n ariaLabel,\n}: {\n useThemeStore: () => ThemeStore;\n ariaLabel: string;\n}) {\n const { theme, setTheme } = useThemeStore();\n const toggleTheme = () => setTheme(theme === 'dark' ? 'light' : 'dark');\n return (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={toggleTheme}\n aria-label={ariaLabel}\n className=\"cursor-pointer\"\n >\n {theme === 'dark' ? <Moon className=\"h-4 w-4\" /> : <Sun className=\"h-4 w-4\" />}\n </Button>\n );\n}\n\nfunction ThemeToggleWithContext({ ariaLabel }: { ariaLabel: string }) {\n const fromContext = useThemeStoreContext();\n const defaultStore = useDefaultThemeStore();\n const store = fromContext ?? defaultStore;\n const toggleTheme = () => store.setTheme(store.theme === 'dark' ? 'light' : 'dark');\n return (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={toggleTheme}\n aria-label={ariaLabel}\n className=\"cursor-pointer\"\n >\n {store.theme === 'dark' ? <Moon className=\"h-4 w-4\" /> : <Sun className=\"h-4 w-4\" />}\n </Button>\n );\n}\n\n/**\n * Theme toggle button component.\n * Displays a sun icon in dark mode and moon icon in light mode.\n * Toggles between light and dark themes when clicked.\n * When useThemeStore is passed, uses that store; otherwise uses ThemeProvider context or internal default (no conditional hook calls).\n *\n * @example\n * ```tsx\n * const useThemeStore = () => {\n * const theme = useStore((state) => state.theme);\n * const setTheme = useStore((state) => state.setTheme);\n * return { theme, setTheme };\n * };\n *\n * <ThemeToggle useThemeStore={useThemeStore} ariaLabel=\"Toggle theme\" />\n * ```\n */\nexport function ThemeToggle({\n useThemeStore: useThemeStoreProp,\n ariaLabel = 'Toggle theme',\n}: ThemeToggleProps) {\n if (useThemeStoreProp) {\n return <ThemeToggleWithStore useThemeStore={useThemeStoreProp} ariaLabel={ariaLabel} />;\n }\n return <ThemeToggleWithContext ariaLabel={ariaLabel} />;\n}\n"]}
@@ -0,0 +1,52 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ // src/lib/utils.ts
5
+ function cn(...inputs) {
6
+ return twMerge(clsx(inputs));
7
+ }
8
+ function getFlagEmoji(langCode) {
9
+ const flagMap = {
10
+ en: "\u{1F1EC}\u{1F1E7}",
11
+ tr: "\u{1F1F9}\u{1F1F7}",
12
+ fr: "\u{1F1EB}\u{1F1F7}",
13
+ es: "\u{1F1EA}\u{1F1F8}",
14
+ de: "\u{1F1E9}\u{1F1EA}",
15
+ it: "\u{1F1EE}\u{1F1F9}",
16
+ pt: "\u{1F1F5}\u{1F1F9}",
17
+ ru: "\u{1F1F7}\u{1F1FA}",
18
+ ja: "\u{1F1EF}\u{1F1F5}",
19
+ zh: "\u{1F1E8}\u{1F1F3}",
20
+ ko: "\u{1F1F0}\u{1F1F7}",
21
+ ar: "\u{1F1F8}\u{1F1E6}",
22
+ hi: "\u{1F1EE}\u{1F1F3}",
23
+ nl: "\u{1F1F3}\u{1F1F1}",
24
+ pl: "\u{1F1F5}\u{1F1F1}",
25
+ sv: "\u{1F1F8}\u{1F1EA}",
26
+ da: "\u{1F1E9}\u{1F1F0}",
27
+ fi: "\u{1F1EB}\u{1F1EE}",
28
+ no: "\u{1F1F3}\u{1F1F4}",
29
+ cs: "\u{1F1E8}\u{1F1FF}",
30
+ ro: "\u{1F1F7}\u{1F1F4}",
31
+ hu: "\u{1F1ED}\u{1F1FA}",
32
+ el: "\u{1F1EC}\u{1F1F7}",
33
+ he: "\u{1F1EE}\u{1F1F1}",
34
+ th: "\u{1F1F9}\u{1F1ED}",
35
+ vi: "\u{1F1FB}\u{1F1F3}",
36
+ id: "\u{1F1EE}\u{1F1E9}",
37
+ ms: "\u{1F1F2}\u{1F1FE}"
38
+ };
39
+ return flagMap[langCode] || "\u{1F310}";
40
+ }
41
+
42
+ // src/lib/surfaces.ts
43
+ var controlBase = "rounded-lg border border-border/50 bg-muted/30 shadow-[var(--shadow-surface)] transition-[color,box-shadow,background-color]";
44
+ var controlFocus = "outline-none focus-visible:border-ring focus-visible:ring-ring/40 focus-visible:ring-[3px]";
45
+ var controlInvalid = "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/35";
46
+ var surfaceCard = "rounded-xl border border-border/45 bg-card text-card-foreground shadow-[var(--shadow-surface)]";
47
+ var surfaceInset = "bg-muted/30";
48
+ var surfaceOverlay = "rounded-xl border border-border/50 bg-popover text-popover-foreground shadow-[var(--shadow-elevated)]";
49
+
50
+ export { cn, controlBase, controlFocus, controlInvalid, getFlagEmoji, surfaceCard, surfaceInset, surfaceOverlay };
51
+ //# sourceMappingURL=chunk-GXWVFQCK.js.map
52
+ //# sourceMappingURL=chunk-GXWVFQCK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/lib/surfaces.ts"],"names":[],"mappings":";;;;AAMO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;AAGO,SAAS,aAAa,QAAA,EAA0B;AACrD,EAAA,MAAM,OAAA,GAAkC;AAAA,IACtC,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI,oBAAA;AAAA,IACJ,EAAA,EAAI;AAAA,GACN;AACA,EAAA,OAAO,OAAA,CAAQ,QAAQ,CAAA,IAAK,WAAA;AAC9B;;;ACrCO,IAAM,WAAA,GACX;AAGK,IAAM,YAAA,GACX;AAGK,IAAM,cAAA,GACX;AAGK,IAAM,WAAA,GACX;AAGK,IAAM,YAAA,GAAe;AAGrB,IAAM,cAAA,GACX","file":"chunk-GXWVFQCK.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind CSS classes with conflict resolution.\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/** Maps ISO 639-1 language codes to flag emojis; defaults to globe. */\nexport function getFlagEmoji(langCode: string): string {\n const flagMap: Record<string, string> = {\n en: '🇬🇧',\n tr: '🇹🇷',\n fr: '🇫🇷',\n es: '🇪🇸',\n de: '🇩🇪',\n it: '🇮🇹',\n pt: '🇵🇹',\n ru: '🇷🇺',\n ja: '🇯🇵',\n zh: '🇨🇳',\n ko: '🇰🇷',\n ar: '🇸🇦',\n hi: '🇮🇳',\n nl: '🇳🇱',\n pl: '🇵🇱',\n sv: '🇸🇪',\n da: '🇩🇰',\n fi: '🇫🇮',\n no: '🇳🇴',\n cs: '🇨🇿',\n ro: '🇷🇴',\n hu: '🇭🇺',\n el: '🇬🇷',\n he: '🇮🇱',\n th: '🇹🇭',\n vi: '🇻🇳',\n id: '🇮🇩',\n ms: '🇲🇾',\n };\n return flagMap[langCode] || '🌐';\n}\n","/**\n * Shared Tailwind class fragments for UI surfaces.\n * Tune the default look via theme tokens; override per-component with `className`.\n */\n\n/** Text inputs, selects, textareas — border, radius, transition. */\nexport const controlBase =\n 'rounded-lg border border-border/50 bg-muted/30 shadow-[var(--shadow-surface)] transition-[color,box-shadow,background-color]';\n\n/** Shared focus ring for interactive controls. */\nexport const controlFocus =\n 'outline-none focus-visible:border-ring focus-visible:ring-ring/40 focus-visible:ring-[3px]';\n\n/** Invalid state ring for form controls. */\nexport const controlInvalid =\n 'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/35';\n\n/** Card and static elevated panels. */\nexport const surfaceCard =\n 'rounded-xl border border-border/45 bg-card text-card-foreground shadow-[var(--shadow-surface)]';\n\n/** Muted inset fill (used inside controlBase; available standalone). */\nexport const surfaceInset = 'bg-muted/30';\n\n/** Dialogs, popovers, dropdown panels. */\nexport const surfaceOverlay =\n 'rounded-xl border border-border/50 bg-popover text-popover-foreground shadow-[var(--shadow-elevated)]';\n"]}
@@ -0,0 +1,13 @@
1
+ // src/constants/cookies.ts
2
+ function createCookieNames(appName, env) {
3
+ const prefix = `@${appName}-${env}`;
4
+ return {
5
+ accessToken: `${prefix}/access-token`,
6
+ refreshToken: `${prefix}/refresh-token`,
7
+ cookieConsent: `${prefix}/cookie-consent`
8
+ };
9
+ }
10
+
11
+ export { createCookieNames };
12
+ //# sourceMappingURL=chunk-KG4JVAKS.js.map
13
+ //# sourceMappingURL=chunk-KG4JVAKS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants/cookies.ts"],"names":[],"mappings":";AAMO,SAAS,iBAAA,CAAkB,SAAiB,GAAA,EAA0B;AAC3E,EAAA,MAAM,MAAA,GAAS,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AACjC,EAAA,OAAO;AAAA,IACL,WAAA,EAAa,GAAG,MAAM,CAAA,aAAA,CAAA;AAAA,IACtB,YAAA,EAAc,GAAG,MAAM,CAAA,cAAA,CAAA;AAAA,IACvB,aAAA,EAAe,GAAG,MAAM,CAAA,eAAA;AAAA,GAC1B;AACF","file":"chunk-KG4JVAKS.js","sourcesContent":["export interface CookieNames {\n accessToken: string;\n refreshToken: string;\n cookieConsent: string;\n}\n\nexport function createCookieNames(appName: string, env: string): CookieNames {\n const prefix = `@${appName}-${env}`;\n return {\n accessToken: `${prefix}/access-token`,\n refreshToken: `${prefix}/refresh-token`,\n cookieConsent: `${prefix}/cookie-consent`,\n };\n}\n"]}
@@ -0,0 +1,183 @@
1
+ import { useRef, useEffect } from 'react';
2
+
3
+ // src/errors/constraintKey.ts
4
+ function constraintNameToErrorKey(constraintName) {
5
+ return constraintName.split("_").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
6
+ }
7
+ function getConstraintNameFromExtra(extra) {
8
+ if (!extra || typeof extra !== "object" || Array.isArray(extra)) {
9
+ return null;
10
+ }
11
+ if (!("constraint_name" in extra)) {
12
+ return null;
13
+ }
14
+ const name = extra.constraint_name;
15
+ return typeof name === "string" && name.length > 0 ? name : null;
16
+ }
17
+ function getConflictFieldFromExtra(extra) {
18
+ if (!extra || typeof extra !== "object" || Array.isArray(extra)) {
19
+ return null;
20
+ }
21
+ if (!("field" in extra)) {
22
+ return null;
23
+ }
24
+ const field = extra.field;
25
+ return typeof field === "string" && field.length > 0 ? field : null;
26
+ }
27
+
28
+ // src/errors/resolveApiError.ts
29
+ var VALIDATION_EXTRA_PREFIX = "validation.";
30
+ function isFieldValidationMap(extra) {
31
+ if (!extra || typeof extra !== "object" || Array.isArray(extra)) {
32
+ return false;
33
+ }
34
+ if (getConstraintNameFromExtra(extra)) {
35
+ return false;
36
+ }
37
+ return Object.values(extra).every(
38
+ (v) => typeof v === "string" || Array.isArray(v) && v.every((x) => typeof x === "string")
39
+ );
40
+ }
41
+ function formatValidationExtra(extra, t) {
42
+ const lines = [];
43
+ for (const [field, messages] of Object.entries(extra)) {
44
+ const list = Array.isArray(messages) ? messages : [messages];
45
+ for (const msg of list) {
46
+ const key = `${VALIDATION_EXTRA_PREFIX}${field}.${msg}`;
47
+ const translated = t(key, { defaultValue: "" });
48
+ lines.push(
49
+ translated || t(`${VALIDATION_EXTRA_PREFIX}${field}`, {
50
+ defaultValue: msg
51
+ }) || msg
52
+ );
53
+ }
54
+ }
55
+ return lines.join("\n");
56
+ }
57
+ function resolveConstraintConflict(extra, t) {
58
+ const constraintName = getConstraintNameFromExtra(extra);
59
+ if (!constraintName) {
60
+ return null;
61
+ }
62
+ const key = `errors:${constraintNameToErrorKey(constraintName)}`;
63
+ const message = t(key, { defaultValue: "" });
64
+ if (message && message !== key) {
65
+ return message;
66
+ }
67
+ return t("errors:ConflictingData", {
68
+ defaultValue: "This action conflicts with existing data."
69
+ });
70
+ }
71
+ function resolveApiError(errorCode, extra, t) {
72
+ if (isFieldValidationMap(extra)) {
73
+ const validationMessage = formatValidationExtra(extra, t);
74
+ if (validationMessage) return validationMessage;
75
+ }
76
+ const constraintMessage = resolveConstraintConflict(extra, t);
77
+ if (constraintMessage) {
78
+ return constraintMessage;
79
+ }
80
+ if (!errorCode) {
81
+ return t("errors:Unknown", { defaultValue: "Something went wrong." });
82
+ }
83
+ const key = `errors:${errorCode}`;
84
+ const message = t(key, { defaultValue: "" });
85
+ if (message && message !== key) {
86
+ return message;
87
+ }
88
+ return errorCode.replace(/([A-Z])/g, " $1").trim();
89
+ }
90
+
91
+ // src/errors/types.ts
92
+ function isApiErrorBody(value) {
93
+ return typeof value === "object" && value !== null && "errorCode" in value && typeof value.errorCode === "string";
94
+ }
95
+ function getApiErrorFromRequestError(error) {
96
+ if (!error || typeof error !== "object") return null;
97
+ if ("error" in error) {
98
+ const inner = error.error;
99
+ if (isApiErrorBody(inner)) return inner;
100
+ if (inner && typeof inner === "object" && "error" in inner) {
101
+ return getApiErrorFromRequestError(inner);
102
+ }
103
+ }
104
+ if (isApiErrorBody(error)) return error;
105
+ return null;
106
+ }
107
+ function getConstraintNameFromError(error) {
108
+ const api = getApiErrorFromRequestError(error);
109
+ if (!api?.extra) return "";
110
+ return getConstraintNameFromExtra(api.extra) ?? "";
111
+ }
112
+ function getConflictFieldFromError(error) {
113
+ const api = getApiErrorFromRequestError(error);
114
+ if (!api?.extra) return "";
115
+ return getConflictFieldFromExtra(api.extra) ?? "";
116
+ }
117
+
118
+ // src/errors/formatApiError.ts
119
+ function isRequestError(error) {
120
+ return typeof error === "object" && error !== null && "status" in error && typeof error.status === "number";
121
+ }
122
+ function errorCodeFromHttpStatus(status) {
123
+ switch (status) {
124
+ case 400:
125
+ case 422:
126
+ return "InvalidRequestBody";
127
+ case 401:
128
+ return "Unauthorized";
129
+ case 403:
130
+ return "Forbidden";
131
+ case 404:
132
+ return "NotFound";
133
+ case 409:
134
+ return "Conflict";
135
+ case 429:
136
+ return "TooManyRequests";
137
+ default:
138
+ if (status >= 500) return "InternalServerError";
139
+ return "Unknown";
140
+ }
141
+ }
142
+ function formatApiError(error, t) {
143
+ const api = getApiErrorFromRequestError(error);
144
+ if (api) {
145
+ const code = api.errorCode || (isRequestError(error) ? errorCodeFromHttpStatus(error.status) : "");
146
+ return resolveApiError(code, api.extra, t);
147
+ }
148
+ if (isRequestError(error)) {
149
+ const code = errorCodeFromHttpStatus(error.status);
150
+ return resolveApiError(code, void 0, t);
151
+ }
152
+ if (error instanceof Error && error.message) {
153
+ return error.message;
154
+ }
155
+ return t("errors:Unknown", { defaultValue: "Something went wrong." });
156
+ }
157
+ function useQueryApiErrorToast(isError, error, formatError, notify) {
158
+ const lastShown = useRef(null);
159
+ useEffect(() => {
160
+ if (isError && error != null && error !== lastShown.current) {
161
+ lastShown.current = error;
162
+ notify(formatError(error));
163
+ return;
164
+ }
165
+ if (!isError) {
166
+ lastShown.current = null;
167
+ }
168
+ }, [isError, error, formatError, notify]);
169
+ }
170
+ function handleApiMutationError(error, formatError, notify, options = {}) {
171
+ const message = formatError(error);
172
+ notify(message);
173
+ const field = getConflictFieldFromError(error);
174
+ if (!field) {
175
+ return;
176
+ }
177
+ options.setFieldErrors?.({ [field]: message });
178
+ options.onConflictField?.(field);
179
+ }
180
+
181
+ export { constraintNameToErrorKey, errorCodeFromHttpStatus, formatApiError, getApiErrorFromRequestError, getConflictFieldFromError, getConflictFieldFromExtra, getConstraintNameFromError, getConstraintNameFromExtra, handleApiMutationError, isApiErrorBody, resolveApiError, useQueryApiErrorToast };
182
+ //# sourceMappingURL=chunk-KZH56VPP.js.map
183
+ //# sourceMappingURL=chunk-KZH56VPP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/constraintKey.ts","../src/errors/resolveApiError.ts","../src/errors/types.ts","../src/errors/formatApiError.ts","../src/errors/mutationErrorToast.ts"],"names":[],"mappings":"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","file":"chunk-KZH56VPP.js","sourcesContent":["/** Maps a Postgres constraint name (snake_case) to a PascalCase i18n key under `errors`. */\nexport function constraintNameToErrorKey(constraintName: string): string {\n return constraintName\n .split('_')\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join('');\n}\n\nexport function getConstraintNameFromExtra(extra: unknown): string | null {\n if (!extra || typeof extra !== 'object' || Array.isArray(extra)) {\n return null;\n }\n if (!('constraint_name' in extra)) {\n return null;\n }\n const name = (extra as { constraint_name?: unknown }).constraint_name;\n return typeof name === 'string' && name.length > 0 ? name : null;\n}\n\nexport function getConflictFieldFromExtra(extra: unknown): string | null {\n if (!extra || typeof extra !== 'object' || Array.isArray(extra)) {\n return null;\n }\n if (!('field' in extra)) {\n return null;\n }\n const field = (extra as { field?: unknown }).field;\n return typeof field === 'string' && field.length > 0 ? field : null;\n}\n","import { constraintNameToErrorKey, getConstraintNameFromExtra } from './constraintKey';\nimport type { TranslateFn } from './types';\n\nconst VALIDATION_EXTRA_PREFIX = 'validation.';\n\nfunction isFieldValidationMap(extra: unknown): extra is Record<string, string | string[]> {\n if (!extra || typeof extra !== 'object' || Array.isArray(extra)) {\n return false;\n }\n if (getConstraintNameFromExtra(extra)) {\n return false;\n }\n return Object.values(extra).every(\n (v) => typeof v === 'string' || (Array.isArray(v) && v.every((x) => typeof x === 'string')),\n );\n}\n\nfunction formatValidationExtra(extra: Record<string, string | string[]>, t: TranslateFn): string {\n const lines: string[] = [];\n for (const [field, messages] of Object.entries(extra)) {\n const list = Array.isArray(messages) ? messages : [messages];\n for (const msg of list) {\n const key = `${VALIDATION_EXTRA_PREFIX}${field}.${msg}`;\n const translated = t(key, { defaultValue: '' });\n lines.push(\n translated ||\n t(`${VALIDATION_EXTRA_PREFIX}${field}`, {\n defaultValue: msg,\n }) ||\n msg,\n );\n }\n }\n return lines.join('\\n');\n}\n\nfunction resolveConstraintConflict(extra: unknown, t: TranslateFn): string | null {\n const constraintName = getConstraintNameFromExtra(extra);\n if (!constraintName) {\n return null;\n }\n\n const key = `errors:${constraintNameToErrorKey(constraintName)}`;\n const message = t(key, { defaultValue: '' });\n if (message && message !== key) {\n return message;\n }\n\n return t('errors:ConflictingData', {\n defaultValue: 'This action conflicts with existing data.',\n });\n}\n\n/**\n * Maps API `errorCode` + optional `extra` to a user-facing i18n string.\n * Keys live under the app `errors` namespace (e.g. `errors:InvalidToken`).\n */\nexport function resolveApiError(errorCode: string, extra: unknown, t: TranslateFn): string {\n if (isFieldValidationMap(extra)) {\n const validationMessage = formatValidationExtra(extra, t);\n if (validationMessage) return validationMessage;\n }\n\n const constraintMessage = resolveConstraintConflict(extra, t);\n if (constraintMessage) {\n return constraintMessage;\n }\n\n if (!errorCode) {\n return t('errors:Unknown', { defaultValue: 'Something went wrong.' });\n }\n\n const key = `errors:${errorCode}`;\n const message = t(key, { defaultValue: '' });\n if (message && message !== key) {\n return message;\n }\n\n return errorCode.replace(/([A-Z])/g, ' $1').trim();\n}\n","import type { ApiErrorBody, RequestError } from '../api/types';\nimport { getConflictFieldFromExtra, getConstraintNameFromExtra } from './constraintKey';\n\n/** Minimal i18n translate callback used by resolveApiError (compatible with i18next TFunction). */\nexport type TranslateFn = (\n key: string | string[],\n options?: { defaultValue?: string } & Record<string, unknown>,\n) => string;\n\nexport interface MutationError {\n status: number;\n error: ApiErrorBody;\n retry?: boolean;\n}\n\nexport function isApiErrorBody(value: unknown): value is ApiErrorBody {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'errorCode' in value &&\n typeof (value as ApiErrorBody).errorCode === 'string'\n );\n}\n\nexport function getApiErrorFromRequestError(error: unknown): ApiErrorBody | null {\n if (!error || typeof error !== 'object') return null;\n if ('error' in error) {\n const inner = (error as RequestError).error;\n if (isApiErrorBody(inner)) return inner;\n if (inner && typeof inner === 'object' && 'error' in inner) {\n return getApiErrorFromRequestError(inner);\n }\n }\n if (isApiErrorBody(error)) return error;\n return null;\n}\n\nexport function getConstraintNameFromError(error: unknown): string {\n const api = getApiErrorFromRequestError(error);\n if (!api?.extra) return '';\n return getConstraintNameFromExtra(api.extra) ?? '';\n}\n\nexport function getConflictFieldFromError(error: unknown): string {\n const api = getApiErrorFromRequestError(error);\n if (!api?.extra) return '';\n return getConflictFieldFromExtra(api.extra) ?? '';\n}\n\nexport {\n constraintNameToErrorKey,\n getConflictFieldFromExtra,\n getConstraintNameFromExtra,\n} from './constraintKey';\n","import type { RequestError } from '../api/types';\nimport { resolveApiError } from './resolveApiError';\nimport type { TranslateFn } from './types';\nimport { getApiErrorFromRequestError } from './types';\n\nfunction isRequestError(error: unknown): error is RequestError {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n typeof (error as RequestError).status === 'number'\n );\n}\n\nexport function errorCodeFromHttpStatus(status: number): string {\n switch (status) {\n case 400:\n case 422:\n return 'InvalidRequestBody';\n case 401:\n return 'Unauthorized';\n case 403:\n return 'Forbidden';\n case 404:\n return 'NotFound';\n case 409:\n return 'Conflict';\n case 429:\n return 'TooManyRequests';\n default:\n if (status >= 500) return 'InternalServerError';\n return 'Unknown';\n }\n}\n\nexport function formatApiError(error: unknown, t: TranslateFn): string {\n const api = getApiErrorFromRequestError(error);\n if (api) {\n const code =\n api.errorCode || (isRequestError(error) ? errorCodeFromHttpStatus(error.status) : '');\n return resolveApiError(code, api.extra, t);\n }\n if (isRequestError(error)) {\n const code = errorCodeFromHttpStatus(error.status);\n return resolveApiError(code, undefined, t);\n }\n if (error instanceof Error && error.message) {\n return error.message;\n }\n return t('errors:Unknown', { defaultValue: 'Something went wrong.' });\n}\n","import { useEffect, useRef } from 'react';\n\nimport { getConflictFieldFromError } from './types';\n\nexport type NotifyFn = (message: string) => void;\n\nexport type HandleApiMutationErrorOptions = {\n setFieldErrors?: (errors: Record<string, string>) => void;\n /** Called when the API returns a conflict on a specific field (e.g. navigate to the right step). */\n onConflictField?: (field: string) => void;\n};\n\n/**\n * Show one toast per distinct query failure (TanStack Query v5 has no `onError`).\n * Pass app-bound `formatError` and `notify` (e.g. sonner toast.error).\n */\nexport function useQueryApiErrorToast(\n isError: boolean,\n error: unknown,\n formatError: (error: unknown) => string,\n notify: NotifyFn,\n): void {\n const lastShown = useRef<unknown>(null);\n\n useEffect(() => {\n if (isError && error != null && error !== lastShown.current) {\n lastShown.current = error;\n notify(formatError(error));\n return;\n }\n if (!isError) {\n lastShown.current = null;\n }\n }, [isError, error, formatError, notify]);\n}\n\n/**\n * Shows a notification for every API mutation failure and optionally sets inline field errors\n * from unique-constraint conflicts. Never suppresses the notification when a field is known.\n */\nexport function handleApiMutationError(\n error: unknown,\n formatError: (error: unknown) => string,\n notify: NotifyFn,\n options: HandleApiMutationErrorOptions = {},\n): void {\n const message = formatError(error);\n notify(message);\n const field = getConflictFieldFromError(error);\n if (!field) {\n return;\n }\n options.setFieldErrors?.({ [field]: message });\n options.onConflictField?.(field);\n}\n"]}
@@ -0,0 +1,96 @@
1
+ import { flattenPaginatedItems } from './chunk-3SCZ4Z6P.js';
2
+ import { useState, useRef, useEffect, useCallback } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ function useAdminListPage(useInfiniteQuery, options) {
6
+ const debounceMs = options?.debounceMs ?? 300;
7
+ const searchMode = options?.searchMode ?? "instant";
8
+ const [search, setSearch] = useState("");
9
+ const [debouncedSearch, setDebouncedSearch] = useState("");
10
+ const loadMoreRef = useRef(null);
11
+ useEffect(() => {
12
+ if (searchMode === "submit") return;
13
+ const id = window.setTimeout(() => setDebouncedSearch(search), debounceMs);
14
+ return () => window.clearTimeout(id);
15
+ }, [search, debounceMs, searchMode]);
16
+ const submitSearch = useCallback(() => {
17
+ setDebouncedSearch(search);
18
+ }, [search]);
19
+ const clearSearch = useCallback(() => {
20
+ setSearch("");
21
+ setDebouncedSearch("");
22
+ }, []);
23
+ const handleSearchKeyDown = useCallback(
24
+ (event) => {
25
+ if (searchMode === "submit" && event.key === "Enter") {
26
+ event.preventDefault();
27
+ submitSearch();
28
+ }
29
+ },
30
+ [searchMode, submitSearch]
31
+ );
32
+ const query = useInfiniteQuery(debouncedSearch);
33
+ const items = flattenPaginatedItems(query.data?.pages);
34
+ const handleLoadMore = useCallback(() => {
35
+ if (query.hasNextPage && !query.isFetchingNextPage) {
36
+ query.fetchNextPage();
37
+ }
38
+ }, [query.hasNextPage, query.isFetchingNextPage, query.fetchNextPage]);
39
+ useEffect(() => {
40
+ const el = loadMoreRef.current;
41
+ if (!el) return;
42
+ const observer = new IntersectionObserver(
43
+ (entries) => {
44
+ if (entries[0]?.isIntersecting) handleLoadMore();
45
+ },
46
+ { rootMargin: "100px", threshold: 0 }
47
+ );
48
+ observer.observe(el);
49
+ return () => observer.disconnect();
50
+ }, [handleLoadMore]);
51
+ return {
52
+ search,
53
+ setSearch,
54
+ debouncedSearch,
55
+ submitSearch,
56
+ clearSearch,
57
+ onSearchKeyDown: searchMode === "submit" ? handleSearchKeyDown : void 0,
58
+ searchMode,
59
+ items,
60
+ query,
61
+ loadMoreRef
62
+ };
63
+ }
64
+ function useTranslationNamespace(namespaces, loadNamespace) {
65
+ const namespaceArray = Array.isArray(namespaces) ? namespaces : [namespaces];
66
+ const { t, i18n } = useTranslation(["common", ...namespaceArray]);
67
+ const [isLoading, setIsLoading] = useState(Boolean(loadNamespace));
68
+ useEffect(() => {
69
+ if (!loadNamespace) {
70
+ setIsLoading(false);
71
+ return;
72
+ }
73
+ const run = async () => {
74
+ const language = i18n.language || i18n.options.fallbackLng || "en";
75
+ const toLoad = namespaceArray.filter((ns) => !i18n.hasResourceBundle(language, ns));
76
+ if (toLoad.length === 0) {
77
+ setIsLoading(false);
78
+ return;
79
+ }
80
+ setIsLoading(true);
81
+ try {
82
+ await Promise.all(toLoad.map((ns) => loadNamespace(ns, language)));
83
+ } catch (err) {
84
+ console.error("Failed to load namespaces:", err);
85
+ } finally {
86
+ setIsLoading(false);
87
+ }
88
+ };
89
+ run();
90
+ }, [i18n, loadNamespace, namespaceArray.join(",")]);
91
+ return { t, isLoading, i18n };
92
+ }
93
+
94
+ export { useAdminListPage, useTranslationNamespace };
95
+ //# sourceMappingURL=chunk-LP7Q75C6.js.map
96
+ //# sourceMappingURL=chunk-LP7Q75C6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useAdminListPage.ts","../src/hooks/useTranslationNamespace.ts"],"names":["useState","useEffect"],"mappings":"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","file":"chunk-LP7Q75C6.js","sourcesContent":["import type { InfiniteData, UseInfiniteQueryResult } from '@tanstack/react-query';\nimport { type KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';\n\nimport { flattenPaginatedItems, type PaginatedList } from '../lib/pagination';\n\nexport type AdminListPageResponse<T> = PaginatedList<T>;\n\nexport type AdminListSearchMode = 'instant' | 'submit';\n\nexport type UseAdminListPageOptions = {\n debounceMs?: number;\n /** `instant` debounces input into the query; `submit` applies search only via submitSearch/clearSearch. */\n searchMode?: AdminListSearchMode;\n};\n\nexport function useAdminListPage<T>(\n useInfiniteQuery: (\n search: string,\n ) => UseInfiniteQueryResult<InfiniteData<AdminListPageResponse<T>>>,\n options?: UseAdminListPageOptions,\n) {\n const debounceMs = options?.debounceMs ?? 300;\n const searchMode = options?.searchMode ?? 'instant';\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const loadMoreRef = useRef<HTMLTableRowElement>(null);\n\n useEffect(() => {\n if (searchMode === 'submit') return;\n const id = window.setTimeout(() => setDebouncedSearch(search), debounceMs);\n return () => window.clearTimeout(id);\n }, [search, debounceMs, searchMode]);\n\n const submitSearch = useCallback(() => {\n setDebouncedSearch(search);\n }, [search]);\n\n const clearSearch = useCallback(() => {\n setSearch('');\n setDebouncedSearch('');\n }, []);\n\n const handleSearchKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n if (searchMode === 'submit' && event.key === 'Enter') {\n event.preventDefault();\n submitSearch();\n }\n },\n [searchMode, submitSearch],\n );\n\n const query = useInfiniteQuery(debouncedSearch);\n const items = flattenPaginatedItems(query.data?.pages);\n\n const handleLoadMore = useCallback(() => {\n if (query.hasNextPage && !query.isFetchingNextPage) {\n query.fetchNextPage();\n }\n }, [query.hasNextPage, query.isFetchingNextPage, query.fetchNextPage]);\n\n useEffect(() => {\n const el = loadMoreRef.current;\n if (!el) return;\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) handleLoadMore();\n },\n { rootMargin: '100px', threshold: 0 },\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, [handleLoadMore]);\n\n return {\n search,\n setSearch,\n debouncedSearch,\n submitSearch,\n clearSearch,\n onSearchKeyDown: searchMode === 'submit' ? handleSearchKeyDown : undefined,\n searchMode,\n items,\n query,\n loadMoreRef,\n };\n}\n","import { useEffect, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nexport type NamespaceLoader = (namespace: string, language: string) => Promise<void>;\n\n/**\n * Loads i18n namespaces on demand. Pass `loadNamespace` from the host app\n * (e.g. templates/react-spa `src/i18n.ts`).\n */\nexport function useTranslationNamespace(\n namespaces: string[] | string,\n loadNamespace?: NamespaceLoader,\n) {\n const namespaceArray = Array.isArray(namespaces) ? namespaces : [namespaces];\n const { t, i18n } = useTranslation(['common', ...namespaceArray]);\n const [isLoading, setIsLoading] = useState(Boolean(loadNamespace));\n\n useEffect(() => {\n if (!loadNamespace) {\n setIsLoading(false);\n return;\n }\n\n const run = async () => {\n const language = (i18n.language || i18n.options.fallbackLng || 'en') as string;\n const toLoad = namespaceArray.filter((ns) => !i18n.hasResourceBundle(language, ns));\n if (toLoad.length === 0) {\n setIsLoading(false);\n return;\n }\n setIsLoading(true);\n try {\n await Promise.all(toLoad.map((ns) => loadNamespace(ns, language)));\n } catch (err) {\n console.error('Failed to load namespaces:', err);\n } finally {\n setIsLoading(false);\n }\n };\n\n run();\n }, [i18n, loadNamespace, namespaceArray.join(',')]);\n\n return { t, isLoading, i18n };\n}\n"]}
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=chunk-NL4NAHXG.js.map
3
+ //# sourceMappingURL=chunk-NL4NAHXG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-NL4NAHXG.js"}