@gofreego/tsutils 0.1.13 → 0.1.15

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.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { createContext, useState, useEffect, useCallback, useContext, useMemo } from 'react';
2
- import { Snackbar, Alert, Dialog, DialogTitle, Box, DialogContent, Typography, DialogActions, Button, createTheme, ThemeProvider as ThemeProvider$1, CssBaseline, IconButton, Tooltip, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Collapse } from '@mui/material';
3
- import { BrightnessAuto, DarkMode, LightMode, ExpandLess, ExpandMore } from '@mui/icons-material';
4
- import { BrowserRouter, useLocation, Routes, Route, Outlet, NavLink } from 'react-router-dom';
1
+ import { createTheme, ThemeProvider as ThemeProvider$1, CssBaseline, IconButton, Tooltip, Box, Typography, Button, CircularProgress, Snackbar, Alert, Dialog, DialogTitle, DialogContent, DialogActions, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Collapse } from '@mui/material';
2
+ import { SentimentDissatisfied, BrightnessAuto, DarkMode, LightMode, ExpandLess, ExpandMore } from '@mui/icons-material';
3
+ import { useNavigate, useSearchParams, BrowserRouter, useLocation, Routes, Route, Outlet, NavLink } from 'react-router-dom';
4
+ import { createContext, useState, useMemo, useEffect, useCallback, useContext } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import ReactMarkdown from 'react-markdown';
7
7
  import remarkGfm from 'remark-gfm';
@@ -18,687 +18,99 @@ var __export = (target, all) => {
18
18
  for (var name in all)
19
19
  __defProp(target, name, { get: all[name], enumerable: true });
20
20
  };
21
- var RouterMenuItem = ({ item, depth = 0, expanded, onToggle }) => {
22
- const hasChildren = item.children && item.children.length > 0;
23
- const isExpanded = expanded.has(item.id);
24
- return /* @__PURE__ */ jsxs(Fragment, { children: [
25
- /* @__PURE__ */ jsx(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs(
26
- ListItemButton,
27
- {
28
- component: hasChildren ? "div" : NavLink,
29
- to: !hasChildren ? item.path || `/${item.id}` : void 0,
30
- end: false,
31
- onClick: hasChildren ? () => onToggle(item.id) : void 0,
32
- sx: {
33
- pl: 2 + depth * 2,
34
- "&.active": {
35
- backgroundColor: "action.selected",
36
- color: "primary.main",
37
- borderLeft: 3,
38
- borderColor: "primary.main",
39
- fontWeight: "medium",
40
- "& .MuiListItemIcon-root": {
41
- color: "primary.main"
42
- }
43
- },
44
- "&:hover": {
45
- backgroundColor: "action.hover"
46
- },
47
- transition: "all 0.15s"
48
- },
49
- children: [
50
- item.icon && /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 40 }, children: item.icon }),
51
- /* @__PURE__ */ jsx(
52
- ListItemText,
53
- {
54
- primary: item.label,
55
- primaryTypographyProps: {
56
- fontSize: "0.875rem"
57
- }
58
- }
59
- ),
60
- hasChildren && /* @__PURE__ */ jsx(IconButton, { size: "small", sx: { p: 0 }, children: isExpanded ? /* @__PURE__ */ jsx(ExpandLess, {}) : /* @__PURE__ */ jsx(ExpandMore, {}) })
61
- ]
62
- }
63
- ) }),
64
- hasChildren && /* @__PURE__ */ jsx(Collapse, { in: isExpanded, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx(List, { component: "div", disablePadding: true, children: item.children.map((child) => /* @__PURE__ */ jsx(
65
- RouterMenuItem,
66
- {
67
- item: child,
68
- depth: depth + 1,
69
- expanded,
70
- onToggle
71
- },
72
- child.id
73
- )) }) })
74
- ] });
21
+
22
+ // src/theme/tokens.ts
23
+ var tokens_exports = {};
24
+ __export(tokens_exports, {
25
+ borderRadius: () => borderRadius,
26
+ elevation: () => elevation,
27
+ fontSize: () => fontSize,
28
+ fontWeight: () => fontWeight,
29
+ lineHeight: () => lineHeight,
30
+ spacing: () => spacing,
31
+ transition: () => transition,
32
+ zIndex: () => zIndex
33
+ });
34
+ var spacing = {
35
+ xs: "0.25rem",
36
+ // 4px
37
+ sm: "0.5rem",
38
+ // 8px
39
+ md: "1rem",
40
+ // 16px
41
+ lg: "1.5rem",
42
+ // 24px
43
+ xl: "2rem",
44
+ // 32px
45
+ "2xl": "3rem",
46
+ // 48px
47
+ "3xl": "4rem"
48
+ // 64px
75
49
  };
76
- var flattenMenuRoutes = (items) => {
77
- const routes = [];
78
- const flatten = (menuItems) => {
79
- for (const item of menuItems) {
80
- if (item.path && item.component) {
81
- routes.push({ path: item.path, component: item.component });
82
- }
83
- if (item.children) {
84
- flatten(item.children);
85
- }
86
- }
87
- };
88
- flatten(items);
89
- return routes;
50
+ var borderRadius = {
51
+ none: "0",
52
+ sm: "0.25rem",
53
+ // 4px
54
+ md: "0.375rem",
55
+ // 6px
56
+ lg: "0.5rem",
57
+ // 8px
58
+ xl: "0.75rem",
59
+ // 12px
60
+ "2xl": "1rem",
61
+ // 16px
62
+ full: "9999px"
90
63
  };
91
- var SidebarLayoutRouterInner = ({
92
- menuItems,
93
- sidebarWidth = 250,
94
- className = "",
95
- onMenuChange,
96
- style,
97
- sidebarStyle,
98
- bodyStyle,
99
- defaultExpanded = []
100
- }) => {
101
- const location = useLocation();
102
- const [expanded, setExpanded] = useState(new Set(defaultExpanded));
103
- const handleToggle = (id) => {
104
- setExpanded((prev) => {
105
- const next = new Set(prev);
106
- if (next.has(id)) {
107
- next.delete(id);
108
- } else {
109
- next.add(id);
110
- }
111
- return next;
112
- });
113
- };
114
- useEffect(() => {
115
- if (onMenuChange) {
116
- const findActiveItem = (items) => {
117
- for (const item of items) {
118
- if (item.path && location.pathname === item.path) {
119
- return item;
120
- }
121
- if (item.children) {
122
- const found = findActiveItem(item.children);
123
- if (found) return found;
124
- }
125
- }
126
- return void 0;
127
- };
128
- const activeItem = findActiveItem(menuItems);
129
- if (activeItem) {
130
- onMenuChange(activeItem.id);
131
- }
132
- }
133
- }, [location.pathname, menuItems, onMenuChange]);
134
- const hasComponents = menuItems.some(
135
- (item) => item.component || item.children?.some((child) => child.component)
136
- );
137
- const routes = hasComponents ? flattenMenuRoutes(menuItems) : [];
138
- return /* @__PURE__ */ jsxs(
139
- Box,
140
- {
141
- className,
142
- sx: {
143
- display: "flex",
144
- height: "100%",
145
- width: "100%",
146
- ...style
147
- },
148
- children: [
149
- /* @__PURE__ */ jsx(
150
- Drawer,
151
- {
152
- variant: "permanent",
153
- sx: {
154
- width: sidebarWidth,
155
- flexShrink: 0,
156
- "& .MuiDrawer-paper": {
157
- width: sidebarWidth,
158
- boxSizing: "border-box",
159
- position: "relative",
160
- borderRight: "1px solid",
161
- borderColor: "divider",
162
- ...sidebarStyle
163
- }
164
- },
165
- children: /* @__PURE__ */ jsx(List, { sx: { p: 0 }, children: menuItems.filter((item) => item.label).map((item) => /* @__PURE__ */ jsx(
166
- RouterMenuItem,
167
- {
168
- item,
169
- expanded,
170
- onToggle: handleToggle
171
- },
172
- item.id
173
- )) })
174
- }
175
- ),
176
- /* @__PURE__ */ jsx(
177
- Box,
178
- {
179
- component: "main",
180
- sx: {
181
- flexGrow: 1,
182
- overflow: "auto",
183
- ...bodyStyle
184
- },
185
- children: hasComponents ? /* @__PURE__ */ jsx(Routes, { children: routes.map(({ path, component }) => /* @__PURE__ */ jsx(
186
- Route,
187
- {
188
- path,
189
- element: component
190
- },
191
- path
192
- )) }) : /* @__PURE__ */ jsx(Outlet, {})
193
- }
194
- )
195
- ]
196
- }
197
- );
64
+ var fontSize = {
65
+ xs: "0.75rem",
66
+ // 12px
67
+ sm: "0.875rem",
68
+ // 14px
69
+ md: "1rem",
70
+ // 16px
71
+ lg: "1.125rem",
72
+ // 18px
73
+ xl: "1.25rem",
74
+ // 20px
75
+ "2xl": "1.5rem",
76
+ // 24px
77
+ "3xl": "1.875rem",
78
+ // 30px
79
+ "4xl": "2.25rem"
80
+ // 36px
198
81
  };
199
- var SidebarLayoutWithRouter = (props) => {
200
- return (
201
- // @ts-expect-error - future prop exists in v6 for v7 migration, but is removed from v7 types
202
- /* @__PURE__ */ jsx(BrowserRouter, { future: { v7_startTransition: true, v7_relativeSplatPath: true }, children: /* @__PURE__ */ jsx(SidebarLayoutRouterInner, { ...props }) })
203
- );
82
+ var fontWeight = {
83
+ light: "300",
84
+ normal: "400",
85
+ medium: "500",
86
+ semibold: "600",
87
+ bold: "700"
204
88
  };
205
- var StateMenuItem = ({ item, selectedId, depth = 0, expanded, onToggle, onClick }) => {
206
- const hasChildren = item.children && item.children.length > 0;
207
- const isExpanded = expanded.has(item.id);
208
- const isActive = item.id === selectedId;
209
- const handleClick = () => {
210
- if (hasChildren) {
211
- onToggle(item.id);
212
- } else {
213
- onClick(item.id);
214
- }
215
- };
216
- return /* @__PURE__ */ jsxs(Fragment, { children: [
217
- /* @__PURE__ */ jsx(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs(
218
- ListItemButton,
219
- {
220
- selected: isActive,
221
- onClick: handleClick,
222
- sx: {
223
- pl: 2 + depth * 2,
224
- "&.Mui-selected": {
225
- backgroundColor: "action.selected",
226
- color: "primary.main",
227
- borderLeft: 3,
228
- borderColor: "primary.main",
229
- fontWeight: "medium",
230
- "& .MuiListItemIcon-root": {
231
- color: "primary.main"
232
- },
233
- "&:hover": {
234
- backgroundColor: "action.selected"
235
- }
236
- },
237
- "&:hover": {
238
- backgroundColor: "action.hover"
239
- },
240
- transition: "all 0.15s"
241
- },
242
- children: [
243
- item.icon && /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 40 }, children: item.icon }),
244
- /* @__PURE__ */ jsx(
245
- ListItemText,
246
- {
247
- primary: item.label,
248
- primaryTypographyProps: {
249
- fontSize: "0.875rem"
250
- }
251
- }
252
- ),
253
- hasChildren && /* @__PURE__ */ jsx(IconButton, { size: "small", sx: { p: 0 }, children: isExpanded ? /* @__PURE__ */ jsx(ExpandLess, {}) : /* @__PURE__ */ jsx(ExpandMore, {}) })
254
- ]
255
- }
256
- ) }),
257
- hasChildren && /* @__PURE__ */ jsx(Collapse, { in: isExpanded, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx(List, { component: "div", disablePadding: true, children: item.children.map((child) => /* @__PURE__ */ jsx(
258
- StateMenuItem,
259
- {
260
- item: child,
261
- selectedId,
262
- depth: depth + 1,
263
- expanded,
264
- onToggle,
265
- onClick
266
- },
267
- child.id
268
- )) }) })
269
- ] });
89
+ var lineHeight = {
90
+ tight: "1.25",
91
+ normal: "1.5",
92
+ relaxed: "1.75"
270
93
  };
271
- var SidebarLayoutWithState = ({
272
- menuItems,
273
- sidebarWidth = 250,
274
- className = "",
275
- defaultSelected,
276
- onMenuChange,
277
- style,
278
- sidebarStyle,
279
- bodyStyle,
280
- defaultExpanded = []
281
- }) => {
282
- const [selectedId, setSelectedId] = useState(
283
- defaultSelected || menuItems[0]?.id
284
- );
285
- const [expanded, setExpanded] = useState(new Set(defaultExpanded));
286
- const handleToggle = (id) => {
287
- setExpanded((prev) => {
288
- const next = new Set(prev);
289
- if (next.has(id)) {
290
- next.delete(id);
291
- } else {
292
- next.add(id);
293
- }
294
- return next;
295
- });
296
- };
297
- const handleMenuClick = (id) => {
298
- setSelectedId(id);
299
- if (onMenuChange) {
300
- onMenuChange(id);
301
- }
302
- };
303
- const findActiveItem = (items, id) => {
304
- for (const item of items) {
305
- if (item.id === id) return item;
306
- if (item.children) {
307
- const found = findActiveItem(item.children, id);
308
- if (found) return found;
309
- }
310
- }
311
- return void 0;
312
- };
313
- const activeItem = findActiveItem(menuItems, selectedId);
314
- return /* @__PURE__ */ jsxs(
315
- Box,
316
- {
317
- className,
318
- sx: {
319
- display: "flex",
320
- height: "100%",
321
- width: "100%",
322
- ...style
323
- },
324
- children: [
325
- /* @__PURE__ */ jsx(
326
- Drawer,
327
- {
328
- variant: "permanent",
329
- sx: {
330
- width: sidebarWidth,
331
- flexShrink: 0,
332
- "& .MuiDrawer-paper": {
333
- width: sidebarWidth,
334
- boxSizing: "border-box",
335
- position: "relative",
336
- borderRight: "1px solid",
337
- borderColor: "divider",
338
- ...sidebarStyle
339
- }
340
- },
341
- children: /* @__PURE__ */ jsx(List, { sx: { p: 0 }, children: menuItems.filter((item) => item.label).map((item) => /* @__PURE__ */ jsx(
342
- StateMenuItem,
343
- {
344
- item,
345
- selectedId,
346
- expanded,
347
- onToggle: handleToggle,
348
- onClick: handleMenuClick
349
- },
350
- item.id
351
- )) })
352
- }
353
- ),
354
- /* @__PURE__ */ jsx(
355
- Box,
356
- {
357
- component: "main",
358
- sx: {
359
- flexGrow: 1,
360
- overflow: "auto",
361
- p: 3,
362
- ...bodyStyle
363
- },
364
- children: activeItem?.component || /* @__PURE__ */ jsx(
365
- Typography,
366
- {
367
- variant: "body2",
368
- color: "text.secondary",
369
- sx: { textAlign: "center", mt: 4 },
370
- children: "No content available for this menu item"
371
- }
372
- )
373
- }
374
- )
375
- ]
376
- }
377
- );
94
+ var elevation = {
95
+ none: "none",
96
+ sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
97
+ md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
98
+ lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
99
+ xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)"
378
100
  };
379
- var SidebarLayout = (props) => {
380
- if (props.isRouter) {
381
- return /* @__PURE__ */ jsx(SidebarLayoutWithRouter, { ...props });
382
- }
383
- return /* @__PURE__ */ jsx(SidebarLayoutWithState, { ...props });
101
+ var transition = {
102
+ fast: "150ms",
103
+ normal: "250ms",
104
+ slow: "350ms"
384
105
  };
385
- var highlighterInstance = null;
386
- var highlighterPromise = null;
387
- var getHighlighter = async () => {
388
- if (highlighterInstance) {
389
- return highlighterInstance;
390
- }
391
- if (highlighterPromise) {
392
- return highlighterPromise;
393
- }
394
- highlighterPromise = createHighlighter({
395
- themes: ["github-light", "github-dark"],
396
- langs: ["js", "ts", "go", "json", "bash", "yaml", "md", "python", "java", "cpp", "c", "html", "css", "sql", "http", "text"]
397
- }).then((highlighter) => {
398
- highlighterInstance = highlighter;
399
- return highlighter;
400
- });
401
- return highlighterPromise;
402
- };
403
- var ReadmeViewer = ({
404
- content,
405
- className = "",
406
- isDark: propIsDark,
407
- muiTheme
408
- }) => {
409
- const isDark = propIsDark !== void 0 ? propIsDark : muiTheme?.palette?.mode === "dark";
410
- const [highlighter, setHighlighter] = useState(null);
411
- const [copiedBlocks, setCopiedBlocks] = useState(/* @__PURE__ */ new Set());
412
- useEffect(() => {
413
- getHighlighter().then(setHighlighter);
414
- }, []);
415
- const copyToClipboard = async (text, blockId) => {
416
- try {
417
- await navigator.clipboard.writeText(text);
418
- setCopiedBlocks((prev) => new Set(prev).add(blockId));
419
- setTimeout(() => {
420
- setCopiedBlocks((prev) => {
421
- const newSet = new Set(prev);
422
- newSet.delete(blockId);
423
- return newSet;
424
- });
425
- }, 2e3);
426
- } catch (err) {
427
- console.error("Failed to copy text: ", err);
428
- }
429
- };
430
- return /* @__PURE__ */ jsxs("div", { className: `readme-viewer markdown-body ${className}`, children: [
431
- /* @__PURE__ */ jsx("style", { children: `
432
- .shiki-wrapper pre.shiki {
433
- padding: 16px !important;
434
- border-radius: 8px !important;
435
- overflow-x: auto;
436
- margin-bottom: 16px;
437
- border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);
438
- }
439
- ` }),
440
- /* @__PURE__ */ jsx(
441
- ReactMarkdown,
442
- {
443
- remarkPlugins: [remarkGfm, remarkMath],
444
- rehypePlugins: [rehypeKatex],
445
- components: {
446
- code({ className: className2, children, ...props }) {
447
- const match = /language-(\w+)/.exec(className2 || "");
448
- if (match && highlighter) {
449
- const codeText = String(children).replace(/\n$/, "");
450
- const blockId = `code-${Math.random().toString(36).substr(2, 9)}`;
451
- let highlightedHtml = "";
452
- try {
453
- highlightedHtml = highlighter.codeToHtml(codeText, {
454
- lang: match[1],
455
- theme: isDark ? "github-dark" : "github-light"
456
- });
457
- } catch (e) {
458
- try {
459
- highlightedHtml = highlighter.codeToHtml(codeText, {
460
- lang: "text",
461
- theme: isDark ? "github-dark" : "github-light"
462
- });
463
- } catch (fallbackError) {
464
- highlightedHtml = `<pre><code>${codeText}</code></pre>`;
465
- }
466
- }
467
- return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
468
- /* @__PURE__ */ jsx("div", { className: "shiki-wrapper", dangerouslySetInnerHTML: { __html: highlightedHtml } }),
469
- /* @__PURE__ */ jsx(
470
- "button",
471
- {
472
- onClick: () => copyToClipboard(codeText, blockId),
473
- style: {
474
- position: "absolute",
475
- top: "8px",
476
- right: "8px",
477
- backgroundColor: "var(--md-sys-color-surface-container-high)",
478
- border: "1px solid var(--md-sys-color-outline-variant)",
479
- borderRadius: "4px",
480
- padding: "6px",
481
- fontSize: "14px",
482
- cursor: "pointer",
483
- color: "var(--md-sys-color-on-surface-variant)",
484
- alignItems: "center",
485
- justifyContent: "center",
486
- transition: "all 0.2s ease",
487
- width: "32px",
488
- height: "32px"
489
- },
490
- onMouseEnter: (e) => {
491
- e.currentTarget.style.backgroundColor = isDark ? "var(--md-sys-color-surface-container-highest)" : "var(--md-sys-color-surface-container-high)";
492
- },
493
- onMouseLeave: (e) => {
494
- e.currentTarget.style.backgroundColor = isDark ? "var(--md-sys-color-surface-container-high)" : "var(--md-sys-color-surface-container-highest)";
495
- },
496
- title: copiedBlocks.has(blockId) ? "Copied!" : "Copy code",
497
- children: copiedBlocks.has(blockId) ? /* @__PURE__ */ jsx(CheckIcon, { style: { fontSize: "16px" } }) : /* @__PURE__ */ jsx(ContentCopyIcon, { style: { fontSize: "16px" } })
498
- }
499
- )
500
- ] });
501
- }
502
- return /* @__PURE__ */ jsx("code", { className: className2, ...props, children });
503
- }
504
- },
505
- children: content
506
- }
507
- )
508
- ] });
509
- };
510
- var ReadmeViewer_default = ReadmeViewer;
511
- var NotificationContext = createContext(void 0);
512
- var NotificationProvider = ({
513
- children,
514
- defaultDuration = 6e3,
515
- maxNotifications = 3,
516
- anchorOrigin = { vertical: "top", horizontal: "right" }
517
- }) => {
518
- const [notifications, setNotifications] = useState([]);
519
- const showNotification = useCallback((message, type, duration = defaultDuration) => {
520
- const id = `${Date.now()}-${Math.random()}`;
521
- const newNotification = { id, message, type, duration };
522
- setNotifications((prev) => {
523
- const updated = [...prev, newNotification];
524
- if (updated.length > maxNotifications) {
525
- return updated.slice(updated.length - maxNotifications);
526
- }
527
- return updated;
528
- });
529
- }, [defaultDuration, maxNotifications]);
530
- const handleClose = useCallback((id) => {
531
- setNotifications((prev) => prev.filter((notif) => notif.id !== id));
532
- }, []);
533
- const success = useCallback((message, duration) => {
534
- showNotification(message, "success", duration);
535
- }, [showNotification]);
536
- const error = useCallback((message, duration) => {
537
- showNotification(message, "error", duration);
538
- }, [showNotification]);
539
- const warning = useCallback((message, duration) => {
540
- showNotification(message, "warning", duration);
541
- }, [showNotification]);
542
- const info = useCallback((message, duration) => {
543
- showNotification(message, "info", duration);
544
- }, [showNotification]);
545
- const contextValue = {
546
- showNotification,
547
- success,
548
- error,
549
- warning,
550
- info
551
- };
552
- return /* @__PURE__ */ jsxs(NotificationContext.Provider, { value: contextValue, children: [
553
- children,
554
- notifications.map((notification, index) => /* @__PURE__ */ jsx(
555
- Snackbar,
556
- {
557
- open: true,
558
- autoHideDuration: notification.duration,
559
- onClose: () => handleClose(notification.id),
560
- anchorOrigin,
561
- style: {
562
- marginTop: index * 70
563
- // Stack notifications vertically
564
- },
565
- children: /* @__PURE__ */ jsx(
566
- Alert,
567
- {
568
- onClose: () => handleClose(notification.id),
569
- severity: notification.type,
570
- variant: "filled",
571
- sx: { width: "100%" },
572
- children: notification.message
573
- }
574
- )
575
- },
576
- notification.id
577
- ))
578
- ] });
579
- };
580
- var useNotification = () => {
581
- const context = useContext(NotificationContext);
582
- if (!context) {
583
- throw new Error("useNotification must be used within a NotificationProvider");
584
- }
585
- return context;
586
- };
587
- var ConfirmDialog = ({
588
- open,
589
- title,
590
- message,
591
- confirmText = "Confirm",
592
- cancelText = "Cancel",
593
- confirmColor = "primary",
594
- onConfirm,
595
- onCancel
596
- }) => {
597
- return /* @__PURE__ */ jsxs(Dialog, { open, onClose: onCancel, maxWidth: "xs", fullWidth: true, slotProps: { paper: { sx: { borderRadius: 4 } } }, children: [
598
- /* @__PURE__ */ jsx(DialogTitle, { children: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
599
- /* @__PURE__ */ jsx(WarningAmberIcon, { color: confirmColor }),
600
- title
601
- ] }) }),
602
- /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(Typography, { variant: "body1", children: message }) }),
603
- /* @__PURE__ */ jsxs(DialogActions, { sx: { px: 3, pb: 2 }, children: [
604
- /* @__PURE__ */ jsx(Button, { onClick: onCancel, variant: "outlined", children: cancelText }),
605
- /* @__PURE__ */ jsx(Button, { onClick: onConfirm, variant: "contained", color: confirmColor, autoFocus: true, children: confirmText })
606
- ] })
607
- ] });
608
- };
609
-
610
- // src/theme/tokens.ts
611
- var tokens_exports = {};
612
- __export(tokens_exports, {
613
- borderRadius: () => borderRadius,
614
- elevation: () => elevation,
615
- fontSize: () => fontSize,
616
- fontWeight: () => fontWeight,
617
- lineHeight: () => lineHeight,
618
- spacing: () => spacing,
619
- transition: () => transition,
620
- zIndex: () => zIndex
621
- });
622
- var spacing = {
623
- xs: "0.25rem",
624
- // 4px
625
- sm: "0.5rem",
626
- // 8px
627
- md: "1rem",
628
- // 16px
629
- lg: "1.5rem",
630
- // 24px
631
- xl: "2rem",
632
- // 32px
633
- "2xl": "3rem",
634
- // 48px
635
- "3xl": "4rem"
636
- // 64px
637
- };
638
- var borderRadius = {
639
- none: "0",
640
- sm: "0.25rem",
641
- // 4px
642
- md: "0.375rem",
643
- // 6px
644
- lg: "0.5rem",
645
- // 8px
646
- xl: "0.75rem",
647
- // 12px
648
- "2xl": "1rem",
649
- // 16px
650
- full: "9999px"
651
- };
652
- var fontSize = {
653
- xs: "0.75rem",
654
- // 12px
655
- sm: "0.875rem",
656
- // 14px
657
- md: "1rem",
658
- // 16px
659
- lg: "1.125rem",
660
- // 18px
661
- xl: "1.25rem",
662
- // 20px
663
- "2xl": "1.5rem",
664
- // 24px
665
- "3xl": "1.875rem",
666
- // 30px
667
- "4xl": "2.25rem"
668
- // 36px
669
- };
670
- var fontWeight = {
671
- light: "300",
672
- normal: "400",
673
- medium: "500",
674
- semibold: "600",
675
- bold: "700"
676
- };
677
- var lineHeight = {
678
- tight: "1.25",
679
- normal: "1.5",
680
- relaxed: "1.75"
681
- };
682
- var elevation = {
683
- none: "none",
684
- sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
685
- md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
686
- lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
687
- xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)"
688
- };
689
- var transition = {
690
- fast: "150ms",
691
- normal: "250ms",
692
- slow: "350ms"
693
- };
694
- var zIndex = {
695
- dropdown: 1e3,
696
- sticky: 1020,
697
- fixed: 1030,
698
- modalBackdrop: 1040,
699
- modal: 1050,
700
- popover: 1060,
701
- tooltip: 1070
106
+ var zIndex = {
107
+ dropdown: 1e3,
108
+ sticky: 1020,
109
+ fixed: 1030,
110
+ modalBackdrop: 1040,
111
+ modal: 1050,
112
+ popover: 1060,
113
+ tooltip: 1070
702
114
  };
703
115
 
704
116
  // src/theme/light.ts
@@ -836,399 +248,1059 @@ var darkTheme = {
836
248
  transition,
837
249
  zIndex
838
250
  };
839
-
840
- // src/utils/localStorage.ts
841
- var LocalStorage = class {
842
- /**
843
- * Get an item from localStorage
844
- * @param key - The key to retrieve
845
- * @returns The value or null if not found or error occurs
846
- */
847
- static getItem(key) {
848
- try {
849
- if (typeof window === "undefined") {
850
- return null;
251
+
252
+ // src/utils/localStorage.ts
253
+ var LocalStorage = class {
254
+ /**
255
+ * Get an item from localStorage
256
+ * @param key - The key to retrieve
257
+ * @returns The value or null if not found or error occurs
258
+ */
259
+ static getItem(key) {
260
+ try {
261
+ if (typeof window === "undefined") {
262
+ return null;
263
+ }
264
+ const item = window.localStorage.getItem(key);
265
+ if (item === null) {
266
+ return null;
267
+ }
268
+ try {
269
+ return JSON.parse(item);
270
+ } catch {
271
+ return item;
272
+ }
273
+ } catch (error) {
274
+ console.error(`Error getting item from localStorage: ${error}`);
275
+ return null;
276
+ }
277
+ }
278
+ /**
279
+ * Set an item in localStorage
280
+ * @param key - The key to store
281
+ * @param value - The value to store
282
+ * @returns true if successful, false otherwise
283
+ */
284
+ static setItem(key, value) {
285
+ try {
286
+ if (typeof window === "undefined") {
287
+ return false;
288
+ }
289
+ const serializedValue = typeof value === "string" ? value : JSON.stringify(value);
290
+ window.localStorage.setItem(key, serializedValue);
291
+ return true;
292
+ } catch (error) {
293
+ console.error(`Error setting item in localStorage: ${error}`);
294
+ return false;
295
+ }
296
+ }
297
+ /**
298
+ * Remove an item from localStorage
299
+ * @param key - The key to remove
300
+ * @returns true if successful, false otherwise
301
+ */
302
+ static removeItem(key) {
303
+ try {
304
+ if (typeof window === "undefined") {
305
+ return false;
306
+ }
307
+ window.localStorage.removeItem(key);
308
+ return true;
309
+ } catch (error) {
310
+ console.error(`Error removing item from localStorage: ${error}`);
311
+ return false;
312
+ }
313
+ }
314
+ /**
315
+ * Clear all items from localStorage
316
+ * @returns true if successful, false otherwise
317
+ */
318
+ static clear() {
319
+ try {
320
+ if (typeof window === "undefined") {
321
+ return false;
322
+ }
323
+ window.localStorage.clear();
324
+ return true;
325
+ } catch (error) {
326
+ console.error(`Error clearing localStorage: ${error}`);
327
+ return false;
328
+ }
329
+ }
330
+ /**
331
+ * Check if a key exists in localStorage
332
+ * @param key - The key to check
333
+ * @returns true if key exists, false otherwise
334
+ */
335
+ static hasItem(key) {
336
+ try {
337
+ if (typeof window === "undefined") {
338
+ return false;
339
+ }
340
+ return window.localStorage.getItem(key) !== null;
341
+ } catch (error) {
342
+ console.error(`Error checking item in localStorage: ${error}`);
343
+ return false;
344
+ }
345
+ }
346
+ /**
347
+ * Get all keys from localStorage
348
+ * @returns Array of keys
349
+ */
350
+ static keys() {
351
+ try {
352
+ if (typeof window === "undefined") {
353
+ return [];
354
+ }
355
+ return Object.keys(window.localStorage);
356
+ } catch (error) {
357
+ console.error(`Error getting keys from localStorage: ${error}`);
358
+ return [];
359
+ }
360
+ }
361
+ };
362
+ var ThemeContext = createContext(void 0);
363
+ var THEME_STORAGE_KEY = "app-theme-mode";
364
+ var getSystemTheme = () => {
365
+ if (typeof window === "undefined") return "light";
366
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
367
+ };
368
+ var applyCssVariables = (theme, resolvedMode) => {
369
+ if (typeof document === "undefined") return;
370
+ const root = document.documentElement;
371
+ root.setAttribute("data-theme", resolvedMode);
372
+ Object.entries(theme.colors).forEach(([key, value]) => {
373
+ root.style.setProperty(`--color-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`, value);
374
+ });
375
+ Object.entries(theme.spacing).forEach(([key, value]) => {
376
+ root.style.setProperty(`--spacing-${key}`, value);
377
+ });
378
+ Object.entries(theme.borderRadius).forEach(([key, value]) => {
379
+ root.style.setProperty(`--radius-${key}`, value);
380
+ });
381
+ Object.entries(theme.fontSize).forEach(([key, value]) => {
382
+ root.style.setProperty(`--text-${key}`, value);
383
+ });
384
+ Object.entries(theme.fontWeight).forEach(([key, value]) => {
385
+ root.style.setProperty(`--font-${key}`, value);
386
+ });
387
+ Object.entries(theme.lineHeight).forEach(([key, value]) => {
388
+ root.style.setProperty(`--leading-${key}`, value);
389
+ });
390
+ Object.entries(theme.elevation).forEach(([key, value]) => {
391
+ root.style.setProperty(`--shadow-${key}`, value);
392
+ });
393
+ Object.entries(theme.transition).forEach(([key, value]) => {
394
+ root.style.setProperty(`--transition-${key}`, value);
395
+ });
396
+ };
397
+ var ThemeProvider = ({
398
+ children,
399
+ initialTheme,
400
+ initialMode = "system",
401
+ storageKey = THEME_STORAGE_KEY,
402
+ enableCssVariables = true
403
+ }) => {
404
+ const getInitialMode = () => {
405
+ const savedMode = LocalStorage.getItem(storageKey);
406
+ return savedMode || initialMode;
407
+ };
408
+ const [themeMode, setThemeModeState] = useState(getInitialMode);
409
+ const [systemTheme, setSystemTheme] = useState(getSystemTheme);
410
+ const [customTheme, setCustomTheme] = useState(initialTheme);
411
+ const resolvedThemeMode = useMemo(() => {
412
+ if (themeMode === "system") return systemTheme;
413
+ return themeMode;
414
+ }, [themeMode, systemTheme]);
415
+ const theme = useMemo(() => {
416
+ if (customTheme) return customTheme;
417
+ return resolvedThemeMode === "light" ? lightTheme : darkTheme;
418
+ }, [customTheme, resolvedThemeMode]);
419
+ useEffect(() => {
420
+ if (typeof window === "undefined") return;
421
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
422
+ const handleChange = (e) => {
423
+ setSystemTheme(e.matches ? "dark" : "light");
424
+ };
425
+ if (mediaQuery.addEventListener) {
426
+ mediaQuery.addEventListener("change", handleChange);
427
+ return () => mediaQuery.removeEventListener("change", handleChange);
428
+ } else if (mediaQuery.addListener) {
429
+ mediaQuery.addListener(handleChange);
430
+ return () => mediaQuery.removeListener(handleChange);
431
+ }
432
+ }, []);
433
+ useEffect(() => {
434
+ if (enableCssVariables) {
435
+ applyCssVariables(theme, resolvedThemeMode);
436
+ }
437
+ }, [theme, resolvedThemeMode, enableCssVariables]);
438
+ useEffect(() => {
439
+ LocalStorage.setItem(storageKey, themeMode);
440
+ }, [themeMode, storageKey]);
441
+ const setThemeMode = useCallback((mode) => {
442
+ setThemeModeState(mode);
443
+ }, []);
444
+ const setTheme = useCallback((newTheme) => {
445
+ setCustomTheme(newTheme);
446
+ }, []);
447
+ const toggleTheme = useCallback(() => {
448
+ setThemeModeState((prevMode) => {
449
+ if (prevMode === "light") return "dark";
450
+ if (prevMode === "dark") return "system";
451
+ return "light";
452
+ });
453
+ }, []);
454
+ const contextValue = useMemo(() => ({
455
+ theme,
456
+ themeMode,
457
+ resolvedThemeMode,
458
+ setTheme,
459
+ setThemeMode,
460
+ toggleTheme
461
+ }), [theme, themeMode, resolvedThemeMode, setTheme, setThemeMode, toggleTheme]);
462
+ const muiTheme = useMemo(() => createTheme({
463
+ palette: {
464
+ mode: resolvedThemeMode,
465
+ primary: {
466
+ main: theme.colors.primary
467
+ },
468
+ secondary: {
469
+ main: theme.colors.secondary
470
+ },
471
+ error: {
472
+ main: theme.colors.error
473
+ },
474
+ success: {
475
+ main: theme.colors.success
476
+ },
477
+ background: {
478
+ default: theme.colors.background,
479
+ paper: theme.colors.backgroundSecondary
480
+ },
481
+ text: {
482
+ primary: theme.colors.text,
483
+ secondary: theme.colors.textSecondary
484
+ }
485
+ }
486
+ }), [resolvedThemeMode, theme]);
487
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(ThemeProvider$1, { theme: muiTheme, children: [
488
+ /* @__PURE__ */ jsx(CssBaseline, {}),
489
+ children
490
+ ] }) });
491
+ };
492
+ var useTheme = () => {
493
+ const context = useContext(ThemeContext);
494
+ if (!context) {
495
+ throw new Error("useTheme must be used within a ThemeProvider");
496
+ }
497
+ return context;
498
+ };
499
+ var ThemeToggle = ({
500
+ lightModeTooltip = "Switch to dark mode",
501
+ darkModeTooltip = "Switch to system theme",
502
+ systemModeTooltip = "Switch to light theme",
503
+ showTooltip = true,
504
+ sx,
505
+ ...props
506
+ }) => {
507
+ const { themeMode, toggleTheme } = useTheme();
508
+ const getIcon = () => {
509
+ switch (themeMode) {
510
+ case "light":
511
+ return /* @__PURE__ */ jsx(LightMode, {});
512
+ case "dark":
513
+ return /* @__PURE__ */ jsx(DarkMode, {});
514
+ case "system":
515
+ return /* @__PURE__ */ jsx(BrightnessAuto, {});
516
+ default:
517
+ return /* @__PURE__ */ jsx(BrightnessAuto, {});
518
+ }
519
+ };
520
+ const getTooltip = () => {
521
+ switch (themeMode) {
522
+ case "light":
523
+ return lightModeTooltip;
524
+ case "dark":
525
+ return darkModeTooltip;
526
+ case "system":
527
+ return systemModeTooltip;
528
+ default:
529
+ return lightModeTooltip;
530
+ }
531
+ };
532
+ const button = /* @__PURE__ */ jsx(
533
+ IconButton,
534
+ {
535
+ onClick: toggleTheme,
536
+ color: "inherit",
537
+ sx: {
538
+ borderRadius: "50%",
539
+ ...sx
540
+ },
541
+ ...props,
542
+ children: getIcon()
543
+ }
544
+ );
545
+ if (!showTooltip) {
546
+ return button;
547
+ }
548
+ return /* @__PURE__ */ jsx(Tooltip, { title: getTooltip(), arrow: true, children: button });
549
+ };
550
+ function NotFoundPage() {
551
+ const navigate = useNavigate();
552
+ const { theme } = useTheme();
553
+ return /* @__PURE__ */ jsxs(
554
+ Box,
555
+ {
556
+ sx: {
557
+ minHeight: "100vh",
558
+ display: "flex",
559
+ flexDirection: "column",
560
+ alignItems: "center",
561
+ justifyContent: "center",
562
+ backgroundColor: theme.colors.backgroundSecondary,
563
+ gap: 2,
564
+ textAlign: "center",
565
+ px: 2
566
+ },
567
+ children: [
568
+ /* @__PURE__ */ jsx(SentimentDissatisfied, { sx: { fontSize: 72, color: "text.disabled" } }),
569
+ /* @__PURE__ */ jsx(Typography, { variant: "h2", fontWeight: "bold", color: "text.primary", children: "404" }),
570
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", color: "text.secondary", children: "Page not found" }),
571
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.disabled", sx: { maxWidth: 360 }, children: "The page you're looking for doesn't exist or has been moved." }),
572
+ /* @__PURE__ */ jsx(
573
+ Button,
574
+ {
575
+ variant: "contained",
576
+ sx: { mt: 2, textTransform: "none" },
577
+ onClick: () => navigate("/", { replace: true }),
578
+ children: "Go to Home"
579
+ }
580
+ )
581
+ ]
582
+ }
583
+ );
584
+ }
585
+
586
+ // src/http/HttpClient.ts
587
+ var HttpClient = class {
588
+ constructor(config = {}) {
589
+ this.baseURL = config.baseURL || "";
590
+ this.timeout = config.timeout || 3e4;
591
+ this.defaultHeaders = config.headers || {};
592
+ }
593
+ buildURL(url, params) {
594
+ const fullURL = url.startsWith("http") ? url : `${this.baseURL}${url}`;
595
+ if (!params) return fullURL;
596
+ const searchParams = new URLSearchParams();
597
+ Object.entries(params).forEach(([key, value]) => {
598
+ searchParams.append(key, String(value));
599
+ });
600
+ return `${fullURL}?${searchParams.toString()}`;
601
+ }
602
+ async request(url, config = {}) {
603
+ const { params, data, timeout = this.timeout, headers = {}, ...restConfig } = config;
604
+ const controller = new AbortController();
605
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
606
+ try {
607
+ const response = await fetch(this.buildURL(url, params), {
608
+ ...restConfig,
609
+ headers: {
610
+ "Content-Type": "application/json",
611
+ ...this.defaultHeaders,
612
+ ...headers
613
+ },
614
+ body: data ? JSON.stringify(data) : void 0,
615
+ signal: controller.signal
616
+ });
617
+ clearTimeout(timeoutId);
618
+ if (!response.ok) {
619
+ const error = new Error(`HTTP Error: ${response.statusText}`);
620
+ error.status = response.status;
621
+ error.statusText = response.statusText;
622
+ try {
623
+ error.data = await response.json();
624
+ } catch {
625
+ error.data = { code: response.status, message: await response.text() };
626
+ }
627
+ throw error;
628
+ }
629
+ const responseData = await response.json();
630
+ return {
631
+ data: responseData,
632
+ status: response.status,
633
+ statusText: response.statusText,
634
+ headers: response.headers
635
+ };
636
+ } catch (error) {
637
+ clearTimeout(timeoutId);
638
+ if (error instanceof Error && error.name === "AbortError") {
639
+ throw new Error("Request timeout");
640
+ }
641
+ throw error;
642
+ }
643
+ }
644
+ setDefaultHeader(key, value) {
645
+ this.defaultHeaders[key] = value;
646
+ }
647
+ removeDefaultHeader(key) {
648
+ delete this.defaultHeaders[key];
649
+ }
650
+ getDefaultHeaders() {
651
+ return { ...this.defaultHeaders };
652
+ }
653
+ get(url, config) {
654
+ return this.request(url, { ...config, method: "GET" });
655
+ }
656
+ post(url, data, config) {
657
+ return this.request(url, { ...config, data, method: "POST" });
658
+ }
659
+ put(url, data, config) {
660
+ return this.request(url, { ...config, data, method: "PUT" });
661
+ }
662
+ patch(url, data, config) {
663
+ return this.request(url, { ...config, data, method: "PATCH" });
664
+ }
665
+ delete(url, config) {
666
+ return this.request(url, { ...config, method: "DELETE" });
667
+ }
668
+ };
669
+ function extractErrorMessage(error) {
670
+ if (error instanceof Error) {
671
+ const httpError = error;
672
+ if (httpError.data) {
673
+ return httpError.data.message;
674
+ }
675
+ return error.message;
676
+ }
677
+ return "An unexpected error occurred";
678
+ }
679
+ function LoginCallbackPage({ authService, navigateTo = "/", onLoginFailed }) {
680
+ const [searchParams] = useSearchParams();
681
+ const navigate = useNavigate();
682
+ const { theme } = useTheme();
683
+ useEffect(() => {
684
+ const loginToken = searchParams.get("login_token");
685
+ if (!loginToken) {
686
+ console.error("Login callback failed: Missing login_token in query parameters");
687
+ onLoginFailed?.();
688
+ return;
689
+ }
690
+ authService.signInWithLoginToken({ loginToken }).then(() => {
691
+ navigate(navigateTo, { replace: true });
692
+ }).catch((err) => {
693
+ console.error("Login callback failed:", extractErrorMessage(err));
694
+ onLoginFailed?.();
695
+ });
696
+ }, []);
697
+ return /* @__PURE__ */ jsxs(
698
+ Box,
699
+ {
700
+ sx: {
701
+ minHeight: "100vh",
702
+ display: "flex",
703
+ flexDirection: "column",
704
+ alignItems: "center",
705
+ justifyContent: "center",
706
+ background: `linear-gradient(135deg, ${theme.colors.primary} 0%, ${theme.colors.primaryActive} 100%)`,
707
+ gap: 2
708
+ },
709
+ children: [
710
+ /* @__PURE__ */ jsx(CircularProgress, { sx: { color: theme.colors.surface } }),
711
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { color: theme.colors.surface, opacity: 0.85 }, children: "Signing you in\u2026" })
712
+ ]
713
+ }
714
+ );
715
+ }
716
+ var RouterMenuItem = ({ item, depth = 0, expanded, onToggle }) => {
717
+ const hasChildren = item.children && item.children.length > 0;
718
+ const isExpanded = expanded.has(item.id);
719
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
720
+ /* @__PURE__ */ jsx(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs(
721
+ ListItemButton,
722
+ {
723
+ component: hasChildren ? "div" : NavLink,
724
+ to: !hasChildren ? item.path || `/${item.id}` : void 0,
725
+ end: false,
726
+ onClick: hasChildren ? () => onToggle(item.id) : void 0,
727
+ sx: {
728
+ pl: 2 + depth * 2,
729
+ "&.active": {
730
+ backgroundColor: "action.selected",
731
+ color: "primary.main",
732
+ borderLeft: 3,
733
+ borderColor: "primary.main",
734
+ fontWeight: "medium",
735
+ "& .MuiListItemIcon-root": {
736
+ color: "primary.main"
737
+ }
738
+ },
739
+ "&:hover": {
740
+ backgroundColor: "action.hover"
741
+ },
742
+ transition: "all 0.15s"
743
+ },
744
+ children: [
745
+ item.icon && /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 40 }, children: item.icon }),
746
+ /* @__PURE__ */ jsx(
747
+ ListItemText,
748
+ {
749
+ primary: item.label,
750
+ primaryTypographyProps: {
751
+ fontSize: "0.875rem"
752
+ }
753
+ }
754
+ ),
755
+ hasChildren && /* @__PURE__ */ jsx(IconButton, { size: "small", sx: { p: 0 }, children: isExpanded ? /* @__PURE__ */ jsx(ExpandLess, {}) : /* @__PURE__ */ jsx(ExpandMore, {}) })
756
+ ]
851
757
  }
852
- const item = window.localStorage.getItem(key);
853
- if (item === null) {
854
- return null;
758
+ ) }),
759
+ hasChildren && /* @__PURE__ */ jsx(Collapse, { in: isExpanded, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx(List, { component: "div", disablePadding: true, children: item.children.map((child) => /* @__PURE__ */ jsx(
760
+ RouterMenuItem,
761
+ {
762
+ item: child,
763
+ depth: depth + 1,
764
+ expanded,
765
+ onToggle
766
+ },
767
+ child.id
768
+ )) }) })
769
+ ] });
770
+ };
771
+ var flattenMenuRoutes = (items) => {
772
+ const routes = [];
773
+ const flatten = (menuItems) => {
774
+ for (const item of menuItems) {
775
+ if (item.path && item.component) {
776
+ routes.push({ path: item.path, component: item.component });
855
777
  }
856
- try {
857
- return JSON.parse(item);
858
- } catch {
859
- return item;
778
+ if (item.children) {
779
+ flatten(item.children);
860
780
  }
861
- } catch (error) {
862
- console.error(`Error getting item from localStorage: ${error}`);
863
- return null;
864
781
  }
865
- }
866
- /**
867
- * Set an item in localStorage
868
- * @param key - The key to store
869
- * @param value - The value to store
870
- * @returns true if successful, false otherwise
871
- */
872
- static setItem(key, value) {
873
- try {
874
- if (typeof window === "undefined") {
875
- return false;
782
+ };
783
+ flatten(items);
784
+ return routes;
785
+ };
786
+ var SidebarLayoutRouterInner = ({
787
+ menuItems,
788
+ sidebarWidth = 250,
789
+ className = "",
790
+ onMenuChange,
791
+ style,
792
+ sidebarStyle,
793
+ bodyStyle,
794
+ defaultExpanded = []
795
+ }) => {
796
+ const location = useLocation();
797
+ const [expanded, setExpanded] = useState(new Set(defaultExpanded));
798
+ const handleToggle = (id) => {
799
+ setExpanded((prev) => {
800
+ const next = new Set(prev);
801
+ if (next.has(id)) {
802
+ next.delete(id);
803
+ } else {
804
+ next.add(id);
876
805
  }
877
- const serializedValue = typeof value === "string" ? value : JSON.stringify(value);
878
- window.localStorage.setItem(key, serializedValue);
879
- return true;
880
- } catch (error) {
881
- console.error(`Error setting item in localStorage: ${error}`);
882
- return false;
806
+ return next;
807
+ });
808
+ };
809
+ useEffect(() => {
810
+ if (onMenuChange) {
811
+ const findActiveItem = (items) => {
812
+ for (const item of items) {
813
+ if (item.path && location.pathname === item.path) {
814
+ return item;
815
+ }
816
+ if (item.children) {
817
+ const found = findActiveItem(item.children);
818
+ if (found) return found;
819
+ }
820
+ }
821
+ return void 0;
822
+ };
823
+ const activeItem = findActiveItem(menuItems);
824
+ if (activeItem) {
825
+ onMenuChange(activeItem.id);
826
+ }
827
+ }
828
+ }, [location.pathname, menuItems, onMenuChange]);
829
+ const hasComponents = menuItems.some(
830
+ (item) => item.component || item.children?.some((child) => child.component)
831
+ );
832
+ const routes = hasComponents ? flattenMenuRoutes(menuItems) : [];
833
+ return /* @__PURE__ */ jsxs(
834
+ Box,
835
+ {
836
+ className,
837
+ sx: {
838
+ display: "flex",
839
+ height: "100%",
840
+ width: "100%",
841
+ ...style
842
+ },
843
+ children: [
844
+ /* @__PURE__ */ jsx(
845
+ Drawer,
846
+ {
847
+ variant: "permanent",
848
+ sx: {
849
+ width: sidebarWidth,
850
+ flexShrink: 0,
851
+ "& .MuiDrawer-paper": {
852
+ width: sidebarWidth,
853
+ boxSizing: "border-box",
854
+ position: "relative",
855
+ borderRight: "1px solid",
856
+ borderColor: "divider",
857
+ ...sidebarStyle
858
+ }
859
+ },
860
+ children: /* @__PURE__ */ jsx(List, { sx: { p: 0 }, children: menuItems.filter((item) => item.label).map((item) => /* @__PURE__ */ jsx(
861
+ RouterMenuItem,
862
+ {
863
+ item,
864
+ expanded,
865
+ onToggle: handleToggle
866
+ },
867
+ item.id
868
+ )) })
869
+ }
870
+ ),
871
+ /* @__PURE__ */ jsx(
872
+ Box,
873
+ {
874
+ component: "main",
875
+ sx: {
876
+ flexGrow: 1,
877
+ overflow: "auto",
878
+ ...bodyStyle
879
+ },
880
+ children: hasComponents ? /* @__PURE__ */ jsx(Routes, { children: routes.map(({ path, component }) => /* @__PURE__ */ jsx(
881
+ Route,
882
+ {
883
+ path,
884
+ element: component
885
+ },
886
+ path
887
+ )) }) : /* @__PURE__ */ jsx(Outlet, {})
888
+ }
889
+ )
890
+ ]
891
+ }
892
+ );
893
+ };
894
+ var SidebarLayoutWithRouter = (props) => {
895
+ return (
896
+ // @ts-expect-error - future prop exists in v6 for v7 migration, but is removed from v7 types
897
+ /* @__PURE__ */ jsx(BrowserRouter, { future: { v7_startTransition: true, v7_relativeSplatPath: true }, children: /* @__PURE__ */ jsx(SidebarLayoutRouterInner, { ...props }) })
898
+ );
899
+ };
900
+ var StateMenuItem = ({ item, selectedId, depth = 0, expanded, onToggle, onClick }) => {
901
+ const hasChildren = item.children && item.children.length > 0;
902
+ const isExpanded = expanded.has(item.id);
903
+ const isActive = item.id === selectedId;
904
+ const handleClick = () => {
905
+ if (hasChildren) {
906
+ onToggle(item.id);
907
+ } else {
908
+ onClick(item.id);
883
909
  }
884
- }
885
- /**
886
- * Remove an item from localStorage
887
- * @param key - The key to remove
888
- * @returns true if successful, false otherwise
889
- */
890
- static removeItem(key) {
891
- try {
892
- if (typeof window === "undefined") {
893
- return false;
910
+ };
911
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
912
+ /* @__PURE__ */ jsx(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs(
913
+ ListItemButton,
914
+ {
915
+ selected: isActive,
916
+ onClick: handleClick,
917
+ sx: {
918
+ pl: 2 + depth * 2,
919
+ "&.Mui-selected": {
920
+ backgroundColor: "action.selected",
921
+ color: "primary.main",
922
+ borderLeft: 3,
923
+ borderColor: "primary.main",
924
+ fontWeight: "medium",
925
+ "& .MuiListItemIcon-root": {
926
+ color: "primary.main"
927
+ },
928
+ "&:hover": {
929
+ backgroundColor: "action.selected"
930
+ }
931
+ },
932
+ "&:hover": {
933
+ backgroundColor: "action.hover"
934
+ },
935
+ transition: "all 0.15s"
936
+ },
937
+ children: [
938
+ item.icon && /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 40 }, children: item.icon }),
939
+ /* @__PURE__ */ jsx(
940
+ ListItemText,
941
+ {
942
+ primary: item.label,
943
+ primaryTypographyProps: {
944
+ fontSize: "0.875rem"
945
+ }
946
+ }
947
+ ),
948
+ hasChildren && /* @__PURE__ */ jsx(IconButton, { size: "small", sx: { p: 0 }, children: isExpanded ? /* @__PURE__ */ jsx(ExpandLess, {}) : /* @__PURE__ */ jsx(ExpandMore, {}) })
949
+ ]
894
950
  }
895
- window.localStorage.removeItem(key);
896
- return true;
897
- } catch (error) {
898
- console.error(`Error removing item from localStorage: ${error}`);
899
- return false;
900
- }
901
- }
902
- /**
903
- * Clear all items from localStorage
904
- * @returns true if successful, false otherwise
905
- */
906
- static clear() {
907
- try {
908
- if (typeof window === "undefined") {
909
- return false;
951
+ ) }),
952
+ hasChildren && /* @__PURE__ */ jsx(Collapse, { in: isExpanded, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx(List, { component: "div", disablePadding: true, children: item.children.map((child) => /* @__PURE__ */ jsx(
953
+ StateMenuItem,
954
+ {
955
+ item: child,
956
+ selectedId,
957
+ depth: depth + 1,
958
+ expanded,
959
+ onToggle,
960
+ onClick
961
+ },
962
+ child.id
963
+ )) }) })
964
+ ] });
965
+ };
966
+ var SidebarLayoutWithState = ({
967
+ menuItems,
968
+ sidebarWidth = 250,
969
+ className = "",
970
+ defaultSelected,
971
+ onMenuChange,
972
+ style,
973
+ sidebarStyle,
974
+ bodyStyle,
975
+ defaultExpanded = []
976
+ }) => {
977
+ const [selectedId, setSelectedId] = useState(
978
+ defaultSelected || menuItems[0]?.id
979
+ );
980
+ const [expanded, setExpanded] = useState(new Set(defaultExpanded));
981
+ const handleToggle = (id) => {
982
+ setExpanded((prev) => {
983
+ const next = new Set(prev);
984
+ if (next.has(id)) {
985
+ next.delete(id);
986
+ } else {
987
+ next.add(id);
910
988
  }
911
- window.localStorage.clear();
912
- return true;
913
- } catch (error) {
914
- console.error(`Error clearing localStorage: ${error}`);
915
- return false;
989
+ return next;
990
+ });
991
+ };
992
+ const handleMenuClick = (id) => {
993
+ setSelectedId(id);
994
+ if (onMenuChange) {
995
+ onMenuChange(id);
916
996
  }
917
- }
918
- /**
919
- * Check if a key exists in localStorage
920
- * @param key - The key to check
921
- * @returns true if key exists, false otherwise
922
- */
923
- static hasItem(key) {
924
- try {
925
- if (typeof window === "undefined") {
926
- return false;
997
+ };
998
+ const findActiveItem = (items, id) => {
999
+ for (const item of items) {
1000
+ if (item.id === id) return item;
1001
+ if (item.children) {
1002
+ const found = findActiveItem(item.children, id);
1003
+ if (found) return found;
927
1004
  }
928
- return window.localStorage.getItem(key) !== null;
929
- } catch (error) {
930
- console.error(`Error checking item in localStorage: ${error}`);
931
- return false;
932
1005
  }
933
- }
934
- /**
935
- * Get all keys from localStorage
936
- * @returns Array of keys
937
- */
938
- static keys() {
939
- try {
940
- if (typeof window === "undefined") {
941
- return [];
942
- }
943
- return Object.keys(window.localStorage);
944
- } catch (error) {
945
- console.error(`Error getting keys from localStorage: ${error}`);
946
- return [];
1006
+ return void 0;
1007
+ };
1008
+ const activeItem = findActiveItem(menuItems, selectedId);
1009
+ return /* @__PURE__ */ jsxs(
1010
+ Box,
1011
+ {
1012
+ className,
1013
+ sx: {
1014
+ display: "flex",
1015
+ height: "100%",
1016
+ width: "100%",
1017
+ ...style
1018
+ },
1019
+ children: [
1020
+ /* @__PURE__ */ jsx(
1021
+ Drawer,
1022
+ {
1023
+ variant: "permanent",
1024
+ sx: {
1025
+ width: sidebarWidth,
1026
+ flexShrink: 0,
1027
+ "& .MuiDrawer-paper": {
1028
+ width: sidebarWidth,
1029
+ boxSizing: "border-box",
1030
+ position: "relative",
1031
+ borderRight: "1px solid",
1032
+ borderColor: "divider",
1033
+ ...sidebarStyle
1034
+ }
1035
+ },
1036
+ children: /* @__PURE__ */ jsx(List, { sx: { p: 0 }, children: menuItems.filter((item) => item.label).map((item) => /* @__PURE__ */ jsx(
1037
+ StateMenuItem,
1038
+ {
1039
+ item,
1040
+ selectedId,
1041
+ expanded,
1042
+ onToggle: handleToggle,
1043
+ onClick: handleMenuClick
1044
+ },
1045
+ item.id
1046
+ )) })
1047
+ }
1048
+ ),
1049
+ /* @__PURE__ */ jsx(
1050
+ Box,
1051
+ {
1052
+ component: "main",
1053
+ sx: {
1054
+ flexGrow: 1,
1055
+ overflow: "auto",
1056
+ p: 3,
1057
+ ...bodyStyle
1058
+ },
1059
+ children: activeItem?.component || /* @__PURE__ */ jsx(
1060
+ Typography,
1061
+ {
1062
+ variant: "body2",
1063
+ color: "text.secondary",
1064
+ sx: { textAlign: "center", mt: 4 },
1065
+ children: "No content available for this menu item"
1066
+ }
1067
+ )
1068
+ }
1069
+ )
1070
+ ]
947
1071
  }
948
- }
949
- };
950
- var ThemeContext = createContext(void 0);
951
- var THEME_STORAGE_KEY = "app-theme-mode";
952
- var getSystemTheme = () => {
953
- if (typeof window === "undefined") return "light";
954
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
955
- };
956
- var applyCssVariables = (theme, resolvedMode) => {
957
- if (typeof document === "undefined") return;
958
- const root = document.documentElement;
959
- root.setAttribute("data-theme", resolvedMode);
960
- Object.entries(theme.colors).forEach(([key, value]) => {
961
- root.style.setProperty(`--color-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`, value);
962
- });
963
- Object.entries(theme.spacing).forEach(([key, value]) => {
964
- root.style.setProperty(`--spacing-${key}`, value);
965
- });
966
- Object.entries(theme.borderRadius).forEach(([key, value]) => {
967
- root.style.setProperty(`--radius-${key}`, value);
968
- });
969
- Object.entries(theme.fontSize).forEach(([key, value]) => {
970
- root.style.setProperty(`--text-${key}`, value);
971
- });
972
- Object.entries(theme.fontWeight).forEach(([key, value]) => {
973
- root.style.setProperty(`--font-${key}`, value);
974
- });
975
- Object.entries(theme.lineHeight).forEach(([key, value]) => {
976
- root.style.setProperty(`--leading-${key}`, value);
977
- });
978
- Object.entries(theme.elevation).forEach(([key, value]) => {
979
- root.style.setProperty(`--shadow-${key}`, value);
980
- });
981
- Object.entries(theme.transition).forEach(([key, value]) => {
982
- root.style.setProperty(`--transition-${key}`, value);
1072
+ );
1073
+ };
1074
+ var SidebarLayout = (props) => {
1075
+ if (props.isRouter) {
1076
+ return /* @__PURE__ */ jsx(SidebarLayoutWithRouter, { ...props });
1077
+ }
1078
+ return /* @__PURE__ */ jsx(SidebarLayoutWithState, { ...props });
1079
+ };
1080
+ var highlighterInstance = null;
1081
+ var highlighterPromise = null;
1082
+ var getHighlighter = async () => {
1083
+ if (highlighterInstance) {
1084
+ return highlighterInstance;
1085
+ }
1086
+ if (highlighterPromise) {
1087
+ return highlighterPromise;
1088
+ }
1089
+ highlighterPromise = createHighlighter({
1090
+ themes: ["github-light", "github-dark"],
1091
+ langs: ["js", "ts", "go", "json", "bash", "yaml", "md", "python", "java", "cpp", "c", "html", "css", "sql", "http", "text"]
1092
+ }).then((highlighter) => {
1093
+ highlighterInstance = highlighter;
1094
+ return highlighter;
983
1095
  });
1096
+ return highlighterPromise;
984
1097
  };
985
- var ThemeProvider = ({
986
- children,
987
- initialTheme,
988
- initialMode = "system",
989
- storageKey = THEME_STORAGE_KEY,
990
- enableCssVariables = true
1098
+ var ReadmeViewer = ({
1099
+ content,
1100
+ className = "",
1101
+ isDark: propIsDark,
1102
+ muiTheme
991
1103
  }) => {
992
- const getInitialMode = () => {
993
- const savedMode = LocalStorage.getItem(storageKey);
994
- return savedMode || initialMode;
995
- };
996
- const [themeMode, setThemeModeState] = useState(getInitialMode);
997
- const [systemTheme, setSystemTheme] = useState(getSystemTheme);
998
- const [customTheme, setCustomTheme] = useState(initialTheme);
999
- const resolvedThemeMode = useMemo(() => {
1000
- if (themeMode === "system") return systemTheme;
1001
- return themeMode;
1002
- }, [themeMode, systemTheme]);
1003
- const theme = useMemo(() => {
1004
- if (customTheme) return customTheme;
1005
- return resolvedThemeMode === "light" ? lightTheme : darkTheme;
1006
- }, [customTheme, resolvedThemeMode]);
1104
+ const isDark = propIsDark !== void 0 ? propIsDark : muiTheme?.palette?.mode === "dark";
1105
+ const [highlighter, setHighlighter] = useState(null);
1106
+ const [copiedBlocks, setCopiedBlocks] = useState(/* @__PURE__ */ new Set());
1007
1107
  useEffect(() => {
1008
- if (typeof window === "undefined") return;
1009
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
1010
- const handleChange = (e) => {
1011
- setSystemTheme(e.matches ? "dark" : "light");
1012
- };
1013
- if (mediaQuery.addEventListener) {
1014
- mediaQuery.addEventListener("change", handleChange);
1015
- return () => mediaQuery.removeEventListener("change", handleChange);
1016
- } else if (mediaQuery.addListener) {
1017
- mediaQuery.addListener(handleChange);
1018
- return () => mediaQuery.removeListener(handleChange);
1019
- }
1108
+ getHighlighter().then(setHighlighter);
1020
1109
  }, []);
1021
- useEffect(() => {
1022
- if (enableCssVariables) {
1023
- applyCssVariables(theme, resolvedThemeMode);
1110
+ const copyToClipboard = async (text, blockId) => {
1111
+ try {
1112
+ await navigator.clipboard.writeText(text);
1113
+ setCopiedBlocks((prev) => new Set(prev).add(blockId));
1114
+ setTimeout(() => {
1115
+ setCopiedBlocks((prev) => {
1116
+ const newSet = new Set(prev);
1117
+ newSet.delete(blockId);
1118
+ return newSet;
1119
+ });
1120
+ }, 2e3);
1121
+ } catch (err) {
1122
+ console.error("Failed to copy text: ", err);
1024
1123
  }
1025
- }, [theme, resolvedThemeMode, enableCssVariables]);
1026
- useEffect(() => {
1027
- LocalStorage.setItem(storageKey, themeMode);
1028
- }, [themeMode, storageKey]);
1029
- const setThemeMode = useCallback((mode) => {
1030
- setThemeModeState(mode);
1031
- }, []);
1032
- const setTheme = useCallback((newTheme) => {
1033
- setCustomTheme(newTheme);
1034
- }, []);
1035
- const toggleTheme = useCallback(() => {
1036
- setThemeModeState((prevMode) => {
1037
- if (prevMode === "light") return "dark";
1038
- if (prevMode === "dark") return "system";
1039
- return "light";
1124
+ };
1125
+ return /* @__PURE__ */ jsxs("div", { className: `readme-viewer markdown-body ${className}`, children: [
1126
+ /* @__PURE__ */ jsx("style", { children: `
1127
+ .shiki-wrapper pre.shiki {
1128
+ padding: 16px !important;
1129
+ border-radius: 8px !important;
1130
+ overflow-x: auto;
1131
+ margin-bottom: 16px;
1132
+ border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);
1133
+ }
1134
+ ` }),
1135
+ /* @__PURE__ */ jsx(
1136
+ ReactMarkdown,
1137
+ {
1138
+ remarkPlugins: [remarkGfm, remarkMath],
1139
+ rehypePlugins: [rehypeKatex],
1140
+ components: {
1141
+ code({ className: className2, children, ...props }) {
1142
+ const match = /language-(\w+)/.exec(className2 || "");
1143
+ if (match && highlighter) {
1144
+ const codeText = String(children).replace(/\n$/, "");
1145
+ const blockId = `code-${Math.random().toString(36).substr(2, 9)}`;
1146
+ let highlightedHtml = "";
1147
+ try {
1148
+ highlightedHtml = highlighter.codeToHtml(codeText, {
1149
+ lang: match[1],
1150
+ theme: isDark ? "github-dark" : "github-light"
1151
+ });
1152
+ } catch (e) {
1153
+ try {
1154
+ highlightedHtml = highlighter.codeToHtml(codeText, {
1155
+ lang: "text",
1156
+ theme: isDark ? "github-dark" : "github-light"
1157
+ });
1158
+ } catch (fallbackError) {
1159
+ highlightedHtml = `<pre><code>${codeText}</code></pre>`;
1160
+ }
1161
+ }
1162
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
1163
+ /* @__PURE__ */ jsx("div", { className: "shiki-wrapper", dangerouslySetInnerHTML: { __html: highlightedHtml } }),
1164
+ /* @__PURE__ */ jsx(
1165
+ "button",
1166
+ {
1167
+ onClick: () => copyToClipboard(codeText, blockId),
1168
+ style: {
1169
+ position: "absolute",
1170
+ top: "8px",
1171
+ right: "8px",
1172
+ backgroundColor: "var(--md-sys-color-surface-container-high)",
1173
+ border: "1px solid var(--md-sys-color-outline-variant)",
1174
+ borderRadius: "4px",
1175
+ padding: "6px",
1176
+ fontSize: "14px",
1177
+ cursor: "pointer",
1178
+ color: "var(--md-sys-color-on-surface-variant)",
1179
+ alignItems: "center",
1180
+ justifyContent: "center",
1181
+ transition: "all 0.2s ease",
1182
+ width: "32px",
1183
+ height: "32px"
1184
+ },
1185
+ onMouseEnter: (e) => {
1186
+ e.currentTarget.style.backgroundColor = isDark ? "var(--md-sys-color-surface-container-highest)" : "var(--md-sys-color-surface-container-high)";
1187
+ },
1188
+ onMouseLeave: (e) => {
1189
+ e.currentTarget.style.backgroundColor = isDark ? "var(--md-sys-color-surface-container-high)" : "var(--md-sys-color-surface-container-highest)";
1190
+ },
1191
+ title: copiedBlocks.has(blockId) ? "Copied!" : "Copy code",
1192
+ children: copiedBlocks.has(blockId) ? /* @__PURE__ */ jsx(CheckIcon, { style: { fontSize: "16px" } }) : /* @__PURE__ */ jsx(ContentCopyIcon, { style: { fontSize: "16px" } })
1193
+ }
1194
+ )
1195
+ ] });
1196
+ }
1197
+ return /* @__PURE__ */ jsx("code", { className: className2, ...props, children });
1198
+ }
1199
+ },
1200
+ children: content
1201
+ }
1202
+ )
1203
+ ] });
1204
+ };
1205
+ var ReadmeViewer_default = ReadmeViewer;
1206
+ var NotificationContext = createContext(void 0);
1207
+ var NotificationProvider = ({
1208
+ children,
1209
+ defaultDuration = 6e3,
1210
+ maxNotifications = 3,
1211
+ anchorOrigin = { vertical: "top", horizontal: "right" }
1212
+ }) => {
1213
+ const [notifications, setNotifications] = useState([]);
1214
+ const showNotification = useCallback((message, type, duration = defaultDuration) => {
1215
+ const id = `${Date.now()}-${Math.random()}`;
1216
+ const newNotification = { id, message, type, duration };
1217
+ setNotifications((prev) => {
1218
+ const updated = [...prev, newNotification];
1219
+ if (updated.length > maxNotifications) {
1220
+ return updated.slice(updated.length - maxNotifications);
1221
+ }
1222
+ return updated;
1040
1223
  });
1224
+ }, [defaultDuration, maxNotifications]);
1225
+ const handleClose = useCallback((id) => {
1226
+ setNotifications((prev) => prev.filter((notif) => notif.id !== id));
1041
1227
  }, []);
1042
- const contextValue = useMemo(() => ({
1043
- theme,
1044
- themeMode,
1045
- resolvedThemeMode,
1046
- setTheme,
1047
- setThemeMode,
1048
- toggleTheme
1049
- }), [theme, themeMode, resolvedThemeMode, setTheme, setThemeMode, toggleTheme]);
1050
- const muiTheme = useMemo(() => createTheme({
1051
- palette: {
1052
- mode: resolvedThemeMode,
1053
- primary: {
1054
- main: theme.colors.primary
1055
- },
1056
- secondary: {
1057
- main: theme.colors.secondary
1058
- },
1059
- error: {
1060
- main: theme.colors.error
1061
- },
1062
- success: {
1063
- main: theme.colors.success
1064
- },
1065
- background: {
1066
- default: theme.colors.background,
1067
- paper: theme.colors.backgroundSecondary
1228
+ const success = useCallback((message, duration) => {
1229
+ showNotification(message, "success", duration);
1230
+ }, [showNotification]);
1231
+ const error = useCallback((message, duration) => {
1232
+ showNotification(message, "error", duration);
1233
+ }, [showNotification]);
1234
+ const warning = useCallback((message, duration) => {
1235
+ showNotification(message, "warning", duration);
1236
+ }, [showNotification]);
1237
+ const info = useCallback((message, duration) => {
1238
+ showNotification(message, "info", duration);
1239
+ }, [showNotification]);
1240
+ const contextValue = {
1241
+ showNotification,
1242
+ success,
1243
+ error,
1244
+ warning,
1245
+ info
1246
+ };
1247
+ return /* @__PURE__ */ jsxs(NotificationContext.Provider, { value: contextValue, children: [
1248
+ children,
1249
+ notifications.map((notification, index) => /* @__PURE__ */ jsx(
1250
+ Snackbar,
1251
+ {
1252
+ open: true,
1253
+ autoHideDuration: notification.duration,
1254
+ onClose: () => handleClose(notification.id),
1255
+ anchorOrigin,
1256
+ style: {
1257
+ marginTop: index * 70
1258
+ // Stack notifications vertically
1259
+ },
1260
+ children: /* @__PURE__ */ jsx(
1261
+ Alert,
1262
+ {
1263
+ onClose: () => handleClose(notification.id),
1264
+ severity: notification.type,
1265
+ variant: "filled",
1266
+ sx: { width: "100%" },
1267
+ children: notification.message
1268
+ }
1269
+ )
1068
1270
  },
1069
- text: {
1070
- primary: theme.colors.text,
1071
- secondary: theme.colors.textSecondary
1072
- }
1073
- }
1074
- }), [resolvedThemeMode, theme]);
1075
- return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(ThemeProvider$1, { theme: muiTheme, children: [
1076
- /* @__PURE__ */ jsx(CssBaseline, {}),
1077
- children
1078
- ] }) });
1271
+ notification.id
1272
+ ))
1273
+ ] });
1079
1274
  };
1080
- var useTheme = () => {
1081
- const context = useContext(ThemeContext);
1275
+ var useNotification = () => {
1276
+ const context = useContext(NotificationContext);
1082
1277
  if (!context) {
1083
- throw new Error("useTheme must be used within a ThemeProvider");
1278
+ throw new Error("useNotification must be used within a NotificationProvider");
1084
1279
  }
1085
1280
  return context;
1086
1281
  };
1087
- var ThemeToggle = ({
1088
- lightModeTooltip = "Switch to dark mode",
1089
- darkModeTooltip = "Switch to system theme",
1090
- systemModeTooltip = "Switch to light theme",
1091
- showTooltip = true,
1092
- sx,
1093
- ...props
1282
+ var ConfirmDialog = ({
1283
+ open,
1284
+ title,
1285
+ message,
1286
+ confirmText = "Confirm",
1287
+ cancelText = "Cancel",
1288
+ confirmColor = "primary",
1289
+ onConfirm,
1290
+ onCancel
1094
1291
  }) => {
1095
- const { themeMode, toggleTheme } = useTheme();
1096
- const getIcon = () => {
1097
- switch (themeMode) {
1098
- case "light":
1099
- return /* @__PURE__ */ jsx(LightMode, {});
1100
- case "dark":
1101
- return /* @__PURE__ */ jsx(DarkMode, {});
1102
- case "system":
1103
- return /* @__PURE__ */ jsx(BrightnessAuto, {});
1104
- default:
1105
- return /* @__PURE__ */ jsx(BrightnessAuto, {});
1106
- }
1107
- };
1108
- const getTooltip = () => {
1109
- switch (themeMode) {
1110
- case "light":
1111
- return lightModeTooltip;
1112
- case "dark":
1113
- return darkModeTooltip;
1114
- case "system":
1115
- return systemModeTooltip;
1116
- default:
1117
- return lightModeTooltip;
1118
- }
1119
- };
1120
- const button = /* @__PURE__ */ jsx(
1121
- IconButton,
1122
- {
1123
- onClick: toggleTheme,
1124
- color: "inherit",
1125
- sx: {
1126
- borderRadius: "50%",
1127
- ...sx
1128
- },
1129
- ...props,
1130
- children: getIcon()
1131
- }
1132
- );
1133
- if (!showTooltip) {
1134
- return button;
1135
- }
1136
- return /* @__PURE__ */ jsx(Tooltip, { title: getTooltip(), arrow: true, children: button });
1137
- };
1138
-
1139
- // src/http/HttpClient.ts
1140
- var HttpClient = class {
1141
- constructor(config = {}) {
1142
- this.baseURL = config.baseURL || "";
1143
- this.timeout = config.timeout || 3e4;
1144
- this.defaultHeaders = config.headers || {};
1145
- }
1146
- buildURL(url, params) {
1147
- const fullURL = url.startsWith("http") ? url : `${this.baseURL}${url}`;
1148
- if (!params) return fullURL;
1149
- const searchParams = new URLSearchParams();
1150
- Object.entries(params).forEach(([key, value]) => {
1151
- searchParams.append(key, String(value));
1152
- });
1153
- return `${fullURL}?${searchParams.toString()}`;
1154
- }
1155
- async request(url, config = {}) {
1156
- const { params, data, timeout = this.timeout, headers = {}, ...restConfig } = config;
1157
- const controller = new AbortController();
1158
- const timeoutId = setTimeout(() => controller.abort(), timeout);
1159
- try {
1160
- const response = await fetch(this.buildURL(url, params), {
1161
- ...restConfig,
1162
- headers: {
1163
- "Content-Type": "application/json",
1164
- ...this.defaultHeaders,
1165
- ...headers
1166
- },
1167
- body: data ? JSON.stringify(data) : void 0,
1168
- signal: controller.signal
1169
- });
1170
- clearTimeout(timeoutId);
1171
- if (!response.ok) {
1172
- const error = new Error(`HTTP Error: ${response.statusText}`);
1173
- error.status = response.status;
1174
- error.statusText = response.statusText;
1175
- try {
1176
- error.data = await response.json();
1177
- } catch {
1178
- error.data = { code: response.status, message: await response.text() };
1179
- }
1180
- throw error;
1181
- }
1182
- const responseData = await response.json();
1183
- return {
1184
- data: responseData,
1185
- status: response.status,
1186
- statusText: response.statusText,
1187
- headers: response.headers
1188
- };
1189
- } catch (error) {
1190
- clearTimeout(timeoutId);
1191
- if (error instanceof Error && error.name === "AbortError") {
1192
- throw new Error("Request timeout");
1193
- }
1194
- throw error;
1195
- }
1196
- }
1197
- setDefaultHeader(key, value) {
1198
- this.defaultHeaders[key] = value;
1199
- }
1200
- removeDefaultHeader(key) {
1201
- delete this.defaultHeaders[key];
1202
- }
1203
- getDefaultHeaders() {
1204
- return { ...this.defaultHeaders };
1205
- }
1206
- get(url, config) {
1207
- return this.request(url, { ...config, method: "GET" });
1208
- }
1209
- post(url, data, config) {
1210
- return this.request(url, { ...config, data, method: "POST" });
1211
- }
1212
- put(url, data, config) {
1213
- return this.request(url, { ...config, data, method: "PUT" });
1214
- }
1215
- patch(url, data, config) {
1216
- return this.request(url, { ...config, data, method: "PATCH" });
1217
- }
1218
- delete(url, config) {
1219
- return this.request(url, { ...config, method: "DELETE" });
1220
- }
1292
+ return /* @__PURE__ */ jsxs(Dialog, { open, onClose: onCancel, maxWidth: "xs", fullWidth: true, slotProps: { paper: { sx: { borderRadius: 4 } } }, children: [
1293
+ /* @__PURE__ */ jsx(DialogTitle, { children: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
1294
+ /* @__PURE__ */ jsx(WarningAmberIcon, { color: confirmColor }),
1295
+ title
1296
+ ] }) }),
1297
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(Typography, { variant: "body1", children: message }) }),
1298
+ /* @__PURE__ */ jsxs(DialogActions, { sx: { px: 3, pb: 2 }, children: [
1299
+ /* @__PURE__ */ jsx(Button, { onClick: onCancel, variant: "outlined", children: cancelText }),
1300
+ /* @__PURE__ */ jsx(Button, { onClick: onConfirm, variant: "contained", color: confirmColor, autoFocus: true, children: confirmText })
1301
+ ] })
1302
+ ] });
1221
1303
  };
1222
- function extractErrorMessage(error) {
1223
- if (error instanceof Error) {
1224
- const httpError = error;
1225
- if (httpError.data) {
1226
- return httpError.data.message;
1227
- }
1228
- return error.message;
1229
- }
1230
- return "An unexpected error occurred";
1231
- }
1232
1304
 
1233
1305
  // src/utils/cn.ts
1234
1306
  function cn(...classes) {
@@ -1269,162 +1341,162 @@ function formatDate(date, options = {
1269
1341
  const dateObj = typeof date === "string" || typeof date === "number" ? new Date(date) : date;
1270
1342
  return new Intl.DateTimeFormat("en-US", options).format(dateObj);
1271
1343
  }
1272
- function getCookie(name) {
1273
- if (typeof document === "undefined") return null;
1274
- const value = `; ${document.cookie}`;
1275
- const parts = value.split(`; ${name}=`);
1276
- if (parts.length === 2) return parts.pop()?.split(";").shift() || null;
1277
- return null;
1278
- }
1279
- function decodeJWT(token) {
1280
- try {
1281
- const base64Url = token.split(".")[1];
1282
- if (!base64Url) return null;
1283
- const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
1284
- const jsonPayload = decodeURIComponent(
1285
- window.atob(base64).split("").map(function(c) {
1286
- return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
1287
- }).join("")
1288
- );
1289
- return JSON.parse(jsonPayload);
1290
- } catch (e) {
1291
- return null;
1344
+
1345
+ // src/auth/sessionManager.ts
1346
+ var SESSION_KEY = "session_details";
1347
+ var SessionManager = class _SessionManager {
1348
+ constructor(client, key = SESSION_KEY) {
1349
+ this.cache = null;
1350
+ this.client = client;
1351
+ this.key = key;
1292
1352
  }
1293
- }
1294
- var AuthContext = createContext({
1295
- isAuthenticated: false,
1296
- isLoading: true
1297
- });
1298
- var AuthProvider = ({
1299
- cookieName = "authorization",
1300
- redirectUrl,
1301
- children,
1302
- onAuthFail,
1303
- onAuthSuccess,
1304
- loadingElement = null
1305
- }) => {
1306
- const [authState, setAuthState] = useState({
1307
- isAuthenticated: false,
1308
- isLoading: true
1309
- // starts loading until cookie check completes
1310
- });
1311
- const [authFailed, setAuthFailed] = useState(false);
1312
- useEffect(() => {
1313
- const verifyAuth = () => {
1314
- const token = getCookie(cookieName);
1315
- if (!token) {
1316
- handleFail();
1317
- return;
1318
- }
1319
- const decoded = decodeJWT(token);
1320
- if (!decoded) {
1321
- handleFail();
1322
- return;
1323
- }
1324
- if (decoded.exp) {
1325
- const expirationDate = new Date(decoded.exp * 1e3);
1326
- if (expirationDate < /* @__PURE__ */ new Date()) {
1327
- handleFail();
1328
- return;
1329
- }
1330
- }
1331
- setAuthState({
1332
- isAuthenticated: true,
1333
- isLoading: false
1334
- });
1335
- if (onAuthSuccess) {
1336
- onAuthSuccess();
1337
- }
1338
- };
1339
- verifyAuth();
1340
- }, [cookieName]);
1341
- const handleFail = () => {
1342
- setAuthState({
1343
- isAuthenticated: false,
1344
- isLoading: false
1345
- });
1346
- setAuthFailed(true);
1347
- if (onAuthFail) {
1348
- onAuthFail();
1349
- } else if (redirectUrl && typeof window !== "undefined") {
1350
- window.location.href = redirectUrl;
1353
+ static getInstance(client) {
1354
+ if (!_SessionManager.instance) {
1355
+ _SessionManager.instance = new _SessionManager(client);
1351
1356
  }
1352
- };
1353
- if (authState.isLoading || authFailed && !onAuthFail) {
1354
- return /* @__PURE__ */ jsx(Fragment, { children: loadingElement });
1357
+ return _SessionManager.instance;
1355
1358
  }
1356
- if (authFailed && onAuthFail) {
1357
- return /* @__PURE__ */ jsx(Fragment, { children });
1359
+ save(session) {
1360
+ this.cache = session;
1361
+ LocalStorage.setItem(this.key, session);
1362
+ this.setAuthToken(session.accessToken);
1363
+ }
1364
+ patch(updates) {
1365
+ const current = this.get();
1366
+ if (!current) return;
1367
+ const updated = { ...current, ...updates };
1368
+ this.cache = updated;
1369
+ LocalStorage.setItem(this.key, updated);
1370
+ if (updates.accessToken) {
1371
+ this.setAuthToken(updates.accessToken);
1372
+ }
1373
+ }
1374
+ clear() {
1375
+ this.cache = null;
1376
+ LocalStorage.removeItem(this.key);
1377
+ this.clearAuthToken();
1378
+ }
1379
+ get() {
1380
+ if (this.cache !== null) return this.cache;
1381
+ this.cache = LocalStorage.getItem(this.key);
1382
+ return this.cache;
1383
+ }
1384
+ getAccessToken() {
1385
+ return this.get()?.accessToken || void 0;
1386
+ }
1387
+ getRefreshToken() {
1388
+ return this.get()?.refreshToken || void 0;
1389
+ }
1390
+ getSessionId() {
1391
+ return this.get()?.sessionId || void 0;
1392
+ }
1393
+ isAuthenticated() {
1394
+ const session = this.get();
1395
+ if (!session?.accessToken) return false;
1396
+ return Number(session.expiresAt) > Date.now() / 1e3;
1397
+ }
1398
+ initialize() {
1399
+ const session = this.get();
1400
+ if (session?.accessToken && Number(session.expiresAt) > Date.now() / 1e3) {
1401
+ this.setAuthToken(session.accessToken);
1402
+ } else {
1403
+ this.clear();
1404
+ }
1405
+ }
1406
+ setAuthToken(token) {
1407
+ this.client.setDefaultHeader("Authorization", `Bearer ${token}`);
1408
+ }
1409
+ clearAuthToken() {
1410
+ this.client.removeDefaultHeader("Authorization");
1358
1411
  }
1359
- return /* @__PURE__ */ jsx(AuthContext.Provider, { value: authState, children });
1360
- };
1361
- var useAuth = () => {
1362
- return useContext(AuthContext);
1363
1412
  };
1364
1413
 
1365
- // src/auth/PermissionManager.ts
1366
- var PERMISSIONS_STORAGE_KEY = "auth_permissions";
1367
- var PermissionManager = class {
1368
- /**
1369
- * Saves an array of permission strings to local storage.
1370
- * @param permissions Array of permission strings.
1371
- */
1372
- static setPermissions(permissions) {
1373
- this.cachedPermissions = new Set(permissions);
1374
- if (typeof window === "undefined") return;
1375
- try {
1376
- localStorage.setItem(PERMISSIONS_STORAGE_KEY, JSON.stringify(permissions));
1377
- } catch (error) {
1378
- console.error("Failed to save permissions to localStorage", error);
1414
+ // src/auth/AuthService.ts
1415
+ var BASE_URL = "/openauth/v1";
1416
+ var AuthService = class _AuthService {
1417
+ constructor(client) {
1418
+ this.client = client;
1419
+ this.sessionManager = SessionManager.getInstance(client);
1420
+ }
1421
+ static getInstance(client) {
1422
+ if (!_AuthService.instance) {
1423
+ _AuthService.instance = new _AuthService(client);
1379
1424
  }
1425
+ return _AuthService.instance;
1380
1426
  }
1381
- /**
1382
- * Retrieves the currently stored permissions from local storage or memory cache.
1383
- * @returns Array of permission strings, or an empty array if none exist.
1384
- */
1385
- static getPermissions() {
1386
- if (this.cachedPermissions !== null) {
1387
- return Array.from(this.cachedPermissions);
1427
+ async signIn(request) {
1428
+ const response = await this.client.post(
1429
+ `${BASE_URL}/auth/signin`,
1430
+ request
1431
+ );
1432
+ if (response.data.accessToken) {
1433
+ this.sessionManager.save(response.data);
1434
+ }
1435
+ return response.data;
1436
+ }
1437
+ async refreshToken(request) {
1438
+ const refreshToken = this.sessionManager.getRefreshToken();
1439
+ if (!refreshToken) {
1440
+ throw new Error("No refresh token available");
1388
1441
  }
1389
- if (typeof window === "undefined") return [];
1442
+ const response = await this.client.post(
1443
+ `${BASE_URL}/auth/refresh`,
1444
+ { refreshToken, ...request }
1445
+ );
1446
+ if (response.data.accessToken) {
1447
+ this.sessionManager.patch({
1448
+ accessToken: response.data.accessToken,
1449
+ refreshToken: response.data.refreshToken
1450
+ });
1451
+ }
1452
+ return response.data;
1453
+ }
1454
+ async logout(request) {
1390
1455
  try {
1391
- const stored = localStorage.getItem(PERMISSIONS_STORAGE_KEY);
1392
- if (!stored) {
1393
- this.cachedPermissions = /* @__PURE__ */ new Set();
1394
- return [];
1395
- }
1396
- const parsed = JSON.parse(stored);
1397
- const permissionsArray = Array.isArray(parsed) ? parsed : [];
1398
- this.cachedPermissions = new Set(permissionsArray);
1399
- return permissionsArray;
1400
- } catch (error) {
1401
- console.error("Failed to parse permissions from localStorage", error);
1402
- this.cachedPermissions = /* @__PURE__ */ new Set();
1403
- return [];
1456
+ request.sessionId = this.sessionManager.getSessionId();
1457
+ const response = await this.client.post(
1458
+ `${BASE_URL}/auth/logout`,
1459
+ request || {}
1460
+ );
1461
+ return response.data;
1462
+ } finally {
1463
+ this.sessionManager.clear();
1404
1464
  }
1405
1465
  }
1406
- /**
1407
- * Clears all stored permissions from memory and local storage.
1408
- */
1409
- static clearPermissions() {
1410
- this.cachedPermissions = null;
1411
- if (typeof window === "undefined") return;
1412
- localStorage.removeItem(PERMISSIONS_STORAGE_KEY);
1466
+ async generateLoginToken(request) {
1467
+ const response = await this.client.put(
1468
+ `${BASE_URL}/auth/login-token`,
1469
+ request ?? {}
1470
+ );
1471
+ return response.data;
1413
1472
  }
1414
- /**
1415
- * Checks whether the given permission string exists in the currently stored permissions.
1416
- * @param permission The permission string to check for.
1417
- * @returns True if the permission exists, false otherwise.
1418
- */
1419
- static hasPermission(permission) {
1420
- if (this.cachedPermissions === null) {
1421
- this.getPermissions();
1473
+ async signInWithLoginToken(request) {
1474
+ const response = await this.client.post(
1475
+ `${BASE_URL}/auth/signin-with-token`,
1476
+ request
1477
+ );
1478
+ if (response.data.accessToken) {
1479
+ this.sessionManager.save(response.data);
1422
1480
  }
1423
- return this.cachedPermissions.has(permission);
1481
+ return response.data;
1482
+ }
1483
+ isAuthenticated() {
1484
+ return this.sessionManager.isAuthenticated();
1485
+ }
1486
+ getAccessToken() {
1487
+ return this.sessionManager.getAccessToken();
1488
+ }
1489
+ getSessionDetails() {
1490
+ return this.sessionManager.get();
1491
+ }
1492
+ getSessionId() {
1493
+ return this.sessionManager.getSessionId();
1494
+ }
1495
+ initializeAuth() {
1496
+ this.sessionManager.initialize();
1424
1497
  }
1425
1498
  };
1426
- PermissionManager.cachedPermissions = null;
1427
1499
 
1428
- export { AuthProvider, ConfirmDialog, HttpClient, LocalStorage, NotificationProvider, PermissionManager, ReadmeViewer_default as ReadmeViewer, SidebarLayout, ThemeProvider, ThemeToggle, borderRadius, cn, darkTheme, debounce, elevation, extractErrorMessage, fontSize, fontWeight, formatDate, getHighlighter, lightTheme, lineHeight, spacing, throttle, tokens_exports as tokens, transition, useAuth, useNotification, useTheme, zIndex };
1500
+ export { AuthService, ConfirmDialog, HttpClient, LocalStorage, LoginCallbackPage, NotFoundPage, NotificationProvider, ReadmeViewer_default as ReadmeViewer, SessionManager, SidebarLayout, ThemeProvider, ThemeToggle, borderRadius, cn, darkTheme, debounce, elevation, extractErrorMessage, fontSize, fontWeight, formatDate, getHighlighter, lightTheme, lineHeight, spacing, throttle, tokens_exports as tokens, transition, useNotification, useTheme, zIndex };
1429
1501
  //# sourceMappingURL=index.mjs.map
1430
1502
  //# sourceMappingURL=index.mjs.map