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.
- package/CHANGELOG.md +22 -2
- package/README.md +33 -22
- package/bin/cli.ts +136 -47
- package/bin/language-config.ts +5 -8
- package/components/assistant/modern-chat-input/ModernChatInput.tsx +17 -7
- package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +1 -3
- package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +13 -3
- package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +10 -6
- package/components/assistant/xertica-assistant/xertica-assistant.tsx +1 -3
- package/components/blocks/card-patterns/FeatureCardSkeleton.tsx +1 -6
- package/components/blocks/card-patterns/ProfileCard.tsx +1 -3
- package/components/blocks/card-patterns/ProjectCardSkeleton.tsx +1 -6
- package/components/brand/language-selector/language-selector.stories.tsx +1 -4
- package/components/brand/theme-toggle/ThemeToggle.tsx +5 -1
- package/components/brand/xertica-provider/XerticaProvider.tsx +1 -4
- package/components/index.ts +1 -5
- package/components/layout/sidebar/sidebar.tsx +9 -3
- package/components/media/audio-player/AudioPlayer.tsx +4 -2
- package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +188 -188
- package/components/pages/home-content/HomeContent.tsx +55 -55
- package/components/pages/home-page/HomePage.tsx +5 -1
- package/components/pages/login-page/LoginPage.tsx +4 -2
- package/components/pages/reset-password-page/ResetPasswordPage.tsx +7 -3
- package/components/pages/template-content/TemplateContent.tsx +268 -149
- package/components/pages/verify-email-page/VerifyEmailPage.tsx +9 -9
- package/components/shared/error-boundary.stories.tsx +114 -132
- package/components/shared/error-boundary.tsx +150 -154
- package/components/shared/error-fallbacks.tsx +222 -226
- package/components/ui/stats-card/stats-card-skeleton.tsx +1 -3
- package/components/ui/stats-card/stats-card.stories.tsx +18 -0
- package/components/ui/stats-card/stats-card.tsx +18 -2
- package/components.json +512 -892
- package/contexts/AuthContext.tsx +121 -118
- package/contexts/LanguageContext.tsx +1 -2
- package/dist/AssistantChart-BKVtGUKF.js +3383 -0
- package/dist/AssistantChart-WeycT5Pd.cjs +3551 -0
- package/dist/VerifyEmailPage-Bp1XXl3H.cjs +3305 -0
- package/dist/VerifyEmailPage-DGhuIqkb.js +3296 -0
- package/dist/XerticaProvider-BErr83Bg.js +42 -0
- package/dist/XerticaProvider-CwOkHxiT.cjs +44 -0
- package/dist/XerticaXLogo-BX3ueACh.js +255 -0
- package/dist/XerticaXLogo-qBPhwK3g.cjs +260 -0
- package/dist/assistant.cjs.js +1 -1
- package/dist/assistant.es.js +1 -1
- package/dist/brand.cjs.js +2 -2
- package/dist/brand.es.js +2 -2
- package/dist/cli.js +90 -37
- package/dist/components/brand/theme-toggle/ThemeToggle.d.ts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/ui/stats-card/stats-card.d.ts +10 -0
- package/dist/index.cjs.js +6 -6
- package/dist/index.es.js +6 -6
- package/dist/layout.cjs.js +1 -1
- package/dist/layout.es.js +1 -1
- package/dist/pages.cjs.js +1 -1
- package/dist/pages.es.js +1 -1
- package/dist/sidebar-B4ZWaMrE.js +792 -0
- package/dist/sidebar-BS1p2V7t.cjs +795 -0
- package/dist/ui.cjs.js +1 -1
- package/dist/ui.es.js +1 -1
- package/dist/xertica-assistant-B1NaSFFj.js +2173 -0
- package/dist/xertica-assistant-CIaUlbIt.cjs +2180 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/architecture-improvements.md +5 -5
- package/docs/architecture.md +16 -10
- package/docs/components/card-patterns.md +19 -17
- package/docs/components/error-boundary.md +201 -191
- package/docs/components/hooks.md +15 -13
- package/docs/components/language-selector.md +20 -16
- package/docs/components/pages.md +323 -309
- package/docs/components/stats-card.md +20 -2
- package/docs/doc-audit.md +12 -11
- package/docs/getting-started.md +41 -28
- package/docs/guidelines.md +14 -12
- package/docs/i18n.md +61 -57
- package/docs/installation.md +268 -267
- package/docs/llms.md +17 -17
- package/docs/state-management.md +17 -17
- package/guidelines/Guidelines.md +17 -14
- package/llms-compact.txt +1 -1
- package/llms-full.txt +11553 -7133
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/styles/xertica/base.css +90 -84
- package/templates/CLAUDE.md +16 -1
- package/templates/guidelines/Guidelines.md +42 -18
- package/templates/package.json +3 -3
- package/templates/src/app/components/AuthGuard.tsx +131 -82
- package/templates/src/features/auth/ui/AuthPageShell.tsx +32 -32
- package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +1 -3
- package/templates/src/features/auth/ui/ResetPasswordContent.tsx +6 -2
- package/templates/src/features/auth/ui/VerifyEmailContent.tsx +2 -6
- package/templates/src/features/home/data/mock.ts +41 -35
- package/templates/src/features/home/ui/HomeContent.tsx +62 -64
- package/templates/src/features/template/ui/CrudTemplate.tsx +1 -4
- package/templates/src/features/template/ui/LoginTemplate.tsx +1 -1
- package/templates/src/features/template/ui/TemplateContent.tsx +28 -20
- package/templates/src/locales/en/pages/templates.json +17 -17
- package/templates/src/locales/es/pages/templates.json +17 -17
- package/templates/src/locales/pt-BR/pages/templates.json +17 -17
- package/templates/src/pages/AssistantPage.tsx +26 -20
- package/templates/src/pages/HomePage.tsx +5 -1
- package/templates/src/shared/error-boundary.tsx +150 -154
- package/templates/src/shared/error-fallbacks.tsx +222 -226
- package/templates/vite.config.ts +12 -9
|
@@ -394,9 +394,7 @@ export function AssistantMessageBubble({
|
|
|
394
394
|
{/* Search Commands */}
|
|
395
395
|
{msg.searchCommands && msg.searchResults && (
|
|
396
396
|
<div>
|
|
397
|
-
<h4 className="mb-2 text-foreground">
|
|
398
|
-
{t('assistant.searchActions')}
|
|
399
|
-
</h4>
|
|
397
|
+
<h4 className="mb-2 text-foreground">{t('assistant.searchActions')}</h4>
|
|
400
398
|
<div className="grid grid-cols-1 gap-2">
|
|
401
399
|
{msg.searchCommands.map(command => (
|
|
402
400
|
<button
|
|
@@ -518,17 +516,23 @@ export function AssistantMessageBubble({
|
|
|
518
516
|
) : (
|
|
519
517
|
<>
|
|
520
518
|
<DropdownMenuItem
|
|
521
|
-
onClick={() =>
|
|
519
|
+
onClick={() =>
|
|
520
|
+
onOpenFeedbackDialog(msg.id, t('assistant.feedback.notWhatIWanted'))
|
|
521
|
+
}
|
|
522
522
|
>
|
|
523
523
|
{t('assistant.feedback.notWhatIWanted')}
|
|
524
524
|
</DropdownMenuItem>
|
|
525
525
|
<DropdownMenuItem
|
|
526
|
-
onClick={() =>
|
|
526
|
+
onClick={() =>
|
|
527
|
+
onOpenFeedbackDialog(msg.id, t('assistant.feedback.incorrectInfo'))
|
|
528
|
+
}
|
|
527
529
|
>
|
|
528
530
|
{t('assistant.feedback.incorrectInfo')}
|
|
529
531
|
</DropdownMenuItem>
|
|
530
532
|
<DropdownMenuItem
|
|
531
|
-
onClick={() =>
|
|
533
|
+
onClick={() =>
|
|
534
|
+
onOpenFeedbackDialog(msg.id, t('assistant.feedback.incompleteAnswer'))
|
|
535
|
+
}
|
|
532
536
|
>
|
|
533
537
|
{t('assistant.feedback.incompleteAnswer')}
|
|
534
538
|
</DropdownMenuItem>
|
|
@@ -389,9 +389,7 @@ export function XerticaAssistant({
|
|
|
389
389
|
// Render
|
|
390
390
|
// ============================================================================
|
|
391
391
|
|
|
392
|
-
const containerWidth =
|
|
393
|
-
width ??
|
|
394
|
-
(isFullPage ? '100%' : isExpanded ? '420px' : '80px'); // Compacto quando fechado - apenas ícones
|
|
392
|
+
const containerWidth = width ?? (isFullPage ? '100%' : isExpanded ? '420px' : '80px'); // Compacto quando fechado - apenas ícones
|
|
395
393
|
const containerHeight = height ?? 'h-full';
|
|
396
394
|
|
|
397
395
|
// Mobile floating button — shown when collapsed on mobile
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import {
|
|
5
|
-
Card,
|
|
6
|
-
CardHeader,
|
|
7
|
-
CardContent,
|
|
8
|
-
CardFooter,
|
|
9
|
-
} from '../../ui/card';
|
|
4
|
+
import { Card, CardHeader, CardContent, CardFooter } from '../../ui/card';
|
|
10
5
|
import { Skeleton } from '../../ui/skeleton';
|
|
11
6
|
import { cn } from '../../shared/utils';
|
|
12
7
|
|
|
@@ -51,9 +51,7 @@ export function ProfileCard({
|
|
|
51
51
|
}),
|
|
52
52
|
[t]
|
|
53
53
|
);
|
|
54
|
-
const s = status
|
|
55
|
-
? { label: statusLabelMap[status], variant: statusVariant[status] }
|
|
56
|
-
: null;
|
|
54
|
+
const s = status ? { label: statusLabelMap[status], variant: statusVariant[status] } : null;
|
|
57
55
|
|
|
58
56
|
return (
|
|
59
57
|
<Card className={cn('w-full', className)} {...props}>
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import {
|
|
5
|
-
Card,
|
|
6
|
-
CardHeader,
|
|
7
|
-
CardContent,
|
|
8
|
-
CardFooter,
|
|
9
|
-
} from '../../ui/card';
|
|
4
|
+
import { Card, CardHeader, CardContent, CardFooter } from '../../ui/card';
|
|
10
5
|
import { Skeleton } from '../../ui/skeleton';
|
|
11
6
|
import { cn } from '../../shared/utils';
|
|
12
7
|
|
|
@@ -75,10 +75,7 @@ export const Minimal: Story = {
|
|
|
75
75
|
export const ExtendedWithFrench: Story = {
|
|
76
76
|
name: 'Extended (with custom language)',
|
|
77
77
|
decorators: [
|
|
78
|
-
withLanguages([
|
|
79
|
-
...DEFAULT_LANGUAGES,
|
|
80
|
-
{ code: 'fr', label: 'Français', shortLabel: 'FR' },
|
|
81
|
-
]),
|
|
78
|
+
withLanguages([...DEFAULT_LANGUAGES, { code: 'fr', label: 'Français', shortLabel: 'FR' }]),
|
|
82
79
|
],
|
|
83
80
|
parameters: {
|
|
84
81
|
docs: {
|
|
@@ -17,9 +17,13 @@ export function ThemeToggle({
|
|
|
17
17
|
className = '',
|
|
18
18
|
showLabel = false,
|
|
19
19
|
}: ThemeToggleProps) {
|
|
20
|
-
const { isDark, toggleTheme } = useTheme();
|
|
20
|
+
const { isDark, toggleTheme, disableDarkMode } = useTheme();
|
|
21
21
|
const { t } = useTranslation();
|
|
22
22
|
|
|
23
|
+
if (disableDarkMode) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
const getSizeClasses = () => {
|
|
24
28
|
switch (size) {
|
|
25
29
|
case 'sm':
|
|
@@ -85,10 +85,7 @@ export function XerticaProvider({
|
|
|
85
85
|
primaryColor={primaryColor}
|
|
86
86
|
useCustomTokens={useCustomTokens}
|
|
87
87
|
>
|
|
88
|
-
<LanguageProvider
|
|
89
|
-
availableLanguages={availableLanguages}
|
|
90
|
-
defaultLanguage={defaultLanguage}
|
|
91
|
-
>
|
|
88
|
+
<LanguageProvider availableLanguages={availableLanguages} defaultLanguage={defaultLanguage}>
|
|
92
89
|
<ApiKeyProvider
|
|
93
90
|
initialApiKey={apiKey}
|
|
94
91
|
initialGeminiApiKey={apiKey}
|
package/components/index.ts
CHANGED
|
@@ -64,11 +64,7 @@ export { LanguageSelector } from './brand/language-selector';
|
|
|
64
64
|
export * from '../contexts/LayoutContext';
|
|
65
65
|
|
|
66
66
|
// Language / i18n
|
|
67
|
-
export {
|
|
68
|
-
useLanguage,
|
|
69
|
-
LanguageProvider,
|
|
70
|
-
DEFAULT_LANGUAGES,
|
|
71
|
-
} from '../contexts/LanguageContext';
|
|
67
|
+
export { useLanguage, LanguageProvider, DEFAULT_LANGUAGES } from '../contexts/LanguageContext';
|
|
72
68
|
export type {
|
|
73
69
|
Language,
|
|
74
70
|
LanguageDefinition,
|
|
@@ -709,7 +709,9 @@ function SidebarNav({
|
|
|
709
709
|
>
|
|
710
710
|
<MoreVertical className="w-5 h-5 flex-shrink-0" />
|
|
711
711
|
{expanded && (
|
|
712
|
-
<span className="truncate text-sidebar-foreground">
|
|
712
|
+
<span className="truncate text-sidebar-foreground">
|
|
713
|
+
{t('sidebar.moreOptions')}
|
|
714
|
+
</span>
|
|
713
715
|
)}
|
|
714
716
|
</button>
|
|
715
717
|
</PopoverTrigger>
|
|
@@ -830,7 +832,9 @@ function SidebarSearch({
|
|
|
830
832
|
)}
|
|
831
833
|
</button>
|
|
832
834
|
</TooltipTrigger>
|
|
833
|
-
<SidebarTooltipContent side="right">
|
|
835
|
+
<SidebarTooltipContent side="right">
|
|
836
|
+
{t('assistant.newConversation')}
|
|
837
|
+
</SidebarTooltipContent>
|
|
834
838
|
</Tooltip>
|
|
835
839
|
)}
|
|
836
840
|
{search?.show && (
|
|
@@ -915,7 +919,9 @@ function SidebarFooter({
|
|
|
915
919
|
{user?.name ? user.name.charAt(0).toUpperCase() : 'U'}
|
|
916
920
|
</AvatarFallback>
|
|
917
921
|
</Avatar>
|
|
918
|
-
<span className="text-sidebar-foreground truncate">
|
|
922
|
+
<span className="text-sidebar-foreground truncate">
|
|
923
|
+
{user?.name || t('sidebar.profile')}
|
|
924
|
+
</span>
|
|
919
925
|
</button>
|
|
920
926
|
))}
|
|
921
927
|
|
|
@@ -395,7 +395,8 @@ export function AudioPlayer({
|
|
|
395
395
|
|
|
396
396
|
<div className="md:hidden">
|
|
397
397
|
<DropdownMenuItem onClick={resetAudio} className="cursor-pointer py-2.5">
|
|
398
|
-
<RotateCcw className="w-4 h-4 mr-3 text-muted-foreground" />
|
|
398
|
+
<RotateCcw className="w-4 h-4 mr-3 text-muted-foreground" />{' '}
|
|
399
|
+
{t('media.restart')}
|
|
399
400
|
</DropdownMenuItem>
|
|
400
401
|
<DropdownMenuItem className="cursor-pointer py-2.5">
|
|
401
402
|
<Info className="w-4 h-4 mr-3 text-muted-foreground" /> {t('media.info')}
|
|
@@ -428,7 +429,8 @@ export function AudioPlayer({
|
|
|
428
429
|
</div>
|
|
429
430
|
<DropdownMenuSeparator className="my-1" />
|
|
430
431
|
<DropdownMenuItem className="cursor-pointer py-2.5">
|
|
431
|
-
|
|
432
|
+
<Download className="w-4 h-4 mr-3 text-muted-foreground" />{' '}
|
|
433
|
+
{t('media.downloadAudio')}
|
|
432
434
|
</DropdownMenuItem>
|
|
433
435
|
</div>
|
|
434
436
|
</DropdownMenuContent>
|
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Button } from '../../ui/button';
|
|
3
|
-
import { Input } from '../../ui/input';
|
|
4
|
-
import { Label } from '../../ui/label';
|
|
5
|
-
import { XerticaLogo } from '../../brand/xertica-logo';
|
|
6
|
-
import { ImageWithFallback } from '../../figma/ImageWithFallback';
|
|
7
|
-
import { LanguageSelector } from '../../brand/language-selector';
|
|
8
|
-
import { ArrowLeft, Lock } from 'lucide-react';
|
|
9
|
-
import { useNavigate } from 'react-router-dom';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Forgot Password Page component.
|
|
13
|
-
*
|
|
14
|
-
* @description
|
|
15
|
-
* A screen that allows users to request a password reset link via email.
|
|
16
|
-
* Includes a hero image side-panel (desktop) and alternative social login
|
|
17
|
-
* options similar to the main Login page.
|
|
18
|
-
*
|
|
19
|
-
* @ai-rules
|
|
20
|
-
* 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
|
-
*/
|
|
23
|
-
export function ForgotPasswordPage() {
|
|
24
|
-
const navigate = useNavigate();
|
|
25
|
-
const [email, setEmail] = useState('');
|
|
26
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
27
|
-
|
|
28
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
29
|
-
e.preventDefault();
|
|
30
|
-
setIsLoading(true);
|
|
31
|
-
|
|
32
|
-
// Simulate email sending
|
|
33
|
-
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
34
|
-
|
|
35
|
-
// Navigate to verification screen
|
|
36
|
-
navigate('/verify-email', { state: { email } });
|
|
37
|
-
|
|
38
|
-
setIsLoading(false);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const handleSocialLogin = (_provider: string) => {
|
|
42
|
-
// Wire your SSO/social provider integration here
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<div className="
|
|
47
|
-
{/* Left side - Full background image */}
|
|
48
|
-
<div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
|
|
49
|
-
{/* Background image filling all space */}
|
|
50
|
-
<ImageWithFallback
|
|
51
|
-
src="https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1200&h=800&fit=crop&auto=format"
|
|
52
|
-
alt="Segurança e tecnologia"
|
|
53
|
-
className="absolute inset-0 w-full h-full object-cover"
|
|
54
|
-
/>
|
|
55
|
-
|
|
56
|
-
{/* Gradient overlay */}
|
|
57
|
-
<div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
{/* Right side - Form */}
|
|
61
|
-
<div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 lg:flex-none lg:w-1/2 relative bg-muted">
|
|
62
|
-
{/* Language selector in the top right corner */}
|
|
63
|
-
<div className="absolute top-4 right-4 z-20">
|
|
64
|
-
<LanguageSelector variant="minimal" showIcon={false} />
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
{/* Mobile background gradient */}
|
|
68
|
-
<div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
|
|
69
|
-
|
|
70
|
-
<div className="w-full max-w-sm space-y-6 relative z-10">
|
|
71
|
-
{/* Form header */}
|
|
72
|
-
<div className="text-center">
|
|
73
|
-
<div className="flex items-center justify-center mb-4">
|
|
74
|
-
<XerticaLogo
|
|
75
|
-
className="h-12 w-auto text-primary dark:text-foreground"
|
|
76
|
-
variant="theme"
|
|
77
|
-
/>
|
|
78
|
-
</div>
|
|
79
|
-
<h2 className="text-sm text-muted-foreground">Recuperar senha</h2>
|
|
80
|
-
<p className="mt-2 text-muted-foreground">
|
|
81
|
-
Digite seu e-mail e enviaremos as instruções para redefinir sua senha
|
|
82
|
-
</p>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
{/* Form */}
|
|
86
|
-
<form className="space-y-6" onSubmit={handleSubmit}>
|
|
87
|
-
<div className="form-group space-y-2">
|
|
88
|
-
<Label htmlFor="email" className="form-label">
|
|
89
|
-
E-mail
|
|
90
|
-
</Label>
|
|
91
|
-
<Input
|
|
92
|
-
id="email"
|
|
93
|
-
name="email"
|
|
94
|
-
type="email"
|
|
95
|
-
required
|
|
96
|
-
className="w-full"
|
|
97
|
-
placeholder="seu@email.com"
|
|
98
|
-
value={email}
|
|
99
|
-
onChange={e => setEmail(e.target.value)}
|
|
100
|
-
/>
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
<div className="space-y-3">
|
|
104
|
-
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
105
|
-
{isLoading ? 'Enviando...' : 'Enviar instruções'}
|
|
106
|
-
</Button>
|
|
107
|
-
|
|
108
|
-
<Button
|
|
109
|
-
type="button"
|
|
110
|
-
onClick={() => navigate('/login')}
|
|
111
|
-
variant="outline"
|
|
112
|
-
className="w-full text-muted-foreground hover:text-foreground"
|
|
113
|
-
>
|
|
114
|
-
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
115
|
-
Voltar para o login
|
|
116
|
-
</Button>
|
|
117
|
-
</div>
|
|
118
|
-
</form>
|
|
119
|
-
|
|
120
|
-
{/* Divider */}
|
|
121
|
-
<div className="relative">
|
|
122
|
-
<div className="absolute inset-0 flex items-center">
|
|
123
|
-
<div className="w-full border-t border-border"></div>
|
|
124
|
-
</div>
|
|
125
|
-
<div className="relative flex justify-center text-sm">
|
|
126
|
-
<span className="bg-muted px-2 text-muted-foreground">ou continue com</span>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
{/* Social/SSO login options */}
|
|
131
|
-
<div className="space-y-3">
|
|
132
|
-
{/* Google */}
|
|
133
|
-
<Button
|
|
134
|
-
type="button"
|
|
135
|
-
variant="outline"
|
|
136
|
-
className="w-full justify-center"
|
|
137
|
-
onClick={() => handleSocialLogin('Google')}
|
|
138
|
-
>
|
|
139
|
-
<svg className="w-5 h-5 mr-2" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
|
140
|
-
<path
|
|
141
|
-
fill="#EA4335"
|
|
142
|
-
d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
|
|
143
|
-
></path>
|
|
144
|
-
<path
|
|
145
|
-
fill="#4285F4"
|
|
146
|
-
d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
|
|
147
|
-
></path>
|
|
148
|
-
<path
|
|
149
|
-
fill="#FBBC05"
|
|
150
|
-
d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
|
|
151
|
-
></path>
|
|
152
|
-
<path
|
|
153
|
-
fill="#34A853"
|
|
154
|
-
d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
|
|
155
|
-
></path>
|
|
156
|
-
<path fill="none" d="M0 0h48v48H0z"></path>
|
|
157
|
-
</svg>
|
|
158
|
-
<span>Entrar com Google</span>
|
|
159
|
-
</Button>
|
|
160
|
-
|
|
161
|
-
{/* MT Login */}
|
|
162
|
-
<Button
|
|
163
|
-
type="button"
|
|
164
|
-
variant="outline"
|
|
165
|
-
className="w-full justify-center"
|
|
166
|
-
onClick={() => handleSocialLogin('MT Login')}
|
|
167
|
-
>
|
|
168
|
-
<Lock className="w-5 h-5 mr-2 text-[var(--chart-4)]" />
|
|
169
|
-
<span>Entrar com MT Login</span>
|
|
170
|
-
</Button>
|
|
171
|
-
|
|
172
|
-
{/* gov.br */}
|
|
173
|
-
<Button
|
|
174
|
-
type="button"
|
|
175
|
-
variant="outline"
|
|
176
|
-
className="w-full justify-center font-normal"
|
|
177
|
-
onClick={() => handleSocialLogin('gov.br')}
|
|
178
|
-
>
|
|
179
|
-
<span>
|
|
180
|
-
Entrar com <strong className="font-semibold">gov.br</strong>
|
|
181
|
-
</span>
|
|
182
|
-
</Button>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
);
|
|
188
|
-
}
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button } from '../../ui/button';
|
|
3
|
+
import { Input } from '../../ui/input';
|
|
4
|
+
import { Label } from '../../ui/label';
|
|
5
|
+
import { XerticaLogo } from '../../brand/xertica-logo';
|
|
6
|
+
import { ImageWithFallback } from '../../figma/ImageWithFallback';
|
|
7
|
+
import { LanguageSelector } from '../../brand/language-selector';
|
|
8
|
+
import { ArrowLeft, Lock } from 'lucide-react';
|
|
9
|
+
import { useNavigate } from 'react-router-dom';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Forgot Password Page component.
|
|
13
|
+
*
|
|
14
|
+
* @description
|
|
15
|
+
* A screen that allows users to request a password reset link via email.
|
|
16
|
+
* Includes a hero image side-panel (desktop) and alternative social login
|
|
17
|
+
* options similar to the main Login page.
|
|
18
|
+
*
|
|
19
|
+
* @ai-rules
|
|
20
|
+
* 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
|
+
*/
|
|
23
|
+
export function ForgotPasswordPage() {
|
|
24
|
+
const navigate = useNavigate();
|
|
25
|
+
const [email, setEmail] = useState('');
|
|
26
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
27
|
+
|
|
28
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
setIsLoading(true);
|
|
31
|
+
|
|
32
|
+
// Simulate email sending
|
|
33
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
34
|
+
|
|
35
|
+
// Navigate to verification screen
|
|
36
|
+
navigate('/verify-email', { state: { email } });
|
|
37
|
+
|
|
38
|
+
setIsLoading(false);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleSocialLogin = (_provider: string) => {
|
|
42
|
+
// Wire your SSO/social provider integration here
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="h-screen w-full flex overflow-y-auto">
|
|
47
|
+
{/* Left side - Full background image */}
|
|
48
|
+
<div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
|
|
49
|
+
{/* Background image filling all space */}
|
|
50
|
+
<ImageWithFallback
|
|
51
|
+
src="https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1200&h=800&fit=crop&auto=format"
|
|
52
|
+
alt="Segurança e tecnologia"
|
|
53
|
+
className="absolute inset-0 w-full h-full object-cover"
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
{/* Gradient overlay */}
|
|
57
|
+
<div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{/* Right side - Form */}
|
|
61
|
+
<div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 lg:flex-none lg:w-1/2 relative bg-muted">
|
|
62
|
+
{/* Language selector in the top right corner */}
|
|
63
|
+
<div className="absolute top-4 right-4 z-20">
|
|
64
|
+
<LanguageSelector variant="minimal" showIcon={false} />
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* Mobile background gradient */}
|
|
68
|
+
<div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
|
|
69
|
+
|
|
70
|
+
<div className="w-full max-w-sm space-y-6 relative z-10">
|
|
71
|
+
{/* Form header */}
|
|
72
|
+
<div className="text-center">
|
|
73
|
+
<div className="flex items-center justify-center mb-4">
|
|
74
|
+
<XerticaLogo
|
|
75
|
+
className="h-12 w-auto text-primary dark:text-foreground"
|
|
76
|
+
variant="theme"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
<h2 className="text-sm text-muted-foreground">Recuperar senha</h2>
|
|
80
|
+
<p className="mt-2 text-muted-foreground">
|
|
81
|
+
Digite seu e-mail e enviaremos as instruções para redefinir sua senha
|
|
82
|
+
</p>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{/* Form */}
|
|
86
|
+
<form className="space-y-6" onSubmit={handleSubmit}>
|
|
87
|
+
<div className="form-group space-y-2">
|
|
88
|
+
<Label htmlFor="email" className="form-label">
|
|
89
|
+
E-mail
|
|
90
|
+
</Label>
|
|
91
|
+
<Input
|
|
92
|
+
id="email"
|
|
93
|
+
name="email"
|
|
94
|
+
type="email"
|
|
95
|
+
required
|
|
96
|
+
className="w-full"
|
|
97
|
+
placeholder="seu@email.com"
|
|
98
|
+
value={email}
|
|
99
|
+
onChange={e => setEmail(e.target.value)}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className="space-y-3">
|
|
104
|
+
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
105
|
+
{isLoading ? 'Enviando...' : 'Enviar instruções'}
|
|
106
|
+
</Button>
|
|
107
|
+
|
|
108
|
+
<Button
|
|
109
|
+
type="button"
|
|
110
|
+
onClick={() => navigate('/login')}
|
|
111
|
+
variant="outline"
|
|
112
|
+
className="w-full text-muted-foreground hover:text-foreground"
|
|
113
|
+
>
|
|
114
|
+
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
115
|
+
Voltar para o login
|
|
116
|
+
</Button>
|
|
117
|
+
</div>
|
|
118
|
+
</form>
|
|
119
|
+
|
|
120
|
+
{/* Divider */}
|
|
121
|
+
<div className="relative">
|
|
122
|
+
<div className="absolute inset-0 flex items-center">
|
|
123
|
+
<div className="w-full border-t border-border"></div>
|
|
124
|
+
</div>
|
|
125
|
+
<div className="relative flex justify-center text-sm">
|
|
126
|
+
<span className="bg-muted px-2 text-muted-foreground">ou continue com</span>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Social/SSO login options */}
|
|
131
|
+
<div className="space-y-3">
|
|
132
|
+
{/* Google */}
|
|
133
|
+
<Button
|
|
134
|
+
type="button"
|
|
135
|
+
variant="outline"
|
|
136
|
+
className="w-full justify-center"
|
|
137
|
+
onClick={() => handleSocialLogin('Google')}
|
|
138
|
+
>
|
|
139
|
+
<svg className="w-5 h-5 mr-2" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
|
140
|
+
<path
|
|
141
|
+
fill="#EA4335"
|
|
142
|
+
d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
|
|
143
|
+
></path>
|
|
144
|
+
<path
|
|
145
|
+
fill="#4285F4"
|
|
146
|
+
d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
|
|
147
|
+
></path>
|
|
148
|
+
<path
|
|
149
|
+
fill="#FBBC05"
|
|
150
|
+
d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
|
|
151
|
+
></path>
|
|
152
|
+
<path
|
|
153
|
+
fill="#34A853"
|
|
154
|
+
d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
|
|
155
|
+
></path>
|
|
156
|
+
<path fill="none" d="M0 0h48v48H0z"></path>
|
|
157
|
+
</svg>
|
|
158
|
+
<span>Entrar com Google</span>
|
|
159
|
+
</Button>
|
|
160
|
+
|
|
161
|
+
{/* MT Login */}
|
|
162
|
+
<Button
|
|
163
|
+
type="button"
|
|
164
|
+
variant="outline"
|
|
165
|
+
className="w-full justify-center"
|
|
166
|
+
onClick={() => handleSocialLogin('MT Login')}
|
|
167
|
+
>
|
|
168
|
+
<Lock className="w-5 h-5 mr-2 text-[var(--chart-4)]" />
|
|
169
|
+
<span>Entrar com MT Login</span>
|
|
170
|
+
</Button>
|
|
171
|
+
|
|
172
|
+
{/* gov.br */}
|
|
173
|
+
<Button
|
|
174
|
+
type="button"
|
|
175
|
+
variant="outline"
|
|
176
|
+
className="w-full justify-center font-normal"
|
|
177
|
+
onClick={() => handleSocialLogin('gov.br')}
|
|
178
|
+
>
|
|
179
|
+
<span>
|
|
180
|
+
Entrar com <strong className="font-semibold">gov.br</strong>
|
|
181
|
+
</span>
|
|
182
|
+
</Button>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|