xertica-ui 1.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 (141) hide show
  1. package/App.tsx +182 -0
  2. package/README.md +330 -0
  3. package/assets/xertica-logo.svg +38 -0
  4. package/assets/xertica-x-logo.svg +21 -0
  5. package/bin/cli.ts +193 -0
  6. package/components/AssistenteXertica.tsx +2003 -0
  7. package/components/AudioPlayer.tsx +203 -0
  8. package/components/CodeBlock.tsx +242 -0
  9. package/components/DocumentEditor.tsx +504 -0
  10. package/components/ForgotPasswordPage.tsx +170 -0
  11. package/components/FormattedDocument.tsx +87 -0
  12. package/components/HomeContent.tsx +123 -0
  13. package/components/HomePage.tsx +70 -0
  14. package/components/LanguageSelector.tsx +54 -0
  15. package/components/LoginPage.tsx +199 -0
  16. package/components/MarkdownMessage.tsx +62 -0
  17. package/components/ModernChatInput.tsx +502 -0
  18. package/components/PodcastPlayer.tsx +409 -0
  19. package/components/ResetPasswordPage.tsx +234 -0
  20. package/components/Sidebar.tsx +489 -0
  21. package/components/TemplateContent.tsx +629 -0
  22. package/components/TemplatePage.tsx +70 -0
  23. package/components/ThemeToggle.tsx +65 -0
  24. package/components/VerifyEmailPage.tsx +187 -0
  25. package/components/XerticaLogo.tsx +69 -0
  26. package/components/XerticaOrbe.tsx +1339 -0
  27. package/components/XerticaXLogo.tsx +53 -0
  28. package/components/examples/DrawingMapExample.tsx +530 -0
  29. package/components/examples/FilterableMapExample.tsx +380 -0
  30. package/components/examples/LocationPickerExample.tsx +330 -0
  31. package/components/examples/MapExamples.tsx +280 -0
  32. package/components/examples/MapShowcase.tsx +446 -0
  33. package/components/examples/RouteMapExamples.tsx +329 -0
  34. package/components/examples/SimpleFilterableMap.tsx +192 -0
  35. package/components/examples/index.ts +52 -0
  36. package/components/figma/ImageWithFallback.tsx +27 -0
  37. package/components/index.ts +44 -0
  38. package/components/media/AudioPlayer.tsx +278 -0
  39. package/components/media/FloatingMediaWrapper.tsx +166 -0
  40. package/components/media/VideoPlayer.tsx +285 -0
  41. package/components/ui/accordion.tsx +66 -0
  42. package/components/ui/alert-dialog.tsx +159 -0
  43. package/components/ui/alert.tsx +91 -0
  44. package/components/ui/aspect-ratio.tsx +11 -0
  45. package/components/ui/avatar.tsx +65 -0
  46. package/components/ui/badge.tsx +55 -0
  47. package/components/ui/breadcrumb.tsx +109 -0
  48. package/components/ui/button.tsx +78 -0
  49. package/components/ui/calendar.tsx +235 -0
  50. package/components/ui/card.tsx +92 -0
  51. package/components/ui/carousel.tsx +241 -0
  52. package/components/ui/chart.tsx +353 -0
  53. package/components/ui/checkbox.tsx +32 -0
  54. package/components/ui/collapsible.tsx +33 -0
  55. package/components/ui/command.tsx +177 -0
  56. package/components/ui/context-menu.tsx +252 -0
  57. package/components/ui/dialog.tsx +138 -0
  58. package/components/ui/drawer.tsx +134 -0
  59. package/components/ui/dropdown-menu.tsx +257 -0
  60. package/components/ui/empty.tsx +90 -0
  61. package/components/ui/file-upload.tsx +152 -0
  62. package/components/ui/form.tsx +195 -0
  63. package/components/ui/google-maps-loader.tsx +379 -0
  64. package/components/ui/hover-card.tsx +44 -0
  65. package/components/ui/index.ts +242 -0
  66. package/components/ui/input-otp.tsx +77 -0
  67. package/components/ui/input.tsx +38 -0
  68. package/components/ui/label.tsx +24 -0
  69. package/components/ui/map-config.ts +12 -0
  70. package/components/ui/map-layers.tsx +129 -0
  71. package/components/ui/map.exports.ts +31 -0
  72. package/components/ui/map.tsx +412 -0
  73. package/components/ui/menubar.tsx +276 -0
  74. package/components/ui/navigation-menu.tsx +162 -0
  75. package/components/ui/notification-badge.tsx +61 -0
  76. package/components/ui/page-header.tsx +229 -0
  77. package/components/ui/pagination.tsx +127 -0
  78. package/components/ui/popover.tsx +48 -0
  79. package/components/ui/progress.tsx +31 -0
  80. package/components/ui/radio-group.tsx +56 -0
  81. package/components/ui/rating.tsx +102 -0
  82. package/components/ui/resizable.tsx +405 -0
  83. package/components/ui/route-map.tsx +246 -0
  84. package/components/ui/scroll-area.tsx +58 -0
  85. package/components/ui/search.tsx +70 -0
  86. package/components/ui/select.tsx +176 -0
  87. package/components/ui/separator.tsx +28 -0
  88. package/components/ui/sheet.tsx +138 -0
  89. package/components/ui/sidebar.tsx +726 -0
  90. package/components/ui/simple-map.tsx +92 -0
  91. package/components/ui/skeleton.tsx +13 -0
  92. package/components/ui/slider.tsx +58 -0
  93. package/components/ui/sonner.tsx +77 -0
  94. package/components/ui/stats-card.tsx +84 -0
  95. package/components/ui/stepper.tsx +126 -0
  96. package/components/ui/switch.tsx +34 -0
  97. package/components/ui/table.tsx +116 -0
  98. package/components/ui/tabs.tsx +66 -0
  99. package/components/ui/textarea.tsx +26 -0
  100. package/components/ui/timeline.tsx +140 -0
  101. package/components/ui/toggle-group.tsx +71 -0
  102. package/components/ui/toggle.tsx +46 -0
  103. package/components/ui/tooltip.tsx +61 -0
  104. package/components/ui/tree-view.tsx +123 -0
  105. package/components/ui/use-mobile.ts +24 -0
  106. package/components/ui/utils.ts +6 -0
  107. package/components/ui/xertica-assistant.tsx +1420 -0
  108. package/contexts/ApiKeyContext.tsx +123 -0
  109. package/contexts/AssistenteContext.tsx +118 -0
  110. package/contexts/BrandColorsContext.tsx +551 -0
  111. package/contexts/LanguageContext.tsx +36 -0
  112. package/contexts/ThemeContext.tsx +85 -0
  113. package/dist/cli.js +20922 -0
  114. package/eslint.config.js +41 -0
  115. package/guidelines/Guidelines.md +61 -0
  116. package/hooks/useTheme.ts +4 -0
  117. package/imports/Podcast.tsx +389 -0
  118. package/imports/XerticaAi.tsx +46 -0
  119. package/imports/XerticaX.tsx +20 -0
  120. package/imports/svg-aueiaqngck.ts +11 -0
  121. package/imports/svg-v9krss1ozd.ts +16 -0
  122. package/imports/svg-vhrdofe3qe.ts +5 -0
  123. package/index.css +4448 -0
  124. package/index.html +14 -0
  125. package/main.tsx +10 -0
  126. package/package.json +119 -0
  127. package/postcss.config.js +6 -0
  128. package/routes.tsx +33 -0
  129. package/styles/globals.css +15 -0
  130. package/styles/xertica/app-overrides/chat.css +61 -0
  131. package/styles/xertica/app-overrides/scrollbar.css +33 -0
  132. package/styles/xertica/base.css +70 -0
  133. package/styles/xertica/integrations/google-maps.css +76 -0
  134. package/styles/xertica/integrations/sonner.css +73 -0
  135. package/styles/xertica/theme-map.css +88 -0
  136. package/styles/xertica/tokens.css +190 -0
  137. package/tsconfig.json +31 -0
  138. package/tsconfig.node.json +10 -0
  139. package/utils/gemini.ts +140 -0
  140. package/vite-env.d.ts +12 -0
  141. package/vite.config.ts +36 -0
@@ -0,0 +1,87 @@
1
+ import React, { useState } from 'react';
2
+ import { Button } from './ui/button';
3
+ import { ChevronDown, ChevronUp } from 'lucide-react';
4
+
5
+ interface FormattedDocumentProps {
6
+ content: string;
7
+ maxPreviewLength?: number;
8
+ className?: string;
9
+ }
10
+
11
+ export function FormattedDocument({ content, maxPreviewLength = 500, className = '' }: FormattedDocumentProps) {
12
+ const [isExpanded, setIsExpanded] = useState(false);
13
+
14
+ // Converter Markdown simples para HTML
15
+ const convertMarkdownToHtml = (markdown: string): string => {
16
+ let html = markdown;
17
+
18
+ // Headers (com estilos melhorados)
19
+ html = html.replace(/^### (.*$)/gim, '<h3 class="text-base font-medium mt-3 mb-1.5 text-foreground">$1</h3>');
20
+ html = html.replace(/^## (.*$)/gim, '<h2 class="text-lg font-medium mt-4 mb-2 text-foreground">$1</h2>');
21
+ html = html.replace(/^# (.*$)/gim, '<h1 class="text-xl font-medium mt-4 mb-3 text-foreground">$1</h1>');
22
+
23
+ // Bold
24
+ html = html.replace(/\*\*(.*?)\*\*/g, '<strong class="font-medium text-foreground">$1</strong>');
25
+
26
+ // Italic
27
+ html = html.replace(/\*(.*?)\*/g, '<em class="italic">$1</em>');
28
+
29
+ // Lists
30
+ html = html.replace(/^\- (.*$)/gim, '<li class="ml-4 my-0.5">• $1</li>');
31
+ html = html.replace(/^\d+\. (.*$)/gim, '<li class="ml-4 my-0.5">$1</li>');
32
+
33
+ // Checkboxes
34
+ html = html.replace(/- \[ \] (.*$)/gim, '<div class="flex items-center gap-2 ml-4 my-1"><input type="checkbox" disabled class="rounded w-3 h-3" /> <span class="text-sm">$1</span></div>');
35
+ html = html.replace(/- \[x\] (.*$)/gim, '<div class="flex items-center gap-2 ml-4 my-1"><input type="checkbox" checked disabled class="rounded w-3 h-3" /> <span class="text-sm">$1</span></div>');
36
+
37
+ // Horizontal rule
38
+ html = html.replace(/^---$/gim, '<hr class="my-3 border-border" />');
39
+
40
+ // Line breaks (mantém parágrafos)
41
+ html = html.replace(/\n\n/g, '</p><p class="my-2">');
42
+ html = html.replace(/\n/g, '<br/>');
43
+
44
+ // Wrap em parágrafo
45
+ html = '<p class="my-1">' + html + '</p>';
46
+
47
+ return html;
48
+ };
49
+
50
+ const htmlContent = convertMarkdownToHtml(content);
51
+ const isLong = content.length > maxPreviewLength;
52
+
53
+ // Se não for longo ou estiver expandido, mostra tudo
54
+ const displayContent = (!isLong || isExpanded)
55
+ ? htmlContent
56
+ : convertMarkdownToHtml(content.substring(0, maxPreviewLength) + '...');
57
+
58
+ return (
59
+ <div className={className}>
60
+ <div
61
+ className="max-w-none text-sm text-foreground leading-relaxed break-words overflow-hidden"
62
+ dangerouslySetInnerHTML={{ __html: displayContent }}
63
+ />
64
+
65
+ {isLong && (
66
+ <Button
67
+ variant="ghost"
68
+ size="sm"
69
+ onClick={() => setIsExpanded(!isExpanded)}
70
+ className="mt-2 w-full text-xs hover:bg-accent text-muted-foreground"
71
+ >
72
+ {isExpanded ? (
73
+ <>
74
+ <ChevronUp className="w-3 h-3 mr-1" />
75
+ Ver menos
76
+ </>
77
+ ) : (
78
+ <>
79
+ <ChevronDown className="w-3 h-3 mr-1" />
80
+ Ver mais
81
+ </>
82
+ )}
83
+ </Button>
84
+ )}
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,123 @@
1
+ import React from 'react';
2
+ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from './ui/card';
3
+ import { Button } from './ui/button';
4
+ import { ChevronRight, Menu, FileText } from 'lucide-react';
5
+ import { ScrollArea } from './ui/scroll-area';
6
+ import { useLocation, useNavigate } from 'react-router';
7
+ import { getRouteByPath } from '../routes';
8
+ import { ThemeToggle } from './ThemeToggle';
9
+ import { LanguageSelector } from './LanguageSelector';
10
+ import { Badge } from './ui/badge';
11
+
12
+ interface HomeContentProps {
13
+ sidebarExpanded: boolean;
14
+ assistenteExpanded?: boolean;
15
+ onToggleSidebar?: () => void;
16
+ onToggleAssistente?: () => void;
17
+ }
18
+
19
+ export function HomeContent({ sidebarExpanded, assistenteExpanded = false, onToggleSidebar, onToggleAssistente }: HomeContentProps) {
20
+ const location = useLocation();
21
+ const navigate = useNavigate();
22
+
23
+ // Tradução direta para português
24
+ const labelTranslations: Record<string, string> = {
25
+ 'home': 'Início',
26
+ 'template': 'Template',
27
+ };
28
+
29
+ // Get current route info
30
+ const currentRoute = getRouteByPath(location.pathname);
31
+ const breadcrumbLabel = currentRoute?.label
32
+ ? labelTranslations[currentRoute.label.toLowerCase()] || currentRoute.label
33
+ : 'Início';
34
+
35
+
36
+
37
+ return (
38
+ <div
39
+ className={`flex-1 flex flex-col overflow-hidden transition-all duration-300 ${
40
+ sidebarExpanded ? 'md:pl-64' : 'md:pl-20'
41
+ } ${
42
+ assistenteExpanded ? 'md:pr-[420px]' : 'md:pr-20'
43
+ }`}
44
+ >
45
+ {/* Header fixo */}
46
+ <header className="bg-card shadow-sm border-b border-border px-[24px] py-[14px] flex-shrink-0">
47
+ <div className="flex items-center justify-between p-[0px]">
48
+ <div className="flex items-center gap-2 text-muted-foreground">
49
+ {/* Botão menu para mobile */}
50
+ <Button
51
+ variant="ghost"
52
+ size="sm"
53
+ onClick={onToggleSidebar}
54
+ className="md:hidden mr-2 p-2"
55
+ >
56
+ <Menu className="w-5 h-5" />
57
+ </Button>
58
+ <span className="text-primary font-medium">Xertica.ai</span>
59
+ <ChevronRight className="w-4 h-4" />
60
+ <span className="text-foreground font-medium">{breadcrumbLabel}</span>
61
+ </div>
62
+
63
+ {/* Controles do usuário */}
64
+ <div className="flex items-center gap-3">
65
+ {/* Seletor de idioma */}
66
+ <LanguageSelector variant="minimal" showIcon={false} className="hidden sm:flex" />
67
+
68
+ {/* Toggle de tema */}
69
+ <ThemeToggle className="hover:bg-accent" />
70
+ </div>
71
+ </div>
72
+ </header>
73
+
74
+ {/* Área de conteúdo */}
75
+ <main className="flex-1 overflow-hidden bg-muted">
76
+ <ScrollArea className="h-full">
77
+ <div className="p-2 sm:p-4 md:p-6">
78
+ <div className="max-w-6xl mx-auto space-y-8">
79
+ {/* Cabeçalho da página */}
80
+ <div className="space-y-2">
81
+ <h2>Bem-vindo, Ariel!</h2>
82
+ <p className="text-muted-foreground">
83
+ Sua plataforma inteligente para automação e análise de dados
84
+ </p>
85
+ </div>
86
+
87
+ {/* Cards principais */}
88
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
89
+ <Card className="hover:shadow-xl transition-shadow duration-200 flex flex-col h-full">
90
+ <CardHeader>
91
+ <div className="flex items-center gap-3">
92
+ <div className="p-2 bg-[var(--chart-2)]/20 rounded-[var(--radius)]">
93
+ <FileText className="w-6 h-6 text-[var(--chart-2)]" />
94
+ </div>
95
+ <div className="flex items-center gap-2">
96
+ <CardTitle className="text-sm">Template CLI</CardTitle>
97
+ <Badge variant="default" className="text-xs">Novo</Badge>
98
+ </div>
99
+ </div>
100
+ </CardHeader>
101
+ <CardContent className="flex-1">
102
+ <p className="text-muted-foreground">
103
+ Página template pronta para uso no CLI com todos os componentes configurados.
104
+ </p>
105
+ </CardContent>
106
+ <CardFooter>
107
+ <Button
108
+ variant="outline"
109
+ className="w-full"
110
+ onClick={() => navigate('/template')}
111
+ >
112
+ Visualizar
113
+ </Button>
114
+ </CardFooter>
115
+ </Card>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </ScrollArea>
120
+ </main>
121
+ </div>
122
+ );
123
+ }
@@ -0,0 +1,70 @@
1
+ import React, { useState } from 'react';
2
+ import { useLocation, useNavigate } from 'react-router';
3
+ import { Sidebar } from './Sidebar';
4
+ import { HomeContent } from './HomeContent';
5
+ import { AssistenteXertica } from './AssistenteXertica';
6
+
7
+ interface HomePageProps {
8
+ user: { email: string } | null;
9
+ onLogout: () => void;
10
+ }
11
+
12
+ export function HomePage({ user, onLogout }: HomePageProps) {
13
+ const [sidebarExpanded, setSidebarExpanded] = useState(false);
14
+ const [assistenteExpanded, setAssistenteExpanded] = useState(false);
15
+ const location = useLocation();
16
+ const navigate = useNavigate();
17
+
18
+ // Função para alternar a sidebar - fecha o assistente se estiver aberto
19
+ const handleToggleSidebar = () => {
20
+ if (!sidebarExpanded && assistenteExpanded) {
21
+ // Se a sidebar vai abrir e o assistente está aberto, fechar o assistente
22
+ setAssistenteExpanded(false);
23
+ }
24
+ setSidebarExpanded(!sidebarExpanded);
25
+ };
26
+
27
+ // Função para alternar o assistente - fecha a sidebar se estiver aberta
28
+ const handleToggleAssistente = () => {
29
+ if (!assistenteExpanded && sidebarExpanded) {
30
+ // Se o assistente vai abrir e a sidebar está aberta, fechar a sidebar
31
+ setSidebarExpanded(false);
32
+ }
33
+ setAssistenteExpanded(!assistenteExpanded);
34
+ };
35
+
36
+ // Função para abrir assistente com tab específica - fecha a sidebar se estiver aberta
37
+ const handleToggleAssistenteWithTab = (tab: string) => {
38
+ if (!assistenteExpanded) {
39
+ if (sidebarExpanded) {
40
+ // Se o assistente vai abrir e a sidebar está aberta, fechar a sidebar
41
+ setSidebarExpanded(false);
42
+ }
43
+ setAssistenteExpanded(true);
44
+ }
45
+ };
46
+
47
+ return (
48
+ <div className="h-screen flex bg-muted overflow-hidden relative">
49
+ <Sidebar
50
+ expanded={sidebarExpanded}
51
+ onToggle={handleToggleSidebar}
52
+ user={user}
53
+ onLogout={onLogout}
54
+ location={location}
55
+ navigate={navigate}
56
+ />
57
+ <HomeContent
58
+ sidebarExpanded={sidebarExpanded}
59
+ assistenteExpanded={assistenteExpanded}
60
+ onToggleSidebar={handleToggleSidebar}
61
+ onToggleAssistente={handleToggleAssistente}
62
+ />
63
+ <AssistenteXertica
64
+ isExpanded={assistenteExpanded}
65
+ onToggle={handleToggleAssistente}
66
+ onToggleWithTab={handleToggleAssistenteWithTab}
67
+ />
68
+ </div>
69
+ );
70
+ }
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
3
+ import { useLanguage, Language } from '../contexts/LanguageContext';
4
+ import { Globe } from 'lucide-react';
5
+
6
+ interface LanguageSelectorProps {
7
+ variant?: 'default' | 'minimal';
8
+ showIcon?: boolean;
9
+ className?: string;
10
+ }
11
+
12
+ export function LanguageSelector({
13
+ variant = 'default',
14
+ showIcon = true,
15
+ className = ''
16
+ }: LanguageSelectorProps) {
17
+ const { language, setLanguage } = useLanguage();
18
+
19
+ const languages = [
20
+ { value: 'pt' as Language, label: 'Português', flag: '🇧🇷' },
21
+ { value: 'en' as Language, label: 'English', flag: '🇺🇸' },
22
+ { value: 'es' as Language, label: 'Español', flag: '🇪🇸' },
23
+ ];
24
+
25
+ const currentLanguage = languages.find(lang => lang.value === language);
26
+
27
+ return (
28
+ <div className={`flex items-center gap-2 ${className}`}>
29
+ {showIcon && variant === 'default' && (
30
+ <Globe className="w-4 h-4 text-muted-foreground" />
31
+ )}
32
+ <Select value={language} onValueChange={setLanguage}>
33
+ <SelectTrigger className={variant === 'minimal' ? 'w-auto border-none shadow-none bg-transparent' : 'w-full'}>
34
+ <SelectValue>
35
+ <div className="flex items-center gap-2">
36
+ <span>{currentLanguage?.flag}</span>
37
+ <span>{currentLanguage?.label}</span>
38
+ </div>
39
+ </SelectValue>
40
+ </SelectTrigger>
41
+ <SelectContent>
42
+ {languages.map((lang) => (
43
+ <SelectItem key={lang.value} value={lang.value}>
44
+ <div className="flex items-center gap-2">
45
+ <span>{lang.flag}</span>
46
+ <span>{lang.label}</span>
47
+ </div>
48
+ </SelectItem>
49
+ ))}
50
+ </SelectContent>
51
+ </Select>
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,199 @@
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 './XerticaLogo';
6
+ import { ImageWithFallback } from './figma/ImageWithFallback';
7
+ import { LanguageSelector } from './LanguageSelector';
8
+ import { Lock } from 'lucide-react';
9
+ import { useNavigate } from 'react-router';
10
+
11
+ interface LoginPageProps {
12
+ onLogin: (email: string, password: string) => boolean;
13
+ }
14
+
15
+ export function LoginPage({ onLogin }: LoginPageProps) {
16
+ const navigate = useNavigate();
17
+ const [email, setEmail] = useState('');
18
+ const [password, setPassword] = useState('');
19
+ const [isLoading, setIsLoading] = useState(false);
20
+ const [error, setError] = useState('');
21
+
22
+ const handleSubmit = async (e: React.FormEvent) => {
23
+ e.preventDefault();
24
+ setError('');
25
+ setIsLoading(true);
26
+
27
+ // Simula um pequeno delay para mostrar o loading
28
+ await new Promise(resolve => setTimeout(resolve, 1000));
29
+
30
+ const success = onLogin(email, password);
31
+
32
+ if (!success) {
33
+ setError('Por favor, preencha todos os campos');
34
+ }
35
+
36
+ setIsLoading(false);
37
+ };
38
+
39
+ const handleSocialLogin = (provider: string) => {
40
+ // Simula login social/SSO
41
+ console.log(`Login com ${provider}`);
42
+ // Aqui seria implementada a integração real com cada provedor
43
+ // Por enquanto, simula um login bem-sucedido
44
+ onLogin('social@user.com', 'social-auth');
45
+ };
46
+
47
+ return (
48
+ <div className="min-h-screen flex">
49
+ {/* Lado esquerdo - Imagem de fundo completa */}
50
+ <div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
51
+ {/* Imagem de fundo preenchendo todo o espaço */}
52
+ <ImageWithFallback
53
+ src="https://images.unsplash.com/photo-1551434678-e076c223a692?w=1200&h=800&fit=crop&auto=format"
54
+ alt="Equipe trabalhando com tecnologia"
55
+ className="absolute inset-0 w-full h-full object-cover"
56
+ />
57
+
58
+ {/* Overlay com gradiente */}
59
+ <div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
60
+ </div>
61
+
62
+ {/* Lado direito - Formulário */}
63
+ <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">
64
+ {/* Seletor de idioma no canto superior direito */}
65
+ <div className="absolute top-4 right-4 z-20">
66
+ <LanguageSelector variant="minimal" showIcon={false} />
67
+ </div>
68
+
69
+ {/* Gradiente de fundo para mobile */}
70
+ <div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
71
+ <div className="w-full max-w-sm space-y-6 relative z-10">
72
+ {/* Header do formulário */}
73
+ <div className="text-center">
74
+ <div className="flex items-center justify-center mb-4">
75
+ <XerticaLogo
76
+ className="h-12 w-auto text-primary dark:text-foreground"
77
+ variant="theme"
78
+ />
79
+ </div>
80
+ <h2 className="text-sm text-muted-foreground">Faça login em sua conta</h2>
81
+ </div>
82
+
83
+ {/* Formulário */}
84
+ <form className="space-y-6" onSubmit={handleSubmit}>
85
+ <div className="space-y-2">
86
+ <Label htmlFor="email">
87
+ E-mail
88
+ </Label>
89
+ <Input
90
+ id="email"
91
+ name="email"
92
+ type="email"
93
+ required
94
+ className="w-full"
95
+ placeholder="seu@email.com"
96
+ value={email}
97
+ onChange={(e) => setEmail(e.target.value)}
98
+ />
99
+ </div>
100
+
101
+ <div className="space-y-2">
102
+ <Label htmlFor="password">
103
+ Senha
104
+ </Label>
105
+ <Input
106
+ id="password"
107
+ name="password"
108
+ type="password"
109
+ required
110
+ className="w-full"
111
+ placeholder="••••••••"
112
+ value={password}
113
+ onChange={(e) => setPassword(e.target.value)}
114
+ />
115
+ </div>
116
+
117
+ {error && (
118
+ <div className="text-destructive text-sm text-center">
119
+ {error}
120
+ </div>
121
+ )}
122
+
123
+ <Button
124
+ type="submit"
125
+ className="w-full"
126
+ disabled={isLoading}
127
+ >
128
+ {isLoading ? 'Entrando...' : 'Entrar'}
129
+ </Button>
130
+
131
+ {/* Link para recuperação de senha */}
132
+ <div className="text-center">
133
+ <button
134
+ type="button"
135
+ onClick={() => navigate('/forgot-password')}
136
+ className="text-sm text-primary hover:opacity-80 transition-colors"
137
+ >
138
+ Esqueceu sua senha?
139
+ </button>
140
+ </div>
141
+ </form>
142
+
143
+ {/* Divider */}
144
+ <div className="relative">
145
+ <div className="absolute inset-0 flex items-center">
146
+ <div className="w-full border-t border-border"></div>
147
+ </div>
148
+ <div className="relative flex justify-center text-sm">
149
+ <span className="bg-muted px-2 text-muted-foreground">
150
+ ou continuar com
151
+ </span>
152
+ </div>
153
+ </div>
154
+
155
+ {/* Opções de login social/SSO */}
156
+ <div className="space-y-3">
157
+ {/* Google */}
158
+ <Button
159
+ type="button"
160
+ variant="outline"
161
+ className="w-full justify-center"
162
+ onClick={() => handleSocialLogin('Google')}
163
+ >
164
+ <svg className="w-5 h-5 mr-2" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
165
+ <path fill="#EA4335" 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"></path>
166
+ <path fill="#4285F4" 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"></path>
167
+ <path fill="#FBBC05" 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"></path>
168
+ <path fill="#34A853" 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"></path>
169
+ <path fill="none" d="M0 0h48v48H0z"></path>
170
+ </svg>
171
+ <span>Entrar com Google</span>
172
+ </Button>
173
+
174
+ {/* MT Login */}
175
+ <Button
176
+ type="button"
177
+ variant="outline"
178
+ className="w-full justify-center"
179
+ onClick={() => handleSocialLogin('MT Login')}
180
+ >
181
+ <Lock className="w-5 h-5 mr-2 text-[var(--chart-4)]" />
182
+ <span>Entrar com MT Login</span>
183
+ </Button>
184
+
185
+ {/* gov.br */}
186
+ <Button
187
+ type="button"
188
+ variant="outline"
189
+ className="w-full justify-center font-normal"
190
+ onClick={() => handleSocialLogin('gov.br')}
191
+ >
192
+ <span>Entrar com <strong className="font-semibold">gov.br</strong></span>
193
+ </Button>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ );
199
+ }
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+
3
+ interface MarkdownMessageProps {
4
+ content: string;
5
+ className?: string;
6
+ }
7
+
8
+ export function MarkdownMessage({ content, className = '' }: MarkdownMessageProps) {
9
+ // Converter Markdown simples para HTML
10
+ const convertMarkdownToHtml = (markdown: string): string => {
11
+ let html = markdown;
12
+
13
+ // Escapar HTML existente
14
+ html = html.replace(/</g, '<').replace(/>/g, '>');
15
+
16
+ // Headers (do maior para o menor para evitar conflitos)
17
+ html = html.replace(/^### (.*$)/gim, '<h3 class="font-medium text-sm mt-2 mb-1 break-words">$1</h3>');
18
+ html = html.replace(/^## (.*$)/gim, '<h2 class="font-medium text-base mt-3 mb-1.5 break-words">$1</h2>');
19
+ html = html.replace(/^# (.*$)/gim, '<h1 class="font-medium text-lg mt-3 mb-2 break-words">$1</h1>');
20
+
21
+ // Bold (negrito)
22
+ html = html.replace(/\*\*(.*?)\*\*/g, '<strong class="font-medium">$1</strong>');
23
+
24
+ // Italic (itálico)
25
+ html = html.replace(/\*(.*?)\*/g, '<em class="italic">$1</em>');
26
+
27
+ // Links
28
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="text-[var(--chart-4)] hover:underline break-all" target="_blank" rel="noopener noreferrer">$1</a>');
29
+
30
+ // Code inline
31
+ html = html.replace(/`([^`]+)`/g, '<code class="px-1.5 py-0.5 rounded bg-muted text-xs font-mono break-all inline-block max-w-full">$1</code>');
32
+
33
+ // Listas não ordenadas (bullet points) - usar list-disc nativo, sem adicionar • manual
34
+ html = html.replace(/^[•\-] (.*$)/gim, '<li class="ml-4 mb-1 list-disc break-words">$1</li>');
35
+
36
+ // Listas ordenadas
37
+ html = html.replace(/^\d+\. (.*$)/gim, '<li class="ml-4 mb-1 list-decimal break-words">$1</li>');
38
+
39
+ // Emojis e ícones (manter como estão)
40
+
41
+ // Line breaks (preservar quebras de linha duplas como parágrafos)
42
+ html = html.replace(/\n\n/g, '</p><p class="mb-2 break-words">');
43
+ html = html.replace(/\n/g, '<br/>');
44
+
45
+ // Wrap em parágrafo inicial
46
+ html = '<p class="mb-2 break-words">' + html + '</p>';
47
+
48
+ // Limpar parágrafos vazios
49
+ html = html.replace(/<p[^>]*>\s*<\/p>/g, '');
50
+
51
+ return html;
52
+ };
53
+
54
+ const htmlContent = convertMarkdownToHtml(content);
55
+
56
+ return (
57
+ <div
58
+ className={`text-sm leading-relaxed break-words overflow-wrap-anywhere max-w-full ${className}`}
59
+ dangerouslySetInnerHTML={{ __html: htmlContent }}
60
+ />
61
+ );
62
+ }