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.
- package/CHANGELOG.md +21 -0
- package/README.md +1 -1
- package/components/layout/sidebar/sidebar.stories.tsx +9 -9
- package/components/layout/sidebar/sidebar.tsx +5 -5
- package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +19 -5
- package/components/pages/login-page/LoginPage.tsx +19 -5
- package/components/pages/reset-password-page/ResetPasswordPage.tsx +15 -2
- package/components/pages/verify-email-page/VerifyEmailPage.tsx +17 -3
- package/contexts/LayoutContext.tsx +1 -1
- package/dist/AssistenteContext-Bm8_RJTB.js +200 -0
- package/dist/AssistenteContext-CLK8tgdt.cjs +205 -0
- package/dist/AudioPlayer-C93eMH6U.cjs +1282 -0
- package/dist/AudioPlayer-DSYSMpXw.js +1278 -0
- package/dist/BrandColorsContext-D3Y0CYZF.js +643 -0
- package/dist/BrandColorsContext-SeOZd2Cq.cjs +646 -0
- package/dist/LayoutContext-BLoSFOuO.cjs +96 -0
- package/dist/LayoutContext-BqtKcSlN.js +92 -0
- package/dist/VerifyEmailPage-BInYcjRd.cjs +3305 -0
- package/dist/VerifyEmailPage-CEtb3BJC.cjs +3305 -0
- package/dist/VerifyEmailPage-Z-cA_IIo.js +3295 -0
- package/dist/VerifyEmailPage-cg4USiDN.js +3295 -0
- package/dist/XerticaProvider-Ck996htf.cjs +47 -0
- package/dist/XerticaProvider-DUbkitXG.js +45 -0
- package/dist/XerticaProvider-Uvdhs7zH.cjs +47 -0
- package/dist/XerticaProvider-kE5l0MQn.js +45 -0
- package/dist/brand.cjs.js +1 -1
- package/dist/brand.es.js +1 -1
- package/dist/components/layout/sidebar/sidebar.d.ts +1 -1
- package/dist/components/pages/forgot-password-page/ForgotPasswordPage.d.ts +4 -3
- package/dist/components/pages/login-page/LoginPage.d.ts +4 -3
- package/dist/components/pages/verify-email-page/VerifyEmailPage.d.ts +2 -1
- package/dist/hooks.cjs.js +8 -7
- package/dist/hooks.es.js +5 -4
- package/dist/index.cjs.js +5 -5
- package/dist/index.es.js +5 -5
- package/dist/layout.cjs.js +1 -1
- package/dist/layout.es.js +1 -1
- package/dist/media.cjs.js +1 -1
- package/dist/media.es.js +1 -1
- package/dist/pages.cjs.js +1 -1
- package/dist/pages.es.js +1 -1
- package/dist/sidebar-BCjbD0Pi.cjs +1154 -0
- package/dist/sidebar-Dg1TVmaI.js +1151 -0
- package/dist/use-audio-player-BjC4swHi.cjs +190 -0
- package/dist/use-audio-player-D56MYa5x.js +188 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/ai-usage.md +1 -1
- package/docs/components/sidebar.md +4 -4
- package/docs/layout.md +1 -1
- package/package.json +1 -1
- package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +15 -2
- package/templates/src/features/auth/ui/LoginContent.tsx +15 -2
- package/templates/src/features/auth/ui/ResetPasswordContent.tsx +15 -2
- 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
|
-
[](https://www.npmjs.com/package/xertica-ui)
|
|
6
6
|
[](./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 (
|
|
90
|
-
const currentWidth = isExpanded ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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={
|
|
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 ??
|
|
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
|
|
17
|
-
*
|
|
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
|
-
|
|
47
|
-
|
|
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
|
|
26
|
-
*
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
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
|
-
|
|
49
|
-
|
|
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(
|
|
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 };
|