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

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 (54) 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/components/data/index.d.ts +1 -0
  6. package/dist/atoms/components/data/index.d.ts.map +1 -1
  7. package/dist/atoms/composed/ConnectionStatus/ConnectionStatus.d.ts +16 -0
  8. package/dist/atoms/composed/ConnectionStatus/ConnectionStatus.d.ts.map +1 -0
  9. package/dist/atoms/composed/ConnectionStatus/index.d.ts +3 -0
  10. package/dist/atoms/composed/ConnectionStatus/index.d.ts.map +1 -0
  11. package/dist/atoms/hooks/index.d.ts +2 -0
  12. package/dist/atoms/hooks/index.d.ts.map +1 -1
  13. package/dist/atoms/hooks/useEntityDetail.d.ts +43 -0
  14. package/dist/atoms/hooks/useEntityDetail.d.ts.map +1 -0
  15. package/dist/atoms/hooks/useOnlineStatus.d.ts +16 -0
  16. package/dist/atoms/hooks/useOnlineStatus.d.ts.map +1 -0
  17. package/dist/atoms/primitives/table.d.ts.map +1 -1
  18. package/dist/atoms/services/api/client.d.ts +12 -2
  19. package/dist/atoms/services/api/client.d.ts.map +1 -1
  20. package/dist/atoms/services/auth-service.d.ts +15 -0
  21. package/dist/atoms/services/auth-service.d.ts.map +1 -1
  22. package/dist/atoms/services/index.d.ts +2 -2
  23. package/dist/atoms/services/index.d.ts.map +1 -1
  24. package/dist/atoms/types/auth.d.ts +44 -2
  25. package/dist/atoms/types/auth.d.ts.map +1 -1
  26. package/dist/atoms/types/ui-config.d.ts +11 -0
  27. package/dist/atoms/types/ui-config.d.ts.map +1 -1
  28. package/dist/atoms/utils/entity-card-mapping.d.ts +105 -0
  29. package/dist/atoms/utils/entity-card-mapping.d.ts.map +1 -0
  30. package/dist/atoms/utils/index.d.ts +1 -0
  31. package/dist/atoms/utils/index.d.ts.map +1 -1
  32. package/dist/features/auth/components/ProtectedRoute.d.ts +3 -1
  33. package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -1
  34. package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
  35. package/dist/features/auth/providers/NoAuthProvider.d.ts +17 -0
  36. package/dist/features/auth/providers/NoAuthProvider.d.ts.map +1 -0
  37. package/dist/features/auth/providers/index.d.ts +1 -0
  38. package/dist/features/auth/providers/index.d.ts.map +1 -1
  39. package/dist/frontend-patterns.css +1 -1
  40. package/dist/index.d.ts +5 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.es.js +857 -91
  43. package/dist/index.es.js.map +1 -1
  44. package/dist/index.js +854 -88
  45. package/dist/index.js.map +1 -1
  46. package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -1
  47. package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts +61 -0
  48. package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts.map +1 -0
  49. package/dist/molecules/layout/FieldGrid/index.d.ts +2 -0
  50. package/dist/molecules/layout/FieldGrid/index.d.ts.map +1 -0
  51. package/dist/molecules/layout/index.d.ts +1 -0
  52. package/dist/molecules/layout/index.d.ts.map +1 -1
  53. package/dist/templates/factory.d.ts.map +1 -1
  54. package/package.json +4 -5
package/dist/index.es.js CHANGED
@@ -9,15 +9,15 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
9
9
  import * as React from "react";
10
10
  import React__default, { useRef, useState, useEffect, useMemo, forwardRef, useCallback, useContext, createContext, useId, Component } from "react";
11
11
  import * as SheetPrimitive from "@radix-ui/react-dialog";
12
- import { Map as Map$1, Calculator, Brain, User, Handshake, Truck, Building, HelpCircle, Info, AlertCircle, Check, ArrowDown, ArrowUp, ArrowLeft, ArrowRight, ChevronUp, ChevronLeft, ChevronDown, ChevronRight, Music, Video, Image, Folder, File, Flag, Tag, Bookmark, Heart, Star, MapPin, Clock, Calendar, Phone, Mail, Unlock, Lock, Share, Upload, Download, Eye, Trash2, Edit, Plus, Search, Bell, Settings, Home, Layout, TrendingUp, Database, BarChart3, Users, Shield, X, Menu, Palette, Loader2, EyeOff, InfoIcon, Circle, ChevronsUpDown, AreaChart, LineChart, TrendingDown, Minus, AlertTriangle, Activity, CheckCircle, FileX, Sun, Moon, LogOut, Square, Waves, Zap, TreePine, Sparkles, Sunset, Building2, DollarSign, ShoppingCart, Target, ExternalLink, MoreHorizontal, Filter, Edit2, Undo2, RefreshCw, FileText, History, Save, Copy, Grid3X3, Layers, XCircle, Briefcase } from "lucide-react";
12
+ import { Map as Map$1, Calculator, Brain, User, Handshake, Truck, Building, HelpCircle, Info, AlertCircle, Check, ArrowDown, ArrowUp, ArrowLeft, ArrowRight, ChevronUp, ChevronLeft, ChevronDown, ChevronRight, Music, Video, Image, Folder, File, Flag, Tag, Bookmark, Heart, Star, MapPin, Clock, Calendar, Phone, Mail, Unlock, Lock, Share, Upload, Download, Eye, Trash2, Edit, Plus, Search, Bell, Settings, Home, Layout, TrendingUp, Database, BarChart3, Users, Shield, X, Menu, Palette, Circle, Building2, Package, FileText, Loader2, EyeOff, InfoIcon, ChevronsUpDown, AreaChart, LineChart, TrendingDown, Minus, AlertTriangle, Activity, WifiOff, CloudOff, Cloud, CheckCircle, FileX, Sun, Moon, LogOut, Square, Waves, Zap, TreePine, Sparkles, Sunset, DollarSign, ShoppingCart, Target, ExternalLink, MoreHorizontal, Filter, Edit2, Undo2, RefreshCw, History, Save, Copy, Grid3X3, Layers, XCircle, Briefcase } from "lucide-react";
13
13
  import * as LabelPrimitive from "@radix-ui/react-label";
14
14
  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,374 @@ 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";
2012
+ const STATUS_COLOR_MAP = {
2013
+ // Success states
2014
+ active: "success",
2015
+ completed: "success",
2016
+ approved: "success",
2017
+ paid: "success",
2018
+ delivered: "success",
2019
+ resolved: "success",
2020
+ closed: "success",
2021
+ // Warning states
2022
+ pending: "warning",
2023
+ processing: "warning",
2024
+ review: "warning",
2025
+ draft: "warning",
2026
+ waiting: "warning",
2027
+ // Error states
2028
+ failed: "error",
2029
+ rejected: "error",
2030
+ cancelled: "error",
2031
+ overdue: "error",
2032
+ blocked: "error",
2033
+ // Info states
2034
+ new: "info",
2035
+ open: "info",
2036
+ in_progress: "info",
2037
+ scheduled: "info",
2038
+ // Neutral (default)
2039
+ inactive: "neutral",
2040
+ archived: "neutral",
2041
+ unknown: "neutral"
2042
+ };
2043
+ function getStatusColor(value) {
2044
+ if (typeof value !== "string") return "neutral";
2045
+ const normalized = value.toLowerCase().replace(/[_-]/g, "_");
2046
+ return STATUS_COLOR_MAP[normalized] ?? "neutral";
2047
+ }
2048
+ const CATEGORY_MAP = {
2049
+ account: 1,
2050
+ customer: 1,
2051
+ client: 1,
2052
+ user: 2,
2053
+ contact: 2,
2054
+ person: 2,
2055
+ order: 3,
2056
+ sale: 3,
2057
+ transaction: 3,
2058
+ product: 4,
2059
+ item: 4,
2060
+ inventory: 4,
2061
+ task: 5,
2062
+ activity: 5,
2063
+ event: 5,
2064
+ file: 6,
2065
+ document: 6,
2066
+ report: 6,
2067
+ tag: 7,
2068
+ category: 7,
2069
+ label: 7,
2070
+ default: 8
2071
+ };
2072
+ function getCategoryForEntity(entityType) {
2073
+ if (!entityType) return 8;
2074
+ const normalized = entityType.toLowerCase();
2075
+ for (const [key, value] of Object.entries(CATEGORY_MAP)) {
2076
+ if (normalized.includes(key)) return value;
2077
+ }
2078
+ return 8;
2079
+ }
2080
+ function getIconForEntity(entityType) {
2081
+ if (!entityType) return /* @__PURE__ */ jsx(Circle, { className: "w-4 h-4" });
2082
+ const normalized = entityType.toLowerCase();
2083
+ if (normalized.includes("account") || normalized.includes("customer") || normalized.includes("client")) {
2084
+ return /* @__PURE__ */ jsx(Building2, { className: "w-4 h-4" });
2085
+ }
2086
+ if (normalized.includes("user") || normalized.includes("contact") || normalized.includes("person")) {
2087
+ return /* @__PURE__ */ jsx(User, { className: "w-4 h-4" });
2088
+ }
2089
+ if (normalized.includes("product") || normalized.includes("item")) {
2090
+ return /* @__PURE__ */ jsx(Package, { className: "w-4 h-4" });
2091
+ }
2092
+ if (normalized.includes("file") || normalized.includes("document")) {
2093
+ return /* @__PURE__ */ jsx(FileText, { className: "w-4 h-4" });
2094
+ }
2095
+ if (normalized.includes("tag") || normalized.includes("category")) {
2096
+ return /* @__PURE__ */ jsx(Tag, { className: "w-4 h-4" });
2097
+ }
2098
+ return /* @__PURE__ */ jsx(Circle, { className: "w-4 h-4" });
2099
+ }
2100
+ function normalizeImportance(importance) {
2101
+ switch (importance) {
2102
+ case "critical":
2103
+ case "high":
2104
+ case "primary":
2105
+ return "primary";
2106
+ case "medium":
2107
+ case "secondary":
2108
+ return "secondary";
2109
+ default:
2110
+ return "tertiary";
2111
+ }
2112
+ }
2113
+ function isPrimaryImportance(importance) {
2114
+ return normalizeImportance(importance) === "primary";
2115
+ }
2116
+ function formatValueForCard(value, type, format) {
2117
+ if (value === null || value === void 0) return "—";
2118
+ switch (type) {
2119
+ case "money": {
2120
+ const num = Number(value);
2121
+ const currency = (format == null ? void 0 : format.currency) ?? "USD";
2122
+ if (num >= 1e6) {
2123
+ return new Intl.NumberFormat("en-US", {
2124
+ style: "currency",
2125
+ currency,
2126
+ notation: "compact",
2127
+ maximumFractionDigits: 1
2128
+ }).format(num);
2129
+ }
2130
+ if (num >= 1e3) {
2131
+ return new Intl.NumberFormat("en-US", {
2132
+ style: "currency",
2133
+ currency,
2134
+ notation: "compact",
2135
+ maximumFractionDigits: 1
2136
+ }).format(num);
2137
+ }
2138
+ return new Intl.NumberFormat("en-US", {
2139
+ style: "currency",
2140
+ currency,
2141
+ maximumFractionDigits: 0
2142
+ }).format(num);
2143
+ }
2144
+ case "percent": {
2145
+ const num = Number(value);
2146
+ return `${num.toFixed(0)}%`;
2147
+ }
2148
+ case "number": {
2149
+ const num = Number(value);
2150
+ if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
2151
+ if (num >= 1e3) return `${(num / 1e3).toFixed(1)}K`;
2152
+ return String(num);
2153
+ }
2154
+ case "date":
2155
+ case "datetime": {
2156
+ const date = new Date(String(value));
2157
+ if (isNaN(date.getTime())) return String(value);
2158
+ return date.toLocaleDateString("en-US", {
2159
+ month: "short",
2160
+ day: "numeric"
2161
+ });
2162
+ }
2163
+ case "boolean":
2164
+ return value ? "Yes" : "No";
2165
+ default:
2166
+ return String(value);
2167
+ }
2168
+ }
2169
+ function autoDetectMapping(columns) {
2170
+ var _a, _b, _c, _d, _e, _f;
2171
+ const primaryCols = columns.filter((c) => isPrimaryImportance(c.importance));
2172
+ const titleField = ((_a = primaryCols.find((c) => ["text", "entity", "user"].includes(c.type))) == null ? void 0 : _a.field) ?? ((_b = primaryCols[0]) == null ? void 0 : _b.field) ?? ((_c = columns[0]) == null ? void 0 : _c.field);
2173
+ const valueField = (_d = primaryCols.find((c) => c.type === "money")) == null ? void 0 : _d.field;
2174
+ const statusField = (_e = primaryCols.find(
2175
+ (c) => c.type === "status" || c.type === "badge"
2176
+ )) == null ? void 0 : _e.field;
2177
+ const usedForMain = new Set([titleField, valueField, statusField].filter(Boolean));
2178
+ const remainingPrimary = primaryCols.filter((c) => !usedForMain.has(c.field));
2179
+ const subtitleField = (_f = remainingPrimary[0]) == null ? void 0 : _f.field;
2180
+ const usedFields = new Set(
2181
+ [titleField, valueField, statusField, subtitleField].filter(Boolean)
2182
+ );
2183
+ const metadataFields = primaryCols.filter((c) => !usedFields.has(c.field)).slice(0, 3).map((c) => c.field);
2184
+ return {
2185
+ titleField: titleField ?? "id",
2186
+ subtitleField,
2187
+ valueField,
2188
+ statusField,
2189
+ metadataFields,
2190
+ iconConfig: void 0
2191
+ };
2192
+ }
2193
+ function entityToListCardProps(item, columns, mapping, entityType) {
2194
+ const autoMapping = autoDetectMapping(columns);
2195
+ const effectiveMapping = { ...autoMapping, ...mapping };
2196
+ const {
2197
+ titleField,
2198
+ subtitleField,
2199
+ valueField,
2200
+ statusField,
2201
+ metadataFields,
2202
+ iconConfig
2203
+ } = effectiveMapping;
2204
+ const getColumn = (field) => field ? columns.find((c) => c.field === field) : void 0;
2205
+ const title = item[titleField] != null ? String(item[titleField]) : "—";
2206
+ const subtitle = subtitleField && item[subtitleField] != null ? String(item[subtitleField]) : void 0;
2207
+ let value;
2208
+ if (valueField && item[valueField] != null) {
2209
+ const valueCol = getColumn(valueField);
2210
+ const formatted = formatValueForCard(
2211
+ item[valueField],
2212
+ (valueCol == null ? void 0 : valueCol.type) ?? "text",
2213
+ valueCol == null ? void 0 : valueCol.format
2214
+ );
2215
+ value = {
2216
+ text: formatted,
2217
+ variant: "success"
2218
+ // Money typically green
2219
+ };
2220
+ }
2221
+ let badge;
2222
+ if (statusField && item[statusField] != null) {
2223
+ const statusValue = String(item[statusField]);
2224
+ badge = {
2225
+ text: statusValue,
2226
+ variant: "status",
2227
+ status: getStatusColor(statusValue),
2228
+ size: "sm"
2229
+ };
2230
+ }
2231
+ const metadata = metadataFields == null ? void 0 : metadataFields.map((field) => {
2232
+ const col = getColumn(field);
2233
+ if (!col || item[field] == null) return null;
2234
+ return formatValueForCard(item[field], col.type, col.format);
2235
+ }).filter((v) => v !== null);
2236
+ let icon;
2237
+ if (iconConfig) {
2238
+ const iconValue = iconConfig.field ? item[iconConfig.field] : void 0;
2239
+ if (iconConfig.variant === "status" && iconValue) {
2240
+ icon = {
2241
+ icon: iconConfig.icon ?? getIconForEntity(entityType),
2242
+ variant: "status",
2243
+ status: getStatusColor(iconValue),
2244
+ size: "sm"
2245
+ };
2246
+ } else {
2247
+ icon = {
2248
+ icon: iconConfig.icon ?? getIconForEntity(entityType),
2249
+ variant: "category",
2250
+ category: getCategoryForEntity(entityType),
2251
+ size: "sm"
2252
+ };
2253
+ }
2254
+ } else {
2255
+ icon = {
2256
+ icon: getIconForEntity(entityType),
2257
+ variant: "category",
2258
+ category: getCategoryForEntity(entityType),
2259
+ size: "sm"
2260
+ };
2261
+ }
2262
+ return {
2263
+ icon,
2264
+ title,
2265
+ subtitle,
2266
+ metadata,
2267
+ value,
2268
+ badge
2269
+ };
2270
+ }
2271
+ const HEADER_ABBREVIATIONS = {
2272
+ customer: "Cust",
2273
+ account: "Acct",
2274
+ amount: "Amt",
2275
+ quantity: "Qty",
2276
+ priority: "Pri",
2277
+ status: "Status",
2278
+ created: "Created",
2279
+ updated: "Updated",
2280
+ description: "Desc",
2281
+ total: "Total",
2282
+ items: "Items",
2283
+ number: "#",
2284
+ date: "Date",
2285
+ time: "Time"
2286
+ };
2287
+ function abbreviateHeader(label) {
2288
+ const lower = label.toLowerCase();
2289
+ for (const [key, abbrev] of Object.entries(HEADER_ABBREVIATIONS)) {
2290
+ if (lower.includes(key)) return abbrev;
2291
+ }
2292
+ if (label.length > 8) return label.slice(0, 6) + "…";
2293
+ return label;
2294
+ }
2295
+ function generateCompactColumns(columns, options = {}) {
2296
+ const {
2297
+ maxColumns = 5,
2298
+ abbreviateHeaders = true,
2299
+ requiredFields = [],
2300
+ excludeFields = []
2301
+ } = options;
2302
+ const excludeSet = new Set(excludeFields);
2303
+ const requiredSet = new Set(requiredFields);
2304
+ const filteredColumns = columns.filter((col) => {
2305
+ if (excludeSet.has(col.field)) return false;
2306
+ return true;
2307
+ });
2308
+ const required = filteredColumns.filter((c) => requiredSet.has(c.field));
2309
+ const primary = filteredColumns.filter(
2310
+ (c) => !requiredSet.has(c.field) && normalizeImportance(c.importance) === "primary"
2311
+ );
2312
+ const secondary = filteredColumns.filter(
2313
+ (c) => !requiredSet.has(c.field) && normalizeImportance(c.importance) === "secondary"
2314
+ );
2315
+ const selectedColumns = [...required, ...primary, ...secondary].slice(
2316
+ 0,
2317
+ maxColumns
2318
+ );
2319
+ return selectedColumns.map((col) => ({
2320
+ key: col.field,
2321
+ header: abbreviateHeaders ? abbreviateHeader(col.label) : col.label,
2322
+ sortable: col.sortable,
2323
+ type: col.type,
2324
+ format: col.format,
2325
+ // Compact widths
2326
+ width: getCompactWidth(col.type)
2327
+ }));
2328
+ }
2329
+ function getCompactWidth(type) {
2330
+ switch (type) {
2331
+ case "money":
2332
+ return "70px";
2333
+ case "number":
2334
+ case "percent":
2335
+ return "50px";
2336
+ case "status":
2337
+ case "badge":
2338
+ return "80px";
2339
+ case "date":
2340
+ return "70px";
2341
+ case "datetime":
2342
+ return "90px";
2343
+ case "boolean":
2344
+ return "50px";
2345
+ default:
2346
+ return "120px";
2347
+ }
2348
+ }
2349
+ function createMobileCardRenderer(columns, mapping, entityType) {
2350
+ const { ListCard: ListCard2 } = require("../components/data/ListCard");
2351
+ return ({ data, onItemClick }) => /* @__PURE__ */ jsx("div", { className: "space-y-2", children: data.map((item, index) => {
2352
+ const cardProps = entityToListCardProps(
2353
+ item,
2354
+ columns,
2355
+ mapping,
2356
+ entityType
2357
+ );
2358
+ return /* @__PURE__ */ jsx(
2359
+ ListCard2,
2360
+ {
2361
+ ...cardProps,
2362
+ onClick: onItemClick ? () => onItemClick(item) : void 0
2363
+ },
2364
+ item.id ?? index
2365
+ );
2366
+ }) });
2367
+ }
2013
2368
  let globalAuthService = null;
2014
2369
  function setGlobalAuthService(authService) {
2015
2370
  globalAuthService = authService;
2016
2371
  }
2372
+ function getGlobalAuthService() {
2373
+ return globalAuthService;
2374
+ }
2017
2375
  class ApiClient {
2018
2376
  constructor(config = {}) {
2019
2377
  __publicField(this, "instance");
2020
2378
  this.instance = axios.create({
2021
- baseURL: config.baseURL || API_BASE_URL,
2379
+ baseURL: config.baseURL || "",
2022
2380
  timeout: config.timeout || 1e4,
2023
2381
  headers: {
2024
2382
  "Content-Type": "application/json",
@@ -2058,11 +2416,15 @@ class ApiClient {
2058
2416
  this.instance.interceptors.response.use(
2059
2417
  (response) => response,
2060
2418
  async (error) => {
2061
- var _a;
2062
- if (((_a = error.response) == null ? void 0 : _a.status) === 401) {
2419
+ var _a, _b, _c, _d;
2420
+ const status = (_a = error.response) == null ? void 0 : _a.status;
2421
+ if (!error.response) {
2422
+ return Promise.reject(error);
2423
+ }
2424
+ if (status === 401 || status === 403) {
2063
2425
  if (globalAuthService) {
2064
2426
  const tokenData = globalAuthService.getTokenData();
2065
- if ((tokenData == null ? void 0 : tokenData.refreshToken) && !error.config._retry) {
2427
+ if (status === 401 && (tokenData == null ? void 0 : tokenData.refreshToken) && !error.config._retry) {
2066
2428
  error.config._retry = true;
2067
2429
  try {
2068
2430
  await globalAuthService.refreshToken();
@@ -2070,17 +2432,18 @@ class ApiClient {
2070
2432
  error.config.headers.Authorization = `Bearer ${newTokenData == null ? void 0 : newTokenData.token}`;
2071
2433
  return this.instance.request(error.config);
2072
2434
  } catch {
2073
- globalAuthService.clearAuth();
2074
- window.location.reload();
2075
2435
  }
2076
- } else {
2077
- globalAuthService.clearAuth();
2078
- window.location.reload();
2436
+ }
2437
+ globalAuthService.clearAuth();
2438
+ const errorInfo = { status, message: (_c = (_b = error.response) == null ? void 0 : _b.data) == null ? void 0 : _c.detail };
2439
+ const handled = (_d = globalAuthService.onAuthError) == null ? void 0 : _d.call(globalAuthService, errorInfo);
2440
+ if (!handled) {
2441
+ window.location.href = "/login";
2079
2442
  }
2080
2443
  } else {
2081
2444
  localStorage.removeItem("auth_token");
2082
2445
  localStorage.removeItem("auth_user");
2083
- window.location.reload();
2446
+ window.location.href = "/login";
2084
2447
  }
2085
2448
  }
2086
2449
  return Promise.reject(error);
@@ -2120,6 +2483,9 @@ class ApiClient {
2120
2483
  return response.data;
2121
2484
  }
2122
2485
  }
2486
+ function createApiClient(config = {}) {
2487
+ return new ApiClient(config);
2488
+ }
2123
2489
  const apiClient = new ApiClient();
2124
2490
  function useToast() {
2125
2491
  const [toasts, setToasts] = useState([]);
@@ -2390,7 +2756,7 @@ function useEntityData(entity, options = {}) {
2390
2756
  const dataQuery = useQuery({
2391
2757
  queryKey: [entity, "list", { limit, offset, filters }],
2392
2758
  queryFn: () => apiClient.get(
2393
- `/api/v1/${entity}/?${dataParams.toString()}`
2759
+ `/api/v1/${entity}?${dataParams.toString()}`
2394
2760
  ),
2395
2761
  enabled
2396
2762
  });
@@ -2418,6 +2784,95 @@ function useEntityData(entity, options = {}) {
2418
2784
  refetch
2419
2785
  };
2420
2786
  }
2787
+ function useEntityDetail(entity, id, options = {}) {
2788
+ var _a;
2789
+ const { view = "detail", sourceId, enabled = true } = options;
2790
+ const metadataParams = new URLSearchParams();
2791
+ metadataParams.set("view", view);
2792
+ if (sourceId) metadataParams.set("source_id", sourceId);
2793
+ const dataQuery = useQuery({
2794
+ queryKey: [entity, "detail", id],
2795
+ queryFn: () => apiClient.get(`/api/v1/${entity}/${id}`),
2796
+ enabled: enabled && !!id
2797
+ });
2798
+ const metadataQuery = useQuery({
2799
+ queryKey: [entity, "metadata", { view, sourceId }],
2800
+ queryFn: () => apiClient.get(
2801
+ `/api/v1/${entity}/fields/metadata?${metadataParams.toString()}`
2802
+ ),
2803
+ enabled,
2804
+ staleTime: 5 * 60 * 1e3
2805
+ // 5 minutes
2806
+ });
2807
+ const refetch = () => {
2808
+ dataQuery.refetch();
2809
+ metadataQuery.refetch();
2810
+ };
2811
+ return {
2812
+ data: dataQuery.data ?? null,
2813
+ fields: ((_a = metadataQuery.data) == null ? void 0 : _a.columns) ?? [],
2814
+ isLoading: dataQuery.isLoading || metadataQuery.isLoading,
2815
+ isLoadingData: dataQuery.isLoading,
2816
+ isLoadingMetadata: metadataQuery.isLoading,
2817
+ error: dataQuery.error ?? metadataQuery.error ?? null,
2818
+ refetch
2819
+ };
2820
+ }
2821
+ function useOnlineStatus(healthEndpoint) {
2822
+ const [isOnline, setIsOnline] = useState(
2823
+ typeof navigator !== "undefined" ? navigator.onLine : true
2824
+ );
2825
+ const [isBackendReachable, setIsBackendReachable] = useState(true);
2826
+ const [lastBackendContact, setLastBackendContact] = useState(
2827
+ null
2828
+ );
2829
+ useEffect(() => {
2830
+ const handleOnline = () => setIsOnline(true);
2831
+ const handleOffline = () => {
2832
+ setIsOnline(false);
2833
+ setIsBackendReachable(false);
2834
+ };
2835
+ window.addEventListener("online", handleOnline);
2836
+ window.addEventListener("offline", handleOffline);
2837
+ return () => {
2838
+ window.removeEventListener("online", handleOnline);
2839
+ window.removeEventListener("offline", handleOffline);
2840
+ };
2841
+ }, []);
2842
+ const checkBackend = useCallback(async () => {
2843
+ if (!isOnline) {
2844
+ setIsBackendReachable(false);
2845
+ return false;
2846
+ }
2847
+ try {
2848
+ const endpoint = healthEndpoint || "/api/v1/health";
2849
+ const response = await fetch(endpoint, {
2850
+ method: "GET",
2851
+ cache: "no-store"
2852
+ });
2853
+ const reachable = response.ok;
2854
+ setIsBackendReachable(reachable);
2855
+ if (reachable) {
2856
+ setLastBackendContact(Date.now());
2857
+ }
2858
+ return reachable;
2859
+ } catch {
2860
+ setIsBackendReachable(false);
2861
+ return false;
2862
+ }
2863
+ }, [isOnline, healthEndpoint]);
2864
+ useEffect(() => {
2865
+ if (isOnline) {
2866
+ checkBackend();
2867
+ }
2868
+ }, [isOnline, checkBackend]);
2869
+ return {
2870
+ isOnline,
2871
+ isBackendReachable,
2872
+ lastBackendContact,
2873
+ checkBackend
2874
+ };
2875
+ }
2421
2876
  const buttonVariants = cva(
2422
2877
  "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
2878
  {
@@ -4108,7 +4563,9 @@ const TableHead$1 = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
4108
4563
  {
4109
4564
  ref,
4110
4565
  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",
4566
+ "h-12 px-4 xs:px-2 2xs:px-1 text-left align-middle font-semibold text-gray-700 text-xs uppercase tracking-wider",
4567
+ // Compact padding for checkbox columns
4568
+ "[&:has(input[type=checkbox])]:px-2 [&:has(input[type=checkbox])]:w-8",
4112
4569
  className
4113
4570
  ),
4114
4571
  ...props
@@ -4120,7 +4577,9 @@ const TableCell$1 = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
4120
4577
  {
4121
4578
  ref,
4122
4579
  className: cn(
4123
- "p-4 xs:p-2 2xs:p-1 align-middle [&:has([role=checkbox])]:pr-0",
4580
+ "p-4 xs:p-2 2xs:p-1 align-middle",
4581
+ // Compact padding for checkbox columns
4582
+ "[&:has(input[type=checkbox])]:px-2 [&:has(input[type=checkbox])]:w-8",
4124
4583
  className
4125
4584
  ),
4126
4585
  ...props
@@ -4170,7 +4629,12 @@ function DataTable({
4170
4629
  ui,
4171
4630
  error = null,
4172
4631
  errorTitle,
4173
- errorRetry
4632
+ errorRetry,
4633
+ selectable = false,
4634
+ selectedIds,
4635
+ onSelectionChange,
4636
+ getRowId,
4637
+ selectAllMode = "page"
4174
4638
  }) {
4175
4639
  const [searchTerm, setSearchTerm] = useState("");
4176
4640
  const [sortColumn, setSortColumn] = useState(null);
@@ -4233,6 +4697,85 @@ function DataTable({
4233
4697
  return true;
4234
4698
  });
4235
4699
  }, [normalizedColumns, currentBreakpoint, responsive]);
4700
+ const defaultGetRowId = useCallback((row) => {
4701
+ if ("id" in row && typeof row.id === "string") return row.id;
4702
+ if ("id" in row && typeof row.id === "number") return String(row.id);
4703
+ throw new Error("DataTable: selectable requires getRowId prop or row.id field");
4704
+ }, []);
4705
+ const effectiveGetRowId = getRowId ?? defaultGetRowId;
4706
+ const selectableData = selectAllMode === "all" ? sortedData : paginatedData;
4707
+ const selectableIds = useMemo(
4708
+ () => {
4709
+ if (!selectable) return /* @__PURE__ */ new Set();
4710
+ return new Set(selectableData.map((row) => effectiveGetRowId(row)));
4711
+ },
4712
+ [selectable, selectableData, effectiveGetRowId]
4713
+ );
4714
+ const selectedInScope = useMemo(
4715
+ () => {
4716
+ if (!selectable || !selectedIds) return /* @__PURE__ */ new Set();
4717
+ return new Set([...selectedIds].filter((id) => selectableIds.has(id)));
4718
+ },
4719
+ [selectable, selectedIds, selectableIds]
4720
+ );
4721
+ const isAllSelected = selectableIds.size > 0 && selectedInScope.size === selectableIds.size;
4722
+ const isIndeterminate = selectedInScope.size > 0 && selectedInScope.size < selectableIds.size;
4723
+ const handleSelectAll = useCallback(() => {
4724
+ if (!onSelectionChange) return;
4725
+ if (isAllSelected) {
4726
+ const newSelection = new Set([...selectedIds ?? []].filter((id) => !selectableIds.has(id)));
4727
+ onSelectionChange(newSelection);
4728
+ } else {
4729
+ const newSelection = /* @__PURE__ */ new Set([...selectedIds ?? [], ...selectableIds]);
4730
+ onSelectionChange(newSelection);
4731
+ }
4732
+ }, [isAllSelected, selectedIds, selectableIds, onSelectionChange]);
4733
+ const handleSelectRow = useCallback((rowId) => {
4734
+ if (!onSelectionChange) return;
4735
+ const newSelection = new Set(selectedIds);
4736
+ if (newSelection.has(rowId)) {
4737
+ newSelection.delete(rowId);
4738
+ } else {
4739
+ newSelection.add(rowId);
4740
+ }
4741
+ onSelectionChange(newSelection);
4742
+ }, [selectedIds, onSelectionChange]);
4743
+ const selectionColumn = useMemo(() => {
4744
+ return {
4745
+ key: "__selection__",
4746
+ header: /* @__PURE__ */ jsx(
4747
+ Checkbox$1,
4748
+ {
4749
+ checked: isAllSelected,
4750
+ indeterminate: isIndeterminate,
4751
+ onCheckedChange: handleSelectAll,
4752
+ "aria-label": isAllSelected ? "Deselect all rows" : "Select all rows",
4753
+ size: "sm"
4754
+ }
4755
+ ),
4756
+ cell: (row) => {
4757
+ const rowId = effectiveGetRowId(row);
4758
+ const isSelected = (selectedIds == null ? void 0 : selectedIds.has(rowId)) ?? false;
4759
+ return /* @__PURE__ */ jsx(
4760
+ Checkbox$1,
4761
+ {
4762
+ checked: isSelected,
4763
+ onCheckedChange: () => handleSelectRow(rowId),
4764
+ "aria-label": isSelected ? `Deselect row ${rowId}` : `Select row ${rowId}`,
4765
+ size: "sm",
4766
+ onClick: (e) => e.stopPropagation()
4767
+ }
4768
+ );
4769
+ },
4770
+ width: "32px",
4771
+ sortable: false,
4772
+ filterable: false
4773
+ };
4774
+ }, [isAllSelected, isIndeterminate, selectedIds, effectiveGetRowId, handleSelectAll, handleSelectRow]);
4775
+ const effectiveColumns = useMemo(() => {
4776
+ if (!selectable) return visibleColumns;
4777
+ return [selectionColumn, ...visibleColumns];
4778
+ }, [selectable, selectionColumn, visibleColumns]);
4236
4779
  const handleSort = (columnKey) => {
4237
4780
  if (sortColumn === columnKey) {
4238
4781
  if (sortDirection === "asc") {
@@ -4295,8 +4838,8 @@ function DataTable({
4295
4838
  children: [
4296
4839
  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
4840
  /* @__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)) })
4841
+ /* @__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)) }) }),
4842
+ /* @__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
4843
  ] }) }),
4301
4844
  showPagination && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
4302
4845
  /* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-48" }),
@@ -4358,7 +4901,7 @@ function DataTable({
4358
4901
  data: paginatedData,
4359
4902
  onItemClick: onRowClick
4360
4903
  }) }) : /* @__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) => {
4904
+ /* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children: effectiveColumns.map((column) => {
4362
4905
  const width = column.width;
4363
4906
  return /* @__PURE__ */ jsx(
4364
4907
  TableHead$1,
@@ -4381,7 +4924,7 @@ function DataTable({
4381
4924
  /* @__PURE__ */ jsx(TableBody$1, { children: paginatedData.length === 0 ? /* @__PURE__ */ jsx(TableRow$1, { children: /* @__PURE__ */ jsx(
4382
4925
  TableCell$1,
4383
4926
  {
4384
- colSpan: visibleColumns.length,
4927
+ colSpan: effectiveColumns.length,
4385
4928
  className: "text-center py-8 text-muted-foreground",
4386
4929
  children: emptyMessage
4387
4930
  }
@@ -4393,7 +4936,7 @@ function DataTable({
4393
4936
  (hover || onRowClick) && "hover:bg-muted/50"
4394
4937
  ),
4395
4938
  onClick: () => onRowClick == null ? void 0 : onRowClick(item),
4396
- children: visibleColumns.map((column) => /* @__PURE__ */ jsx(TableCell$1, { children: renderCell(column, item) }, column.key))
4939
+ children: effectiveColumns.map((column) => /* @__PURE__ */ jsx(TableCell$1, { children: renderCell(column, item) }, column.key))
4397
4940
  },
4398
4941
  index
4399
4942
  )) })
@@ -5586,6 +6129,75 @@ const ActivityFeed = ({
5586
6129
  ] }) })
5587
6130
  ] });
5588
6131
  };
6132
+ const statusConfig = {
6133
+ online: {
6134
+ icon: Cloud,
6135
+ label: "Online",
6136
+ color: "text-green-600 dark:text-green-400",
6137
+ bgColor: "bg-green-100 dark:bg-green-900/30"
6138
+ },
6139
+ degraded: {
6140
+ icon: CloudOff,
6141
+ label: "Server Offline",
6142
+ color: "text-yellow-600 dark:text-yellow-400",
6143
+ bgColor: "bg-yellow-100 dark:bg-yellow-900/30"
6144
+ },
6145
+ offline: {
6146
+ icon: WifiOff,
6147
+ label: "Offline",
6148
+ color: "text-red-600 dark:text-red-400",
6149
+ bgColor: "bg-red-100 dark:bg-red-900/30"
6150
+ }
6151
+ };
6152
+ function ConnectionStatus({
6153
+ healthEndpoint,
6154
+ variant = "badge",
6155
+ className,
6156
+ showWhenOnline = false
6157
+ }) {
6158
+ const { isOnline, isBackendReachable } = useOnlineStatus(healthEndpoint);
6159
+ const status = !isOnline ? "offline" : !isBackendReachable ? "degraded" : "online";
6160
+ if (status === "online" && !showWhenOnline) {
6161
+ return null;
6162
+ }
6163
+ const config = statusConfig[status];
6164
+ const Icon2 = config.icon;
6165
+ if (variant === "badge") {
6166
+ return /* @__PURE__ */ jsxs(
6167
+ "div",
6168
+ {
6169
+ className: cn(
6170
+ "inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium",
6171
+ config.bgColor,
6172
+ config.color,
6173
+ className
6174
+ ),
6175
+ children: [
6176
+ /* @__PURE__ */ jsx(Icon2, { className: "h-3 w-3" }),
6177
+ /* @__PURE__ */ jsx("span", { children: config.label })
6178
+ ]
6179
+ }
6180
+ );
6181
+ }
6182
+ return /* @__PURE__ */ jsxs(
6183
+ "div",
6184
+ {
6185
+ className: cn(
6186
+ "flex items-center gap-2 px-3 py-2 rounded-lg",
6187
+ config.bgColor,
6188
+ className
6189
+ ),
6190
+ children: [
6191
+ /* @__PURE__ */ jsx(Icon2, { className: cn("h-4 w-4", config.color) }),
6192
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
6193
+ /* @__PURE__ */ jsx("span", { className: cn("text-sm font-medium", config.color), children: config.label }),
6194
+ status === "degraded" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Working with cached data" }),
6195
+ status === "offline" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Check your connection" })
6196
+ ] })
6197
+ ]
6198
+ }
6199
+ );
6200
+ }
5589
6201
  const FormField = ({
5590
6202
  label,
5591
6203
  description,
@@ -8927,7 +9539,10 @@ const AppHeader = ({ className }) => {
8927
9539
  placeholder: "Search models, tests, jobs, and components..."
8928
9540
  }
8929
9541
  ) }),
8930
- /* @__PURE__ */ jsx("div", { className: "flex items-center space-x-4", children: /* @__PURE__ */ jsx(UserMenu, { category: 8 }) })
9542
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-4", children: [
9543
+ /* @__PURE__ */ jsx(ConnectionStatus, {}),
9544
+ /* @__PURE__ */ jsx(UserMenu, { category: 8 })
9545
+ ] })
8931
9546
  ] }) })
8932
9547
  }
8933
9548
  );
@@ -9218,6 +9833,55 @@ const ListToolbar = ({
9218
9833
  }
9219
9834
  );
9220
9835
  };
9836
+ const FieldGrid = ({
9837
+ data,
9838
+ fields,
9839
+ sections,
9840
+ columns = 2,
9841
+ showLabels = true,
9842
+ compact = false,
9843
+ showEmpty = true,
9844
+ className
9845
+ }) => {
9846
+ const gridCols = {
9847
+ 1: "grid-cols-1",
9848
+ 2: "grid-cols-1 md:grid-cols-2",
9849
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
9850
+ };
9851
+ const renderValue = (field) => {
9852
+ const value = data[field.field];
9853
+ if (value === null || value === void 0 || value === "") {
9854
+ if (!showEmpty) return null;
9855
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" });
9856
+ }
9857
+ const format = field.format;
9858
+ return renderField(value, field.field, field.type, "lg", data, void 0, format);
9859
+ };
9860
+ const renderField_ = (field) => {
9861
+ const value = data[field.field];
9862
+ if (!showEmpty && (value === null || value === void 0 || value === "")) {
9863
+ return null;
9864
+ }
9865
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1", compact ? "py-1" : "py-2"), children: [
9866
+ showLabels && /* @__PURE__ */ jsx("dt", { className: "text-sm font-medium text-muted-foreground", children: field.label }),
9867
+ /* @__PURE__ */ jsx("dd", { className: "text-sm text-foreground", children: renderValue(field) })
9868
+ ] }, field.field);
9869
+ };
9870
+ const renderFields = (fieldList) => /* @__PURE__ */ jsx("dl", { className: cn("grid gap-x-6", gridCols[columns]), children: fieldList.map(renderField_) });
9871
+ if (sections && sections.length > 0) {
9872
+ 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: [
9873
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
9874
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground", children: section.title }),
9875
+ section.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: section.description })
9876
+ ] }),
9877
+ renderFields(section.fields)
9878
+ ] }, section.title)) });
9879
+ }
9880
+ if (fields && fields.length > 0) {
9881
+ return /* @__PURE__ */ jsx(Card, { className: cn(compact ? "p-4" : "p-6", className), children: renderFields(fields) });
9882
+ }
9883
+ return null;
9884
+ };
9221
9885
  const Pagination = ({
9222
9886
  currentPage,
9223
9887
  totalPages,
@@ -9897,9 +10561,11 @@ function ProtectedRoute({
9897
10561
  children,
9898
10562
  requirePermission,
9899
10563
  requireRole,
9900
- fallback
10564
+ fallback,
10565
+ loginPath = "/login"
9901
10566
  }) {
9902
10567
  const { isAuthenticated, isLoading, hasPermission, hasRole } = useAuthContext();
10568
+ const location = useLocation();
9903
10569
  if (isLoading) {
9904
10570
  return /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
9905
10571
  /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto" }),
@@ -9907,7 +10573,7 @@ function ProtectedRoute({
9907
10573
  ] }) });
9908
10574
  }
9909
10575
  if (!isAuthenticated) {
9910
- return /* @__PURE__ */ jsx(LoginForm, {});
10576
+ return /* @__PURE__ */ jsx(Navigate, { to: loginPath, state: { from: location }, replace: true });
9911
10577
  }
9912
10578
  if (requirePermission && !hasPermission(requirePermission)) {
9913
10579
  return fallback || /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
@@ -9934,17 +10600,39 @@ function LogoutButton() {
9934
10600
  /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: logout, children: "Logout" })
9935
10601
  ] });
9936
10602
  }
10603
+ const AUTH_TOKEN_KEY = "auth_token";
10604
+ const DEFAULT_ENDPOINTS = {
10605
+ login: "/auth/login",
10606
+ register: "/auth/register",
10607
+ refresh: "/auth/refresh",
10608
+ me: "/auth/me",
10609
+ logout: "/auth/logout"
10610
+ };
9937
10611
  class AuthService {
9938
10612
  constructor(config) {
9939
10613
  __publicField(this, "config");
10614
+ __publicField(this, "apiClient");
9940
10615
  __publicField(this, "refreshPromise", null);
10616
+ __publicField(this, "endpoints");
9941
10617
  this.config = {
10618
+ mode: "pattern-stack",
9942
10619
  tokenStorage: "localStorage",
9943
10620
  tokenRefreshBuffer: 5,
9944
10621
  // 5 minutes before expiry
9945
10622
  autoRefresh: true,
9946
10623
  ...config
9947
10624
  };
10625
+ this.endpoints = {
10626
+ ...DEFAULT_ENDPOINTS,
10627
+ ...config.endpoints
10628
+ };
10629
+ this.apiClient = createApiClient({ baseURL: this.config.apiUrl || "" });
10630
+ }
10631
+ /**
10632
+ * Get the auth mode
10633
+ */
10634
+ get mode() {
10635
+ return this.config.mode || "pattern-stack";
9948
10636
  }
9949
10637
  getStorageKey(key) {
9950
10638
  return `auth_${key}`;
@@ -10028,21 +10716,33 @@ class AuthService {
10028
10716
  this.removeItem("user");
10029
10717
  }
10030
10718
  async login(credentials) {
10031
- const response = await apiClient.post(`${this.config.apiUrl}${this.config.endpoints.login}`, credentials);
10032
10719
  let token;
10033
10720
  let refreshToken;
10034
10721
  let expiresIn;
10035
- if ("access_token" in response) {
10036
- const backendResponse = response;
10037
- token = backendResponse.access_token;
10038
- refreshToken = backendResponse.refresh_token;
10722
+ let user;
10723
+ if (this.mode === "pattern-stack") {
10724
+ const response = await this.apiClient.post(
10725
+ this.endpoints.login,
10726
+ credentials
10727
+ );
10728
+ token = response.access_token;
10729
+ refreshToken = response.refresh_token;
10730
+ user = response.user;
10731
+ } else if (this.mode === "custom" && this.config.responseMapper) {
10732
+ const rawResponse = await this.apiClient.post(
10733
+ this.endpoints.login,
10734
+ credentials
10735
+ );
10736
+ const mapped = this.config.responseMapper(rawResponse);
10737
+ token = mapped.token;
10738
+ refreshToken = mapped.refreshToken;
10739
+ expiresIn = mapped.expiresIn;
10740
+ user = mapped.user;
10039
10741
  } else {
10040
- const oldResponse = response;
10041
- token = oldResponse.token;
10042
- refreshToken = oldResponse.refreshToken;
10043
- expiresIn = oldResponse.expiresIn;
10742
+ throw new Error(
10743
+ `Invalid auth mode: ${this.mode}. Use 'pattern-stack' or 'custom' with responseMapper.`
10744
+ );
10044
10745
  }
10045
- const user = response.user;
10046
10746
  const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
10047
10747
  this.setTokenData({ token, refreshToken, expiresAt });
10048
10748
  this.setStoredUser(user);
@@ -10068,21 +10768,27 @@ class AuthService {
10068
10768
  }
10069
10769
  async performTokenRefresh(currentRefreshToken) {
10070
10770
  try {
10071
- const response = await apiClient.post(`${this.config.apiUrl}${this.config.endpoints.refresh}`, {
10072
- refresh_token: currentRefreshToken
10073
- });
10074
10771
  let token;
10075
10772
  let newRefreshToken;
10076
10773
  let expiresIn;
10077
- if ("access_token" in response) {
10078
- const backendResponse = response;
10079
- token = backendResponse.access_token;
10774
+ if (this.mode === "pattern-stack") {
10775
+ const response = await this.apiClient.post(
10776
+ this.endpoints.refresh,
10777
+ { refresh_token: currentRefreshToken }
10778
+ );
10779
+ token = response.access_token;
10080
10780
  newRefreshToken = currentRefreshToken;
10781
+ } else if (this.mode === "custom" && this.config.responseMapper) {
10782
+ const rawResponse = await this.apiClient.post(
10783
+ this.endpoints.refresh,
10784
+ { refresh_token: currentRefreshToken }
10785
+ );
10786
+ const mapped = this.config.responseMapper(rawResponse);
10787
+ token = mapped.token;
10788
+ newRefreshToken = mapped.refreshToken || currentRefreshToken;
10789
+ expiresIn = mapped.expiresIn;
10081
10790
  } else {
10082
- const oldResponse = response;
10083
- token = oldResponse.token;
10084
- newRefreshToken = oldResponse.refreshToken;
10085
- expiresIn = oldResponse.expiresIn;
10791
+ throw new Error(`Invalid auth mode for token refresh: ${this.mode}`);
10086
10792
  }
10087
10793
  const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
10088
10794
  this.setTokenData({
@@ -10124,36 +10830,39 @@ class AuthService {
10124
10830
  const tokenData = this.getTokenData();
10125
10831
  if (!(tokenData == null ? void 0 : tokenData.token)) return null;
10126
10832
  try {
10127
- return await apiClient.get(
10128
- `${this.config.apiUrl}${this.config.endpoints.me}`
10129
- );
10833
+ return await this.apiClient.get(this.endpoints.me);
10130
10834
  } catch {
10131
10835
  return null;
10132
10836
  }
10133
10837
  }
10134
10838
  async logout() {
10135
10839
  const tokenData = this.getTokenData();
10136
- if (this.config.endpoints.logout && (tokenData == null ? void 0 : tokenData.token)) {
10840
+ if (this.endpoints.logout && (tokenData == null ? void 0 : tokenData.token)) {
10137
10841
  try {
10138
- await apiClient.post(
10139
- `${this.config.apiUrl}${this.config.endpoints.logout}`
10140
- );
10842
+ await this.apiClient.post(this.endpoints.logout);
10141
10843
  } catch {
10142
10844
  }
10143
10845
  }
10144
10846
  this.clearAuth();
10145
10847
  }
10848
+ /**
10849
+ * Get the configured onAuthError handler
10850
+ */
10851
+ get onAuthError() {
10852
+ return this.config.onAuthError;
10853
+ }
10146
10854
  }
10147
10855
  function AuthProvider({
10148
10856
  children,
10149
10857
  config
10150
10858
  }) {
10859
+ var _a;
10151
10860
  const [user, setUser] = useState(null);
10152
10861
  const [isLoading, setIsLoading] = useState(true);
10153
10862
  const [permissions, setPermissions] = useState([]);
10154
10863
  const authService = useMemo(() => {
10155
10864
  const defaultConfig = {
10156
- apiUrl: "http://localhost:8080",
10865
+ apiUrl: (config == null ? void 0 : config.apiUrl) ?? "",
10157
10866
  endpoints: {
10158
10867
  login: "/auth/login",
10159
10868
  register: "/auth/register",
@@ -10168,7 +10877,7 @@ function AuthProvider({
10168
10877
  enabled: false
10169
10878
  }
10170
10879
  };
10171
- const mergedConfig = config ? { ...defaultConfig, ...config } : defaultConfig;
10880
+ const mergedConfig = config ? { ...defaultConfig, ...config, apiUrl: config.apiUrl ?? defaultConfig.apiUrl } : defaultConfig;
10172
10881
  const service = new AuthService(mergedConfig);
10173
10882
  setGlobalAuthService(service);
10174
10883
  return service;
@@ -10186,10 +10895,12 @@ function AuthProvider({
10186
10895
  try {
10187
10896
  await authService.refreshToken();
10188
10897
  setUser(storedUser);
10189
- try {
10190
- const freshUser = await authService.getCurrentUser();
10191
- if (freshUser) setUser(freshUser);
10192
- } catch {
10898
+ if (!(config == null ? void 0 : config.offlineFirst)) {
10899
+ try {
10900
+ const freshUser = await authService.getCurrentUser();
10901
+ if (freshUser) setUser(freshUser);
10902
+ } catch {
10903
+ }
10193
10904
  }
10194
10905
  } catch {
10195
10906
  authService.clearAuth();
@@ -10199,10 +10910,12 @@ function AuthProvider({
10199
10910
  }
10200
10911
  } else {
10201
10912
  setUser(storedUser);
10202
- try {
10203
- const freshUser = await authService.getCurrentUser();
10204
- if (freshUser) setUser(freshUser);
10205
- } catch {
10913
+ if (!(config == null ? void 0 : config.offlineFirst)) {
10914
+ try {
10915
+ const freshUser = await authService.getCurrentUser();
10916
+ if (freshUser) setUser(freshUser);
10917
+ } catch {
10918
+ }
10206
10919
  }
10207
10920
  }
10208
10921
  }
@@ -10215,48 +10928,56 @@ function AuthProvider({
10215
10928
  };
10216
10929
  initAuth();
10217
10930
  }, [authService]);
10218
- const login = async (credentials) => {
10219
- var _a;
10931
+ const login = useCallback(async (credentials) => {
10220
10932
  setIsLoading(true);
10221
10933
  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 {
10934
+ const loggedInUser = await authService.login(credentials);
10935
+ flushSync(() => {
10936
+ var _a2;
10937
+ setUser(loggedInUser);
10938
+ if (((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled) && "permissions" in loggedInUser) {
10939
+ const userPermissions = loggedInUser.permissions;
10940
+ setPermissions(
10941
+ Array.isArray(userPermissions) ? userPermissions : []
10942
+ );
10943
+ }
10944
+ setIsLoading(false);
10945
+ });
10946
+ } catch (error) {
10231
10947
  setIsLoading(false);
10948
+ throw error;
10232
10949
  }
10233
- };
10234
- const logout = async () => {
10950
+ }, [authService, (_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled]);
10951
+ const logout = useCallback(async () => {
10235
10952
  setIsLoading(true);
10236
10953
  try {
10237
10954
  await authService.logout();
10238
- setUser(null);
10239
- setPermissions([]);
10955
+ flushSync(() => {
10956
+ setUser(null);
10957
+ setPermissions([]);
10958
+ setIsLoading(false);
10959
+ });
10240
10960
  } catch (error) {
10241
10961
  console.error("Logout error:", error);
10242
10962
  authService.clearAuth();
10243
- setUser(null);
10244
- setPermissions([]);
10245
- } finally {
10246
- setIsLoading(false);
10963
+ flushSync(() => {
10964
+ setUser(null);
10965
+ setPermissions([]);
10966
+ setIsLoading(false);
10967
+ });
10247
10968
  }
10248
- };
10969
+ }, [authService]);
10249
10970
  const refreshToken = async () => {
10250
10971
  await authService.refreshToken();
10251
10972
  };
10252
10973
  const hasPermission = (permission) => {
10253
- var _a;
10254
- if (!((_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled)) return true;
10974
+ var _a2;
10975
+ if (!((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled)) return true;
10255
10976
  return permissions.includes(permission);
10256
10977
  };
10257
10978
  const hasRole = (role) => {
10258
- var _a;
10259
- if (!((_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled)) return true;
10979
+ var _a2;
10980
+ if (!((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled)) return true;
10260
10981
  if (user && "role" in user) {
10261
10982
  return user.role === role;
10262
10983
  }
@@ -10465,6 +11186,32 @@ function MockAuthProvider({
10465
11186
  };
10466
11187
  return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
10467
11188
  }
11189
+ function NoAuthProvider({ children }) {
11190
+ const noOpLogin = async () => {
11191
+ console.warn("NoAuthProvider: login() called but auth is disabled");
11192
+ };
11193
+ const noOpLogout = async () => {
11194
+ console.warn("NoAuthProvider: logout() called but auth is disabled");
11195
+ };
11196
+ const noOpRefresh = async () => {
11197
+ console.warn("NoAuthProvider: refreshToken() called but auth is disabled");
11198
+ };
11199
+ const value = {
11200
+ user: null,
11201
+ isAuthenticated: true,
11202
+ // Always authenticated in no-auth mode
11203
+ isLoading: false,
11204
+ permissions: [],
11205
+ login: noOpLogin,
11206
+ logout: noOpLogout,
11207
+ refreshToken: noOpRefresh,
11208
+ hasPermission: () => true,
11209
+ // All permissions granted
11210
+ hasRole: () => true
11211
+ // All roles granted
11212
+ };
11213
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
11214
+ }
10468
11215
  const MockAuthContext = createContext(
10469
11216
  void 0
10470
11217
  );
@@ -13604,7 +14351,13 @@ function createReactApp(config) {
13604
14351
  tree = /* @__PURE__ */ jsx(NavigationProvider, { initialNavigation: navigation, children: /* @__PURE__ */ jsx(SidebarProvider, { children: tree }) });
13605
14352
  }
13606
14353
  if (enableAuth) {
13607
- tree = /* @__PURE__ */ jsx(AuthProvider, { config: auth, children: tree });
14354
+ const authMode = (auth == null ? void 0 : auth.mode) || "pattern-stack";
14355
+ if (authMode === "none") {
14356
+ setGlobalAuthService(null);
14357
+ tree = /* @__PURE__ */ jsx(NoAuthProvider, { children: tree });
14358
+ } else {
14359
+ tree = /* @__PURE__ */ jsx(AuthProvider, { config: auth, children: tree });
14360
+ }
13608
14361
  }
13609
14362
  if (enableQuery) {
13610
14363
  tree = /* @__PURE__ */ jsxs(QueryClientProvider, { client: queryClient, children: [
@@ -13622,7 +14375,8 @@ function createReactApp(config) {
13622
14375
  routes.push({ path, component });
13623
14376
  },
13624
14377
  render() {
13625
- const shouldProtect = enableAuth && (auth == null ? void 0 : auth.requireAuth) !== false;
14378
+ const authMode = (auth == null ? void 0 : auth.mode) || "pattern-stack";
14379
+ const shouldProtect = enableAuth && authMode !== "none" && (auth == null ? void 0 : auth.requireAuth) !== false;
13626
14380
  const publicPaths = (auth == null ? void 0 : auth.publicPaths) ?? ["/login", "/register", "/forgot-password"];
13627
14381
  const isPublicPath = (path) => {
13628
14382
  return publicPaths.some((p) => path === p || path.startsWith(p + "/"));
@@ -16810,12 +17564,14 @@ export {
16810
17564
  APIDataTemplate,
16811
17565
  API_ENDPOINTS,
16812
17566
  APP_NAME,
17567
+ AUTH_TOKEN_KEY,
16813
17568
  Accordion,
16814
17569
  ActivityFeed,
16815
17570
  AdminCRUDTemplate,
16816
17571
  AdminDashboardTemplate,
16817
17572
  AdminDetailTemplate,
16818
17573
  Alert,
17574
+ ApiClient,
16819
17575
  AppHeader,
16820
17576
  AppLayout,
16821
17577
  AuthDivider,
@@ -16842,6 +17598,7 @@ export {
16842
17598
  ColorSwatch,
16843
17599
  ComponentShowcasePage,
16844
17600
  ComponentShowcaseTemplate,
17601
+ ConnectionStatus,
16845
17602
  DASHBOARD_CHART_HEIGHTS,
16846
17603
  DASHBOARD_CONTAINER_HEIGHTS,
16847
17604
  DASHBOARD_HEIGHT_CLASSES,
@@ -16882,6 +17639,7 @@ export {
16882
17639
  EnhancedDataTemplate,
16883
17640
  EntityIcon,
16884
17641
  ErrorBoundary2 as ErrorBoundary,
17642
+ FieldGrid,
16885
17643
  FileUpload,
16886
17644
  FormField,
16887
17645
  FormGroup,
@@ -16901,6 +17659,7 @@ export {
16901
17659
  Modal,
16902
17660
  NavMenu,
16903
17661
  NavigationProvider,
17662
+ NoAuthProvider,
16904
17663
  PageTemplate,
16905
17664
  PageTitle,
16906
17665
  Pagination,
@@ -16964,19 +17723,24 @@ export {
16964
17723
  breakpoints,
16965
17724
  cn,
16966
17725
  createAPIDataTemplate,
17726
+ createApiClient,
17727
+ createMobileCardRenderer,
16967
17728
  createReactApp,
16968
17729
  createSimpleApp,
16969
17730
  defaultFieldRenderers,
16970
17731
  detectBulkOperationType,
16971
17732
  detectUIConfig,
17733
+ entityToListCardProps,
16972
17734
  env,
16973
17735
  formatNumberWithTooltip,
16974
17736
  generateBulkOperationName,
17737
+ generateCompactColumns,
16975
17738
  getAnimationClasses,
16976
17739
  getChartHeight,
16977
17740
  getContainerHeightClass,
16978
17741
  getCurrentBreakpoint,
16979
17742
  getFieldType,
17743
+ getGlobalAuthService,
16980
17744
  getIcon,
16981
17745
  getNavigationItems,
16982
17746
  getResponsiveClasses,
@@ -17000,6 +17764,7 @@ export {
17000
17764
  useCreateExample,
17001
17765
  useDeleteExample,
17002
17766
  useEntityData,
17767
+ useEntityDetail,
17003
17768
  useErrorBoundary,
17004
17769
  useFieldMetadata,
17005
17770
  useGetExample,
@@ -17011,6 +17776,7 @@ export {
17011
17776
  useMediaQuery,
17012
17777
  useMockAuth,
17013
17778
  useNavigation,
17779
+ useOnlineStatus,
17014
17780
  useOverflowDetection,
17015
17781
  usePermissions,
17016
17782
  useResponsiveClasses,