@pattern-stack/frontend-patterns 0.2.0-alpha.4 → 0.2.0-alpha.5

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 (41) hide show
  1. package/dist/atoms/components/data/DataTable/DataTable.d.ts +1 -1
  2. package/dist/atoms/components/data/DataTable/DataTable.d.ts.map +1 -1
  3. package/dist/atoms/components/data/DataTable/DataTable.types.d.ts +28 -0
  4. package/dist/atoms/components/data/DataTable/DataTable.types.d.ts.map +1 -1
  5. package/dist/atoms/hooks/index.d.ts +1 -0
  6. package/dist/atoms/hooks/index.d.ts.map +1 -1
  7. package/dist/atoms/hooks/useEntityDetail.d.ts +43 -0
  8. package/dist/atoms/hooks/useEntityDetail.d.ts.map +1 -0
  9. package/dist/atoms/primitives/table.d.ts.map +1 -1
  10. package/dist/atoms/services/api/client.d.ts +12 -2
  11. package/dist/atoms/services/api/client.d.ts.map +1 -1
  12. package/dist/atoms/services/auth-service.d.ts +15 -0
  13. package/dist/atoms/services/auth-service.d.ts.map +1 -1
  14. package/dist/atoms/services/index.d.ts +2 -2
  15. package/dist/atoms/services/index.d.ts.map +1 -1
  16. package/dist/atoms/types/auth.d.ts +38 -2
  17. package/dist/atoms/types/auth.d.ts.map +1 -1
  18. package/dist/atoms/types/ui-config.d.ts +11 -0
  19. package/dist/atoms/types/ui-config.d.ts.map +1 -1
  20. package/dist/features/auth/components/ProtectedRoute.d.ts +3 -1
  21. package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -1
  22. package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
  23. package/dist/features/auth/providers/NoAuthProvider.d.ts +17 -0
  24. package/dist/features/auth/providers/NoAuthProvider.d.ts.map +1 -0
  25. package/dist/features/auth/providers/index.d.ts +1 -0
  26. package/dist/features/auth/providers/index.d.ts.map +1 -1
  27. package/dist/frontend-patterns.css +1 -1
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.es.js +350 -79
  31. package/dist/index.es.js.map +1 -1
  32. package/dist/index.js +348 -77
  33. package/dist/index.js.map +1 -1
  34. package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts +61 -0
  35. package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts.map +1 -0
  36. package/dist/molecules/layout/FieldGrid/index.d.ts +2 -0
  37. package/dist/molecules/layout/FieldGrid/index.d.ts.map +1 -0
  38. package/dist/molecules/layout/index.d.ts +1 -0
  39. package/dist/molecules/layout/index.d.ts.map +1 -1
  40. package/dist/templates/factory.d.ts.map +1 -1
  41. package/package.json +4 -5
package/dist/index.js CHANGED
@@ -77,8 +77,8 @@ const UI_CONFIG = {
77
77
  TOAST_DURATION: 5e3
78
78
  };
79
79
  const env = {
80
- API_BASE_URL: "http://localhost:8080",
81
- OPENAPI_URL: "http://localhost:8080/openapi.json",
80
+ API_BASE_URL: "",
81
+ OPENAPI_URL: "/openapi.json",
82
82
  NODE_ENV: "development",
83
83
  IS_DEVELOPMENT: false,
84
84
  IS_PRODUCTION: false,
@@ -2052,16 +2052,18 @@ function renderField(value, fieldName, fieldType, breakpoint, item, customRender
2052
2052
  function cn(...inputs) {
2053
2053
  return tailwindMerge.twMerge(clsx.clsx(inputs));
2054
2054
  }
2055
- const API_BASE_URL = "http://localhost:8080";
2056
2055
  let globalAuthService = null;
2057
2056
  function setGlobalAuthService(authService) {
2058
2057
  globalAuthService = authService;
2059
2058
  }
2059
+ function getGlobalAuthService() {
2060
+ return globalAuthService;
2061
+ }
2060
2062
  class ApiClient {
2061
2063
  constructor(config = {}) {
2062
2064
  __publicField(this, "instance");
2063
2065
  this.instance = axios.create({
2064
- baseURL: config.baseURL || API_BASE_URL,
2066
+ baseURL: config.baseURL || "",
2065
2067
  timeout: config.timeout || 1e4,
2066
2068
  headers: {
2067
2069
  "Content-Type": "application/json",
@@ -2101,11 +2103,12 @@ class ApiClient {
2101
2103
  this.instance.interceptors.response.use(
2102
2104
  (response) => response,
2103
2105
  async (error) => {
2104
- var _a;
2105
- if (((_a = error.response) == null ? void 0 : _a.status) === 401) {
2106
+ var _a, _b, _c, _d;
2107
+ const status = (_a = error.response) == null ? void 0 : _a.status;
2108
+ if (status === 401 || status === 403) {
2106
2109
  if (globalAuthService) {
2107
2110
  const tokenData = globalAuthService.getTokenData();
2108
- if ((tokenData == null ? void 0 : tokenData.refreshToken) && !error.config._retry) {
2111
+ if (status === 401 && (tokenData == null ? void 0 : tokenData.refreshToken) && !error.config._retry) {
2109
2112
  error.config._retry = true;
2110
2113
  try {
2111
2114
  await globalAuthService.refreshToken();
@@ -2113,11 +2116,12 @@ class ApiClient {
2113
2116
  error.config.headers.Authorization = `Bearer ${newTokenData == null ? void 0 : newTokenData.token}`;
2114
2117
  return this.instance.request(error.config);
2115
2118
  } catch {
2116
- globalAuthService.clearAuth();
2117
- window.location.reload();
2118
2119
  }
2119
- } else {
2120
- globalAuthService.clearAuth();
2120
+ }
2121
+ globalAuthService.clearAuth();
2122
+ const errorInfo = { status, message: (_c = (_b = error.response) == null ? void 0 : _b.data) == null ? void 0 : _c.detail };
2123
+ const handled = (_d = globalAuthService.onAuthError) == null ? void 0 : _d.call(globalAuthService, errorInfo);
2124
+ if (!handled) {
2121
2125
  window.location.reload();
2122
2126
  }
2123
2127
  } else {
@@ -2163,6 +2167,9 @@ class ApiClient {
2163
2167
  return response.data;
2164
2168
  }
2165
2169
  }
2170
+ function createApiClient(config = {}) {
2171
+ return new ApiClient(config);
2172
+ }
2166
2173
  const apiClient = new ApiClient();
2167
2174
  function useToast() {
2168
2175
  const [toasts, setToasts] = React.useState([]);
@@ -2433,7 +2440,7 @@ function useEntityData(entity, options = {}) {
2433
2440
  const dataQuery = reactQuery.useQuery({
2434
2441
  queryKey: [entity, "list", { limit, offset, filters }],
2435
2442
  queryFn: () => apiClient.get(
2436
- `/api/v1/${entity}/?${dataParams.toString()}`
2443
+ `/api/v1/${entity}?${dataParams.toString()}`
2437
2444
  ),
2438
2445
  enabled
2439
2446
  });
@@ -2461,6 +2468,40 @@ function useEntityData(entity, options = {}) {
2461
2468
  refetch
2462
2469
  };
2463
2470
  }
2471
+ function useEntityDetail(entity, id, options = {}) {
2472
+ var _a;
2473
+ const { view = "detail", sourceId, enabled = true } = options;
2474
+ const metadataParams = new URLSearchParams();
2475
+ metadataParams.set("view", view);
2476
+ if (sourceId) metadataParams.set("source_id", sourceId);
2477
+ const dataQuery = reactQuery.useQuery({
2478
+ queryKey: [entity, "detail", id],
2479
+ queryFn: () => apiClient.get(`/api/v1/${entity}/${id}`),
2480
+ enabled: enabled && !!id
2481
+ });
2482
+ const metadataQuery = reactQuery.useQuery({
2483
+ queryKey: [entity, "metadata", { view, sourceId }],
2484
+ queryFn: () => apiClient.get(
2485
+ `/api/v1/${entity}/fields/metadata?${metadataParams.toString()}`
2486
+ ),
2487
+ enabled,
2488
+ staleTime: 5 * 60 * 1e3
2489
+ // 5 minutes
2490
+ });
2491
+ const refetch = () => {
2492
+ dataQuery.refetch();
2493
+ metadataQuery.refetch();
2494
+ };
2495
+ return {
2496
+ data: dataQuery.data ?? null,
2497
+ fields: ((_a = metadataQuery.data) == null ? void 0 : _a.columns) ?? [],
2498
+ isLoading: dataQuery.isLoading || metadataQuery.isLoading,
2499
+ isLoadingData: dataQuery.isLoading,
2500
+ isLoadingMetadata: metadataQuery.isLoading,
2501
+ error: dataQuery.error ?? metadataQuery.error ?? null,
2502
+ refetch
2503
+ };
2504
+ }
2464
2505
  const buttonVariants = classVarianceAuthority.cva(
2465
2506
  "inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98]",
2466
2507
  {
@@ -4151,7 +4192,9 @@ const TableHead$1 = React__namespace.forwardRef(({ className, ...props }, ref) =
4151
4192
  {
4152
4193
  ref,
4153
4194
  className: cn(
4154
- "h-12 px-4 xs:px-2 2xs:px-1 text-left align-middle font-semibold text-gray-700 text-xs uppercase tracking-wider [&:has([role=checkbox])]:pr-0",
4195
+ "h-12 px-4 xs:px-2 2xs:px-1 text-left align-middle font-semibold text-gray-700 text-xs uppercase tracking-wider",
4196
+ // Compact padding for checkbox columns
4197
+ "[&:has(input[type=checkbox])]:px-2 [&:has(input[type=checkbox])]:w-8",
4155
4198
  className
4156
4199
  ),
4157
4200
  ...props
@@ -4163,7 +4206,9 @@ const TableCell$1 = React__namespace.forwardRef(({ className, ...props }, ref) =
4163
4206
  {
4164
4207
  ref,
4165
4208
  className: cn(
4166
- "p-4 xs:p-2 2xs:p-1 align-middle [&:has([role=checkbox])]:pr-0",
4209
+ "p-4 xs:p-2 2xs:p-1 align-middle",
4210
+ // Compact padding for checkbox columns
4211
+ "[&:has(input[type=checkbox])]:px-2 [&:has(input[type=checkbox])]:w-8",
4167
4212
  className
4168
4213
  ),
4169
4214
  ...props
@@ -4213,7 +4258,12 @@ function DataTable({
4213
4258
  ui,
4214
4259
  error = null,
4215
4260
  errorTitle,
4216
- errorRetry
4261
+ errorRetry,
4262
+ selectable = false,
4263
+ selectedIds,
4264
+ onSelectionChange,
4265
+ getRowId,
4266
+ selectAllMode = "page"
4217
4267
  }) {
4218
4268
  const [searchTerm, setSearchTerm] = React.useState("");
4219
4269
  const [sortColumn, setSortColumn] = React.useState(null);
@@ -4276,6 +4326,85 @@ function DataTable({
4276
4326
  return true;
4277
4327
  });
4278
4328
  }, [normalizedColumns, currentBreakpoint, responsive]);
4329
+ const defaultGetRowId = React.useCallback((row) => {
4330
+ if ("id" in row && typeof row.id === "string") return row.id;
4331
+ if ("id" in row && typeof row.id === "number") return String(row.id);
4332
+ throw new Error("DataTable: selectable requires getRowId prop or row.id field");
4333
+ }, []);
4334
+ const effectiveGetRowId = getRowId ?? defaultGetRowId;
4335
+ const selectableData = selectAllMode === "all" ? sortedData : paginatedData;
4336
+ const selectableIds = React.useMemo(
4337
+ () => {
4338
+ if (!selectable) return /* @__PURE__ */ new Set();
4339
+ return new Set(selectableData.map((row) => effectiveGetRowId(row)));
4340
+ },
4341
+ [selectable, selectableData, effectiveGetRowId]
4342
+ );
4343
+ const selectedInScope = React.useMemo(
4344
+ () => {
4345
+ if (!selectable || !selectedIds) return /* @__PURE__ */ new Set();
4346
+ return new Set([...selectedIds].filter((id) => selectableIds.has(id)));
4347
+ },
4348
+ [selectable, selectedIds, selectableIds]
4349
+ );
4350
+ const isAllSelected = selectableIds.size > 0 && selectedInScope.size === selectableIds.size;
4351
+ const isIndeterminate = selectedInScope.size > 0 && selectedInScope.size < selectableIds.size;
4352
+ const handleSelectAll = React.useCallback(() => {
4353
+ if (!onSelectionChange) return;
4354
+ if (isAllSelected) {
4355
+ const newSelection = new Set([...selectedIds ?? []].filter((id) => !selectableIds.has(id)));
4356
+ onSelectionChange(newSelection);
4357
+ } else {
4358
+ const newSelection = /* @__PURE__ */ new Set([...selectedIds ?? [], ...selectableIds]);
4359
+ onSelectionChange(newSelection);
4360
+ }
4361
+ }, [isAllSelected, selectedIds, selectableIds, onSelectionChange]);
4362
+ const handleSelectRow = React.useCallback((rowId) => {
4363
+ if (!onSelectionChange) return;
4364
+ const newSelection = new Set(selectedIds);
4365
+ if (newSelection.has(rowId)) {
4366
+ newSelection.delete(rowId);
4367
+ } else {
4368
+ newSelection.add(rowId);
4369
+ }
4370
+ onSelectionChange(newSelection);
4371
+ }, [selectedIds, onSelectionChange]);
4372
+ const selectionColumn = React.useMemo(() => {
4373
+ return {
4374
+ key: "__selection__",
4375
+ header: /* @__PURE__ */ jsxRuntime.jsx(
4376
+ Checkbox$1,
4377
+ {
4378
+ checked: isAllSelected,
4379
+ indeterminate: isIndeterminate,
4380
+ onCheckedChange: handleSelectAll,
4381
+ "aria-label": isAllSelected ? "Deselect all rows" : "Select all rows",
4382
+ size: "sm"
4383
+ }
4384
+ ),
4385
+ cell: (row) => {
4386
+ const rowId = effectiveGetRowId(row);
4387
+ const isSelected = (selectedIds == null ? void 0 : selectedIds.has(rowId)) ?? false;
4388
+ return /* @__PURE__ */ jsxRuntime.jsx(
4389
+ Checkbox$1,
4390
+ {
4391
+ checked: isSelected,
4392
+ onCheckedChange: () => handleSelectRow(rowId),
4393
+ "aria-label": isSelected ? `Deselect row ${rowId}` : `Select row ${rowId}`,
4394
+ size: "sm",
4395
+ onClick: (e) => e.stopPropagation()
4396
+ }
4397
+ );
4398
+ },
4399
+ width: "32px",
4400
+ sortable: false,
4401
+ filterable: false
4402
+ };
4403
+ }, [isAllSelected, isIndeterminate, selectedIds, effectiveGetRowId, handleSelectAll, handleSelectRow]);
4404
+ const effectiveColumns = React.useMemo(() => {
4405
+ if (!selectable) return visibleColumns;
4406
+ return [selectionColumn, ...visibleColumns];
4407
+ }, [selectable, selectionColumn, visibleColumns]);
4279
4408
  const handleSort = (columnKey) => {
4280
4409
  if (sortColumn === columnKey) {
4281
4410
  if (sortDirection === "asc") {
@@ -4338,8 +4467,8 @@ function DataTable({
4338
4467
  children: [
4339
4468
  showSearch && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex-1 max-w-sm", children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton$1, { className: "h-10 w-full" }) }) }),
4340
4469
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded border overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs(Table$1, { children: [
4341
- /* @__PURE__ */ jsxRuntime.jsx(TableHeader$1, { children: /* @__PURE__ */ jsxRuntime.jsx(TableRow$1, { children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(TableHead$1, { style: { width: column.width }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton$1, { className: "h-4 w-20" }) }) }, column.key)) }) }),
4342
- /* @__PURE__ */ jsxRuntime.jsx(TableBody$1, { children: Array.from({ length: loadingItemCount }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(TableRow$1, { children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(TableCell$1, { children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton$1, { className: "h-4 w-full max-w-32" }) }, column.key)) }, index)) })
4470
+ /* @__PURE__ */ jsxRuntime.jsx(TableHeader$1, { children: /* @__PURE__ */ jsxRuntime.jsx(TableRow$1, { children: effectiveColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(TableHead$1, { style: { width: column.width }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton$1, { className: "h-4 w-20" }) }) }, column.key)) }) }),
4471
+ /* @__PURE__ */ jsxRuntime.jsx(TableBody$1, { children: Array.from({ length: loadingItemCount }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(TableRow$1, { children: effectiveColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(TableCell$1, { children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton$1, { className: "h-4 w-full max-w-32" }) }, column.key)) }, index)) })
4343
4472
  ] }) }),
4344
4473
  showPagination && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
4345
4474
  /* @__PURE__ */ jsxRuntime.jsx(Skeleton$1, { className: "h-4 w-48" }),
@@ -4401,7 +4530,7 @@ function DataTable({
4401
4530
  data: paginatedData,
4402
4531
  onItemClick: onRowClick
4403
4532
  }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded border overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs(Table$1, { children: [
4404
- /* @__PURE__ */ jsxRuntime.jsx(TableHeader$1, { children: /* @__PURE__ */ jsxRuntime.jsx(TableRow$1, { children: visibleColumns.map((column) => {
4533
+ /* @__PURE__ */ jsxRuntime.jsx(TableHeader$1, { children: /* @__PURE__ */ jsxRuntime.jsx(TableRow$1, { children: effectiveColumns.map((column) => {
4405
4534
  const width = column.width;
4406
4535
  return /* @__PURE__ */ jsxRuntime.jsx(
4407
4536
  TableHead$1,
@@ -4424,7 +4553,7 @@ function DataTable({
4424
4553
  /* @__PURE__ */ jsxRuntime.jsx(TableBody$1, { children: paginatedData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(TableRow$1, { children: /* @__PURE__ */ jsxRuntime.jsx(
4425
4554
  TableCell$1,
4426
4555
  {
4427
- colSpan: visibleColumns.length,
4556
+ colSpan: effectiveColumns.length,
4428
4557
  className: "text-center py-8 text-muted-foreground",
4429
4558
  children: emptyMessage
4430
4559
  }
@@ -4436,7 +4565,7 @@ function DataTable({
4436
4565
  (hover || onRowClick) && "hover:bg-muted/50"
4437
4566
  ),
4438
4567
  onClick: () => onRowClick == null ? void 0 : onRowClick(item),
4439
- children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(TableCell$1, { children: renderCell(column, item) }, column.key))
4568
+ children: effectiveColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(TableCell$1, { children: renderCell(column, item) }, column.key))
4440
4569
  },
4441
4570
  index
4442
4571
  )) })
@@ -9261,6 +9390,55 @@ const ListToolbar = ({
9261
9390
  }
9262
9391
  );
9263
9392
  };
9393
+ const FieldGrid = ({
9394
+ data,
9395
+ fields,
9396
+ sections,
9397
+ columns = 2,
9398
+ showLabels = true,
9399
+ compact = false,
9400
+ showEmpty = true,
9401
+ className
9402
+ }) => {
9403
+ const gridCols = {
9404
+ 1: "grid-cols-1",
9405
+ 2: "grid-cols-1 md:grid-cols-2",
9406
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
9407
+ };
9408
+ const renderValue = (field) => {
9409
+ const value = data[field.field];
9410
+ if (value === null || value === void 0 || value === "") {
9411
+ if (!showEmpty) return null;
9412
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "-" });
9413
+ }
9414
+ const format = field.format;
9415
+ return renderField(value, field.field, field.type, "lg", data, void 0, format);
9416
+ };
9417
+ const renderField_ = (field) => {
9418
+ const value = data[field.field];
9419
+ if (!showEmpty && (value === null || value === void 0 || value === "")) {
9420
+ return null;
9421
+ }
9422
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-1", compact ? "py-1" : "py-2"), children: [
9423
+ showLabels && /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-sm font-medium text-muted-foreground", children: field.label }),
9424
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "text-sm text-foreground", children: renderValue(field) })
9425
+ ] }, field.field);
9426
+ };
9427
+ const renderFields = (fieldList) => /* @__PURE__ */ jsxRuntime.jsx("dl", { className: cn("grid gap-x-6", gridCols[columns]), children: fieldList.map(renderField_) });
9428
+ if (sections && sections.length > 0) {
9429
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-6", className), children: sections.map((section) => /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: cn(compact ? "p-4" : "p-6"), children: [
9430
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
9431
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-foreground", children: section.title }),
9432
+ section.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-1", children: section.description })
9433
+ ] }),
9434
+ renderFields(section.fields)
9435
+ ] }, section.title)) });
9436
+ }
9437
+ if (fields && fields.length > 0) {
9438
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cn(compact ? "p-4" : "p-6", className), children: renderFields(fields) });
9439
+ }
9440
+ return null;
9441
+ };
9264
9442
  const Pagination = ({
9265
9443
  currentPage,
9266
9444
  totalPages,
@@ -9940,9 +10118,11 @@ function ProtectedRoute({
9940
10118
  children,
9941
10119
  requirePermission,
9942
10120
  requireRole,
9943
- fallback
10121
+ fallback,
10122
+ loginPath = "/login"
9944
10123
  }) {
9945
10124
  const { isAuthenticated, isLoading, hasPermission, hasRole } = useAuthContext();
10125
+ const location = reactRouterDom.useLocation();
9946
10126
  if (isLoading) {
9947
10127
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
9948
10128
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto" }),
@@ -9950,7 +10130,7 @@ function ProtectedRoute({
9950
10130
  ] }) });
9951
10131
  }
9952
10132
  if (!isAuthenticated) {
9953
- return /* @__PURE__ */ jsxRuntime.jsx(LoginForm, {});
10133
+ return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: loginPath, state: { from: location }, replace: true });
9954
10134
  }
9955
10135
  if (requirePermission && !hasPermission(requirePermission)) {
9956
10136
  return fallback || /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
@@ -9977,17 +10157,39 @@ function LogoutButton() {
9977
10157
  /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: logout, children: "Logout" })
9978
10158
  ] });
9979
10159
  }
10160
+ const AUTH_TOKEN_KEY = "auth_token";
10161
+ const DEFAULT_ENDPOINTS = {
10162
+ login: "/auth/login",
10163
+ register: "/auth/register",
10164
+ refresh: "/auth/refresh",
10165
+ me: "/auth/me",
10166
+ logout: "/auth/logout"
10167
+ };
9980
10168
  class AuthService {
9981
10169
  constructor(config) {
9982
10170
  __publicField(this, "config");
10171
+ __publicField(this, "apiClient");
9983
10172
  __publicField(this, "refreshPromise", null);
10173
+ __publicField(this, "endpoints");
9984
10174
  this.config = {
10175
+ mode: "pattern-stack",
9985
10176
  tokenStorage: "localStorage",
9986
10177
  tokenRefreshBuffer: 5,
9987
10178
  // 5 minutes before expiry
9988
10179
  autoRefresh: true,
9989
10180
  ...config
9990
10181
  };
10182
+ this.endpoints = {
10183
+ ...DEFAULT_ENDPOINTS,
10184
+ ...config.endpoints
10185
+ };
10186
+ this.apiClient = createApiClient({ baseURL: this.config.apiUrl || "" });
10187
+ }
10188
+ /**
10189
+ * Get the auth mode
10190
+ */
10191
+ get mode() {
10192
+ return this.config.mode || "pattern-stack";
9991
10193
  }
9992
10194
  getStorageKey(key) {
9993
10195
  return `auth_${key}`;
@@ -10071,21 +10273,33 @@ class AuthService {
10071
10273
  this.removeItem("user");
10072
10274
  }
10073
10275
  async login(credentials) {
10074
- const response = await apiClient.post(`${this.config.apiUrl}${this.config.endpoints.login}`, credentials);
10075
10276
  let token;
10076
10277
  let refreshToken;
10077
10278
  let expiresIn;
10078
- if ("access_token" in response) {
10079
- const backendResponse = response;
10080
- token = backendResponse.access_token;
10081
- refreshToken = backendResponse.refresh_token;
10279
+ let user;
10280
+ if (this.mode === "pattern-stack") {
10281
+ const response = await this.apiClient.post(
10282
+ this.endpoints.login,
10283
+ credentials
10284
+ );
10285
+ token = response.access_token;
10286
+ refreshToken = response.refresh_token;
10287
+ user = response.user;
10288
+ } else if (this.mode === "custom" && this.config.responseMapper) {
10289
+ const rawResponse = await this.apiClient.post(
10290
+ this.endpoints.login,
10291
+ credentials
10292
+ );
10293
+ const mapped = this.config.responseMapper(rawResponse);
10294
+ token = mapped.token;
10295
+ refreshToken = mapped.refreshToken;
10296
+ expiresIn = mapped.expiresIn;
10297
+ user = mapped.user;
10082
10298
  } else {
10083
- const oldResponse = response;
10084
- token = oldResponse.token;
10085
- refreshToken = oldResponse.refreshToken;
10086
- expiresIn = oldResponse.expiresIn;
10299
+ throw new Error(
10300
+ `Invalid auth mode: ${this.mode}. Use 'pattern-stack' or 'custom' with responseMapper.`
10301
+ );
10087
10302
  }
10088
- const user = response.user;
10089
10303
  const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
10090
10304
  this.setTokenData({ token, refreshToken, expiresAt });
10091
10305
  this.setStoredUser(user);
@@ -10111,21 +10325,27 @@ class AuthService {
10111
10325
  }
10112
10326
  async performTokenRefresh(currentRefreshToken) {
10113
10327
  try {
10114
- const response = await apiClient.post(`${this.config.apiUrl}${this.config.endpoints.refresh}`, {
10115
- refresh_token: currentRefreshToken
10116
- });
10117
10328
  let token;
10118
10329
  let newRefreshToken;
10119
10330
  let expiresIn;
10120
- if ("access_token" in response) {
10121
- const backendResponse = response;
10122
- token = backendResponse.access_token;
10331
+ if (this.mode === "pattern-stack") {
10332
+ const response = await this.apiClient.post(
10333
+ this.endpoints.refresh,
10334
+ { refresh_token: currentRefreshToken }
10335
+ );
10336
+ token = response.access_token;
10123
10337
  newRefreshToken = currentRefreshToken;
10338
+ } else if (this.mode === "custom" && this.config.responseMapper) {
10339
+ const rawResponse = await this.apiClient.post(
10340
+ this.endpoints.refresh,
10341
+ { refresh_token: currentRefreshToken }
10342
+ );
10343
+ const mapped = this.config.responseMapper(rawResponse);
10344
+ token = mapped.token;
10345
+ newRefreshToken = mapped.refreshToken || currentRefreshToken;
10346
+ expiresIn = mapped.expiresIn;
10124
10347
  } else {
10125
- const oldResponse = response;
10126
- token = oldResponse.token;
10127
- newRefreshToken = oldResponse.refreshToken;
10128
- expiresIn = oldResponse.expiresIn;
10348
+ throw new Error(`Invalid auth mode for token refresh: ${this.mode}`);
10129
10349
  }
10130
10350
  const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
10131
10351
  this.setTokenData({
@@ -10167,36 +10387,39 @@ class AuthService {
10167
10387
  const tokenData = this.getTokenData();
10168
10388
  if (!(tokenData == null ? void 0 : tokenData.token)) return null;
10169
10389
  try {
10170
- return await apiClient.get(
10171
- `${this.config.apiUrl}${this.config.endpoints.me}`
10172
- );
10390
+ return await this.apiClient.get(this.endpoints.me);
10173
10391
  } catch {
10174
10392
  return null;
10175
10393
  }
10176
10394
  }
10177
10395
  async logout() {
10178
10396
  const tokenData = this.getTokenData();
10179
- if (this.config.endpoints.logout && (tokenData == null ? void 0 : tokenData.token)) {
10397
+ if (this.endpoints.logout && (tokenData == null ? void 0 : tokenData.token)) {
10180
10398
  try {
10181
- await apiClient.post(
10182
- `${this.config.apiUrl}${this.config.endpoints.logout}`
10183
- );
10399
+ await this.apiClient.post(this.endpoints.logout);
10184
10400
  } catch {
10185
10401
  }
10186
10402
  }
10187
10403
  this.clearAuth();
10188
10404
  }
10405
+ /**
10406
+ * Get the configured onAuthError handler
10407
+ */
10408
+ get onAuthError() {
10409
+ return this.config.onAuthError;
10410
+ }
10189
10411
  }
10190
10412
  function AuthProvider({
10191
10413
  children,
10192
10414
  config
10193
10415
  }) {
10416
+ var _a;
10194
10417
  const [user, setUser] = React.useState(null);
10195
10418
  const [isLoading, setIsLoading] = React.useState(true);
10196
10419
  const [permissions, setPermissions] = React.useState([]);
10197
10420
  const authService = React.useMemo(() => {
10198
10421
  const defaultConfig = {
10199
- apiUrl: "http://localhost:8080",
10422
+ apiUrl: (config == null ? void 0 : config.apiUrl) ?? "",
10200
10423
  endpoints: {
10201
10424
  login: "/auth/login",
10202
10425
  register: "/auth/register",
@@ -10211,7 +10434,7 @@ function AuthProvider({
10211
10434
  enabled: false
10212
10435
  }
10213
10436
  };
10214
- const mergedConfig = config ? { ...defaultConfig, ...config } : defaultConfig;
10437
+ const mergedConfig = config ? { ...defaultConfig, ...config, apiUrl: config.apiUrl ?? defaultConfig.apiUrl } : defaultConfig;
10215
10438
  const service = new AuthService(mergedConfig);
10216
10439
  setGlobalAuthService(service);
10217
10440
  return service;
@@ -10258,48 +10481,56 @@ function AuthProvider({
10258
10481
  };
10259
10482
  initAuth();
10260
10483
  }, [authService]);
10261
- const login = async (credentials) => {
10262
- var _a;
10484
+ const login = React.useCallback(async (credentials) => {
10263
10485
  setIsLoading(true);
10264
10486
  try {
10265
- const user2 = await authService.login(credentials);
10266
- setUser(user2);
10267
- if (((_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled) && "permissions" in user2) {
10268
- const userPermissions = user2.permissions;
10269
- setPermissions(
10270
- Array.isArray(userPermissions) ? userPermissions : []
10271
- );
10272
- }
10273
- } finally {
10487
+ const loggedInUser = await authService.login(credentials);
10488
+ reactDom.flushSync(() => {
10489
+ var _a2;
10490
+ setUser(loggedInUser);
10491
+ if (((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled) && "permissions" in loggedInUser) {
10492
+ const userPermissions = loggedInUser.permissions;
10493
+ setPermissions(
10494
+ Array.isArray(userPermissions) ? userPermissions : []
10495
+ );
10496
+ }
10497
+ setIsLoading(false);
10498
+ });
10499
+ } catch (error) {
10274
10500
  setIsLoading(false);
10501
+ throw error;
10275
10502
  }
10276
- };
10277
- const logout = async () => {
10503
+ }, [authService, (_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled]);
10504
+ const logout = React.useCallback(async () => {
10278
10505
  setIsLoading(true);
10279
10506
  try {
10280
10507
  await authService.logout();
10281
- setUser(null);
10282
- setPermissions([]);
10508
+ reactDom.flushSync(() => {
10509
+ setUser(null);
10510
+ setPermissions([]);
10511
+ setIsLoading(false);
10512
+ });
10283
10513
  } catch (error) {
10284
10514
  console.error("Logout error:", error);
10285
10515
  authService.clearAuth();
10286
- setUser(null);
10287
- setPermissions([]);
10288
- } finally {
10289
- setIsLoading(false);
10516
+ reactDom.flushSync(() => {
10517
+ setUser(null);
10518
+ setPermissions([]);
10519
+ setIsLoading(false);
10520
+ });
10290
10521
  }
10291
- };
10522
+ }, [authService]);
10292
10523
  const refreshToken = async () => {
10293
10524
  await authService.refreshToken();
10294
10525
  };
10295
10526
  const hasPermission = (permission) => {
10296
- var _a;
10297
- if (!((_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled)) return true;
10527
+ var _a2;
10528
+ if (!((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled)) return true;
10298
10529
  return permissions.includes(permission);
10299
10530
  };
10300
10531
  const hasRole = (role) => {
10301
- var _a;
10302
- if (!((_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled)) return true;
10532
+ var _a2;
10533
+ if (!((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled)) return true;
10303
10534
  if (user && "role" in user) {
10304
10535
  return user.role === role;
10305
10536
  }
@@ -10508,6 +10739,32 @@ function MockAuthProvider({
10508
10739
  };
10509
10740
  return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value, children });
10510
10741
  }
10742
+ function NoAuthProvider({ children }) {
10743
+ const noOpLogin = async () => {
10744
+ console.warn("NoAuthProvider: login() called but auth is disabled");
10745
+ };
10746
+ const noOpLogout = async () => {
10747
+ console.warn("NoAuthProvider: logout() called but auth is disabled");
10748
+ };
10749
+ const noOpRefresh = async () => {
10750
+ console.warn("NoAuthProvider: refreshToken() called but auth is disabled");
10751
+ };
10752
+ const value = {
10753
+ user: null,
10754
+ isAuthenticated: true,
10755
+ // Always authenticated in no-auth mode
10756
+ isLoading: false,
10757
+ permissions: [],
10758
+ login: noOpLogin,
10759
+ logout: noOpLogout,
10760
+ refreshToken: noOpRefresh,
10761
+ hasPermission: () => true,
10762
+ // All permissions granted
10763
+ hasRole: () => true
10764
+ // All roles granted
10765
+ };
10766
+ return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value, children });
10767
+ }
10511
10768
  const MockAuthContext = React.createContext(
10512
10769
  void 0
10513
10770
  );
@@ -13647,7 +13904,13 @@ function createReactApp(config) {
13647
13904
  tree = /* @__PURE__ */ jsxRuntime.jsx(NavigationProvider, { initialNavigation: navigation, children: /* @__PURE__ */ jsxRuntime.jsx(SidebarProvider, { children: tree }) });
13648
13905
  }
13649
13906
  if (enableAuth) {
13650
- tree = /* @__PURE__ */ jsxRuntime.jsx(AuthProvider, { config: auth, children: tree });
13907
+ const authMode = (auth == null ? void 0 : auth.mode) || "pattern-stack";
13908
+ if (authMode === "none") {
13909
+ setGlobalAuthService(null);
13910
+ tree = /* @__PURE__ */ jsxRuntime.jsx(NoAuthProvider, { children: tree });
13911
+ } else {
13912
+ tree = /* @__PURE__ */ jsxRuntime.jsx(AuthProvider, { config: auth, children: tree });
13913
+ }
13651
13914
  }
13652
13915
  if (enableQuery) {
13653
13916
  tree = /* @__PURE__ */ jsxRuntime.jsxs(reactQuery.QueryClientProvider, { client: queryClient, children: [
@@ -13665,7 +13928,8 @@ function createReactApp(config) {
13665
13928
  routes.push({ path, component });
13666
13929
  },
13667
13930
  render() {
13668
- const shouldProtect = enableAuth && (auth == null ? void 0 : auth.requireAuth) !== false;
13931
+ const authMode = (auth == null ? void 0 : auth.mode) || "pattern-stack";
13932
+ const shouldProtect = enableAuth && authMode !== "none" && (auth == null ? void 0 : auth.requireAuth) !== false;
13669
13933
  const publicPaths = (auth == null ? void 0 : auth.publicPaths) ?? ["/login", "/register", "/forgot-password"];
13670
13934
  const isPublicPath = (path) => {
13671
13935
  return publicPaths.some((p) => path === p || path.startsWith(p + "/"));
@@ -16852,12 +17116,14 @@ function capitalize(str) {
16852
17116
  exports.APIDataTemplate = APIDataTemplate;
16853
17117
  exports.API_ENDPOINTS = API_ENDPOINTS;
16854
17118
  exports.APP_NAME = APP_NAME;
17119
+ exports.AUTH_TOKEN_KEY = AUTH_TOKEN_KEY;
16855
17120
  exports.Accordion = Accordion;
16856
17121
  exports.ActivityFeed = ActivityFeed;
16857
17122
  exports.AdminCRUDTemplate = AdminCRUDTemplate;
16858
17123
  exports.AdminDashboardTemplate = AdminDashboardTemplate;
16859
17124
  exports.AdminDetailTemplate = AdminDetailTemplate;
16860
17125
  exports.Alert = Alert;
17126
+ exports.ApiClient = ApiClient;
16861
17127
  exports.AppHeader = AppHeader;
16862
17128
  exports.AppLayout = AppLayout;
16863
17129
  exports.AuthDivider = AuthDivider;
@@ -16924,6 +17190,7 @@ exports.EmptyState = EmptyState;
16924
17190
  exports.EnhancedDataTemplate = EnhancedDataTemplate;
16925
17191
  exports.EntityIcon = EntityIcon;
16926
17192
  exports.ErrorBoundary = ErrorBoundary2;
17193
+ exports.FieldGrid = FieldGrid;
16927
17194
  exports.FileUpload = FileUpload;
16928
17195
  exports.FormField = FormField;
16929
17196
  exports.FormGroup = FormGroup;
@@ -16943,6 +17210,7 @@ exports.MockAuthProvider = MockAuthProvider;
16943
17210
  exports.Modal = Modal;
16944
17211
  exports.NavMenu = NavMenu;
16945
17212
  exports.NavigationProvider = NavigationProvider;
17213
+ exports.NoAuthProvider = NoAuthProvider;
16946
17214
  exports.PageTemplate = PageTemplate;
16947
17215
  exports.PageTitle = PageTitle;
16948
17216
  exports.Pagination = Pagination;
@@ -17006,6 +17274,7 @@ exports.apiClient = apiClient;
17006
17274
  exports.breakpoints = breakpoints;
17007
17275
  exports.cn = cn;
17008
17276
  exports.createAPIDataTemplate = createAPIDataTemplate;
17277
+ exports.createApiClient = createApiClient;
17009
17278
  exports.createReactApp = createReactApp;
17010
17279
  exports.createSimpleApp = createSimpleApp;
17011
17280
  exports.defaultFieldRenderers = defaultFieldRenderers;
@@ -17019,6 +17288,7 @@ exports.getChartHeight = getChartHeight;
17019
17288
  exports.getContainerHeightClass = getContainerHeightClass;
17020
17289
  exports.getCurrentBreakpoint = getCurrentBreakpoint;
17021
17290
  exports.getFieldType = getFieldType;
17291
+ exports.getGlobalAuthService = getGlobalAuthService;
17022
17292
  exports.getIcon = getIcon;
17023
17293
  exports.getNavigationItems = getNavigationItems;
17024
17294
  exports.getResponsiveClasses = getResponsiveClasses;
@@ -17042,6 +17312,7 @@ exports.useCategoryColors = useCategoryColors;
17042
17312
  exports.useCreateExample = useCreateExample;
17043
17313
  exports.useDeleteExample = useDeleteExample;
17044
17314
  exports.useEntityData = useEntityData;
17315
+ exports.useEntityDetail = useEntityDetail;
17045
17316
  exports.useErrorBoundary = useErrorBoundary;
17046
17317
  exports.useFieldMetadata = useFieldMetadata;
17047
17318
  exports.useGetExample = useGetExample;