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,504 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { X, Save, Download, FileText, Undo, Redo, Bold, Italic, List, ListOrdered, Type } from 'lucide-react';
3
+ import { Button } from './ui/button';
4
+ import { Separator } from './ui/separator';
5
+
6
+ interface DocumentEditorProps {
7
+ initialContent: string;
8
+ initialTitle?: string;
9
+ onClose: () => void;
10
+ }
11
+
12
+ export function DocumentEditor({ initialContent, initialTitle = 'Novo Documento', onClose }: DocumentEditorProps) {
13
+ // Converter Markdown para HTML formatado
14
+ const convertMarkdownToHtml = (markdown: string): string => {
15
+ let html = markdown;
16
+
17
+ // Headers
18
+ html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
19
+ html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
20
+ html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
21
+
22
+ // Bold
23
+ html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
24
+
25
+ // Italic
26
+ html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
27
+
28
+ // Lists (remover marcadores markdown, manter como texto)
29
+ html = html.replace(/^\- (.*$)/gim, '• $1');
30
+ html = html.replace(/^\d+\. (.*$)/gim, '$1');
31
+
32
+ // Checkboxes (converter para texto)
33
+ html = html.replace(/- \[ \] (.*$)/gim, '☐ $1');
34
+ html = html.replace(/- \[x\] (.*$)/gim, '☑ $1');
35
+
36
+ // Horizontal rule
37
+ html = html.replace(/^---$/gim, '─────────────────────');
38
+
39
+ // Line breaks
40
+ html = html.replace(/\n/g, '<br/>');
41
+
42
+ return html;
43
+ };
44
+
45
+ const formattedInitialContent = convertMarkdownToHtml(initialContent);
46
+
47
+ const [title, setTitle] = useState(initialTitle);
48
+ const [isSaving, setIsSaving] = useState(false);
49
+ const [isUserEditing, setIsUserEditing] = useState(false);
50
+ const [lastSavedContent, setLastSavedContent] = useState(initialContent);
51
+ const [isActivelyTyping, setIsActivelyTyping] = useState(false);
52
+ const [history, setHistory] = useState<string[]>([initialContent]);
53
+ const [historyIndex, setHistoryIndex] = useState(0);
54
+
55
+ const editorRef = useRef<HTMLDivElement>(null);
56
+ const typingTimer = useRef<NodeJS.Timeout | null>(null);
57
+ const debounceTimer = useRef<NodeJS.Timeout | null>(null);
58
+ const highlightTimer = useRef<NodeJS.Timeout | null>(null);
59
+ const lastContentRef = useRef(initialContent);
60
+ const isComposingRef = useRef(false);
61
+ const isProcessingRef = useRef(false);
62
+
63
+ // Handler para input
64
+ const handleInput = () => {
65
+ if (!editorRef.current || isComposingRef.current || isProcessingRef.current) return;
66
+
67
+ const newContent = editorRef.current.innerText;
68
+ const oldContent = lastContentRef.current;
69
+
70
+ if (newContent !== oldContent) {
71
+ lastContentRef.current = newContent;
72
+ setIsUserEditing(true);
73
+ setIsActivelyTyping(true);
74
+
75
+ // Adicionar ao histórico com debounce
76
+ if (debounceTimer.current) {
77
+ clearTimeout(debounceTimer.current);
78
+ }
79
+
80
+ debounceTimer.current = setTimeout(() => {
81
+ setHistory(prev => {
82
+ const newHistory = prev.slice(0, historyIndex + 1);
83
+ return [...newHistory, newContent];
84
+ });
85
+ setHistoryIndex(prev => prev + 1);
86
+ }, 500);
87
+
88
+ // Aplicar destaque temporário
89
+ applyTemporaryHighlight();
90
+
91
+ // Timer para parar indicador de digitação
92
+ if (typingTimer.current) {
93
+ clearTimeout(typingTimer.current);
94
+ }
95
+
96
+ typingTimer.current = setTimeout(() => {
97
+ setIsActivelyTyping(false);
98
+ }, 1500);
99
+ }
100
+ };
101
+
102
+ // Aplicar destaque temporário ao fundo
103
+ const applyTemporaryHighlight = () => {
104
+ if (!editorRef.current) return;
105
+
106
+ isProcessingRef.current = true;
107
+
108
+ // Adicionar cor de destaque usando variável do sistema
109
+ editorRef.current.style.backgroundColor = 'var(--accent)';
110
+
111
+ // Remover destaque após 2 segundos
112
+ if (highlightTimer.current) {
113
+ clearTimeout(highlightTimer.current);
114
+ }
115
+
116
+ highlightTimer.current = setTimeout(() => {
117
+ if (editorRef.current) {
118
+ editorRef.current.style.backgroundColor = '';
119
+ }
120
+ isProcessingRef.current = false;
121
+ }, 2000);
122
+ };
123
+
124
+ // Undo
125
+ const handleUndo = () => {
126
+ if (historyIndex > 0) {
127
+ const newIndex = historyIndex - 1;
128
+ setHistoryIndex(newIndex);
129
+ const content = history[newIndex];
130
+ if (editorRef.current) {
131
+ isProcessingRef.current = true;
132
+ editorRef.current.innerText = content;
133
+ lastContentRef.current = content;
134
+ setTimeout(() => {
135
+ isProcessingRef.current = false;
136
+ }, 100);
137
+ }
138
+ }
139
+ };
140
+
141
+ // Redo
142
+ const handleRedo = () => {
143
+ if (historyIndex < history.length - 1) {
144
+ const newIndex = historyIndex + 1;
145
+ setHistoryIndex(newIndex);
146
+ const content = history[newIndex];
147
+ if (editorRef.current) {
148
+ isProcessingRef.current = true;
149
+ editorRef.current.innerText = content;
150
+ lastContentRef.current = content;
151
+ setTimeout(() => {
152
+ isProcessingRef.current = false;
153
+ }, 100);
154
+ }
155
+ }
156
+ };
157
+
158
+ // Aplicar formatação
159
+ const applyFormat = (command: string, value?: string) => {
160
+ document.execCommand(command, false, value);
161
+ editorRef.current?.focus();
162
+ };
163
+
164
+ // Handlers para composição (IME)
165
+ const handleCompositionStart = () => {
166
+ isComposingRef.current = true;
167
+ };
168
+
169
+ const handleCompositionEnd = () => {
170
+ isComposingRef.current = false;
171
+ handleInput();
172
+ };
173
+
174
+ // Atalhos de teclado
175
+ useEffect(() => {
176
+ const handleKeyDown = (e: KeyboardEvent) => {
177
+ if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
178
+ e.preventDefault();
179
+ handleUndo();
180
+ } else if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
181
+ e.preventDefault();
182
+ handleRedo();
183
+ } else if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
184
+ e.preventDefault();
185
+ applyFormat('bold');
186
+ } else if ((e.ctrlKey || e.metaKey) && e.key === 'i') {
187
+ e.preventDefault();
188
+ applyFormat('italic');
189
+ }
190
+ };
191
+
192
+ window.addEventListener('keydown', handleKeyDown);
193
+ return () => window.removeEventListener('keydown', handleKeyDown);
194
+ }, [historyIndex, history]);
195
+
196
+ // Carregar conteúdo formatado inicialmente
197
+ useEffect(() => {
198
+ if (editorRef.current && !editorRef.current.innerHTML) {
199
+ editorRef.current.innerHTML = formattedInitialContent;
200
+ lastContentRef.current = initialContent;
201
+ }
202
+ }, []);
203
+
204
+ // Limpar timers
205
+ useEffect(() => {
206
+ return () => {
207
+ if (typingTimer.current) clearTimeout(typingTimer.current);
208
+ if (debounceTimer.current) clearTimeout(debounceTimer.current);
209
+ if (highlightTimer.current) clearTimeout(highlightTimer.current);
210
+ };
211
+ }, []);
212
+
213
+ const handleSave = () => {
214
+ setIsSaving(true);
215
+ setIsActivelyTyping(false);
216
+
217
+ if (typingTimer.current) {
218
+ clearTimeout(typingTimer.current);
219
+ }
220
+ if (highlightTimer.current) {
221
+ clearTimeout(highlightTimer.current);
222
+ }
223
+
224
+ setTimeout(() => {
225
+ setIsSaving(false);
226
+ setLastSavedContent(editorRef.current?.innerText || '');
227
+ setIsUserEditing(false);
228
+
229
+ // Remover destaque
230
+ if (editorRef.current) {
231
+ editorRef.current.style.backgroundColor = '';
232
+ }
233
+ }, 1000);
234
+ };
235
+
236
+ const handleDownload = () => {
237
+ const content = editorRef.current?.innerText || '';
238
+ const blob = new Blob([content], { type: 'text/plain' });
239
+ const url = URL.createObjectURL(blob);
240
+ const a = document.createElement('a');
241
+ a.href = url;
242
+ a.download = `${title}.txt`;
243
+ document.body.appendChild(a);
244
+ a.click();
245
+ document.body.removeChild(a);
246
+ URL.revokeObjectURL(url);
247
+ };
248
+
249
+ const getWordCount = () => {
250
+ const content = editorRef.current?.innerText || '';
251
+ return content.split(/\s+/).filter(word => word.length > 0).length;
252
+ };
253
+
254
+ const hasUnsavedChanges = () => {
255
+ return (editorRef.current?.innerText || '') !== lastSavedContent;
256
+ };
257
+
258
+ const canUndo = historyIndex > 0;
259
+ const canRedo = historyIndex < history.length - 1;
260
+
261
+ return (
262
+ <div className="flex flex-col h-full bg-background">
263
+ {/* Header */}
264
+ <div className="flex-shrink-0 border-b border-border">
265
+ <div className="flex items-center justify-between px-[10px] py-[16px]">
266
+ <div className="flex items-center gap-2 flex-1 min-w-0">
267
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[var(--chart-4)] to-[var(--chart-4)]/80 flex items-center justify-center flex-shrink-0">
268
+ <FileText className="w-4 h-4 text-white" />
269
+ </div>
270
+ <input
271
+ type="text"
272
+ value={title}
273
+ onChange={(e) => setTitle(e.target.value)}
274
+ className="flex-1 bg-transparent border-none outline-none text-sm font-medium text-foreground min-w-0"
275
+ placeholder="Título do documento"
276
+ />
277
+ </div>
278
+ <div className="flex items-center gap-1 flex-shrink-0 ml-2">
279
+ <Button
280
+ variant="ghost"
281
+ size="sm"
282
+ onClick={handleUndo}
283
+ disabled={!canUndo}
284
+ className="h-8 w-8 p-0"
285
+ title="Desfazer (Ctrl+Z)"
286
+ >
287
+ <Undo className="w-4 h-4" />
288
+ </Button>
289
+ <Button
290
+ variant="ghost"
291
+ size="sm"
292
+ onClick={handleRedo}
293
+ disabled={!canRedo}
294
+ className="h-8 w-8 p-0"
295
+ title="Refazer (Ctrl+Y)"
296
+ >
297
+ <Redo className="w-4 h-4" />
298
+ </Button>
299
+ <Separator orientation="vertical" className="h-5 mx-1" />
300
+ <Button
301
+ variant="ghost"
302
+ size="sm"
303
+ onClick={onClose}
304
+ className="h-8 w-8 p-0"
305
+ >
306
+ <X className="w-4 h-4" />
307
+ </Button>
308
+ </div>
309
+ </div>
310
+
311
+ {/* Toolbar */}
312
+ <div className="px-3 pb-2 border-t border-border bg-muted">
313
+ <div className="flex items-center gap-1 py-2">
314
+ <Button
315
+ variant="ghost"
316
+ size="sm"
317
+ onClick={() => applyFormat('bold')}
318
+ className="h-8 w-8 p-0"
319
+ title="Negrito (Ctrl+B)"
320
+ >
321
+ <Bold className="w-4 h-4" />
322
+ </Button>
323
+ <Button
324
+ variant="ghost"
325
+ size="sm"
326
+ onClick={() => applyFormat('italic')}
327
+ className="h-8 w-8 p-0"
328
+ title="Itálico (Ctrl+I)"
329
+ >
330
+ <Italic className="w-4 h-4" />
331
+ </Button>
332
+ <Separator orientation="vertical" className="h-5 mx-1" />
333
+ <Button
334
+ variant="ghost"
335
+ size="sm"
336
+ onClick={() => applyFormat('formatBlock', 'h1')}
337
+ className="h-8 px-2"
338
+ title="Título 1"
339
+ >
340
+ <span className="text-xs font-medium">H1</span>
341
+ </Button>
342
+ <Button
343
+ variant="ghost"
344
+ size="sm"
345
+ onClick={() => applyFormat('formatBlock', 'h2')}
346
+ className="h-8 px-2"
347
+ title="Título 2"
348
+ >
349
+ <span className="text-xs font-medium">H2</span>
350
+ </Button>
351
+ <Button
352
+ variant="ghost"
353
+ size="sm"
354
+ onClick={() => applyFormat('formatBlock', 'p')}
355
+ className="h-8 px-2"
356
+ title="Parágrafo"
357
+ >
358
+ <Type className="w-4 h-4" />
359
+ </Button>
360
+ <Separator orientation="vertical" className="h-5 mx-1" />
361
+ <Button
362
+ variant="ghost"
363
+ size="sm"
364
+ onClick={() => applyFormat('insertUnorderedList')}
365
+ className="h-8 w-8 p-0"
366
+ title="Lista com marcadores"
367
+ >
368
+ <List className="w-4 h-4" />
369
+ </Button>
370
+ <Button
371
+ variant="ghost"
372
+ size="sm"
373
+ onClick={() => applyFormat('insertOrderedList')}
374
+ className="h-8 w-8 p-0"
375
+ title="Lista numerada"
376
+ >
377
+ <ListOrdered className="w-4 h-4" />
378
+ </Button>
379
+ </div>
380
+ </div>
381
+ </div>
382
+
383
+ {/* Content Editor */}
384
+ <div className="flex-1 overflow-auto">
385
+ <div
386
+ ref={editorRef}
387
+ contentEditable
388
+ onInput={handleInput}
389
+ onCompositionStart={handleCompositionStart}
390
+ onCompositionEnd={handleCompositionEnd}
391
+ className="p-4 min-h-full outline-none text-foreground leading-relaxed text-sm transition-colors duration-300 editor-content font-sans whitespace-pre-wrap break-word"
392
+ suppressContentEditableWarning
393
+ data-placeholder="Comece a escrever seu documento..."
394
+ />
395
+ </div>
396
+
397
+ {/* Footer Actions */}
398
+ <div className="flex-shrink-0 border-t border-border p-3">
399
+ <div className="flex items-center justify-between gap-2">
400
+ <div className="flex items-center gap-2">
401
+ <div className="text-xs text-muted-foreground">
402
+ {getWordCount()} palavras
403
+ </div>
404
+ {isActivelyTyping ? (
405
+ <div className="flex items-center gap-1 text-xs text-[var(--toast-success-icon)]">
406
+ <span className="w-1.5 h-1.5 rounded-full bg-[var(--toast-success-icon)] animate-pulse" />
407
+ <span>Digitando...</span>
408
+ </div>
409
+ ) : isUserEditing && hasUnsavedChanges() ? (
410
+ <div className="flex items-center gap-1 text-xs text-[var(--toast-warning-icon)]">
411
+ <span className="w-1.5 h-1.5 rounded-full bg-[var(--toast-warning-icon)] animate-pulse" />
412
+ <span>Alterações não salvas</span>
413
+ </div>
414
+ ) : null}
415
+ </div>
416
+ <div className="flex items-center gap-2">
417
+ <Button
418
+ variant="outline"
419
+ size="sm"
420
+ onClick={handleDownload}
421
+ className="h-8"
422
+ >
423
+ <Download className="w-3.5 h-3.5 mr-1.5" />
424
+ Baixar
425
+ </Button>
426
+ <Button
427
+ size="sm"
428
+ onClick={handleSave}
429
+ disabled={isSaving}
430
+ className="bg-primary hover:bg-primary/90 text-primary-foreground h-8"
431
+ >
432
+ <Save className="w-3.5 h-3.5 mr-1.5" />
433
+ {isSaving ? 'Salvando...' : 'Salvar'}
434
+ </Button>
435
+ </div>
436
+ </div>
437
+ </div>
438
+
439
+ <style>{`
440
+ [contenteditable]:empty:before {
441
+ content: attr(data-placeholder);
442
+ color: var(--muted-foreground);
443
+ pointer-events: none;
444
+ }
445
+ .dark [contenteditable]:empty:before {
446
+ color: var(--muted-foreground);
447
+ }
448
+
449
+ /* Estilos do editor sem margens extras */
450
+ .editor-content h1,
451
+ .editor-content h2,
452
+ .editor-content h3,
453
+ .editor-content h4,
454
+ .editor-content h5,
455
+ .editor-content h6 {
456
+ margin: 0;
457
+ padding: 0;
458
+ font-weight: 600;
459
+ }
460
+
461
+ .editor-content h1 {
462
+ font-size: 2em;
463
+ margin-top: 0.5em;
464
+ margin-bottom: 0.5em;
465
+ }
466
+
467
+ .editor-content h2 {
468
+ font-size: 1.5em;
469
+ margin-top: 0.5em;
470
+ margin-bottom: 0.5em;
471
+ }
472
+
473
+ .editor-content h3 {
474
+ font-size: 1.25em;
475
+ margin-top: 0.5em;
476
+ margin-bottom: 0.5em;
477
+ }
478
+
479
+ .editor-content p {
480
+ margin: 0;
481
+ padding: 0;
482
+ }
483
+
484
+ .editor-content ul,
485
+ .editor-content ol {
486
+ margin: 0.5em 0;
487
+ padding-left: 1.5em;
488
+ }
489
+
490
+ .editor-content li {
491
+ margin: 0.25em 0;
492
+ }
493
+
494
+ .editor-content strong {
495
+ font-weight: 600;
496
+ }
497
+
498
+ .editor-content em {
499
+ font-style: italic;
500
+ }
501
+ `}</style>
502
+ </div>
503
+ );
504
+ }
@@ -0,0 +1,170 @@
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 { ArrowLeft, Lock } from 'lucide-react';
9
+ import { useNavigate } from 'react-router';
10
+
11
+ export function ForgotPasswordPage() {
12
+ const navigate = useNavigate();
13
+ const [email, setEmail] = useState('');
14
+ const [isLoading, setIsLoading] = useState(false);
15
+
16
+ const handleSubmit = async (e: React.FormEvent) => {
17
+ e.preventDefault();
18
+ setIsLoading(true);
19
+
20
+ // Simula envio de email
21
+ await new Promise(resolve => setTimeout(resolve, 1500));
22
+
23
+ // Navega para a tela de verificação
24
+ navigate('/verify-email', { state: { email } });
25
+
26
+ setIsLoading(false);
27
+ };
28
+
29
+ const handleSocialLogin = (provider: string) => {
30
+ // Simula login social/SSO
31
+ console.log(`Login com ${provider}`);
32
+ // Aqui seria implementada a integração real com cada provedor
33
+ };
34
+
35
+ return (
36
+ <div className="min-h-screen flex">
37
+ {/* Lado esquerdo - Imagem de fundo completa */}
38
+ <div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
39
+ {/* Imagem de fundo preenchendo todo o espaço */}
40
+ <ImageWithFallback
41
+ src="https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1200&h=800&fit=crop&auto=format"
42
+ alt="Segurança e tecnologia"
43
+ className="absolute inset-0 w-full h-full object-cover"
44
+ />
45
+
46
+ {/* Overlay com gradiente */}
47
+ <div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
48
+ </div>
49
+
50
+ {/* Lado direito - Formulário */}
51
+ <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">
52
+ {/* Seletor de idioma no canto superior direito */}
53
+ <div className="absolute top-4 right-4 z-20">
54
+ <LanguageSelector variant="minimal" showIcon={false} />
55
+ </div>
56
+
57
+ {/* Gradiente de fundo para mobile */}
58
+ <div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
59
+
60
+ <div className="w-full max-w-sm space-y-6 relative z-10">
61
+ {/* Header do formulário */}
62
+ <div className="text-center">
63
+ <div className="flex items-center justify-center mb-4">
64
+ <XerticaLogo
65
+ className="h-12 w-auto text-primary dark:text-foreground"
66
+ variant="theme"
67
+ />
68
+ </div>
69
+ <h2 className="text-sm text-muted-foreground">Recuperar senha</h2>
70
+ <p className="mt-2 text-muted-foreground">
71
+ Digite seu email e enviaremos instruções para redefinir sua senha
72
+ </p>
73
+ </div>
74
+
75
+ {/* Formulário */}
76
+ <form className="space-y-6" onSubmit={handleSubmit}>
77
+ <div className="form-group space-y-2">
78
+ <Label htmlFor="email" className="form-label">
79
+ E-mail
80
+ </Label>
81
+ <Input
82
+ id="email"
83
+ name="email"
84
+ type="email"
85
+ required
86
+ className="w-full"
87
+ placeholder="seu@email.com"
88
+ value={email}
89
+ onChange={(e) => setEmail(e.target.value)}
90
+ />
91
+ </div>
92
+
93
+ <div className="space-y-3">
94
+ <Button
95
+ type="submit"
96
+ className="w-full"
97
+ disabled={isLoading}
98
+ >
99
+ {isLoading ? 'Enviando...' : 'Enviar instruções'}
100
+ </Button>
101
+
102
+ <Button
103
+ type="button"
104
+ onClick={() => navigate('/login')}
105
+ variant="outline"
106
+ className="w-full text-muted-foreground hover:text-foreground"
107
+ >
108
+ <ArrowLeft className="w-4 h-4 mr-2" />
109
+ Voltar para login
110
+ </Button>
111
+ </div>
112
+ </form>
113
+
114
+ {/* Divider */}
115
+ <div className="relative">
116
+ <div className="absolute inset-0 flex items-center">
117
+ <div className="w-full border-t border-border"></div>
118
+ </div>
119
+ <div className="relative flex justify-center text-sm">
120
+ <span className="bg-muted px-2 text-muted-foreground">
121
+ ou continuar com
122
+ </span>
123
+ </div>
124
+ </div>
125
+
126
+ {/* Opções de login social/SSO */}
127
+ <div className="space-y-3">
128
+ {/* Google */}
129
+ <Button
130
+ type="button"
131
+ variant="outline"
132
+ className="w-full justify-center"
133
+ onClick={() => handleSocialLogin('Google')}
134
+ >
135
+ <svg className="w-5 h-5 mr-2" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
136
+ <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>
137
+ <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>
138
+ <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>
139
+ <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>
140
+ <path fill="none" d="M0 0h48v48H0z"></path>
141
+ </svg>
142
+ <span>Entrar com Google</span>
143
+ </Button>
144
+
145
+ {/* MT Login */}
146
+ <Button
147
+ type="button"
148
+ variant="outline"
149
+ className="w-full justify-center"
150
+ onClick={() => handleSocialLogin('MT Login')}
151
+ >
152
+ <Lock className="w-5 h-5 mr-2 text-[var(--chart-4)]" />
153
+ <span>Entrar com MT Login</span>
154
+ </Button>
155
+
156
+ {/* gov.br */}
157
+ <Button
158
+ type="button"
159
+ variant="outline"
160
+ className="w-full justify-center font-normal"
161
+ onClick={() => handleSocialLogin('gov.br')}
162
+ >
163
+ <span>Entrar com <strong className="font-semibold">gov.br</strong></span>
164
+ </Button>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ );
170
+ }