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,409 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import {
4
+ PlayCircle,
5
+ PauseCircle,
6
+ Volume2,
7
+ RotateCcw,
8
+ Gauge,
9
+ Info,
10
+ RefreshCw,
11
+ Download,
12
+ X,
13
+ ExternalLink,
14
+ VolumeX,
15
+ Radio,
16
+ MoreHorizontal
17
+ } from 'lucide-react';
18
+ import {
19
+ DropdownMenu,
20
+ DropdownMenuContent,
21
+ DropdownMenuItem,
22
+ DropdownMenuTrigger,
23
+ } from "./ui/dropdown-menu";
24
+ import { Button } from './ui/button';
25
+ import { Slider } from './ui/slider';
26
+ import {
27
+ Tooltip,
28
+ TooltipContent,
29
+ TooltipProvider,
30
+ TooltipTrigger
31
+ } from './ui/tooltip';
32
+ import { cn } from './ui/utils';
33
+
34
+ interface PodcastPlayerProps {
35
+ isOpen: boolean;
36
+ onClose: () => void;
37
+ sidebarExpanded: boolean;
38
+ title?: string;
39
+ subtitle?: string;
40
+ duration?: number; // in seconds
41
+ currentTime?: number;
42
+ variant?: 'fixed' | 'inline';
43
+ layoutMode?: 'viewport' | 'container';
44
+ rightSidebarState?: 'none' | 'collapsed' | 'expanded';
45
+ }
46
+
47
+ export function PodcastPlayer({
48
+ isOpen,
49
+ onClose,
50
+ sidebarExpanded,
51
+ title = "Processo 50002396220258210104",
52
+ subtitle = "Podcast atualizado até Evento 26",
53
+ duration = 1200, // 20:00
54
+ currentTime = 0,
55
+ variant = 'fixed',
56
+ layoutMode = 'viewport',
57
+ rightSidebarState = 'none'
58
+ }: PodcastPlayerProps) {
59
+ const [isPlaying, setIsPlaying] = useState(false);
60
+ const [current, setCurrent] = useState(currentTime);
61
+ const [volume, setVolume] = useState([80]);
62
+ const [isMuted, setIsMuted] = useState(false);
63
+ const [isVisible, setIsVisible] = useState(false);
64
+
65
+ useEffect(() => {
66
+ if (isOpen) {
67
+ setIsVisible(true);
68
+ } else {
69
+ if (variant === 'fixed') {
70
+ const timer = setTimeout(() => setIsVisible(false), 300);
71
+ return () => clearTimeout(timer);
72
+ } else {
73
+ setIsVisible(false);
74
+ }
75
+ }
76
+ }, [isOpen, variant]);
77
+
78
+ if (!isVisible && variant === 'fixed') return null;
79
+ if (!isOpen && variant === 'inline') return null;
80
+
81
+ const formatTime = (seconds: number) => {
82
+ const mins = Math.floor(seconds / 60);
83
+ const secs = Math.floor(seconds % 60);
84
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
85
+ };
86
+
87
+ const togglePlay = () => setIsPlaying(!isPlaying);
88
+ const toggleMute = () => setIsMuted(!isMuted);
89
+
90
+ const isFixed = variant === 'fixed';
91
+ const isViewport = layoutMode === 'viewport';
92
+
93
+ const rightClasses = {
94
+ none: "right-0",
95
+ collapsed: "right-0 md:right-20",
96
+ expanded: "right-0 md:right-[420px]"
97
+ }[rightSidebarState];
98
+
99
+ const playerContent = (
100
+ <div
101
+ className={cn(
102
+ "backdrop-blur-md transition-all duration-300 ease-in-out shadow-[var(--elevation-sm)]",
103
+ isFixed
104
+ ? "bg-primary/80 text-primary-foreground border-t border-primary/20 [&_.text-muted-foreground]:text-primary-foreground/80 [&_.text-foreground]:text-primary-foreground [&_.text-[var(--chart-4)]]:text-primary-foreground [&_button:hover]:bg-primary-foreground/10 [&_button:hover]:text-primary-foreground [&_[data-slot=slider-track]]:bg-primary-foreground/20 [&_[data-slot=slider-range]]:bg-primary-foreground [&_[data-slot=slider-thumb]]:border-primary-foreground"
105
+ : "bg-card/95 text-card-foreground border border-border",
106
+ isFixed ? [
107
+ "z-50",
108
+ isViewport ? "fixed bottom-0" : "absolute bottom-0 left-0 w-full",
109
+ isViewport && rightClasses,
110
+ isViewport && (sidebarExpanded ? "left-0 md:left-64" : "left-0 md:left-20"),
111
+ isOpen ? "translate-y-0 opacity-100" : "translate-y-full opacity-0"
112
+ ] : [
113
+ "relative w-full rounded-lg",
114
+ "opacity-100 translate-y-0"
115
+ ]
116
+ )}
117
+ >
118
+ {/* Desktop Layout: Horizontal */}
119
+ <div className="hidden md:flex h-[72px] px-4 md:px-6 items-center justify-between gap-4">
120
+
121
+ {/* Left: Info */}
122
+ <div className="flex flex-col min-w-0 w-[180px] lg:w-[240px] shrink-0">
123
+ <div className="flex items-center gap-2 mb-0.5">
124
+ <Radio className="w-3 h-3 text-[var(--chart-4)] animate-pulse" />
125
+ <h4 className="text-sm md:text-base truncate font-medium" title={title}>
126
+ {title}
127
+ </h4>
128
+ </div>
129
+ <div className="flex items-center gap-1">
130
+ <span className="truncate text-xs text-muted-foreground">{subtitle}</span>
131
+ <ExternalLink className="w-3 h-3 cursor-pointer hover:text-foreground ml-1 opacity-70 hover:opacity-100" />
132
+ </div>
133
+ </div>
134
+
135
+ {/* Center: Controls */}
136
+ <div className="flex-1 flex items-center justify-center gap-4 max-w-3xl">
137
+ <button
138
+ onClick={togglePlay}
139
+ className="text-muted-foreground hover:text-foreground transition-colors focus:outline-none hover:scale-105 active:scale-95 transform duration-100"
140
+ aria-label={isPlaying ? "Pausar" : "Reproduzir"}
141
+ >
142
+ {isPlaying ? (
143
+ <PauseCircle className="w-10 h-10" strokeWidth={1.5} />
144
+ ) : (
145
+ <PlayCircle className="w-10 h-10" strokeWidth={1.5} />
146
+ )}
147
+ </button>
148
+
149
+ <div className="flex-1 flex items-center gap-3">
150
+ <span className="text-xs text-muted-foreground font-mono shrink-0 w-10 text-right">
151
+ {formatTime(current)}
152
+ </span>
153
+ <Slider
154
+ defaultValue={[0]}
155
+ max={duration}
156
+ value={[current]}
157
+ onValueChange={(val) => setCurrent(val[0])}
158
+ className="cursor-pointer"
159
+ aria-label="Barra de progresso"
160
+ />
161
+ <span className="text-xs text-muted-foreground font-mono shrink-0 w-10">
162
+ {formatTime(duration)}
163
+ </span>
164
+ </div>
165
+ </div>
166
+
167
+ {/* Right: Actions */}
168
+ <div className="flex items-center gap-1 md:gap-3 shrink-0">
169
+ {/* Volume */}
170
+ <div className="hidden lg:flex items-center gap-2 w-28 mr-2 group">
171
+ <button
172
+ onClick={toggleMute}
173
+ className="text-muted-foreground hover:text-foreground"
174
+ aria-label={isMuted ? "Ativar som" : "Silenciar"}
175
+ >
176
+ {isMuted || volume[0] === 0 ? <VolumeX className="w-5 h-5" /> : <Volume2 className="w-5 h-5" />}
177
+ </button>
178
+ <Slider
179
+ defaultValue={[80]}
180
+ max={100}
181
+ value={volume}
182
+ onValueChange={setVolume}
183
+ className="w-full opacity-60 group-hover:opacity-100 transition-opacity"
184
+ aria-label="Controle de volume"
185
+ />
186
+ </div>
187
+
188
+ <div className="flex items-center gap-1">
189
+ {/* Desktop: Restart & Speed */}
190
+ <div className={cn("hidden md:flex items-center gap-1", variant === 'inline' && "!hidden")}>
191
+ <TooltipProvider>
192
+ <Tooltip>
193
+ <TooltipTrigger asChild>
194
+ <Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-full h-9 w-9">
195
+ <RotateCcw className="w-4 h-4" />
196
+ </Button>
197
+ </TooltipTrigger>
198
+ <TooltipContent className="text-xs">Reiniciar</TooltipContent>
199
+ </Tooltip>
200
+ </TooltipProvider>
201
+
202
+ <TooltipProvider>
203
+ <Tooltip>
204
+ <TooltipTrigger asChild>
205
+ <Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-full h-9 w-9">
206
+ <Gauge className="w-4 h-4" />
207
+ </Button>
208
+ </TooltipTrigger>
209
+ <TooltipContent className="text-xs">Velocidade (1.0x)</TooltipContent>
210
+ </Tooltip>
211
+ </TooltipProvider>
212
+ </div>
213
+
214
+ {/* Info (Always visible) */}
215
+ <TooltipProvider>
216
+ <Tooltip>
217
+ <TooltipTrigger asChild>
218
+ <Button variant="ghost" size="icon" className="text-[var(--chart-4)] hover:text-[var(--chart-4)]/80 hover:bg-accent rounded-full h-9 w-9">
219
+ <Info className="w-4 h-4 fill-current" />
220
+ </Button>
221
+ </TooltipTrigger>
222
+ <TooltipContent className="max-w-[300px]">
223
+ <p className="text-xs">Identificamos a entrada de novos eventos neste processo desde a última atualização deste podcast.</p>
224
+ </TooltipContent>
225
+ </Tooltip>
226
+ </TooltipProvider>
227
+
228
+ {/* Desktop: Refresh & Download */}
229
+ <div className={cn("hidden md:flex items-center gap-1", variant === 'inline' && "!hidden")}>
230
+ <TooltipProvider>
231
+ <Tooltip>
232
+ <TooltipTrigger asChild>
233
+ <Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-full h-9 w-9">
234
+ <RefreshCw className="w-4 h-4" />
235
+ </Button>
236
+ </TooltipTrigger>
237
+ <TooltipContent className="text-xs">Atualizar Versão</TooltipContent>
238
+ </Tooltip>
239
+ </TooltipProvider>
240
+
241
+ <Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-full h-9 w-9">
242
+ <Download className="w-4 h-4" />
243
+ </Button>
244
+ </div>
245
+
246
+ {/* Mobile: Menu (containing Restart, Speed, Refresh, Download) */}
247
+ <div className={cn("md:hidden", variant === 'inline' && "!block")}>
248
+ <DropdownMenu>
249
+ <DropdownMenuTrigger asChild>
250
+ <Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-full h-9 w-9">
251
+ <MoreHorizontal className="w-5 h-5" />
252
+ </Button>
253
+ </DropdownMenuTrigger>
254
+ <DropdownMenuContent align="end" className="w-48">
255
+ <DropdownMenuItem className="cursor-pointer">
256
+ <RotateCcw className="w-4 h-4 mr-2" />
257
+ <span className="text-xs">Reiniciar</span>
258
+ </DropdownMenuItem>
259
+ <DropdownMenuItem className="cursor-pointer">
260
+ <Gauge className="w-4 h-4 mr-2" />
261
+ <span className="text-xs">Velocidade (1.0x)</span>
262
+ </DropdownMenuItem>
263
+ <DropdownMenuItem className="cursor-pointer">
264
+ <RefreshCw className="w-4 h-4 mr-2" />
265
+ <span className="text-xs">Atualizar Versão</span>
266
+ </DropdownMenuItem>
267
+ <DropdownMenuItem className="cursor-pointer">
268
+ <Download className="w-4 h-4 mr-2" />
269
+ <span className="text-xs">Download</span>
270
+ </DropdownMenuItem>
271
+ </DropdownMenuContent>
272
+ </DropdownMenu>
273
+ </div>
274
+
275
+ <div className="w-px h-8 bg-border mx-1" />
276
+
277
+ <Button onClick={onClose} variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-full h-9 w-9">
278
+ <X className="w-5 h-5" />
279
+ </Button>
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ {/* Mobile Layout: Vertical */}
285
+ <div className="flex md:hidden flex-col py-3 px-4 gap-3">
286
+ {/* Top: Info & Close Button */}
287
+ <div className="flex items-start justify-between gap-3">
288
+ <div className="flex flex-col min-w-0 flex-1">
289
+ <div className="flex items-center gap-2 mb-1">
290
+ <Radio className="w-3 h-3 text-[var(--chart-4)] animate-pulse shrink-0" />
291
+ <h4 className="text-sm truncate font-medium" title={title}>
292
+ {title}
293
+ </h4>
294
+ </div>
295
+ <div className="flex items-center gap-1.5">
296
+ <span className="truncate text-xs text-muted-foreground">{subtitle}</span>
297
+ <ExternalLink className="w-3 h-3 cursor-pointer hover:text-foreground opacity-70 hover:opacity-100 shrink-0" />
298
+ </div>
299
+ </div>
300
+
301
+ <Button onClick={onClose} variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-full h-8 w-8 shrink-0">
302
+ <X className="w-4 h-4" />
303
+ </Button>
304
+ </div>
305
+
306
+ {/* Middle: Progress Bar */}
307
+ <div className="flex items-center gap-2">
308
+ <span className="text-xs text-muted-foreground font-mono shrink-0 w-10 text-right">
309
+ {formatTime(current)}
310
+ </span>
311
+ <Slider
312
+ defaultValue={[0]}
313
+ max={duration}
314
+ value={[current]}
315
+ onValueChange={(val) => setCurrent(val[0])}
316
+ className="cursor-pointer"
317
+ aria-label="Barra de progresso"
318
+ />
319
+ <span className="text-xs text-muted-foreground font-mono shrink-0 w-10">
320
+ {formatTime(duration)}
321
+ </span>
322
+ </div>
323
+
324
+ {/* Bottom: Controls */}
325
+ <div className="flex items-center justify-between gap-2">
326
+ {/* Play/Pause Button */}
327
+ <button
328
+ onClick={togglePlay}
329
+ className="text-muted-foreground hover:text-foreground transition-colors focus:outline-none hover:scale-105 active:scale-95 transform duration-100"
330
+ aria-label={isPlaying ? "Pausar" : "Reproduzir"}
331
+ >
332
+ {isPlaying ? (
333
+ <PauseCircle className="w-12 h-12" strokeWidth={1.5} />
334
+ ) : (
335
+ <PlayCircle className="w-12 h-12" strokeWidth={1.5} />
336
+ )}
337
+ </button>
338
+
339
+ {/* Volume Control */}
340
+ <div className="flex items-center gap-2 flex-1 max-w-[140px]">
341
+ <button
342
+ onClick={toggleMute}
343
+ className="text-muted-foreground hover:text-foreground shrink-0"
344
+ aria-label={isMuted ? "Ativar som" : "Silenciar"}
345
+ >
346
+ {isMuted || volume[0] === 0 ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />}
347
+ </button>
348
+ <Slider
349
+ defaultValue={[80]}
350
+ max={100}
351
+ value={volume}
352
+ onValueChange={setVolume}
353
+ className="w-full"
354
+ aria-label="Controle de volume"
355
+ />
356
+ </div>
357
+
358
+ {/* Action Buttons */}
359
+ <div className="flex items-center gap-1">
360
+ <TooltipProvider>
361
+ <Tooltip>
362
+ <TooltipTrigger asChild>
363
+ <Button variant="ghost" size="icon" className="text-[var(--chart-4)] hover:text-[var(--chart-4)]/80 hover:bg-accent rounded-full h-8 w-8">
364
+ <Info className="w-4 h-4 fill-current" />
365
+ </Button>
366
+ </TooltipTrigger>
367
+ <TooltipContent className="max-w-[300px]">
368
+ <p className="text-xs">Identificamos a entrada de novos eventos neste processo desde a última atualização deste podcast.</p>
369
+ </TooltipContent>
370
+ </Tooltip>
371
+ </TooltipProvider>
372
+
373
+ <DropdownMenu>
374
+ <DropdownMenuTrigger asChild>
375
+ <Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-full h-8 w-8">
376
+ <MoreHorizontal className="w-4 h-4" />
377
+ </Button>
378
+ </DropdownMenuTrigger>
379
+ <DropdownMenuContent align="end" className="w-48">
380
+ <DropdownMenuItem className="cursor-pointer">
381
+ <RotateCcw className="w-4 h-4 mr-2" />
382
+ <span className="text-xs">Reiniciar</span>
383
+ </DropdownMenuItem>
384
+ <DropdownMenuItem className="cursor-pointer">
385
+ <Gauge className="w-4 h-4 mr-2" />
386
+ <span className="text-xs">Velocidade (1.0x)</span>
387
+ </DropdownMenuItem>
388
+ <DropdownMenuItem className="cursor-pointer">
389
+ <RefreshCw className="w-4 h-4 mr-2" />
390
+ <span className="text-xs">Atualizar Versão</span>
391
+ </DropdownMenuItem>
392
+ <DropdownMenuItem className="cursor-pointer">
393
+ <Download className="w-4 h-4 mr-2" />
394
+ <span className="text-xs">Download</span>
395
+ </DropdownMenuItem>
396
+ </DropdownMenuContent>
397
+ </DropdownMenu>
398
+ </div>
399
+ </div>
400
+ </div>
401
+ </div>
402
+ );
403
+
404
+ if (isFixed && isViewport && typeof document !== 'undefined') {
405
+ return createPortal(playerContent, document.body);
406
+ }
407
+
408
+ return playerContent;
409
+ }
@@ -0,0 +1,234 @@
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, CheckCircle2, AlertCircle } from 'lucide-react';
9
+ import { useNavigate } from 'react-router';
10
+
11
+ export function ResetPasswordPage() {
12
+ const navigate = useNavigate();
13
+ const [password, setPassword] = useState('');
14
+ const [confirmPassword, setConfirmPassword] = useState('');
15
+ const [isLoading, setIsLoading] = useState(false);
16
+ const [error, setError] = useState('');
17
+ const [passwordStrength, setPasswordStrength] = useState<'weak' | 'medium' | 'strong' | null>(null);
18
+
19
+ // Validação de força da senha
20
+ const checkPasswordStrength = (pwd: string) => {
21
+ if (pwd.length === 0) {
22
+ setPasswordStrength(null);
23
+ return;
24
+ }
25
+
26
+ if (pwd.length < 6) {
27
+ setPasswordStrength('weak');
28
+ } else if (pwd.length < 10) {
29
+ setPasswordStrength('medium');
30
+ } else {
31
+ setPasswordStrength('strong');
32
+ }
33
+ };
34
+
35
+ const handlePasswordChange = (value: string) => {
36
+ setPassword(value);
37
+ checkPasswordStrength(value);
38
+ setError('');
39
+ };
40
+
41
+ const handleSubmit = async (e: React.FormEvent) => {
42
+ e.preventDefault();
43
+ setError('');
44
+
45
+ // Validações
46
+ if (password.length < 6) {
47
+ setError('A senha deve ter no mínimo 6 caracteres');
48
+ return;
49
+ }
50
+
51
+ if (password !== confirmPassword) {
52
+ setError('As senhas não coincidem');
53
+ return;
54
+ }
55
+
56
+ setIsLoading(true);
57
+
58
+ // Simula redefinição de senha
59
+ await new Promise(resolve => setTimeout(resolve, 1500));
60
+
61
+ // Redireciona para login com mensagem de sucesso
62
+ navigate('/login', { state: { resetSuccess: true } });
63
+
64
+ setIsLoading(false);
65
+ };
66
+
67
+ const getStrengthColor = () => {
68
+ switch (passwordStrength) {
69
+ case 'weak':
70
+ return 'text-destructive';
71
+ case 'medium':
72
+ return 'text-[var(--chart-3)]';
73
+ case 'strong':
74
+ return 'text-[var(--chart-2)]';
75
+ default:
76
+ return 'text-muted-foreground';
77
+ }
78
+ };
79
+
80
+ const getStrengthText = () => {
81
+ switch (passwordStrength) {
82
+ case 'weak':
83
+ return 'Senha fraca';
84
+ case 'medium':
85
+ return 'Senha média';
86
+ case 'strong':
87
+ return 'Senha forte';
88
+ default:
89
+ return '';
90
+ }
91
+ };
92
+
93
+ return (
94
+ <div className="min-h-screen flex">
95
+ {/* Lado esquerdo - Imagem de fundo completa */}
96
+ <div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
97
+ {/* Imagem de fundo preenchendo todo o espaço */}
98
+ <ImageWithFallback
99
+ src="https://images.unsplash.com/photo-1555949963-aa79dcee981c?w=1200&h=800&fit=crop&auto=format"
100
+ alt="Segurança e proteção"
101
+ className="absolute inset-0 w-full h-full object-cover"
102
+ />
103
+
104
+ {/* Overlay com gradiente */}
105
+ <div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
106
+ </div>
107
+
108
+ {/* Lado direito - Formulário */}
109
+ <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">
110
+ {/* Seletor de idioma no canto superior direito */}
111
+ <div className="absolute top-4 right-4 z-20">
112
+ <LanguageSelector variant="minimal" showIcon={false} />
113
+ </div>
114
+
115
+ {/* Gradiente de fundo para mobile */}
116
+ <div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
117
+
118
+ <div className="w-full max-w-sm space-y-6 relative z-10">
119
+ {/* Botão voltar */}
120
+ <button
121
+ onClick={() => navigate('/login')}
122
+ className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
123
+ >
124
+ <ArrowLeft className="w-4 h-4" />
125
+ <span className="text-small">Voltar para login</span>
126
+ </button>
127
+
128
+ {/* Header do formulário */}
129
+ <div className="text-center">
130
+ <div className="flex items-center justify-center mb-4">
131
+ <XerticaLogo
132
+ className="h-12 w-auto text-primary dark:text-foreground"
133
+ variant="theme"
134
+ />
135
+ </div>
136
+ <h2 className="text-sm text-muted-foreground">Redefinir senha</h2>
137
+ <p className="mt-2 text-muted-foreground">
138
+ Crie uma nova senha segura para sua conta
139
+ </p>
140
+ </div>
141
+
142
+ {/* Formulário */}
143
+ <form className="space-y-6" onSubmit={handleSubmit}>
144
+ <div className="space-y-2">
145
+ <Label htmlFor="password">
146
+ Nova senha
147
+ </Label>
148
+ <Input
149
+ id="password"
150
+ name="password"
151
+ type="password"
152
+ required
153
+ className="w-full"
154
+ placeholder="••••••••"
155
+ value={password}
156
+ onChange={(e) => handlePasswordChange(e.target.value)}
157
+ />
158
+ {passwordStrength && (
159
+ <div className="flex items-center gap-2">
160
+ <div className="flex-1 h-1 bg-muted rounded-full overflow-hidden">
161
+ <div
162
+ className={`h-full transition-all duration-300 ${
163
+ passwordStrength === 'weak'
164
+ ? 'w-1/3 bg-destructive'
165
+ : passwordStrength === 'medium'
166
+ ? 'w-2/3 bg-[var(--chart-3)]'
167
+ : 'w-full bg-[var(--chart-2)]'
168
+ }`}
169
+ />
170
+ </div>
171
+ <span className={`text-small ${getStrengthColor()}`}>
172
+ {getStrengthText()}
173
+ </span>
174
+ </div>
175
+ )}
176
+ </div>
177
+
178
+ <div className="space-y-2">
179
+ <Label htmlFor="confirmPassword">
180
+ Confirmar senha
181
+ </Label>
182
+ <Input
183
+ id="confirmPassword"
184
+ name="confirmPassword"
185
+ type="password"
186
+ required
187
+ className="w-full"
188
+ placeholder="••••••••"
189
+ value={confirmPassword}
190
+ onChange={(e) => {
191
+ setConfirmPassword(e.target.value);
192
+ setError('');
193
+ }}
194
+ />
195
+ </div>
196
+
197
+ <div className="bg-accent rounded-[var(--radius)] p-4 space-y-2">
198
+ <p className="text-sm">Requisitos da senha:</p>
199
+ <div className="space-y-1">
200
+ <div className="flex items-center gap-2">
201
+ <CheckCircle2
202
+ className={`w-4 h-4 ${password.length >= 6 ? 'text-[var(--chart-2)]' : 'text-muted-foreground'}`}
203
+ />
204
+ <span className="text-sm text-muted-foreground">Mínimo de 6 caracteres</span>
205
+ </div>
206
+ <div className="flex items-center gap-2">
207
+ <CheckCircle2
208
+ className={`w-4 h-4 ${password === confirmPassword && password.length > 0 ? 'text-[var(--chart-2)]' : 'text-muted-foreground'}`}
209
+ />
210
+ <span className="text-sm text-muted-foreground">Senhas coincidem</span>
211
+ </div>
212
+ </div>
213
+ </div>
214
+
215
+ {error && (
216
+ <div className="flex items-center gap-2 text-destructive bg-destructive/10 rounded-[var(--radius)] p-3">
217
+ <AlertCircle className="w-4 h-4 flex-shrink-0" />
218
+ <span className="text-sm">{error}</span>
219
+ </div>
220
+ )}
221
+
222
+ <Button
223
+ type="submit"
224
+ className="w-full"
225
+ disabled={isLoading}
226
+ >
227
+ {isLoading ? 'Redefinindo...' : 'Redefinir senha'}
228
+ </Button>
229
+ </form>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ );
234
+ }