xertica-ui 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +22 -2
  2. package/README.md +33 -22
  3. package/bin/cli.ts +136 -47
  4. package/bin/language-config.ts +5 -8
  5. package/components/assistant/modern-chat-input/ModernChatInput.tsx +17 -7
  6. package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +1 -3
  7. package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +13 -3
  8. package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +10 -6
  9. package/components/assistant/xertica-assistant/xertica-assistant.tsx +1 -3
  10. package/components/blocks/card-patterns/FeatureCardSkeleton.tsx +1 -6
  11. package/components/blocks/card-patterns/ProfileCard.tsx +1 -3
  12. package/components/blocks/card-patterns/ProjectCardSkeleton.tsx +1 -6
  13. package/components/brand/language-selector/language-selector.stories.tsx +1 -4
  14. package/components/brand/theme-toggle/ThemeToggle.tsx +5 -1
  15. package/components/brand/xertica-provider/XerticaProvider.tsx +1 -4
  16. package/components/index.ts +1 -5
  17. package/components/layout/sidebar/sidebar.tsx +9 -3
  18. package/components/media/audio-player/AudioPlayer.tsx +4 -2
  19. package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +188 -188
  20. package/components/pages/home-content/HomeContent.tsx +55 -55
  21. package/components/pages/home-page/HomePage.tsx +5 -1
  22. package/components/pages/login-page/LoginPage.tsx +4 -2
  23. package/components/pages/reset-password-page/ResetPasswordPage.tsx +7 -3
  24. package/components/pages/template-content/TemplateContent.tsx +268 -149
  25. package/components/pages/verify-email-page/VerifyEmailPage.tsx +9 -9
  26. package/components/shared/error-boundary.stories.tsx +114 -132
  27. package/components/shared/error-boundary.tsx +150 -154
  28. package/components/shared/error-fallbacks.tsx +222 -226
  29. package/components/ui/stats-card/stats-card-skeleton.tsx +1 -3
  30. package/components/ui/stats-card/stats-card.stories.tsx +18 -0
  31. package/components/ui/stats-card/stats-card.tsx +18 -2
  32. package/components.json +512 -892
  33. package/contexts/AuthContext.tsx +121 -118
  34. package/contexts/LanguageContext.tsx +1 -2
  35. package/dist/AssistantChart-BKVtGUKF.js +3383 -0
  36. package/dist/AssistantChart-WeycT5Pd.cjs +3551 -0
  37. package/dist/VerifyEmailPage-Bp1XXl3H.cjs +3305 -0
  38. package/dist/VerifyEmailPage-DGhuIqkb.js +3296 -0
  39. package/dist/XerticaProvider-BErr83Bg.js +42 -0
  40. package/dist/XerticaProvider-CwOkHxiT.cjs +44 -0
  41. package/dist/XerticaXLogo-BX3ueACh.js +255 -0
  42. package/dist/XerticaXLogo-qBPhwK3g.cjs +260 -0
  43. package/dist/assistant.cjs.js +1 -1
  44. package/dist/assistant.es.js +1 -1
  45. package/dist/brand.cjs.js +2 -2
  46. package/dist/brand.es.js +2 -2
  47. package/dist/cli.js +90 -37
  48. package/dist/components/brand/theme-toggle/ThemeToggle.d.ts +1 -1
  49. package/dist/components/index.d.ts +1 -1
  50. package/dist/components/ui/stats-card/stats-card.d.ts +10 -0
  51. package/dist/index.cjs.js +6 -6
  52. package/dist/index.es.js +6 -6
  53. package/dist/layout.cjs.js +1 -1
  54. package/dist/layout.es.js +1 -1
  55. package/dist/pages.cjs.js +1 -1
  56. package/dist/pages.es.js +1 -1
  57. package/dist/sidebar-B4ZWaMrE.js +792 -0
  58. package/dist/sidebar-BS1p2V7t.cjs +795 -0
  59. package/dist/ui.cjs.js +1 -1
  60. package/dist/ui.es.js +1 -1
  61. package/dist/xertica-assistant-B1NaSFFj.js +2173 -0
  62. package/dist/xertica-assistant-CIaUlbIt.cjs +2180 -0
  63. package/dist/xertica-ui.css +1 -1
  64. package/docs/architecture-improvements.md +5 -5
  65. package/docs/architecture.md +16 -10
  66. package/docs/components/card-patterns.md +19 -17
  67. package/docs/components/error-boundary.md +201 -191
  68. package/docs/components/hooks.md +15 -13
  69. package/docs/components/language-selector.md +20 -16
  70. package/docs/components/pages.md +323 -309
  71. package/docs/components/stats-card.md +20 -2
  72. package/docs/doc-audit.md +12 -11
  73. package/docs/getting-started.md +41 -28
  74. package/docs/guidelines.md +14 -12
  75. package/docs/i18n.md +61 -57
  76. package/docs/installation.md +268 -267
  77. package/docs/llms.md +17 -17
  78. package/docs/state-management.md +17 -17
  79. package/guidelines/Guidelines.md +17 -14
  80. package/llms-compact.txt +1 -1
  81. package/llms-full.txt +11553 -7133
  82. package/llms.txt +1 -1
  83. package/package.json +1 -1
  84. package/styles/xertica/base.css +90 -84
  85. package/templates/CLAUDE.md +16 -1
  86. package/templates/guidelines/Guidelines.md +42 -18
  87. package/templates/package.json +3 -3
  88. package/templates/src/app/components/AuthGuard.tsx +131 -82
  89. package/templates/src/features/auth/ui/AuthPageShell.tsx +32 -32
  90. package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +1 -3
  91. package/templates/src/features/auth/ui/ResetPasswordContent.tsx +6 -2
  92. package/templates/src/features/auth/ui/VerifyEmailContent.tsx +2 -6
  93. package/templates/src/features/home/data/mock.ts +41 -35
  94. package/templates/src/features/home/ui/HomeContent.tsx +62 -64
  95. package/templates/src/features/template/ui/CrudTemplate.tsx +1 -4
  96. package/templates/src/features/template/ui/LoginTemplate.tsx +1 -1
  97. package/templates/src/features/template/ui/TemplateContent.tsx +28 -20
  98. package/templates/src/locales/en/pages/templates.json +17 -17
  99. package/templates/src/locales/es/pages/templates.json +17 -17
  100. package/templates/src/locales/pt-BR/pages/templates.json +17 -17
  101. package/templates/src/pages/AssistantPage.tsx +26 -20
  102. package/templates/src/pages/HomePage.tsx +5 -1
  103. package/templates/src/shared/error-boundary.tsx +150 -154
  104. package/templates/src/shared/error-fallbacks.tsx +222 -226
  105. package/templates/vite.config.ts +12 -9
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Xertica UI
2
2
 
3
- > Enterprise-grade React design system — 97 components built on Tailwind CSS v4, Radix UI, and Lucide Icons. Designed to be consumed directly by AI coding agents (LLMs, Cursor, Claude Code, Copilot). v2.3.0
3
+ > Enterprise-grade React design system — 97 components built on Tailwind CSS v4, Radix UI, and Lucide Icons. Designed to be consumed directly by AI coding agents (LLMs, Cursor, Claude Code, Copilot). v2.4.1
4
4
 
5
5
  Xertica UI ships with full source, typed declarations, Storybook stories, and structured documentation inside `node_modules/xertica-ui/`. AI agents should read this file first, then follow links for component-specific detail.
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xertica-ui",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "Xertica UI — Enterprise-grade React design system with Tailwind CSS v4, Radix UI, and AI-first documentation.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs.js",
@@ -1,84 +1,90 @@
1
- /* ============================================
2
- 🧱 BASE STYLES
3
- ============================================ */
4
-
5
- @import 'tailwindcss';
6
- @plugin "tailwindcss-animate";
7
-
8
- @custom-variant dark (&:is(.dark *));
9
- @custom-variant mobile (@media (max-width: 767px));
10
-
11
- @layer base {
12
- * {
13
- @apply border-border outline-ring/50;
14
- }
15
-
16
- body {
17
- @apply bg-background text-foreground;
18
- font-family: 'Roboto', sans-serif;
19
- }
20
-
21
- html {
22
- font-size: var(--font-size);
23
- }
24
-
25
- h1 {
26
- font-size: var(--text-h1);
27
- font-weight: var(--font-weight-extrabold);
28
- line-height: 1.2;
29
- }
30
-
31
- h2 {
32
- font-size: var(--text-h2);
33
- font-weight: var(--font-weight-semibold);
34
- line-height: 1.2;
35
- }
36
-
37
- h3 {
38
- font-size: var(--text-h3);
39
- font-weight: var(--font-weight-semibold);
40
- line-height: 1.2;
41
- }
42
-
43
- h4 {
44
- font-size: var(--text-h4);
45
- font-weight: var(--font-weight-semibold);
46
- line-height: 1.2;
47
- }
48
-
49
- p {
50
- font-size: var(--text-p);
51
- font-weight: var(--font-weight-regular);
52
- line-height: 1.5;
53
- }
54
-
55
- label {
56
- font-size: var(--text-label);
57
- font-weight: var(--font-weight-semibold);
58
- line-height: 1.3;
59
- }
60
-
61
- button {
62
- font-size: var(--text-small);
63
- font-weight: var(--font-weight-medium);
64
- line-height: 1.4;
65
- }
66
-
67
- input {
68
- font-size: var(--text-small);
69
- font-weight: var(--font-weight-regular);
70
- line-height: 1.4;
71
- }
72
- }
73
-
74
- @layer utilities {
75
- /* Global mobile content padding utility class.
76
- Add class="content-area" to any wrapper to receive the mobile padding.
77
- Change --mobile-content-padding in tokens.css to adjust globally. */
78
- @media (max-width: 767px) {
79
- .content-area {
80
- padding-left: var(--mobile-content-padding);
81
- padding-right: var(--mobile-content-padding);
82
- }
83
- }
84
- }
1
+ /* ============================================
2
+ 🧱 BASE STYLES
3
+ ============================================ */
4
+
5
+ @import 'tailwindcss';
6
+ @plugin "tailwindcss-animate";
7
+
8
+ @custom-variant dark (&:is(.dark *));
9
+ @custom-variant mobile (@media (max-width: 767px));
10
+
11
+ @layer base {
12
+ * {
13
+ @apply border-border outline-ring/50;
14
+ }
15
+
16
+ html,
17
+ body {
18
+ height: 100%;
19
+ overflow: hidden;
20
+ }
21
+
22
+ body {
23
+ @apply bg-background text-foreground;
24
+ font-family: 'Roboto', sans-serif;
25
+ }
26
+
27
+ html {
28
+ font-size: var(--font-size);
29
+ }
30
+
31
+ h1 {
32
+ font-size: var(--text-h1);
33
+ font-weight: var(--font-weight-extrabold);
34
+ line-height: 1.2;
35
+ }
36
+
37
+ h2 {
38
+ font-size: var(--text-h2);
39
+ font-weight: var(--font-weight-semibold);
40
+ line-height: 1.2;
41
+ }
42
+
43
+ h3 {
44
+ font-size: var(--text-h3);
45
+ font-weight: var(--font-weight-semibold);
46
+ line-height: 1.2;
47
+ }
48
+
49
+ h4 {
50
+ font-size: var(--text-h4);
51
+ font-weight: var(--font-weight-semibold);
52
+ line-height: 1.2;
53
+ }
54
+
55
+ p {
56
+ font-size: var(--text-p);
57
+ font-weight: var(--font-weight-regular);
58
+ line-height: 1.5;
59
+ }
60
+
61
+ label {
62
+ font-size: var(--text-label);
63
+ font-weight: var(--font-weight-semibold);
64
+ line-height: 1.3;
65
+ }
66
+
67
+ button {
68
+ font-size: var(--text-small);
69
+ font-weight: var(--font-weight-medium);
70
+ line-height: 1.4;
71
+ }
72
+
73
+ input {
74
+ font-size: var(--text-small);
75
+ font-weight: var(--font-weight-regular);
76
+ line-height: 1.4;
77
+ }
78
+ }
79
+
80
+ @layer utilities {
81
+ /* Global mobile content padding utility class.
82
+ Add class="content-area" to any wrapper to receive the mobile padding.
83
+ Change --mobile-content-padding in tokens.css to adjust globally. */
84
+ @media (max-width: 767px) {
85
+ .content-area {
86
+ padding-left: var(--mobile-content-padding);
87
+ padding-right: var(--mobile-content-padding);
88
+ }
89
+ }
90
+ }
@@ -23,6 +23,7 @@ This project follows **FSD (Feature-Sliced Design)** layered architecture combin
23
23
  - **`src/pages/`**: Thin route wrappers composing `AppLayout` + feature content. No logic or API calls here.
24
24
 
25
25
  ### Adding a New Route
26
+
26
27
  1. **Create the feature content**: `src/features/<name>/ui/<NameContent>.tsx`
27
28
  2. **Export from the barrel**: `src/features/<name>/index.ts`
28
29
  3. **Create the page**: `src/pages/<NamePage>.tsx` (thin AppLayout wrapper)
@@ -34,6 +35,7 @@ This project follows **FSD (Feature-Sliced Design)** layered architecture combin
34
35
  ## Import Rules
35
36
 
36
37
  Always use the correct `xertica-ui` subpath:
38
+
37
39
  - **UI Primitives**: `xertica-ui/ui` (e.g. `Button`, `Card`, `Input`, `Badge`, `Table`, `Dialog`, `Select`)
38
40
  - **Providers & Brand**: `xertica-ui/brand` (e.g. `XerticaProvider`, `XerticaLogo`, `ThemeToggle`, `LanguageSelector`)
39
41
  - **Navigation Shell**: `xertica-ui/layout` (e.g. `Sidebar`, `Header`)
@@ -49,7 +51,9 @@ Icons always come from `lucide-react` — never from `xertica-ui`.
49
51
  ## Non-Negotiable Coding Rules
50
52
 
51
53
  ### HTML & Radix Elements
54
+
52
55
  Never use native HTML interactive elements where design system components exist:
56
+
53
57
  - `<Button>` instead of `<button>`
54
58
  - `<Input>` instead of `<input>`
55
59
  - `<Select>` instead of `<select>`
@@ -58,19 +62,24 @@ Never use native HTML interactive elements where design system components exist:
58
62
  - `<ScrollArea>` instead of custom scrollbars
59
63
 
60
64
  ### Color & Border Radius Styling
65
+
61
66
  - **Never use raw color values** (`#hex`, `rgb()`, `hsl()`) or inline styles for theming.
62
67
  - **Never use standard Tailwind color classes for semantic/status contexts** (e.g., do not use `bg-red-500` or `text-green-500` for status/feedback/errors). Use semantic tokens: `bg-destructive`/`text-destructive` for errors, `bg-success` for success, `bg-warning` for warnings, `bg-info` for info.
63
68
  - **Tailwind colors** (`bg-blue-500`, `text-gray-700`) are allowed **only** for layout, spacing, and general non-semantic UI where no semantic token applies.
64
69
  - **Borders**: Always use `rounded-[var(--radius)]` — never use fixed radius classes like `rounded-lg` or `rounded-xl`.
65
70
 
66
71
  ### Layout State
72
+
67
73
  Never hardcode sidebar or layout widths (e.g. `256px`). Read them dynamically:
74
+
68
75
  - `const { sidebarExpanded, sidebarWidth } = useLayout()`
69
76
  - For components that might render outside the layout provider:
70
77
  `const layout = useOptionalLayout(); const fallbackSidebarWidth = layout?.sidebarWidth ?? 80;`
71
78
 
72
79
  ### Confirm Destructive Actions
80
+
73
81
  Always wrap destructive actions in `<AlertDialog>` for confirmation:
82
+
74
83
  ```tsx
75
84
  <AlertDialog>
76
85
  <AlertDialogTrigger asChild>
@@ -90,11 +99,14 @@ Always wrap destructive actions in `<AlertDialog>` for confirmation:
90
99
  ```
91
100
 
92
101
  ### Toast Notifications
102
+
93
103
  Always use `toast` from `sonner` and translate messages via `t()`:
104
+
94
105
  ```tsx
95
106
  toast.success(t('common.saveSuccess'));
96
107
  toast.error(t('errors.somethingWentWrong'));
97
108
  ```
109
+
98
110
  Never render `<Toaster>` manually — it is auto-injected by `<XerticaProvider>`.
99
111
 
100
112
  ---
@@ -102,6 +114,7 @@ Never render `<Toaster>` manually — it is auto-injected by `<XerticaProvider>`
102
114
  ## Internationalization (i18n)
103
115
 
104
116
  Every user-facing string must go through `useTranslation()`:
117
+
105
118
  - Translate all labels, placeholders, titles, aria-labels, tooltips, toasts, etc.
106
119
  - Add translation keys to split JSON files in `src/locales/<lang>/` for all languages.
107
120
  - **Monolingual Mode**: `<LanguageSelector>` auto-hides when a single language is configured. Use `<LanguageSelector showWhenMonolingual />` to override.
@@ -124,7 +137,7 @@ Every user-facing string must go through `useTranslation()`:
124
137
  return useQuery({
125
138
  queryKey: ['home', 'feature-cards', language],
126
139
  queryFn: fetchFeatureCards,
127
- staleTime: 10 * 60 * 1000
140
+ staleTime: 10 * 60 * 1000,
128
141
  });
129
142
  ```
130
143
 
@@ -133,6 +146,7 @@ Every user-facing string must go through `useTranslation()`:
133
146
  ## Loading States (Skeletons)
134
147
 
135
148
  Always render matching skeletons instead of spinners for data loading:
149
+
136
150
  - Wrap cards/lists with their matching FSD skeleton companions (`ActivityCardSkeleton`, `ProfileCardSkeleton`, `ProjectCardSkeleton`, `NotificationCardSkeleton`, `QuickActionCardSkeleton`, `FeatureCardSkeleton`, `StatsCardSkeleton`).
137
151
  - Pass `rows` to skeletons (e.g. `<ActivityCardSkeleton rows={5} />`) to match the loaded state height.
138
152
  - For tables, build loading rows using `<TableRow>` + `<TableCell>` + `<Skeleton className="h-3.5 w-28" />`.
@@ -149,6 +163,7 @@ Always render matching skeletons instead of spinners for data loading:
149
163
  ## Authentication Pattern
150
164
 
151
165
  The auth flow is managed in `src/app/context/AuthContext.tsx` via the `useAuth()` hook:
166
+
152
167
  - `getStoredUser()` / `storeUser()` / `clearStoredUser()` — from `src/shared/lib/auth.ts`
153
168
  - Auth pages (`/login`, `/forgot-password`, etc.) redirect to `/home` when user is already logged in
154
169
  - Protected routes use `<ProtectedRoute>` wrapper that redirects to `/login` if user is null
@@ -302,11 +302,11 @@ Every user-facing string must come from `useTranslation()`. Add new keys to the
302
302
 
303
303
  ```tsx
304
304
  // ❌ Wrong
305
- <Button aria-label="Save">Save</Button>
305
+ <Button aria-label="Save">Save</Button>;
306
306
 
307
307
  // ✅ Correct
308
308
  const { t } = useTranslation();
309
- <Button aria-label={t('common.save')}>{t('common.save')}</Button>
309
+ <Button aria-label={t('common.save')}>{t('common.save')}</Button>;
310
310
  ```
311
311
 
312
312
  This applies to `aria-label`, `placeholder`, `title`, toast messages, error text, dropdown items — everything the user can read.
@@ -340,6 +340,7 @@ npx xertica-ui update
340
340
  ```
341
341
 
342
342
  The CLI shows your current selection, prompts for the new set, displays the diff (`+ es`, `- en`), and on confirm:
343
+
343
344
  - copies any newly-added locale JSONs from `node_modules/xertica-ui/templates/src/locales/`
344
345
  - removes JSONs of unselected languages
345
346
  - regenerates `src/i18n.ts` and `src/app/App.tsx`
@@ -347,6 +348,24 @@ The CLI shows your current selection, prompts for the new set, displays the diff
347
348
 
348
349
  > The `update` → **Project files** flow also reads `.languages.json` and preserves your selection. Updating App.tsx and i18n.ts won't reset your languages.
349
350
 
351
+ ### Enabling or disabling Dark Mode support later
352
+
353
+ ```bash
354
+ npx xertica-ui update
355
+ # → select "Dark Mode"
356
+ ```
357
+
358
+ The CLI reads your current setting from `.xertica.json` and prompts you to enable or disable dark mode. On confirmation, it updates `.xertica.json` and regenerates `App.tsx` to set `disableDarkMode={true}` or `false`.
359
+
360
+ ### Adding or removing the AI Assistant later
361
+
362
+ ```bash
363
+ npx xertica-ui update
364
+ # → select "Assistant"
365
+ ```
366
+
367
+ The CLI detects whether the assistant is currently active (via `.xertica.json` or page presence) and prompts to add or remove the feature. It automatically copies/deletes the feature files and updates `AuthGuard.tsx`, `HomePage.tsx`, and `TemplatePage.tsx` to reflect the change.
368
+
350
369
  ### Using translations in components
351
370
 
352
371
  ```tsx
@@ -385,10 +404,13 @@ export function getMockOptions() {
385
404
  Inside components, build label maps for enums with `useMemo([..., t])`:
386
405
 
387
406
  ```tsx
388
- const statusLabels = useMemo(() => ({
389
- active: t('status.active'),
390
- inactive: t('status.inactive'),
391
- }), [t]);
407
+ const statusLabels = useMemo(
408
+ () => ({
409
+ active: t('status.active'),
410
+ inactive: t('status.inactive'),
411
+ }),
412
+ [t]
413
+ );
392
414
  ```
393
415
 
394
416
  ---
@@ -447,9 +469,9 @@ Every card pattern ships with a matching `*Skeleton`:
447
469
  ```tsx
448
470
  import { ActivityCard, ActivityCardSkeleton } from 'xertica-ui';
449
471
 
450
- {isLoading
451
- ? <ActivityCardSkeleton rows={5} />
452
- : <ActivityCard items={items} />}
472
+ {
473
+ isLoading ? <ActivityCardSkeleton rows={5} /> : <ActivityCard items={items} />;
474
+ }
453
475
  ```
454
476
 
455
477
  Available skeleton companions: `ActivityCardSkeleton`, `ProfileCardSkeleton`, `ProjectCardSkeleton`, `NotificationCardSkeleton`, `QuickActionCardSkeleton`, `FeatureCardSkeleton`, `StatsCardSkeleton`.
@@ -457,15 +479,17 @@ Available skeleton companions: `ActivityCardSkeleton`, `ProfileCardSkeleton`, `P
457
479
  For grids, render one skeleton per expected card:
458
480
 
459
481
  ```tsx
460
- {isLoading ? (
461
- <>
462
- <FeatureCardSkeleton showAction />
463
- <FeatureCardSkeleton showAction />
464
- <FeatureCardSkeleton showAction />
465
- </>
466
- ) : (
467
- data.map(item => <FeatureCard key={item.id} {...item} />)
468
- )}
482
+ {
483
+ isLoading ? (
484
+ <>
485
+ <FeatureCardSkeleton showAction />
486
+ <FeatureCardSkeleton showAction />
487
+ <FeatureCardSkeleton showAction />
488
+ </>
489
+ ) : (
490
+ data.map(item => <FeatureCard key={item.id} {...item} />)
491
+ );
492
+ }
469
493
  ```
470
494
 
471
495
  For tables, render skeleton rows with the `<Skeleton>` primitive (from `xertica-ui/ui`):
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xertica-ui-template",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -24,7 +24,7 @@
24
24
  "react-i18next": "^17.0.8",
25
25
  "react-router-dom": "^7.1.3",
26
26
  "sonner": "^1.7.3",
27
- "xertica-ui": "^2.3.0",
27
+ "xertica-ui": "^2.4.1",
28
28
  "zustand": "^5.0.13"
29
29
  },
30
30
  "devDependencies": {
@@ -66,4 +66,4 @@
66
66
  "vite": "6.3.5"
67
67
  }
68
68
  }
69
- }
69
+ }
@@ -1,82 +1,131 @@
1
- import React from 'react';
2
- import { Routes, Route, Navigate } from 'react-router-dom';
3
- import { useAuth } from '../context/AuthContext';
4
-
5
- // ─── Lazy page imports ────────────────────────────────────────────────────────
6
- // Each route bundle is loaded only when the route is first visited.
7
-
8
- const LoginPage = React.lazy(() =>
9
- import('../../pages/LoginPage').then(m => ({ default: m.LoginPage }))
10
- );
11
- const ForgotPasswordPage = React.lazy(() =>
12
- import('../../pages/ForgotPasswordPage').then(m => ({ default: m.ForgotPasswordPage }))
13
- );
14
- const VerifyEmailPage = React.lazy(() =>
15
- import('../../pages/VerifyEmailPage').then(m => ({ default: m.VerifyEmailPage }))
16
- );
17
- const ResetPasswordPage = React.lazy(() =>
18
- import('../../pages/ResetPasswordPage').then(m => ({ default: m.ResetPasswordPage }))
19
- );
20
- const HomePage = React.lazy(() =>
21
- import('../../pages/HomePage').then(m => ({ default: m.HomePage }))
22
- );
23
- const TemplatePage = React.lazy(() =>
24
- import('../../pages/TemplatePage').then(m => ({ default: m.TemplatePage }))
25
- );
26
- const AssistantPage = React.lazy(() =>
27
- import('../../pages/AssistantPage').then(m => ({ default: m.AssistantPage }))
28
- );
29
-
30
- // ─── Route guards ─────────────────────────────────────────────────────────────
31
-
32
- /** Redirects unauthenticated visitors to /login. */
33
- function ProtectedRoute({ children }: { children: React.ReactNode }) {
34
- const { user, isLoading } = useAuth();
35
- if (isLoading) return null;
36
- if (!user) return <Navigate to="/login" replace />;
37
- return <>{children}</>;
38
- }
39
-
40
- /** Redirects authenticated users away from auth-only pages. */
41
- function GuestRoute({ children }: { children: React.ReactNode }) {
42
- const { user, isLoading } = useAuth();
43
- if (isLoading) return null;
44
- if (user) return <Navigate to="/home" replace />;
45
- return <>{children}</>;
46
- }
47
-
48
- // ─── Bridge for LoginPage ─────────────────────────────────────────────────────
49
- // LoginPage expects an onLogin prop — this bridge wires AuthContext.login to it,
50
- // keeping the LoginPage component decoupled from the AuthContext.
51
-
52
- function LoginPageWithAuth() {
53
- const { login } = useAuth();
54
- return <LoginPage onLogin={login} />;
55
- }
56
-
57
- // ─── Route tree ───────────────────────────────────────────────────────────────
58
-
59
- export function AuthGuard() {
60
- const { user } = useAuth();
61
-
62
- return (
63
- <div className="min-h-screen bg-muted overflow-x-hidden max-w-full">
64
- <Routes>
65
- {/* Auth pages — redirect to /home when already signed in */}
66
- <Route path="/login" element={<GuestRoute><LoginPageWithAuth /></GuestRoute>} />
67
- <Route path="/forgot-password" element={<GuestRoute><ForgotPasswordPage /></GuestRoute>} />
68
- <Route path="/verify-email" element={<GuestRoute><VerifyEmailPage /></GuestRoute>} />
69
- <Route path="/reset-password" element={<GuestRoute><ResetPasswordPage /></GuestRoute>} />
70
-
71
- {/* Protected pages */}
72
- <Route path="/home" element={<ProtectedRoute><HomePage /></ProtectedRoute>} />
73
- <Route path="/template" element={<ProtectedRoute><TemplatePage /></ProtectedRoute>} />
74
- <Route path="/assistente" element={<ProtectedRoute><AssistantPage /></ProtectedRoute>} />
75
-
76
- {/* Fallback */}
77
- <Route path="/" element={<Navigate to={user ? '/home' : '/login'} replace />} />
78
- <Route path="*" element={<Navigate to={user ? '/home' : '/login'} replace />} />
79
- </Routes>
80
- </div>
81
- );
82
- }
1
+ import React from 'react';
2
+ import { Routes, Route, Navigate } from 'react-router-dom';
3
+ import { useAuth } from '../context/AuthContext';
4
+
5
+ // ─── Lazy page imports ────────────────────────────────────────────────────────
6
+ // Each route bundle is loaded only when the route is first visited.
7
+
8
+ const LoginPage = React.lazy(() =>
9
+ import('../../pages/LoginPage').then(m => ({ default: m.LoginPage }))
10
+ );
11
+ const ForgotPasswordPage = React.lazy(() =>
12
+ import('../../pages/ForgotPasswordPage').then(m => ({ default: m.ForgotPasswordPage }))
13
+ );
14
+ const VerifyEmailPage = React.lazy(() =>
15
+ import('../../pages/VerifyEmailPage').then(m => ({ default: m.VerifyEmailPage }))
16
+ );
17
+ const ResetPasswordPage = React.lazy(() =>
18
+ import('../../pages/ResetPasswordPage').then(m => ({ default: m.ResetPasswordPage }))
19
+ );
20
+ const HomePage = React.lazy(() =>
21
+ import('../../pages/HomePage').then(m => ({ default: m.HomePage }))
22
+ );
23
+ const TemplatePage = React.lazy(() =>
24
+ import('../../pages/TemplatePage').then(m => ({ default: m.TemplatePage }))
25
+ );
26
+ const AssistantPage = React.lazy(() =>
27
+ import('../../pages/AssistantPage').then(m => ({ default: m.AssistantPage }))
28
+ );
29
+
30
+ // ─── Route guards ─────────────────────────────────────────────────────────────
31
+
32
+ /** Redirects unauthenticated visitors to /login. */
33
+ function ProtectedRoute({ children }: { children: React.ReactNode }) {
34
+ const { user, isLoading } = useAuth();
35
+ if (isLoading) return null;
36
+ if (!user) return <Navigate to="/login" replace />;
37
+ return <>{children}</>;
38
+ }
39
+
40
+ /** Redirects authenticated users away from auth-only pages. */
41
+ function GuestRoute({ children }: { children: React.ReactNode }) {
42
+ const { user, isLoading } = useAuth();
43
+ if (isLoading) return null;
44
+ if (user) return <Navigate to="/home" replace />;
45
+ return <>{children}</>;
46
+ }
47
+
48
+ // ─── Bridge for LoginPage ─────────────────────────────────────────────────────
49
+ // LoginPage expects an onLogin prop — this bridge wires AuthContext.login to it,
50
+ // keeping the LoginPage component decoupled from the AuthContext.
51
+
52
+ function LoginPageWithAuth() {
53
+ const { login } = useAuth();
54
+ return <LoginPage onLogin={login} />;
55
+ }
56
+
57
+ // ─── Route tree ───────────────────────────────────────────────────────────────
58
+
59
+ export function AuthGuard() {
60
+ const { user } = useAuth();
61
+
62
+ return (
63
+ <div className="h-screen bg-muted overflow-hidden max-w-full">
64
+ <Routes>
65
+ {/* Auth pages — redirect to /home when already signed in */}
66
+ <Route
67
+ path="/login"
68
+ element={
69
+ <GuestRoute>
70
+ <LoginPageWithAuth />
71
+ </GuestRoute>
72
+ }
73
+ />
74
+ <Route
75
+ path="/forgot-password"
76
+ element={
77
+ <GuestRoute>
78
+ <ForgotPasswordPage />
79
+ </GuestRoute>
80
+ }
81
+ />
82
+ <Route
83
+ path="/verify-email"
84
+ element={
85
+ <GuestRoute>
86
+ <VerifyEmailPage />
87
+ </GuestRoute>
88
+ }
89
+ />
90
+ <Route
91
+ path="/reset-password"
92
+ element={
93
+ <GuestRoute>
94
+ <ResetPasswordPage />
95
+ </GuestRoute>
96
+ }
97
+ />
98
+
99
+ {/* Protected pages */}
100
+ <Route
101
+ path="/home"
102
+ element={
103
+ <ProtectedRoute>
104
+ <HomePage />
105
+ </ProtectedRoute>
106
+ }
107
+ />
108
+ <Route
109
+ path="/template"
110
+ element={
111
+ <ProtectedRoute>
112
+ <TemplatePage />
113
+ </ProtectedRoute>
114
+ }
115
+ />
116
+ <Route
117
+ path="/assistente"
118
+ element={
119
+ <ProtectedRoute>
120
+ <AssistantPage />
121
+ </ProtectedRoute>
122
+ }
123
+ />
124
+
125
+ {/* Fallback */}
126
+ <Route path="/" element={<Navigate to={user ? '/home' : '/login'} replace />} />
127
+ <Route path="*" element={<Navigate to={user ? '/home' : '/login'} replace />} />
128
+ </Routes>
129
+ </div>
130
+ );
131
+ }