@hed-hog/core 0.0.215 → 0.0.216

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 (36) hide show
  1. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +5 -5
  2. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +1 -1
  3. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +312 -0
  4. package/hedhog/frontend/app/dashboard/components/dashboard-grid.tsx.ejs +54 -0
  5. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +132 -0
  6. package/hedhog/frontend/app/dashboard/components/dynamic-widget.tsx.ejs +88 -0
  7. package/hedhog/frontend/app/dashboard/components/index.ts.ejs +6 -0
  8. package/hedhog/frontend/app/dashboard/components/stats.tsx.ejs +93 -0
  9. package/hedhog/frontend/app/dashboard/components/widget-wrapper.tsx.ejs +150 -0
  10. package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +184 -0
  11. package/hedhog/frontend/app/dashboard/components/widgets/active-users-card.tsx.ejs +58 -0
  12. package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +219 -0
  13. package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +191 -0
  14. package/hedhog/frontend/app/dashboard/components/widgets/locale-config.tsx.ejs +309 -0
  15. package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +111 -0
  16. package/hedhog/frontend/app/dashboard/components/widgets/mail-config.tsx.ejs +445 -0
  17. package/hedhog/frontend/app/dashboard/components/widgets/mail-sent-card.tsx.ejs +58 -0
  18. package/hedhog/frontend/app/dashboard/components/widgets/mail-sent-chart.tsx.ejs +149 -0
  19. package/hedhog/frontend/app/dashboard/components/widgets/oauth-config.tsx.ejs +296 -0
  20. package/hedhog/frontend/app/dashboard/components/widgets/permissions-card.tsx.ejs +61 -0
  21. package/hedhog/frontend/app/dashboard/components/widgets/permissions-chart.tsx.ejs +152 -0
  22. package/hedhog/frontend/app/dashboard/components/widgets/profile-card.tsx.ejs +186 -0
  23. package/hedhog/frontend/app/dashboard/components/widgets/session-activity-chart.tsx.ejs +183 -0
  24. package/hedhog/frontend/app/dashboard/components/widgets/sessions-today-card.tsx.ejs +62 -0
  25. package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +57 -0
  26. package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +57 -0
  27. package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +57 -0
  28. package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +57 -0
  29. package/hedhog/frontend/app/dashboard/components/widgets/storage-config.tsx.ejs +340 -0
  30. package/hedhog/frontend/app/dashboard/components/widgets/theme-config.tsx.ejs +275 -0
  31. package/hedhog/frontend/app/dashboard/components/widgets/user-growth-chart.tsx.ejs +210 -0
  32. package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +130 -0
  33. package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +233 -0
  34. package/hedhog/frontend/messages/en.json +143 -1
  35. package/hedhog/frontend/messages/pt.json +143 -1
  36. package/package.json +3 -3
@@ -0,0 +1,340 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '@/components/ui/card';
12
+ import { Input } from '@/components/ui/input';
13
+ import { Label } from '@/components/ui/label';
14
+ import {
15
+ Select,
16
+ SelectContent,
17
+ SelectItem,
18
+ SelectTrigger,
19
+ SelectValue,
20
+ } from '@/components/ui/select';
21
+ import {
22
+ AlertCircle,
23
+ CheckCircle2,
24
+ Cloud,
25
+ FolderOpen,
26
+ HardDrive,
27
+ } from 'lucide-react';
28
+ import { useState } from 'react';
29
+
30
+ type StorageProvider = 'local' | 's3' | 'gcs' | 'azure';
31
+
32
+ const providerList: {
33
+ value: StorageProvider;
34
+ label: string;
35
+ icon: React.ReactNode;
36
+ description: string;
37
+ }[] = [
38
+ {
39
+ value: 'local',
40
+ label: 'Local',
41
+ icon: <FolderOpen className="h-4 w-4" />,
42
+ description: 'Disco do servidor',
43
+ },
44
+ {
45
+ value: 's3',
46
+ label: 'AWS S3',
47
+ icon: <Cloud className="h-4 w-4" />,
48
+ description: 'Amazon S3 Bucket',
49
+ },
50
+ {
51
+ value: 'gcs',
52
+ label: 'Google Cloud',
53
+ icon: <Cloud className="h-4 w-4" />,
54
+ description: 'Google Cloud Storage',
55
+ },
56
+ {
57
+ value: 'azure',
58
+ label: 'Azure Blob',
59
+ icon: <Cloud className="h-4 w-4" />,
60
+ description: 'Azure Blob Storage',
61
+ },
62
+ ];
63
+
64
+ export default function StorageConfig() {
65
+ const [provider, setProvider] = useState<StorageProvider>('local');
66
+ const [localPath, setLocalPath] = useState('/var/data/uploads');
67
+ const [localMaxSize, setLocalMaxSize] = useState('50');
68
+
69
+ const [s3Bucket, setS3Bucket] = useState('');
70
+ const [s3Region, setS3Region] = useState('us-east-1');
71
+ const [s3AccessKey, setS3AccessKey] = useState('');
72
+ const [s3SecretKey, setS3SecretKey] = useState('');
73
+ const [s3Prefix, setS3Prefix] = useState('uploads/');
74
+
75
+ const [gcsBucket, setGcsBucket] = useState('');
76
+ const [gcsProjectId, setGcsProjectId] = useState('');
77
+ const [gcsKeyFile, setGcsKeyFile] = useState('');
78
+
79
+ const [azureAccount, setAzureAccount] = useState('');
80
+ const [azureKey, setAzureKey] = useState('');
81
+ const [azureContainer, setAzureContainer] = useState('');
82
+ const [azureEndpoint, setAzureEndpoint] = useState('');
83
+
84
+ return (
85
+ <Card className="h-full">
86
+ <CardHeader>
87
+ <div className="flex items-center justify-between">
88
+ <div className="flex items-center gap-3">
89
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-violet-50">
90
+ <HardDrive className="h-5 w-5 text-violet-600" />
91
+ </div>
92
+ <div>
93
+ <CardTitle className="text-base">
94
+ Armazenamento de Arquivos
95
+ </CardTitle>
96
+ <CardDescription>
97
+ Configure onde os arquivos do sistema serao armazenados
98
+ </CardDescription>
99
+ </div>
100
+ </div>
101
+ <Badge variant="secondary" className="bg-emerald-50 text-emerald-700">
102
+ <CheckCircle2 className="mr-1 h-3 w-3" />
103
+ Configurado
104
+ </Badge>
105
+ </div>
106
+ </CardHeader>
107
+ <CardContent className="space-y-6">
108
+ {/* Provider Selector */}
109
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
110
+ {providerList.map((p) => (
111
+ <button
112
+ key={p.value}
113
+ type="button"
114
+ onClick={() => setProvider(p.value)}
115
+ className={`flex flex-col items-center gap-1.5 rounded-lg border-2 p-3 text-center transition-all ${
116
+ provider === p.value
117
+ ? 'border-foreground bg-foreground/3'
118
+ : 'border-border hover:border-foreground/20'
119
+ }`}
120
+ >
121
+ <div
122
+ className={`flex h-8 w-8 items-center justify-center rounded-full ${
123
+ provider === p.value
124
+ ? 'bg-foreground text-background'
125
+ : 'bg-muted text-muted-foreground'
126
+ }`}
127
+ >
128
+ {p.icon}
129
+ </div>
130
+ <span className="text-xs font-medium">{p.label}</span>
131
+ </button>
132
+ ))}
133
+ </div>
134
+
135
+ {/* Local Fields */}
136
+ {provider === 'local' && (
137
+ <div className="space-y-4">
138
+ <div className="space-y-2">
139
+ <Label htmlFor="local-path">Caminho do diretorio</Label>
140
+ <Input
141
+ id="local-path"
142
+ value={localPath}
143
+ onChange={(e) => setLocalPath(e.target.value)}
144
+ placeholder="/var/data/uploads"
145
+ className="font-mono text-sm"
146
+ />
147
+ </div>
148
+ <div className="space-y-2">
149
+ <Label htmlFor="local-max-size">
150
+ Tamanho maximo por arquivo (MB)
151
+ </Label>
152
+ <Input
153
+ id="local-max-size"
154
+ type="number"
155
+ value={localMaxSize}
156
+ onChange={(e) => setLocalMaxSize(e.target.value)}
157
+ />
158
+ </div>
159
+ <div className="rounded-lg border border-blue-200 bg-blue-50 p-3">
160
+ <div className="flex items-start gap-2">
161
+ <AlertCircle className="mt-0.5 h-4 w-4 text-blue-600" />
162
+ <p className="text-xs text-blue-800">
163
+ Certifique-se de que o diretorio existe e que o servidor tem
164
+ permissoes de leitura e escrita.
165
+ </p>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ )}
170
+
171
+ {/* AWS S3 Fields */}
172
+ {provider === 's3' && (
173
+ <div className="space-y-4">
174
+ <div className="grid gap-4 sm:grid-cols-2">
175
+ <div className="space-y-2">
176
+ <Label htmlFor="s3-bucket">Bucket</Label>
177
+ <Input
178
+ id="s3-bucket"
179
+ value={s3Bucket}
180
+ onChange={(e) => setS3Bucket(e.target.value)}
181
+ placeholder="meu-bucket"
182
+ className="font-mono text-sm"
183
+ />
184
+ </div>
185
+ <div className="space-y-2">
186
+ <Label>Regiao</Label>
187
+ <Select value={s3Region} onValueChange={setS3Region}>
188
+ <SelectTrigger>
189
+ <SelectValue />
190
+ </SelectTrigger>
191
+ <SelectContent>
192
+ <SelectItem value="us-east-1">
193
+ US East (N. Virginia)
194
+ </SelectItem>
195
+ <SelectItem value="us-west-2">US West (Oregon)</SelectItem>
196
+ <SelectItem value="eu-west-1">EU (Ireland)</SelectItem>
197
+ <SelectItem value="eu-central-1">EU (Frankfurt)</SelectItem>
198
+ <SelectItem value="sa-east-1">
199
+ South America (Sao Paulo)
200
+ </SelectItem>
201
+ </SelectContent>
202
+ </Select>
203
+ </div>
204
+ </div>
205
+ <div className="space-y-2">
206
+ <Label htmlFor="s3-access-key">Access Key ID</Label>
207
+ <Input
208
+ id="s3-access-key"
209
+ value={s3AccessKey}
210
+ onChange={(e) => setS3AccessKey(e.target.value)}
211
+ placeholder="AKIAIOSFODNN7EXAMPLE"
212
+ className="font-mono text-sm"
213
+ />
214
+ </div>
215
+ <div className="space-y-2">
216
+ <Label htmlFor="s3-secret-key">Secret Access Key</Label>
217
+ <Input
218
+ id="s3-secret-key"
219
+ type="password"
220
+ value={s3SecretKey}
221
+ onChange={(e) => setS3SecretKey(e.target.value)}
222
+ className="font-mono text-sm"
223
+ />
224
+ </div>
225
+ <div className="space-y-2">
226
+ <Label htmlFor="s3-prefix">Prefixo (pasta)</Label>
227
+ <Input
228
+ id="s3-prefix"
229
+ value={s3Prefix}
230
+ onChange={(e) => setS3Prefix(e.target.value)}
231
+ placeholder="uploads/"
232
+ className="font-mono text-sm"
233
+ />
234
+ </div>
235
+ </div>
236
+ )}
237
+
238
+ {/* Google Cloud Storage Fields */}
239
+ {provider === 'gcs' && (
240
+ <div className="space-y-4">
241
+ <div className="grid gap-4 sm:grid-cols-2">
242
+ <div className="space-y-2">
243
+ <Label htmlFor="gcs-project-id">Project ID</Label>
244
+ <Input
245
+ id="gcs-project-id"
246
+ value={gcsProjectId}
247
+ onChange={(e) => setGcsProjectId(e.target.value)}
248
+ placeholder="meu-projeto-123"
249
+ className="font-mono text-sm"
250
+ />
251
+ </div>
252
+ <div className="space-y-2">
253
+ <Label htmlFor="gcs-bucket">Bucket</Label>
254
+ <Input
255
+ id="gcs-bucket"
256
+ value={gcsBucket}
257
+ onChange={(e) => setGcsBucket(e.target.value)}
258
+ placeholder="meu-bucket-gcs"
259
+ className="font-mono text-sm"
260
+ />
261
+ </div>
262
+ </div>
263
+ <div className="space-y-2">
264
+ <Label htmlFor="gcs-key-file">
265
+ Chave JSON da Service Account
266
+ </Label>
267
+ <textarea
268
+ id="gcs-key-file"
269
+ className="flex min-h-[100px] w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
270
+ value={gcsKeyFile}
271
+ onChange={(e) => setGcsKeyFile(e.target.value)}
272
+ placeholder='{"type": "service_account", ...}'
273
+ />
274
+ </div>
275
+ </div>
276
+ )}
277
+
278
+ {/* Azure Blob Storage Fields */}
279
+ {provider === 'azure' && (
280
+ <div className="space-y-4">
281
+ <div className="grid gap-4 sm:grid-cols-2">
282
+ <div className="space-y-2">
283
+ <Label htmlFor="azure-account">Storage Account Name</Label>
284
+ <Input
285
+ id="azure-account"
286
+ value={azureAccount}
287
+ onChange={(e) => setAzureAccount(e.target.value)}
288
+ placeholder="meuaccount"
289
+ className="font-mono text-sm"
290
+ />
291
+ </div>
292
+ <div className="space-y-2">
293
+ <Label htmlFor="azure-container">Container</Label>
294
+ <Input
295
+ id="azure-container"
296
+ value={azureContainer}
297
+ onChange={(e) => setAzureContainer(e.target.value)}
298
+ placeholder="arquivos"
299
+ className="font-mono text-sm"
300
+ />
301
+ </div>
302
+ </div>
303
+ <div className="space-y-2">
304
+ <Label htmlFor="azure-key">Access Key</Label>
305
+ <Input
306
+ id="azure-key"
307
+ type="password"
308
+ value={azureKey}
309
+ onChange={(e) => setAzureKey(e.target.value)}
310
+ className="font-mono text-sm"
311
+ />
312
+ </div>
313
+ <div className="space-y-2">
314
+ <Label htmlFor="azure-endpoint">
315
+ Endpoint personalizado (opcional)
316
+ </Label>
317
+ <Input
318
+ id="azure-endpoint"
319
+ value={azureEndpoint}
320
+ onChange={(e) => setAzureEndpoint(e.target.value)}
321
+ placeholder="https://meuaccount.blob.core.windows.net"
322
+ className="font-mono text-sm"
323
+ />
324
+ </div>
325
+ </div>
326
+ )}
327
+
328
+ {/* Actions */}
329
+ <div className="flex items-center justify-end border-t pt-4">
330
+ <div className="flex gap-2">
331
+ <Button variant="outline" size="sm">
332
+ Testar conexao
333
+ </Button>
334
+ <Button size="sm">Salvar</Button>
335
+ </div>
336
+ </div>
337
+ </CardContent>
338
+ </Card>
339
+ );
340
+ }
@@ -0,0 +1,275 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@/components/ui/button';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@/components/ui/card';
11
+ import { Input } from '@/components/ui/input';
12
+ import { Label } from '@/components/ui/label';
13
+ import { ImageIcon, Palette, Upload } from 'lucide-react';
14
+ import { useRef, useState } from 'react';
15
+
16
+ const PRESET_COLORS = [
17
+ { name: 'Azul', value: '#3b82f6' },
18
+ { name: 'Indigo', value: '#6366f1' },
19
+ { name: 'Violeta', value: '#8b5cf6' },
20
+ { name: 'Rosa', value: '#ec4899' },
21
+ { name: 'Vermelho', value: '#ef4444' },
22
+ { name: 'Laranja', value: '#f97316' },
23
+ { name: 'Amber', value: '#f59e0b' },
24
+ { name: 'Esmeralda', value: '#10b981' },
25
+ { name: 'Teal', value: '#14b8a6' },
26
+ { name: 'Ciano', value: '#06b6d4' },
27
+ { name: 'Cinza', value: '#6b7280' },
28
+ { name: 'Zinc', value: '#18181b' },
29
+ ];
30
+
31
+ export default function ThemeConfig() {
32
+ const [title, setTitle] = useState('HeroAdmin');
33
+ const [slogan, setSlogan] = useState('Painel de Controle Inteligente');
34
+ const [primaryColor, setPrimaryColor] = useState('#3b82f6');
35
+ const [logoPreview, setLogoPreview] = useState<string | null>(null);
36
+ const [faviconPreview, setFaviconPreview] = useState<string | null>(null);
37
+ const logoRef = useRef<HTMLInputElement>(null);
38
+ const faviconRef = useRef<HTMLInputElement>(null);
39
+
40
+ function handleFileChange(
41
+ e: React.ChangeEvent<HTMLInputElement>,
42
+ setter: (url: string | null) => void
43
+ ) {
44
+ const file = e.target.files?.[0];
45
+ if (file) {
46
+ const reader = new FileReader();
47
+ reader.onloadend = () => setter(reader.result as string);
48
+ reader.readAsDataURL(file);
49
+ }
50
+ }
51
+
52
+ return (
53
+ <Card className="h-full">
54
+ <CardHeader>
55
+ <div className="flex items-center gap-3">
56
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-rose-50">
57
+ <Palette className="h-5 w-5 text-rose-600" />
58
+ </div>
59
+ <div>
60
+ <CardTitle className="text-base">Aparencia e Tema</CardTitle>
61
+ <CardDescription>
62
+ Personalize a identidade visual do sistema
63
+ </CardDescription>
64
+ </div>
65
+ </div>
66
+ </CardHeader>
67
+ <CardContent className="space-y-6">
68
+ {/* Preview */}
69
+ <div className="rounded-lg border bg-muted/30 p-4">
70
+ <p className="mb-3 text-xs font-medium text-muted-foreground">
71
+ Pre-visualizacao
72
+ </p>
73
+ <div className="flex items-center gap-3 rounded-lg border bg-background p-3">
74
+ <div
75
+ className="flex h-10 w-10 items-center justify-center rounded-lg"
76
+ style={{ backgroundColor: primaryColor }}
77
+ >
78
+ {logoPreview ? (
79
+ <img
80
+ src={logoPreview}
81
+ alt="Logo"
82
+ className="h-6 w-6 rounded object-contain"
83
+ />
84
+ ) : (
85
+ <span className="text-sm font-bold text-white">
86
+ {title.charAt(0)}
87
+ </span>
88
+ )}
89
+ </div>
90
+ <div>
91
+ <span
92
+ className="text-sm font-bold"
93
+ style={{ color: primaryColor }}
94
+ >
95
+ {title || 'Titulo do Sistema'}
96
+ </span>
97
+ <p className="text-xs text-muted-foreground">
98
+ {slogan || 'Slogan do sistema'}
99
+ </p>
100
+ </div>
101
+ {faviconPreview && (
102
+ <div className="ml-auto flex items-center gap-1.5 text-xs text-muted-foreground">
103
+ <img
104
+ src={faviconPreview}
105
+ alt="Favicon"
106
+ className="h-4 w-4 object-contain"
107
+ />
108
+ Favicon
109
+ </div>
110
+ )}
111
+ </div>
112
+ </div>
113
+
114
+ {/* Logo + Favicon */}
115
+ <div className="grid gap-4 sm:grid-cols-2">
116
+ <div className="space-y-2">
117
+ <Label>Logo do sistema</Label>
118
+ <div
119
+ onClick={() => logoRef.current?.click()}
120
+ onKeyDown={(e) => {
121
+ if (e.key === 'Enter') logoRef.current?.click();
122
+ }}
123
+ role="button"
124
+ tabIndex={0}
125
+ className="flex h-28 cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed transition-colors hover:border-foreground/30 hover:bg-muted/50"
126
+ >
127
+ {logoPreview ? (
128
+ <img
129
+ src={logoPreview}
130
+ alt="Logo"
131
+ className="h-12 w-12 object-contain"
132
+ />
133
+ ) : (
134
+ <>
135
+ <Upload className="h-5 w-5 text-muted-foreground" />
136
+ <span className="text-xs text-muted-foreground">
137
+ Clique para enviar
138
+ </span>
139
+ </>
140
+ )}
141
+ </div>
142
+ <input
143
+ ref={logoRef}
144
+ type="file"
145
+ accept="image/*"
146
+ className="hidden"
147
+ onChange={(e) => handleFileChange(e, setLogoPreview)}
148
+ />
149
+ </div>
150
+ <div className="space-y-2">
151
+ <Label>Favicon</Label>
152
+ <div
153
+ onClick={() => faviconRef.current?.click()}
154
+ onKeyDown={(e) => {
155
+ if (e.key === 'Enter') faviconRef.current?.click();
156
+ }}
157
+ role="button"
158
+ tabIndex={0}
159
+ className="flex h-28 cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed transition-colors hover:border-foreground/30 hover:bg-muted/50"
160
+ >
161
+ {faviconPreview ? (
162
+ <img
163
+ src={faviconPreview}
164
+ alt="Favicon"
165
+ className="h-8 w-8 object-contain"
166
+ />
167
+ ) : (
168
+ <>
169
+ <ImageIcon className="h-5 w-5 text-muted-foreground" />
170
+ <span className="text-xs text-muted-foreground">
171
+ 16x16 ou 32x32 px
172
+ </span>
173
+ </>
174
+ )}
175
+ </div>
176
+ <input
177
+ ref={faviconRef}
178
+ type="file"
179
+ accept="image/png,image/ico,image/x-icon,image/svg+xml"
180
+ className="hidden"
181
+ onChange={(e) => handleFileChange(e, setFaviconPreview)}
182
+ />
183
+ </div>
184
+ </div>
185
+
186
+ {/* Title + Slogan */}
187
+ <div className="grid gap-4 sm:grid-cols-2">
188
+ <div className="space-y-2">
189
+ <Label htmlFor="sys-title">Titulo do sistema</Label>
190
+ <Input
191
+ id="sys-title"
192
+ value={title}
193
+ onChange={(e) => setTitle(e.target.value)}
194
+ placeholder="Meu Sistema"
195
+ />
196
+ </div>
197
+ <div className="space-y-2">
198
+ <Label htmlFor="sys-slogan">Slogan</Label>
199
+ <Input
200
+ id="sys-slogan"
201
+ value={slogan}
202
+ onChange={(e) => setSlogan(e.target.value)}
203
+ placeholder="Seu slogan aqui"
204
+ />
205
+ </div>
206
+ </div>
207
+
208
+ {/* Color Picker */}
209
+ <div className="space-y-3">
210
+ <Label>Cor primaria</Label>
211
+ <div className="flex items-center gap-3">
212
+ <div
213
+ className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg border"
214
+ style={{ backgroundColor: primaryColor }}
215
+ >
216
+ <input
217
+ type="color"
218
+ value={primaryColor}
219
+ onChange={(e) => setPrimaryColor(e.target.value)}
220
+ className="h-full w-full cursor-pointer opacity-0"
221
+ title="Selecionar cor"
222
+ />
223
+ </div>
224
+ <Input
225
+ value={primaryColor}
226
+ onChange={(e) => setPrimaryColor(e.target.value)}
227
+ className="w-28 font-mono text-sm uppercase"
228
+ maxLength={7}
229
+ />
230
+ </div>
231
+ <div className="flex flex-wrap gap-2 pt-1">
232
+ {PRESET_COLORS.map((c) => (
233
+ <button
234
+ key={c.value}
235
+ type="button"
236
+ onClick={() => setPrimaryColor(c.value)}
237
+ className={`group relative flex h-7 w-7 items-center justify-center rounded-full border-2 transition-all ${
238
+ primaryColor === c.value
239
+ ? 'border-foreground scale-110'
240
+ : 'border-transparent hover:scale-105'
241
+ }`}
242
+ title={c.name}
243
+ >
244
+ <span
245
+ className="h-5 w-5 rounded-full"
246
+ style={{ backgroundColor: c.value }}
247
+ />
248
+ </button>
249
+ ))}
250
+ </div>
251
+ </div>
252
+
253
+ {/* Actions */}
254
+ <div className="flex items-center justify-end border-t pt-4">
255
+ <div className="flex gap-2">
256
+ <Button
257
+ variant="outline"
258
+ size="sm"
259
+ onClick={() => {
260
+ setTitle('HeroAdmin');
261
+ setSlogan('Painel de Controle Inteligente');
262
+ setPrimaryColor('#3b82f6');
263
+ setLogoPreview(null);
264
+ setFaviconPreview(null);
265
+ }}
266
+ >
267
+ Restaurar padrao
268
+ </Button>
269
+ <Button size="sm">Salvar</Button>
270
+ </div>
271
+ </div>
272
+ </CardContent>
273
+ </Card>
274
+ );
275
+ }