@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.es.js CHANGED
@@ -15,9 +15,9 @@ import { cva } from "class-variance-authority";
15
15
  import * as AvatarPrimitive from "@radix-ui/react-avatar";
16
16
  import { useQuery, useQueryClient, useMutation, QueryClient, QueryClientProvider } from "@tanstack/react-query";
17
17
  import { Slot } from "@radix-ui/react-slot";
18
- import { createPortal } from "react-dom";
18
+ import { createPortal, flushSync } from "react-dom";
19
19
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
20
- import { useLocation, useNavigate, useSearchParams, Outlet, BrowserRouter, Routes, Route } from "react-router-dom";
20
+ import { useLocation, useNavigate, useSearchParams, Outlet, Navigate, BrowserRouter, Routes, Route } from "react-router-dom";
21
21
  import { createRoot } from "react-dom/client";
22
22
  const APP_NAME = "Design System Showcase";
23
23
  const API_ENDPOINTS = {
@@ -34,8 +34,8 @@ const UI_CONFIG = {
34
34
  TOAST_DURATION: 5e3
35
35
  };
36
36
  const env = {
37
- API_BASE_URL: "http://localhost:8080",
38
- OPENAPI_URL: "http://localhost:8080/openapi.json",
37
+ API_BASE_URL: "",
38
+ OPENAPI_URL: "/openapi.json",
39
39
  NODE_ENV: "development",
40
40
  IS_DEVELOPMENT: false,
41
41
  IS_PRODUCTION: false,
@@ -2009,16 +2009,18 @@ function renderField(value, fieldName, fieldType, breakpoint, item, customRender
2009
2009
  function cn(...inputs) {
2010
2010
  return twMerge(clsx(inputs));
2011
2011
  }
2012
- const API_BASE_URL = "http://localhost:8080";
2013
2012
  let globalAuthService = null;
2014
2013
  function setGlobalAuthService(authService) {
2015
2014
  globalAuthService = authService;
2016
2015
  }
2016
+ function getGlobalAuthService() {
2017
+ return globalAuthService;
2018
+ }
2017
2019
  class ApiClient {
2018
2020
  constructor(config = {}) {
2019
2021
  __publicField(this, "instance");
2020
2022
  this.instance = axios.create({
2021
- baseURL: config.baseURL || API_BASE_URL,
2023
+ baseURL: config.baseURL || "",
2022
2024
  timeout: config.timeout || 1e4,
2023
2025
  headers: {
2024
2026
  "Content-Type": "application/json",
@@ -2058,11 +2060,12 @@ class ApiClient {
2058
2060
  this.instance.interceptors.response.use(
2059
2061
  (response) => response,
2060
2062
  async (error) => {
2061
- var _a;
2062
- if (((_a = error.response) == null ? void 0 : _a.status) === 401) {
2063
+ var _a, _b, _c, _d;
2064
+ const status = (_a = error.response) == null ? void 0 : _a.status;
2065
+ if (status === 401 || status === 403) {
2063
2066
  if (globalAuthService) {
2064
2067
  const tokenData = globalAuthService.getTokenData();
2065
- if ((tokenData == null ? void 0 : tokenData.refreshToken) && !error.config._retry) {
2068
+ if (status === 401 && (tokenData == null ? void 0 : tokenData.refreshToken) && !error.config._retry) {
2066
2069
  error.config._retry = true;
2067
2070
  try {
2068
2071
  await globalAuthService.refreshToken();
@@ -2070,11 +2073,12 @@ class ApiClient {
2070
2073
  error.config.headers.Authorization = `Bearer ${newTokenData == null ? void 0 : newTokenData.token}`;
2071
2074
  return this.instance.request(error.config);
2072
2075
  } catch {
2073
- globalAuthService.clearAuth();
2074
- window.location.reload();
2075
2076
  }
2076
- } else {
2077
- globalAuthService.clearAuth();
2077
+ }
2078
+ globalAuthService.clearAuth();
2079
+ const errorInfo = { status, message: (_c = (_b = error.response) == null ? void 0 : _b.data) == null ? void 0 : _c.detail };
2080
+ const handled = (_d = globalAuthService.onAuthError) == null ? void 0 : _d.call(globalAuthService, errorInfo);
2081
+ if (!handled) {
2078
2082
  window.location.reload();
2079
2083
  }
2080
2084
  } else {
@@ -2120,6 +2124,9 @@ class ApiClient {
2120
2124
  return response.data;
2121
2125
  }
2122
2126
  }
2127
+ function createApiClient(config = {}) {
2128
+ return new ApiClient(config);
2129
+ }
2123
2130
  const apiClient = new ApiClient();
2124
2131
  function useToast() {
2125
2132
  const [toasts, setToasts] = useState([]);
@@ -2390,7 +2397,7 @@ function useEntityData(entity, options = {}) {
2390
2397
  const dataQuery = useQuery({
2391
2398
  queryKey: [entity, "list", { limit, offset, filters }],
2392
2399
  queryFn: () => apiClient.get(
2393
- `/api/v1/${entity}/?${dataParams.toString()}`
2400
+ `/api/v1/${entity}?${dataParams.toString()}`
2394
2401
  ),
2395
2402
  enabled
2396
2403
  });
@@ -2418,6 +2425,40 @@ function useEntityData(entity, options = {}) {
2418
2425
  refetch
2419
2426
  };
2420
2427
  }
2428
+ function useEntityDetail(entity, id, options = {}) {
2429
+ var _a;
2430
+ const { view = "detail", sourceId, enabled = true } = options;
2431
+ const metadataParams = new URLSearchParams();
2432
+ metadataParams.set("view", view);
2433
+ if (sourceId) metadataParams.set("source_id", sourceId);
2434
+ const dataQuery = useQuery({
2435
+ queryKey: [entity, "detail", id],
2436
+ queryFn: () => apiClient.get(`/api/v1/${entity}/${id}`),
2437
+ enabled: enabled && !!id
2438
+ });
2439
+ const metadataQuery = useQuery({
2440
+ queryKey: [entity, "metadata", { view, sourceId }],
2441
+ queryFn: () => apiClient.get(
2442
+ `/api/v1/${entity}/fields/metadata?${metadataParams.toString()}`
2443
+ ),
2444
+ enabled,
2445
+ staleTime: 5 * 60 * 1e3
2446
+ // 5 minutes
2447
+ });
2448
+ const refetch = () => {
2449
+ dataQuery.refetch();
2450
+ metadataQuery.refetch();
2451
+ };
2452
+ return {
2453
+ data: dataQuery.data ?? null,
2454
+ fields: ((_a = metadataQuery.data) == null ? void 0 : _a.columns) ?? [],
2455
+ isLoading: dataQuery.isLoading || metadataQuery.isLoading,
2456
+ isLoadingData: dataQuery.isLoading,
2457
+ isLoadingMetadata: metadataQuery.isLoading,
2458
+ error: dataQuery.error ?? metadataQuery.error ?? null,
2459
+ refetch
2460
+ };
2461
+ }
2421
2462
  const buttonVariants = cva(
2422
2463
  "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]",
2423
2464
  {
@@ -4108,7 +4149,9 @@ const TableHead$1 = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
4108
4149
  {
4109
4150
  ref,
4110
4151
  className: cn(
4111
- "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",
4152
+ "h-12 px-4 xs:px-2 2xs:px-1 text-left align-middle font-semibold text-gray-700 text-xs uppercase tracking-wider",
4153
+ // Compact padding for checkbox columns
4154
+ "[&:has(input[type=checkbox])]:px-2 [&:has(input[type=checkbox])]:w-8",
4112
4155
  className
4113
4156
  ),
4114
4157
  ...props
@@ -4120,7 +4163,9 @@ const TableCell$1 = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
4120
4163
  {
4121
4164
  ref,
4122
4165
  className: cn(
4123
- "p-4 xs:p-2 2xs:p-1 align-middle [&:has([role=checkbox])]:pr-0",
4166
+ "p-4 xs:p-2 2xs:p-1 align-middle",
4167
+ // Compact padding for checkbox columns
4168
+ "[&:has(input[type=checkbox])]:px-2 [&:has(input[type=checkbox])]:w-8",
4124
4169
  className
4125
4170
  ),
4126
4171
  ...props
@@ -4170,7 +4215,12 @@ function DataTable({
4170
4215
  ui,
4171
4216
  error = null,
4172
4217
  errorTitle,
4173
- errorRetry
4218
+ errorRetry,
4219
+ selectable = false,
4220
+ selectedIds,
4221
+ onSelectionChange,
4222
+ getRowId,
4223
+ selectAllMode = "page"
4174
4224
  }) {
4175
4225
  const [searchTerm, setSearchTerm] = useState("");
4176
4226
  const [sortColumn, setSortColumn] = useState(null);
@@ -4233,6 +4283,85 @@ function DataTable({
4233
4283
  return true;
4234
4284
  });
4235
4285
  }, [normalizedColumns, currentBreakpoint, responsive]);
4286
+ const defaultGetRowId = useCallback((row) => {
4287
+ if ("id" in row && typeof row.id === "string") return row.id;
4288
+ if ("id" in row && typeof row.id === "number") return String(row.id);
4289
+ throw new Error("DataTable: selectable requires getRowId prop or row.id field");
4290
+ }, []);
4291
+ const effectiveGetRowId = getRowId ?? defaultGetRowId;
4292
+ const selectableData = selectAllMode === "all" ? sortedData : paginatedData;
4293
+ const selectableIds = useMemo(
4294
+ () => {
4295
+ if (!selectable) return /* @__PURE__ */ new Set();
4296
+ return new Set(selectableData.map((row) => effectiveGetRowId(row)));
4297
+ },
4298
+ [selectable, selectableData, effectiveGetRowId]
4299
+ );
4300
+ const selectedInScope = useMemo(
4301
+ () => {
4302
+ if (!selectable || !selectedIds) return /* @__PURE__ */ new Set();
4303
+ return new Set([...selectedIds].filter((id) => selectableIds.has(id)));
4304
+ },
4305
+ [selectable, selectedIds, selectableIds]
4306
+ );
4307
+ const isAllSelected = selectableIds.size > 0 && selectedInScope.size === selectableIds.size;
4308
+ const isIndeterminate = selectedInScope.size > 0 && selectedInScope.size < selectableIds.size;
4309
+ const handleSelectAll = useCallback(() => {
4310
+ if (!onSelectionChange) return;
4311
+ if (isAllSelected) {
4312
+ const newSelection = new Set([...selectedIds ?? []].filter((id) => !selectableIds.has(id)));
4313
+ onSelectionChange(newSelection);
4314
+ } else {
4315
+ const newSelection = /* @__PURE__ */ new Set([...selectedIds ?? [], ...selectableIds]);
4316
+ onSelectionChange(newSelection);
4317
+ }
4318
+ }, [isAllSelected, selectedIds, selectableIds, onSelectionChange]);
4319
+ const handleSelectRow = useCallback((rowId) => {
4320
+ if (!onSelectionChange) return;
4321
+ const newSelection = new Set(selectedIds);
4322
+ if (newSelection.has(rowId)) {
4323
+ newSelection.delete(rowId);
4324
+ } else {
4325
+ newSelection.add(rowId);
4326
+ }
4327
+ onSelectionChange(newSelection);
4328
+ }, [selectedIds, onSelectionChange]);
4329
+ const selectionColumn = useMemo(() => {
4330
+ return {
4331
+ key: "__selection__",
4332
+ header: /* @__PURE__ */ jsx(
4333
+ Checkbox$1,
4334
+ {
4335
+ checked: isAllSelected,
4336
+ indeterminate: isIndeterminate,
4337
+ onCheckedChange: handleSelectAll,
4338
+ "aria-label": isAllSelected ? "Deselect all rows" : "Select all rows",
4339
+ size: "sm"
4340
+ }
4341
+ ),
4342
+ cell: (row) => {
4343
+ const rowId = effectiveGetRowId(row);
4344
+ const isSelected = (selectedIds == null ? void 0 : selectedIds.has(rowId)) ?? false;
4345
+ return /* @__PURE__ */ jsx(
4346
+ Checkbox$1,
4347
+ {
4348
+ checked: isSelected,
4349
+ onCheckedChange: () => handleSelectRow(rowId),
4350
+ "aria-label": isSelected ? `Deselect row ${rowId}` : `Select row ${rowId}`,
4351
+ size: "sm",
4352
+ onClick: (e) => e.stopPropagation()
4353
+ }
4354
+ );
4355
+ },
4356
+ width: "32px",
4357
+ sortable: false,
4358
+ filterable: false
4359
+ };
4360
+ }, [isAllSelected, isIndeterminate, selectedIds, effectiveGetRowId, handleSelectAll, handleSelectRow]);
4361
+ const effectiveColumns = useMemo(() => {
4362
+ if (!selectable) return visibleColumns;
4363
+ return [selectionColumn, ...visibleColumns];
4364
+ }, [selectable, selectionColumn, visibleColumns]);
4236
4365
  const handleSort = (columnKey) => {
4237
4366
  if (sortColumn === columnKey) {
4238
4367
  if (sortDirection === "asc") {
@@ -4295,8 +4424,8 @@ function DataTable({
4295
4424
  children: [
4296
4425
  showSearch && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx("div", { className: "relative flex-1 max-w-sm", children: /* @__PURE__ */ jsx(Skeleton$1, { className: "h-10 w-full" }) }) }),
4297
4426
  /* @__PURE__ */ jsx("div", { className: "rounded border overflow-hidden", children: /* @__PURE__ */ jsxs(Table$1, { children: [
4298
- /* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children: visibleColumns.map((column) => /* @__PURE__ */ jsx(TableHead$1, { style: { width: column.width }, children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-20" }) }) }, column.key)) }) }),
4299
- /* @__PURE__ */ jsx(TableBody$1, { children: Array.from({ length: loadingItemCount }, (_, index) => /* @__PURE__ */ jsx(TableRow$1, { children: visibleColumns.map((column) => /* @__PURE__ */ jsx(TableCell$1, { children: /* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-full max-w-32" }) }, column.key)) }, index)) })
4427
+ /* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children: effectiveColumns.map((column) => /* @__PURE__ */ jsx(TableHead$1, { style: { width: column.width }, children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-20" }) }) }, column.key)) }) }),
4428
+ /* @__PURE__ */ jsx(TableBody$1, { children: Array.from({ length: loadingItemCount }, (_, index) => /* @__PURE__ */ jsx(TableRow$1, { children: effectiveColumns.map((column) => /* @__PURE__ */ jsx(TableCell$1, { children: /* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-full max-w-32" }) }, column.key)) }, index)) })
4300
4429
  ] }) }),
4301
4430
  showPagination && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
4302
4431
  /* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-48" }),
@@ -4358,7 +4487,7 @@ function DataTable({
4358
4487
  data: paginatedData,
4359
4488
  onItemClick: onRowClick
4360
4489
  }) }) : /* @__PURE__ */ jsx("div", { className: "rounded border overflow-hidden", children: /* @__PURE__ */ jsxs(Table$1, { children: [
4361
- /* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children: visibleColumns.map((column) => {
4490
+ /* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children: effectiveColumns.map((column) => {
4362
4491
  const width = column.width;
4363
4492
  return /* @__PURE__ */ jsx(
4364
4493
  TableHead$1,
@@ -4381,7 +4510,7 @@ function DataTable({
4381
4510
  /* @__PURE__ */ jsx(TableBody$1, { children: paginatedData.length === 0 ? /* @__PURE__ */ jsx(TableRow$1, { children: /* @__PURE__ */ jsx(
4382
4511
  TableCell$1,
4383
4512
  {
4384
- colSpan: visibleColumns.length,
4513
+ colSpan: effectiveColumns.length,
4385
4514
  className: "text-center py-8 text-muted-foreground",
4386
4515
  children: emptyMessage
4387
4516
  }
@@ -4393,7 +4522,7 @@ function DataTable({
4393
4522
  (hover || onRowClick) && "hover:bg-muted/50"
4394
4523
  ),
4395
4524
  onClick: () => onRowClick == null ? void 0 : onRowClick(item),
4396
- children: visibleColumns.map((column) => /* @__PURE__ */ jsx(TableCell$1, { children: renderCell(column, item) }, column.key))
4525
+ children: effectiveColumns.map((column) => /* @__PURE__ */ jsx(TableCell$1, { children: renderCell(column, item) }, column.key))
4397
4526
  },
4398
4527
  index
4399
4528
  )) })
@@ -9218,6 +9347,55 @@ const ListToolbar = ({
9218
9347
  }
9219
9348
  );
9220
9349
  };
9350
+ const FieldGrid = ({
9351
+ data,
9352
+ fields,
9353
+ sections,
9354
+ columns = 2,
9355
+ showLabels = true,
9356
+ compact = false,
9357
+ showEmpty = true,
9358
+ className
9359
+ }) => {
9360
+ const gridCols = {
9361
+ 1: "grid-cols-1",
9362
+ 2: "grid-cols-1 md:grid-cols-2",
9363
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
9364
+ };
9365
+ const renderValue = (field) => {
9366
+ const value = data[field.field];
9367
+ if (value === null || value === void 0 || value === "") {
9368
+ if (!showEmpty) return null;
9369
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" });
9370
+ }
9371
+ const format = field.format;
9372
+ return renderField(value, field.field, field.type, "lg", data, void 0, format);
9373
+ };
9374
+ const renderField_ = (field) => {
9375
+ const value = data[field.field];
9376
+ if (!showEmpty && (value === null || value === void 0 || value === "")) {
9377
+ return null;
9378
+ }
9379
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1", compact ? "py-1" : "py-2"), children: [
9380
+ showLabels && /* @__PURE__ */ jsx("dt", { className: "text-sm font-medium text-muted-foreground", children: field.label }),
9381
+ /* @__PURE__ */ jsx("dd", { className: "text-sm text-foreground", children: renderValue(field) })
9382
+ ] }, field.field);
9383
+ };
9384
+ const renderFields = (fieldList) => /* @__PURE__ */ jsx("dl", { className: cn("grid gap-x-6", gridCols[columns]), children: fieldList.map(renderField_) });
9385
+ if (sections && sections.length > 0) {
9386
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-6", className), children: sections.map((section) => /* @__PURE__ */ jsxs(Card, { className: cn(compact ? "p-4" : "p-6"), children: [
9387
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
9388
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground", children: section.title }),
9389
+ section.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: section.description })
9390
+ ] }),
9391
+ renderFields(section.fields)
9392
+ ] }, section.title)) });
9393
+ }
9394
+ if (fields && fields.length > 0) {
9395
+ return /* @__PURE__ */ jsx(Card, { className: cn(compact ? "p-4" : "p-6", className), children: renderFields(fields) });
9396
+ }
9397
+ return null;
9398
+ };
9221
9399
  const Pagination = ({
9222
9400
  currentPage,
9223
9401
  totalPages,
@@ -9897,9 +10075,11 @@ function ProtectedRoute({
9897
10075
  children,
9898
10076
  requirePermission,
9899
10077
  requireRole,
9900
- fallback
10078
+ fallback,
10079
+ loginPath = "/login"
9901
10080
  }) {
9902
10081
  const { isAuthenticated, isLoading, hasPermission, hasRole } = useAuthContext();
10082
+ const location = useLocation();
9903
10083
  if (isLoading) {
9904
10084
  return /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
9905
10085
  /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto" }),
@@ -9907,7 +10087,7 @@ function ProtectedRoute({
9907
10087
  ] }) });
9908
10088
  }
9909
10089
  if (!isAuthenticated) {
9910
- return /* @__PURE__ */ jsx(LoginForm, {});
10090
+ return /* @__PURE__ */ jsx(Navigate, { to: loginPath, state: { from: location }, replace: true });
9911
10091
  }
9912
10092
  if (requirePermission && !hasPermission(requirePermission)) {
9913
10093
  return fallback || /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
@@ -9934,17 +10114,39 @@ function LogoutButton() {
9934
10114
  /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: logout, children: "Logout" })
9935
10115
  ] });
9936
10116
  }
10117
+ const AUTH_TOKEN_KEY = "auth_token";
10118
+ const DEFAULT_ENDPOINTS = {
10119
+ login: "/auth/login",
10120
+ register: "/auth/register",
10121
+ refresh: "/auth/refresh",
10122
+ me: "/auth/me",
10123
+ logout: "/auth/logout"
10124
+ };
9937
10125
  class AuthService {
9938
10126
  constructor(config) {
9939
10127
  __publicField(this, "config");
10128
+ __publicField(this, "apiClient");
9940
10129
  __publicField(this, "refreshPromise", null);
10130
+ __publicField(this, "endpoints");
9941
10131
  this.config = {
10132
+ mode: "pattern-stack",
9942
10133
  tokenStorage: "localStorage",
9943
10134
  tokenRefreshBuffer: 5,
9944
10135
  // 5 minutes before expiry
9945
10136
  autoRefresh: true,
9946
10137
  ...config
9947
10138
  };
10139
+ this.endpoints = {
10140
+ ...DEFAULT_ENDPOINTS,
10141
+ ...config.endpoints
10142
+ };
10143
+ this.apiClient = createApiClient({ baseURL: this.config.apiUrl || "" });
10144
+ }
10145
+ /**
10146
+ * Get the auth mode
10147
+ */
10148
+ get mode() {
10149
+ return this.config.mode || "pattern-stack";
9948
10150
  }
9949
10151
  getStorageKey(key) {
9950
10152
  return `auth_${key}`;
@@ -10028,21 +10230,33 @@ class AuthService {
10028
10230
  this.removeItem("user");
10029
10231
  }
10030
10232
  async login(credentials) {
10031
- const response = await apiClient.post(`${this.config.apiUrl}${this.config.endpoints.login}`, credentials);
10032
10233
  let token;
10033
10234
  let refreshToken;
10034
10235
  let expiresIn;
10035
- if ("access_token" in response) {
10036
- const backendResponse = response;
10037
- token = backendResponse.access_token;
10038
- refreshToken = backendResponse.refresh_token;
10236
+ let user;
10237
+ if (this.mode === "pattern-stack") {
10238
+ const response = await this.apiClient.post(
10239
+ this.endpoints.login,
10240
+ credentials
10241
+ );
10242
+ token = response.access_token;
10243
+ refreshToken = response.refresh_token;
10244
+ user = response.user;
10245
+ } else if (this.mode === "custom" && this.config.responseMapper) {
10246
+ const rawResponse = await this.apiClient.post(
10247
+ this.endpoints.login,
10248
+ credentials
10249
+ );
10250
+ const mapped = this.config.responseMapper(rawResponse);
10251
+ token = mapped.token;
10252
+ refreshToken = mapped.refreshToken;
10253
+ expiresIn = mapped.expiresIn;
10254
+ user = mapped.user;
10039
10255
  } else {
10040
- const oldResponse = response;
10041
- token = oldResponse.token;
10042
- refreshToken = oldResponse.refreshToken;
10043
- expiresIn = oldResponse.expiresIn;
10256
+ throw new Error(
10257
+ `Invalid auth mode: ${this.mode}. Use 'pattern-stack' or 'custom' with responseMapper.`
10258
+ );
10044
10259
  }
10045
- const user = response.user;
10046
10260
  const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
10047
10261
  this.setTokenData({ token, refreshToken, expiresAt });
10048
10262
  this.setStoredUser(user);
@@ -10068,21 +10282,27 @@ class AuthService {
10068
10282
  }
10069
10283
  async performTokenRefresh(currentRefreshToken) {
10070
10284
  try {
10071
- const response = await apiClient.post(`${this.config.apiUrl}${this.config.endpoints.refresh}`, {
10072
- refresh_token: currentRefreshToken
10073
- });
10074
10285
  let token;
10075
10286
  let newRefreshToken;
10076
10287
  let expiresIn;
10077
- if ("access_token" in response) {
10078
- const backendResponse = response;
10079
- token = backendResponse.access_token;
10288
+ if (this.mode === "pattern-stack") {
10289
+ const response = await this.apiClient.post(
10290
+ this.endpoints.refresh,
10291
+ { refresh_token: currentRefreshToken }
10292
+ );
10293
+ token = response.access_token;
10080
10294
  newRefreshToken = currentRefreshToken;
10295
+ } else if (this.mode === "custom" && this.config.responseMapper) {
10296
+ const rawResponse = await this.apiClient.post(
10297
+ this.endpoints.refresh,
10298
+ { refresh_token: currentRefreshToken }
10299
+ );
10300
+ const mapped = this.config.responseMapper(rawResponse);
10301
+ token = mapped.token;
10302
+ newRefreshToken = mapped.refreshToken || currentRefreshToken;
10303
+ expiresIn = mapped.expiresIn;
10081
10304
  } else {
10082
- const oldResponse = response;
10083
- token = oldResponse.token;
10084
- newRefreshToken = oldResponse.refreshToken;
10085
- expiresIn = oldResponse.expiresIn;
10305
+ throw new Error(`Invalid auth mode for token refresh: ${this.mode}`);
10086
10306
  }
10087
10307
  const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
10088
10308
  this.setTokenData({
@@ -10124,36 +10344,39 @@ class AuthService {
10124
10344
  const tokenData = this.getTokenData();
10125
10345
  if (!(tokenData == null ? void 0 : tokenData.token)) return null;
10126
10346
  try {
10127
- return await apiClient.get(
10128
- `${this.config.apiUrl}${this.config.endpoints.me}`
10129
- );
10347
+ return await this.apiClient.get(this.endpoints.me);
10130
10348
  } catch {
10131
10349
  return null;
10132
10350
  }
10133
10351
  }
10134
10352
  async logout() {
10135
10353
  const tokenData = this.getTokenData();
10136
- if (this.config.endpoints.logout && (tokenData == null ? void 0 : tokenData.token)) {
10354
+ if (this.endpoints.logout && (tokenData == null ? void 0 : tokenData.token)) {
10137
10355
  try {
10138
- await apiClient.post(
10139
- `${this.config.apiUrl}${this.config.endpoints.logout}`
10140
- );
10356
+ await this.apiClient.post(this.endpoints.logout);
10141
10357
  } catch {
10142
10358
  }
10143
10359
  }
10144
10360
  this.clearAuth();
10145
10361
  }
10362
+ /**
10363
+ * Get the configured onAuthError handler
10364
+ */
10365
+ get onAuthError() {
10366
+ return this.config.onAuthError;
10367
+ }
10146
10368
  }
10147
10369
  function AuthProvider({
10148
10370
  children,
10149
10371
  config
10150
10372
  }) {
10373
+ var _a;
10151
10374
  const [user, setUser] = useState(null);
10152
10375
  const [isLoading, setIsLoading] = useState(true);
10153
10376
  const [permissions, setPermissions] = useState([]);
10154
10377
  const authService = useMemo(() => {
10155
10378
  const defaultConfig = {
10156
- apiUrl: "http://localhost:8080",
10379
+ apiUrl: (config == null ? void 0 : config.apiUrl) ?? "",
10157
10380
  endpoints: {
10158
10381
  login: "/auth/login",
10159
10382
  register: "/auth/register",
@@ -10168,7 +10391,7 @@ function AuthProvider({
10168
10391
  enabled: false
10169
10392
  }
10170
10393
  };
10171
- const mergedConfig = config ? { ...defaultConfig, ...config } : defaultConfig;
10394
+ const mergedConfig = config ? { ...defaultConfig, ...config, apiUrl: config.apiUrl ?? defaultConfig.apiUrl } : defaultConfig;
10172
10395
  const service = new AuthService(mergedConfig);
10173
10396
  setGlobalAuthService(service);
10174
10397
  return service;
@@ -10215,48 +10438,56 @@ function AuthProvider({
10215
10438
  };
10216
10439
  initAuth();
10217
10440
  }, [authService]);
10218
- const login = async (credentials) => {
10219
- var _a;
10441
+ const login = useCallback(async (credentials) => {
10220
10442
  setIsLoading(true);
10221
10443
  try {
10222
- const user2 = await authService.login(credentials);
10223
- setUser(user2);
10224
- if (((_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled) && "permissions" in user2) {
10225
- const userPermissions = user2.permissions;
10226
- setPermissions(
10227
- Array.isArray(userPermissions) ? userPermissions : []
10228
- );
10229
- }
10230
- } finally {
10444
+ const loggedInUser = await authService.login(credentials);
10445
+ flushSync(() => {
10446
+ var _a2;
10447
+ setUser(loggedInUser);
10448
+ if (((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled) && "permissions" in loggedInUser) {
10449
+ const userPermissions = loggedInUser.permissions;
10450
+ setPermissions(
10451
+ Array.isArray(userPermissions) ? userPermissions : []
10452
+ );
10453
+ }
10454
+ setIsLoading(false);
10455
+ });
10456
+ } catch (error) {
10231
10457
  setIsLoading(false);
10458
+ throw error;
10232
10459
  }
10233
- };
10234
- const logout = async () => {
10460
+ }, [authService, (_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled]);
10461
+ const logout = useCallback(async () => {
10235
10462
  setIsLoading(true);
10236
10463
  try {
10237
10464
  await authService.logout();
10238
- setUser(null);
10239
- setPermissions([]);
10465
+ flushSync(() => {
10466
+ setUser(null);
10467
+ setPermissions([]);
10468
+ setIsLoading(false);
10469
+ });
10240
10470
  } catch (error) {
10241
10471
  console.error("Logout error:", error);
10242
10472
  authService.clearAuth();
10243
- setUser(null);
10244
- setPermissions([]);
10245
- } finally {
10246
- setIsLoading(false);
10473
+ flushSync(() => {
10474
+ setUser(null);
10475
+ setPermissions([]);
10476
+ setIsLoading(false);
10477
+ });
10247
10478
  }
10248
- };
10479
+ }, [authService]);
10249
10480
  const refreshToken = async () => {
10250
10481
  await authService.refreshToken();
10251
10482
  };
10252
10483
  const hasPermission = (permission) => {
10253
- var _a;
10254
- if (!((_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled)) return true;
10484
+ var _a2;
10485
+ if (!((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled)) return true;
10255
10486
  return permissions.includes(permission);
10256
10487
  };
10257
10488
  const hasRole = (role) => {
10258
- var _a;
10259
- if (!((_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled)) return true;
10489
+ var _a2;
10490
+ if (!((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled)) return true;
10260
10491
  if (user && "role" in user) {
10261
10492
  return user.role === role;
10262
10493
  }
@@ -10465,6 +10696,32 @@ function MockAuthProvider({
10465
10696
  };
10466
10697
  return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
10467
10698
  }
10699
+ function NoAuthProvider({ children }) {
10700
+ const noOpLogin = async () => {
10701
+ console.warn("NoAuthProvider: login() called but auth is disabled");
10702
+ };
10703
+ const noOpLogout = async () => {
10704
+ console.warn("NoAuthProvider: logout() called but auth is disabled");
10705
+ };
10706
+ const noOpRefresh = async () => {
10707
+ console.warn("NoAuthProvider: refreshToken() called but auth is disabled");
10708
+ };
10709
+ const value = {
10710
+ user: null,
10711
+ isAuthenticated: true,
10712
+ // Always authenticated in no-auth mode
10713
+ isLoading: false,
10714
+ permissions: [],
10715
+ login: noOpLogin,
10716
+ logout: noOpLogout,
10717
+ refreshToken: noOpRefresh,
10718
+ hasPermission: () => true,
10719
+ // All permissions granted
10720
+ hasRole: () => true
10721
+ // All roles granted
10722
+ };
10723
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
10724
+ }
10468
10725
  const MockAuthContext = createContext(
10469
10726
  void 0
10470
10727
  );
@@ -13604,7 +13861,13 @@ function createReactApp(config) {
13604
13861
  tree = /* @__PURE__ */ jsx(NavigationProvider, { initialNavigation: navigation, children: /* @__PURE__ */ jsx(SidebarProvider, { children: tree }) });
13605
13862
  }
13606
13863
  if (enableAuth) {
13607
- tree = /* @__PURE__ */ jsx(AuthProvider, { config: auth, children: tree });
13864
+ const authMode = (auth == null ? void 0 : auth.mode) || "pattern-stack";
13865
+ if (authMode === "none") {
13866
+ setGlobalAuthService(null);
13867
+ tree = /* @__PURE__ */ jsx(NoAuthProvider, { children: tree });
13868
+ } else {
13869
+ tree = /* @__PURE__ */ jsx(AuthProvider, { config: auth, children: tree });
13870
+ }
13608
13871
  }
13609
13872
  if (enableQuery) {
13610
13873
  tree = /* @__PURE__ */ jsxs(QueryClientProvider, { client: queryClient, children: [
@@ -13622,7 +13885,8 @@ function createReactApp(config) {
13622
13885
  routes.push({ path, component });
13623
13886
  },
13624
13887
  render() {
13625
- const shouldProtect = enableAuth && (auth == null ? void 0 : auth.requireAuth) !== false;
13888
+ const authMode = (auth == null ? void 0 : auth.mode) || "pattern-stack";
13889
+ const shouldProtect = enableAuth && authMode !== "none" && (auth == null ? void 0 : auth.requireAuth) !== false;
13626
13890
  const publicPaths = (auth == null ? void 0 : auth.publicPaths) ?? ["/login", "/register", "/forgot-password"];
13627
13891
  const isPublicPath = (path) => {
13628
13892
  return publicPaths.some((p) => path === p || path.startsWith(p + "/"));
@@ -16810,12 +17074,14 @@ export {
16810
17074
  APIDataTemplate,
16811
17075
  API_ENDPOINTS,
16812
17076
  APP_NAME,
17077
+ AUTH_TOKEN_KEY,
16813
17078
  Accordion,
16814
17079
  ActivityFeed,
16815
17080
  AdminCRUDTemplate,
16816
17081
  AdminDashboardTemplate,
16817
17082
  AdminDetailTemplate,
16818
17083
  Alert,
17084
+ ApiClient,
16819
17085
  AppHeader,
16820
17086
  AppLayout,
16821
17087
  AuthDivider,
@@ -16882,6 +17148,7 @@ export {
16882
17148
  EnhancedDataTemplate,
16883
17149
  EntityIcon,
16884
17150
  ErrorBoundary2 as ErrorBoundary,
17151
+ FieldGrid,
16885
17152
  FileUpload,
16886
17153
  FormField,
16887
17154
  FormGroup,
@@ -16901,6 +17168,7 @@ export {
16901
17168
  Modal,
16902
17169
  NavMenu,
16903
17170
  NavigationProvider,
17171
+ NoAuthProvider,
16904
17172
  PageTemplate,
16905
17173
  PageTitle,
16906
17174
  Pagination,
@@ -16964,6 +17232,7 @@ export {
16964
17232
  breakpoints,
16965
17233
  cn,
16966
17234
  createAPIDataTemplate,
17235
+ createApiClient,
16967
17236
  createReactApp,
16968
17237
  createSimpleApp,
16969
17238
  defaultFieldRenderers,
@@ -16977,6 +17246,7 @@ export {
16977
17246
  getContainerHeightClass,
16978
17247
  getCurrentBreakpoint,
16979
17248
  getFieldType,
17249
+ getGlobalAuthService,
16980
17250
  getIcon,
16981
17251
  getNavigationItems,
16982
17252
  getResponsiveClasses,
@@ -17000,6 +17270,7 @@ export {
17000
17270
  useCreateExample,
17001
17271
  useDeleteExample,
17002
17272
  useEntityData,
17273
+ useEntityDetail,
17003
17274
  useErrorBoundary,
17004
17275
  useFieldMetadata,
17005
17276
  useGetExample,