xertica-ui 2.9.8 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/components/layout/sidebar/sidebar.stories.tsx +9 -9
  4. package/components/layout/sidebar/sidebar.tsx +5 -5
  5. package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +19 -5
  6. package/components/pages/login-page/LoginPage.tsx +19 -5
  7. package/components/pages/reset-password-page/ResetPasswordPage.tsx +15 -2
  8. package/components/pages/verify-email-page/VerifyEmailPage.tsx +17 -3
  9. package/contexts/LayoutContext.tsx +1 -1
  10. package/dist/AssistenteContext-Bm8_RJTB.js +200 -0
  11. package/dist/AssistenteContext-CLK8tgdt.cjs +205 -0
  12. package/dist/AudioPlayer-C93eMH6U.cjs +1282 -0
  13. package/dist/AudioPlayer-DSYSMpXw.js +1278 -0
  14. package/dist/BrandColorsContext-D3Y0CYZF.js +643 -0
  15. package/dist/BrandColorsContext-SeOZd2Cq.cjs +646 -0
  16. package/dist/LayoutContext-BLoSFOuO.cjs +96 -0
  17. package/dist/LayoutContext-BqtKcSlN.js +92 -0
  18. package/dist/VerifyEmailPage-BInYcjRd.cjs +3305 -0
  19. package/dist/VerifyEmailPage-CEtb3BJC.cjs +3305 -0
  20. package/dist/VerifyEmailPage-Z-cA_IIo.js +3295 -0
  21. package/dist/VerifyEmailPage-cg4USiDN.js +3295 -0
  22. package/dist/XerticaProvider-Ck996htf.cjs +47 -0
  23. package/dist/XerticaProvider-DUbkitXG.js +45 -0
  24. package/dist/XerticaProvider-Uvdhs7zH.cjs +47 -0
  25. package/dist/XerticaProvider-kE5l0MQn.js +45 -0
  26. package/dist/brand.cjs.js +1 -1
  27. package/dist/brand.es.js +1 -1
  28. package/dist/components/layout/sidebar/sidebar.d.ts +1 -1
  29. package/dist/components/pages/forgot-password-page/ForgotPasswordPage.d.ts +4 -3
  30. package/dist/components/pages/login-page/LoginPage.d.ts +4 -3
  31. package/dist/components/pages/verify-email-page/VerifyEmailPage.d.ts +2 -1
  32. package/dist/hooks.cjs.js +8 -7
  33. package/dist/hooks.es.js +5 -4
  34. package/dist/index.cjs.js +5 -5
  35. package/dist/index.es.js +5 -5
  36. package/dist/layout.cjs.js +1 -1
  37. package/dist/layout.es.js +1 -1
  38. package/dist/media.cjs.js +1 -1
  39. package/dist/media.es.js +1 -1
  40. package/dist/pages.cjs.js +1 -1
  41. package/dist/pages.es.js +1 -1
  42. package/dist/sidebar-BCjbD0Pi.cjs +1154 -0
  43. package/dist/sidebar-Dg1TVmaI.js +1151 -0
  44. package/dist/use-audio-player-BjC4swHi.cjs +190 -0
  45. package/dist/use-audio-player-D56MYa5x.js +188 -0
  46. package/dist/xertica-ui.css +1 -1
  47. package/docs/ai-usage.md +1 -1
  48. package/docs/components/sidebar.md +4 -4
  49. package/docs/layout.md +1 -1
  50. package/package.json +1 -1
  51. package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +15 -2
  52. package/templates/src/features/auth/ui/LoginContent.tsx +15 -2
  53. package/templates/src/features/auth/ui/ResetPasswordContent.tsx +15 -2
  54. package/templates/src/features/auth/ui/VerifyEmailContent.tsx +15 -2
package/CHANGELOG.md CHANGED
@@ -11,6 +11,27 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
11
 
12
12
  ---
13
13
 
14
+ ## [3.0.0] — 2026-07-17
15
+
16
+ ### Fixed
17
+
18
+ - **Scroll da `Sidebar` aparecia com a barra nativa do navegador, "colorida" e destoante do fundo escuro** — os containers de navegação com scroll real (variante `assistant` e o acordeão de overflow no mobile) usavam `overflowY: 'auto'` puro, sem nenhum estilo de scrollbar. Corrigido aplicando as utilities nativas do Tailwind v4 `scrollbar-thin scrollbar-thumb-sidebar-border scrollbar-track-transparent`, usando o token `--sidebar-border` (que resolve para um overlay de baixa opacidade sobre o fundo da sidebar em todos os 10 temas de marca), deixando o scroll discreto em qualquer tema.
19
+ - **Botão de filtro da `Sidebar.Search` perdia contraste no hover** — herdava o `hover:text-accent-foreground` (cor escura, pensada para superfícies claras) da variante `ghost` do `Button`, por sobrescrever apenas o `hover:bg-*` e não o `hover:text-*`. Corrigido adicionando `hover:text-sidebar-foreground` explicitamente, no mesmo padrão já usado pelo botão de "mais opções".
20
+
21
+ ### Changed
22
+
23
+ - **Largura padrão da `Sidebar` expandida aumentada de 256px para 320px** (`LayoutContext`), com o fallback standalone de `Sidebar.Root` (fora de um `LayoutProvider`) também atualizado de 280px para 320px para manter os dois caminhos consistentes.
24
+
25
+ ---
26
+
27
+ ## [2.9.9] — 2026-07-14
28
+
29
+ ### Fixed
30
+
31
+ - **Isotipo da marca e a inversão de cores apareciam nas telas de auth em qualquer tema, não só no "Xertica"** — os commits que introduziram o isotipo (`31e3bb51`, `7f047240`) trocaram o `leftPanel` do `AuthPageShell` para `"isotype"` de forma incondicional nas 4 páginas de autenticação (`LoginPage`, `ForgotPasswordPage`, `ResetPasswordPage`, `VerifyEmailPage`), então o painel esquerdo sempre exibia a marca e a inversão de cores (`bg-card`/`bg-muted`) associada a ela, mesmo com qualquer outro tema de cor ativo — inclusive o `xertica-original`, o tema clássico padrão. Corrigido lendo `currentTheme` via `useBrandColors()` em cada página e só usando `leftPanel="isotype"` quando `currentTheme === 'xertica'`; para os demais temas, o painel volta a exibir a imagem de capa e o gradiente originais (recuperados do histórico do git, anteriores ao rebranding). Aplicado tanto nos componentes reais (`components/pages/*-page`) quanto nos templates espelho usados pelo CLI (`templates/src/features/auth/ui/*Content.tsx`).
32
+
33
+ ---
34
+
14
35
  ## [2.9.8] — 2026-07-14
15
36
 
16
37
  ### Fixed
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Enterprise-grade React design system** built on Tailwind CSS v4, Radix UI, and Lucide Icons — with a robust AI-first documentation layer for precise LLM-driven composition and autonomous agent interaction.
4
4
 
5
- [![npm version](https://img.shields.io/badge/npm-2.9.8-blue)](https://www.npmjs.com/package/xertica-ui)
5
+ [![npm version](https://img.shields.io/badge/npm-3.0.0-blue)](https://www.npmjs.com/package/xertica-ui)
6
6
  [![license](https://img.shields.io/badge/license-proprietary-red)](./LICENSE)
7
7
 
8
8
  ---
@@ -86,8 +86,8 @@ export const Default: Story = {
86
86
  const { sidebarWidth: contextWidth } = useLayout();
87
87
  const [isExpanded, setIsExpanded] = useState(args.expanded);
88
88
 
89
- // In Storybook, we match the sidebar widths (280 expanded, 80 collapsed)
90
- const currentWidth = isExpanded ? 280 : 80;
89
+ // In Storybook, we match the sidebar widths (320 expanded, 80 collapsed)
90
+ const currentWidth = isExpanded ? 320 : 80;
91
91
 
92
92
  return (
93
93
  <div
@@ -165,7 +165,7 @@ export const DefaultWithGroups: Story = {
165
165
  },
166
166
  render: args => {
167
167
  const [isExpanded, setIsExpanded] = useState(args.expanded);
168
- const currentWidth = isExpanded ? 280 : 80;
168
+ const currentWidth = isExpanded ? 320 : 80;
169
169
 
170
170
  return (
171
171
  <div
@@ -315,7 +315,7 @@ export const Assistant: Story = {
315
315
  },
316
316
  render: args => {
317
317
  const [isExpanded, setIsExpanded] = useState(args.expanded);
318
- const currentWidth = isExpanded ? 280 : 80;
318
+ const currentWidth = isExpanded ? 320 : 80;
319
319
 
320
320
  return (
321
321
  <div
@@ -350,7 +350,7 @@ export const Collapsed: Story = {
350
350
  },
351
351
  render: args => {
352
352
  const [isExpanded, setIsExpanded] = useState(args.expanded);
353
- const currentWidth = isExpanded ? 280 : 80;
353
+ const currentWidth = isExpanded ? 320 : 80;
354
354
 
355
355
  return (
356
356
  <div
@@ -384,7 +384,7 @@ export const NoFooter: Story = {
384
384
  },
385
385
  render: args => {
386
386
  const [isExpanded, setIsExpanded] = useState(args.expanded);
387
- const currentWidth = isExpanded ? 280 : 80;
387
+ const currentWidth = isExpanded ? 320 : 80;
388
388
 
389
389
  return (
390
390
  <div
@@ -614,7 +614,7 @@ export const WithSubitemsDesktop: Story = {
614
614
  },
615
615
  render: args => {
616
616
  const [isExpanded, setIsExpanded] = useState(true);
617
- const currentWidth = isExpanded ? 280 : 80;
617
+ const currentWidth = isExpanded ? 320 : 80;
618
618
 
619
619
  return (
620
620
  <div
@@ -880,7 +880,7 @@ export const CompoundComponentAPI: Story = {
880
880
  name: 'Compound Component API',
881
881
  render: () => {
882
882
  const [isExpanded, setIsExpanded] = React.useState(true);
883
- const currentWidth = isExpanded ? 280 : 80;
883
+ const currentWidth = isExpanded ? 320 : 80;
884
884
  const groups = [
885
885
  {
886
886
  id: 'main',
@@ -973,7 +973,7 @@ export const HeadlessHook: Story = {
973
973
  navigationGroups: resolvedGroups,
974
974
  } = useSidebar({ defaultExpanded: true, navigationGroups: groups });
975
975
 
976
- const width = expanded ? 280 : 80;
976
+ const width = expanded ? 320 : 80;
977
977
 
978
978
  return (
979
979
  <div
@@ -200,7 +200,7 @@ function useSidebarContext() {
200
200
  * Use this when building a fully custom sidebar layout.
201
201
  *
202
202
  * @example
203
- * <Sidebar.Root expanded={expanded} onToggle={toggle} width={280}>
203
+ * <Sidebar.Root expanded={expanded} onToggle={toggle} width={320}>
204
204
  * <Sidebar.Header logo={<MyLogo />} />
205
205
  * <Sidebar.Nav navigationGroups={groups} />
206
206
  * <Sidebar.Footer user={user} onLogout={logout} />
@@ -231,7 +231,7 @@ function SidebarRoot({
231
231
  expandedProp !== undefined ? expandedProp : (layoutContext?.sidebarExpanded ?? localExpanded);
232
232
  const onToggle =
233
233
  onToggleProp || layoutContext?.toggleSidebar || (() => setLocalExpanded(prev => !prev));
234
- const width = widthProp !== undefined ? widthProp : (layoutContext?.sidebarWidth ?? 280);
234
+ const width = widthProp !== undefined ? widthProp : (layoutContext?.sidebarWidth ?? 320);
235
235
  const navigate =
236
236
  navigateProp ||
237
237
  ((path: string) => {
@@ -925,7 +925,7 @@ function SidebarNav({
925
925
  return (
926
926
  <div className="flex-1 min-h-0 overflow-hidden">
927
927
  <div
928
- className="h-full px-4"
928
+ className="h-full px-4 scrollbar-thin scrollbar-thumb-sidebar-border scrollbar-track-transparent"
929
929
  style={{ overflowY: 'auto', overflowX: 'hidden' }}
930
930
  >
931
931
  {navigationGroups.map(group => renderAssistantGroup(group))}
@@ -938,7 +938,7 @@ function SidebarNav({
938
938
  return (
939
939
  <div className="flex-1 min-h-0 overflow-hidden">
940
940
  <div
941
- className="h-full"
941
+ className="h-full scrollbar-thin scrollbar-thumb-sidebar-border scrollbar-track-transparent"
942
942
  style={{ overflowY: 'auto', overflowX: 'hidden' }}
943
943
  >
944
944
  <nav
@@ -1215,7 +1215,7 @@ function SidebarSearch({
1215
1215
  size="icon"
1216
1216
  onClick={() => setIsFilterOpen(!isFilterOpen)}
1217
1217
  className={cn(
1218
- 'h-9 w-9 text-sidebar-foreground transition-all duration-200',
1218
+ 'h-9 w-9 text-sidebar-foreground hover:text-sidebar-foreground transition-all duration-200',
1219
1219
  isFilterOpen ? 'bg-sidebar-foreground/20' : 'hover:bg-sidebar-foreground/15'
1220
1220
  )}
1221
1221
  aria-label={isFilterOpen ? t('sidebar.closeFilters') : t('sidebar.openFilters')}
@@ -7,22 +7,25 @@ import { AuthPageShell, SocialLoginButtons } from '../../blocks/auth';
7
7
  import { ArrowLeft } from 'lucide-react';
8
8
  import { useNavigate } from 'react-router-dom';
9
9
  import { useTranslation } from 'react-i18next';
10
+ import { useBrandColors } from '../../../contexts/BrandColorsContext';
10
11
 
11
12
  /**
12
13
  * Forgot Password Page component.
13
14
  *
14
15
  * @description
15
16
  * A screen that allows users to request a password reset link via email.
16
- * Includes the shared brand isotype side-panel (desktop) and alternative
17
- * social login options similar to the main Login page.
17
+ * Includes a side-panel (desktop) and alternative social login options
18
+ * similar to the main Login page.
18
19
  *
19
20
  * @ai-rules
20
21
  * 1. Navigation: Successfully submitting the email triggers a navigation to `/verify-email`.
21
- * 2. Visuals: Keeps consistency with `LoginPage` in terms of layout and branding.
22
+ * 2. Visuals: Keeps consistency with `LoginPage` in terms of layout and branding, including
23
+ * the theme-conditional isotype/hero-image left panel.
22
24
  */
23
25
  export function ForgotPasswordPage() {
24
26
  const navigate = useNavigate();
25
27
  const { t } = useTranslation();
28
+ const { currentTheme } = useBrandColors();
26
29
  const [email, setEmail] = useState('');
27
30
  const [isLoading, setIsLoading] = useState(false);
28
31
 
@@ -43,8 +46,8 @@ export function ForgotPasswordPage() {
43
46
  // Wire your SSO/social provider integration here
44
47
  };
45
48
 
46
- return (
47
- <AuthPageShell leftPanel="isotype">
49
+ const content = (
50
+ <>
48
51
  <div className="text-center">
49
52
  <div className="flex items-center justify-center mb-4">
50
53
  <XerticaLogo className="h-12 w-auto text-primary dark:text-foreground" variant="theme" />
@@ -86,6 +89,17 @@ export function ForgotPasswordPage() {
86
89
  </form>
87
90
 
88
91
  <SocialLoginButtons onSocialLogin={handleSocialLogin} />
92
+ </>
93
+ );
94
+
95
+ return currentTheme === 'xertica' ? (
96
+ <AuthPageShell leftPanel="isotype">{content}</AuthPageShell>
97
+ ) : (
98
+ <AuthPageShell
99
+ imageSrc="https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1200&h=800&fit=crop&auto=format"
100
+ imageAlt={t('forgotPassword.heroImageAlt')}
101
+ >
102
+ {content}
89
103
  </AuthPageShell>
90
104
  );
91
105
  }
@@ -6,6 +6,7 @@ import { XerticaLogo } from '../../brand/xertica-logo';
6
6
  import { AuthPageShell, SocialLoginButtons } from '../../blocks/auth';
7
7
  import { useNavigate } from 'react-router-dom';
8
8
  import { useTranslation } from 'react-i18next';
9
+ import { useBrandColors } from '../../../contexts/BrandColorsContext';
9
10
 
10
11
  /**
11
12
  * Props for the LoginPage component.
@@ -22,18 +23,20 @@ interface LoginPageProps {
22
23
  * Standard Login Page component.
23
24
  *
24
25
  * @description
25
- * A complete, responsive login screen featuring a brand isotype side-panel (on desktop)
26
- * and a focused authentication form. Supports email/password login and placeholders
26
+ * A complete, responsive login screen featuring a side-panel (on desktop) and a
27
+ * focused authentication form. Supports email/password login and placeholders
27
28
  * for Social/SSO providers (Google, MT Login, gov.br).
28
29
  *
29
30
  * @ai-rules
30
31
  * 1. State Management: You must provide the `onLogin` callback to handle the actual authentication logic.
31
- * 2. Visuals: Left panel renders `IsotypeTwist` via `AuthPageShell`'s `leftPanel="isotype"` no hero image on this page.
32
+ * 2. Visuals: Left panel renders the brand isotype only when the `xertica` theme is active
33
+ * (`AuthPageShell`'s `leftPanel="isotype"`); every other theme shows the original hero image.
32
34
  * 3. Routing: Uses `react-router` for navigation to the forgot-password flow.
33
35
  */
34
36
  export function LoginPage({ onLogin }: LoginPageProps) {
35
37
  const navigate = useNavigate();
36
38
  const { t } = useTranslation();
39
+ const { currentTheme } = useBrandColors();
37
40
  const [email, setEmail] = useState('');
38
41
  const [password, setPassword] = useState('');
39
42
  const [isLoading, setIsLoading] = useState(false);
@@ -61,8 +64,8 @@ export function LoginPage({ onLogin }: LoginPageProps) {
61
64
  onLogin('social@user.com', 'social-auth');
62
65
  };
63
66
 
64
- return (
65
- <AuthPageShell leftPanel="isotype">
67
+ const content = (
68
+ <>
66
69
  <div className="text-center">
67
70
  <div className="flex items-center justify-center mb-4">
68
71
  <XerticaLogo className="h-12 w-auto text-primary dark:text-foreground" variant="theme" />
@@ -117,6 +120,17 @@ export function LoginPage({ onLogin }: LoginPageProps) {
117
120
  </form>
118
121
 
119
122
  <SocialLoginButtons onSocialLogin={handleSocialLogin} />
123
+ </>
124
+ );
125
+
126
+ return currentTheme === 'xertica' ? (
127
+ <AuthPageShell leftPanel="isotype">{content}</AuthPageShell>
128
+ ) : (
129
+ <AuthPageShell
130
+ imageSrc="https://images.unsplash.com/photo-1551434678-e076c223a692?w=1200&h=800&fit=crop&auto=format"
131
+ imageAlt={t('login.heroImageAlt')}
132
+ >
133
+ {content}
120
134
  </AuthPageShell>
121
135
  );
122
136
  }
@@ -7,6 +7,7 @@ import { AuthPageShell, usePasswordStrength, PasswordStrengthMeter } from '../..
7
7
  import { ArrowLeft, CheckCircle2, AlertCircle } from 'lucide-react';
8
8
  import { useNavigate } from 'react-router-dom';
9
9
  import { useTranslation } from 'react-i18next';
10
+ import { useBrandColors } from '../../../contexts/BrandColorsContext';
10
11
 
11
12
  /**
12
13
  * Reset Password Page component.
@@ -24,6 +25,7 @@ import { useTranslation } from 'react-i18next';
24
25
  export function ResetPasswordPage() {
25
26
  const navigate = useNavigate();
26
27
  const { t } = useTranslation();
28
+ const { currentTheme } = useBrandColors();
27
29
  const [password, setPassword] = useState('');
28
30
  const [confirmPassword, setConfirmPassword] = useState('');
29
31
  const [isLoading, setIsLoading] = useState(false);
@@ -61,8 +63,8 @@ export function ResetPasswordPage() {
61
63
  setIsLoading(false);
62
64
  };
63
65
 
64
- return (
65
- <AuthPageShell leftPanel="isotype">
66
+ const content = (
67
+ <>
66
68
  <button
67
69
  onClick={() => navigate('/login')}
68
70
  className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
@@ -145,6 +147,17 @@ export function ResetPasswordPage() {
145
147
  {isLoading ? t('resetPassword.submitting') : t('resetPassword.submit')}
146
148
  </Button>
147
149
  </form>
150
+ </>
151
+ );
152
+
153
+ return currentTheme === 'xertica' ? (
154
+ <AuthPageShell leftPanel="isotype">{content}</AuthPageShell>
155
+ ) : (
156
+ <AuthPageShell
157
+ imageSrc="https://images.unsplash.com/photo-1555949963-aa79dcee981c?w=1200&h=800&fit=crop&auto=format"
158
+ imageAlt={t('resetPassword.heroImageAlt')}
159
+ >
160
+ {content}
148
161
  </AuthPageShell>
149
162
  );
150
163
  }
@@ -5,6 +5,7 @@ import { AuthPageShell, SocialLoginButtons } from '../../blocks/auth';
5
5
  import { ArrowLeft, Mail, CheckCircle2 } from 'lucide-react';
6
6
  import { useNavigate, useLocation } from 'react-router-dom';
7
7
  import { useTranslation } from 'react-i18next';
8
+ import { useBrandColors } from '../../../contexts/BrandColorsContext';
8
9
 
9
10
  /**
10
11
  * Email Verification Page component.
@@ -12,7 +13,8 @@ import { useTranslation } from 'react-i18next';
12
13
  * @description
13
14
  * An informative screen shown after a password reset request. It instructs
14
15
  * the user to check their email and provides a "Resend email" feature.
15
- * Includes the same consistent layout with the shared brand isotype side-panel.
16
+ * Includes the same consistent layout as the other auth pages, including the
17
+ * theme-conditional isotype/hero-image left panel.
16
18
  *
17
19
  * @ai-rules
18
20
  * 1. Data Source: It retrieves the email from the router's `location.state`.
@@ -23,6 +25,7 @@ export function VerifyEmailPage() {
23
25
  const navigate = useNavigate();
24
26
  const location = useLocation();
25
27
  const { t } = useTranslation();
28
+ const { currentTheme } = useBrandColors();
26
29
  const email = location.state?.email || 'your@email.com';
27
30
  const [isResending, setIsResending] = useState(false);
28
31
  const [resendSuccess, setResendSuccess] = useState(false);
@@ -45,8 +48,8 @@ export function VerifyEmailPage() {
45
48
  // Wire your SSO/social provider integration here
46
49
  };
47
50
 
48
- return (
49
- <AuthPageShell leftPanel="isotype">
51
+ const content = (
52
+ <>
50
53
  <div className="text-center">
51
54
  <div className="flex items-center justify-center mb-4">
52
55
  <XerticaLogo className="h-12 w-auto text-primary dark:text-foreground" variant="theme" />
@@ -96,6 +99,17 @@ export function VerifyEmailPage() {
96
99
  </div>
97
100
 
98
101
  <SocialLoginButtons onSocialLogin={handleSocialLogin} />
102
+ </>
103
+ );
104
+
105
+ return currentTheme === 'xertica' ? (
106
+ <AuthPageShell leftPanel="isotype">{content}</AuthPageShell>
107
+ ) : (
108
+ <AuthPageShell
109
+ imageSrc="https://images.unsplash.com/photo-1563986768609-322da13575f3?w=1200&h=800&fit=crop&auto=format"
110
+ imageAlt={t('verifyEmail.heroImageAlt')}
111
+ >
112
+ {content}
99
113
  </AuthPageShell>
100
114
  );
101
115
  }
@@ -62,7 +62,7 @@ export function LayoutProvider({ children }: { children: ReactNode }) {
62
62
  });
63
63
 
64
64
  const [assistenteExpanded, setAssistenteExpandedState] = useState(false);
65
- const [sidebarWidth, setSidebarWidth] = useState(256);
65
+ const [sidebarWidth, setSidebarWidth] = useState(320);
66
66
  const isMobile = useIsMobile();
67
67
 
68
68
  // Persist sidebar state
@@ -0,0 +1,200 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { createContext, useState, useEffect, useContext } from 'react';
3
+
4
+ const ApiKeyContext = createContext(void 0);
5
+ const readStorage = (key) => typeof window !== "undefined" ? window.localStorage?.getItem(key) : null;
6
+ const writeStorage = (key, value) => {
7
+ if (typeof window === "undefined") return;
8
+ window.localStorage?.setItem(key, value);
9
+ };
10
+ const removeStorage = (key) => {
11
+ if (typeof window === "undefined") return;
12
+ window.localStorage?.removeItem(key);
13
+ };
14
+ function ApiKeyProvider({
15
+ children,
16
+ initialApiKey,
17
+ initialGeminiApiKey,
18
+ initialGoogleMapsApiKey
19
+ }) {
20
+ const LEAKED_KEYS = [
21
+ "AIzaSyCMsAMytBeOK0Qd7RKFyA5IW9eWt2WTJg",
22
+ "AIzaSyAiYWEIEmx212Up9zfM8kqyMXB4jLs8gq0"
23
+ ];
24
+ const [apiKey, setApiKeyState] = useState(() => {
25
+ if (initialApiKey) return initialApiKey;
26
+ const saved = readStorage("xertica-api-key");
27
+ return saved || "";
28
+ });
29
+ const [geminiApiKey, setGeminiApiKeyState] = useState(() => {
30
+ if (initialGeminiApiKey) return initialGeminiApiKey;
31
+ const saved = readStorage("xertica-gemini-api-key");
32
+ if (saved && LEAKED_KEYS.includes(saved)) {
33
+ removeStorage("xertica-gemini-api-key");
34
+ return "";
35
+ }
36
+ return saved || "";
37
+ });
38
+ const [googleMapsApiKey, setGoogleMapsApiKeyState] = useState(() => {
39
+ if (initialGoogleMapsApiKey) return initialGoogleMapsApiKey;
40
+ const saved = readStorage("xertica-googlemaps-api-key");
41
+ return saved || "";
42
+ });
43
+ const [isApiKeyValid, setIsApiKeyValid] = useState(false);
44
+ const [isGoogleMapsKeyValid, setIsGoogleMapsKeyValid] = useState(false);
45
+ useEffect(() => {
46
+ if (initialApiKey !== void 0) {
47
+ setApiKeyState(initialApiKey.trim());
48
+ }
49
+ }, [initialApiKey]);
50
+ useEffect(() => {
51
+ if (initialGeminiApiKey !== void 0) {
52
+ setGeminiApiKeyState(initialGeminiApiKey.trim());
53
+ }
54
+ }, [initialGeminiApiKey]);
55
+ useEffect(() => {
56
+ if (initialGoogleMapsApiKey !== void 0) {
57
+ setGoogleMapsApiKeyState(initialGoogleMapsApiKey.trim());
58
+ }
59
+ }, [initialGoogleMapsApiKey]);
60
+ useEffect(() => {
61
+ if (apiKey) {
62
+ writeStorage("xertica-api-key", apiKey);
63
+ setIsApiKeyValid(apiKey.length > 0);
64
+ } else {
65
+ removeStorage("xertica-api-key");
66
+ setIsApiKeyValid(false);
67
+ }
68
+ }, [apiKey]);
69
+ useEffect(() => {
70
+ if (geminiApiKey) {
71
+ writeStorage("xertica-gemini-api-key", geminiApiKey);
72
+ setIsApiKeyValid(geminiApiKey.startsWith("AIzaSy") && geminiApiKey.length > 20);
73
+ } else {
74
+ removeStorage("xertica-gemini-api-key");
75
+ setIsApiKeyValid(false);
76
+ }
77
+ }, [geminiApiKey]);
78
+ useEffect(() => {
79
+ if (googleMapsApiKey) {
80
+ writeStorage("xertica-googlemaps-api-key", googleMapsApiKey);
81
+ setIsGoogleMapsKeyValid(
82
+ googleMapsApiKey.startsWith("AIzaSy") && googleMapsApiKey.length > 20
83
+ );
84
+ const reloadMaps = async () => {
85
+ if (googleMapsApiKey.length < 10) return;
86
+ try {
87
+ const { reloadGoogleMaps } = await import('./index-Bei5DzAL.js');
88
+ await reloadGoogleMaps(googleMapsApiKey);
89
+ console.log("[ApiKeyContext] Google Maps recarregado com sucesso");
90
+ } catch (error) {
91
+ console.error("[ApiKeyContext] Erro ao recarregar Google Maps:", error);
92
+ }
93
+ };
94
+ reloadMaps();
95
+ } else {
96
+ removeStorage("xertica-googlemaps-api-key");
97
+ setIsGoogleMapsKeyValid(false);
98
+ }
99
+ }, [googleMapsApiKey]);
100
+ const setApiKey = (key) => {
101
+ setApiKeyState(key.trim());
102
+ };
103
+ const setGeminiApiKey = (key) => {
104
+ setGeminiApiKeyState(key.trim());
105
+ };
106
+ const setGoogleMapsApiKey = (key) => {
107
+ setGoogleMapsApiKeyState(key.trim());
108
+ };
109
+ const reloadMapsApi = async () => {
110
+ if (!googleMapsApiKey || googleMapsApiKey.length < 10) {
111
+ return;
112
+ }
113
+ try {
114
+ const { reloadGoogleMaps } = await import('./index-Bei5DzAL.js');
115
+ await reloadGoogleMaps(googleMapsApiKey);
116
+ console.log("[ApiKeyContext] Google Maps recarregado manualmente");
117
+ } catch (error) {
118
+ console.error("[ApiKeyContext] Erro ao recarregar Google Maps:", error);
119
+ }
120
+ };
121
+ return /* @__PURE__ */ jsx(
122
+ ApiKeyContext.Provider,
123
+ {
124
+ value: {
125
+ apiKey,
126
+ setApiKey,
127
+ geminiApiKey,
128
+ setGeminiApiKey,
129
+ isApiKeyValid,
130
+ googleMapsApiKey,
131
+ setGoogleMapsApiKey,
132
+ isGoogleMapsKeyValid,
133
+ reloadMapsApi
134
+ },
135
+ children
136
+ }
137
+ );
138
+ }
139
+ function useApiKey() {
140
+ const context = useContext(ApiKeyContext);
141
+ if (!context) {
142
+ throw new Error("useApiKey must be used within ApiKeyProvider");
143
+ }
144
+ return context;
145
+ }
146
+
147
+ const AssistenteContext = createContext(void 0);
148
+ function AssistenteProvider({ children }) {
149
+ const [conversaAtual, setConversaAtual] = useState("1");
150
+ const [conversas, setConversas] = useState([
151
+ {
152
+ id: "1",
153
+ titulo: "Nova Conversa",
154
+ mensagens: [],
155
+ ultimaMensagem: "",
156
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
157
+ favorita: false
158
+ }
159
+ ]);
160
+ const [isTyping, setIsTyping] = useState(false);
161
+ const [abaSelecionada, setAbaSelecionada] = useState("chat");
162
+ const [editingDocument, setEditingDocument] = useState(
163
+ null
164
+ );
165
+ const [searchFilter, setSearchFilter] = useState("all");
166
+ const [savedSearches, setSavedSearches] = useState([]);
167
+ const historico = conversas.find((c) => c.id === conversaAtual)?.mensagens || [];
168
+ return /* @__PURE__ */ jsx(
169
+ AssistenteContext.Provider,
170
+ {
171
+ value: {
172
+ historico,
173
+ conversaAtual,
174
+ setConversaAtual,
175
+ conversas,
176
+ setConversas,
177
+ isTyping,
178
+ setIsTyping,
179
+ abaSelecionada,
180
+ setAbaSelecionada,
181
+ editingDocument,
182
+ setEditingDocument,
183
+ searchFilter,
184
+ setSearchFilter,
185
+ savedSearches,
186
+ setSavedSearches
187
+ },
188
+ children
189
+ }
190
+ );
191
+ }
192
+ function useAssistente() {
193
+ const context = useContext(AssistenteContext);
194
+ if (!context) {
195
+ throw new Error("useAssistente must be used within AssistenteProvider");
196
+ }
197
+ return context;
198
+ }
199
+
200
+ export { ApiKeyProvider as A, AssistenteProvider as a, useAssistente as b, useApiKey as u };