@hed-hog/core 0.0.185 → 0.0.190

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 (55) hide show
  1. package/hedhog/frontend/app/account/2fa/page.tsx.ejs +5 -0
  2. package/hedhog/frontend/app/account/accounts/page.tsx.ejs +5 -0
  3. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +356 -0
  4. package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +379 -0
  5. package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +184 -0
  6. package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +144 -0
  7. package/hedhog/frontend/app/account/components/email-request-dialog.tsx.ejs +96 -0
  8. package/hedhog/frontend/app/account/components/mfa-add-buttons.tsx.ejs +43 -0
  9. package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +115 -0
  10. package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +236 -0
  11. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +209 -0
  12. package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +192 -0
  13. package/hedhog/frontend/app/account/components/regenerate-codes-dialog.tsx.ejs +372 -0
  14. package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +337 -0
  15. package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +393 -0
  16. package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +332 -0
  17. package/hedhog/frontend/app/account/email/page.tsx.ejs +5 -0
  18. package/hedhog/frontend/app/account/hooks/use-mfa-methods.ts.ejs +27 -0
  19. package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +461 -0
  20. package/hedhog/frontend/app/account/layout.tsx.ejs +105 -0
  21. package/hedhog/frontend/app/account/lib/mfa-utils.tsx.ejs +37 -0
  22. package/hedhog/frontend/app/account/page.tsx.ejs +5 -0
  23. package/hedhog/frontend/app/account/password/page.tsx.ejs +5 -0
  24. package/hedhog/frontend/app/account/profile/page.tsx.ejs +5 -0
  25. package/hedhog/frontend/app/account/sessions/page.tsx.ejs +5 -0
  26. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +490 -0
  27. package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +62 -0
  28. package/hedhog/frontend/app/configurations/layout.tsx.ejs +316 -0
  29. package/hedhog/frontend/app/configurations/page.tsx.ejs +35 -0
  30. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +351 -0
  31. package/hedhog/frontend/app/dashboard/[slug]/page.tsx.ejs +11 -0
  32. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +62 -0
  33. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +45 -0
  34. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +196 -0
  35. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +63 -0
  36. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +516 -0
  37. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +753 -0
  38. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +516 -0
  39. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +489 -0
  40. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +621 -0
  41. package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -0
  42. package/hedhog/frontend/app/mail/log/page.tsx.ejs +312 -0
  43. package/hedhog/frontend/app/mail/template/page.tsx.ejs +1177 -0
  44. package/hedhog/frontend/app/preferences/page.tsx.ejs +448 -0
  45. package/hedhog/frontend/app/roles/menus.tsx.ejs +504 -0
  46. package/hedhog/frontend/app/roles/page.tsx.ejs +814 -0
  47. package/hedhog/frontend/app/roles/routes.tsx.ejs +397 -0
  48. package/hedhog/frontend/app/roles/users.tsx.ejs +306 -0
  49. package/hedhog/frontend/app/users/active-session.tsx.ejs +159 -0
  50. package/hedhog/frontend/app/users/identifiers.tsx.ejs +279 -0
  51. package/hedhog/frontend/app/users/page.tsx.ejs +1257 -0
  52. package/hedhog/frontend/app/users/permissions.tsx.ejs +155 -0
  53. package/hedhog/frontend/messages/en.json +1080 -0
  54. package/hedhog/frontend/messages/pt.json +1135 -0
  55. package/package.json +4 -4
@@ -0,0 +1,448 @@
1
+ 'use client';
2
+
3
+ import { PageHeader } from '@/components/entity-list';
4
+ import { LanguageSelector } from '@/components/language-selector';
5
+ import { Card, CardContent } from '@/components/ui/card';
6
+ import { Form } from '@/components/ui/form';
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from '@/components/ui/select';
14
+ import { PaginatedResult } from '@hed-hog/api-pagination';
15
+ import { Setting, SettingList } from '@hed-hog/api-types';
16
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
17
+ import { zodResolver } from '@hookform/resolvers/zod';
18
+ import { Loader } from 'lucide-react';
19
+ import { useTranslations } from 'next-intl';
20
+ import { useForm } from 'react-hook-form';
21
+ import { z } from 'zod';
22
+
23
+ export default function PreferencesPage() {
24
+ const {
25
+ currentTheme,
26
+ setCurrentTheme,
27
+ request,
28
+ getSettingValue,
29
+ setSettingValue,
30
+ showToastHandler,
31
+ currentLocaleCode,
32
+ } = useApp();
33
+ const t = useTranslations('core.PreferencesPage');
34
+
35
+ const applyThemeSettings = async (
36
+ themeMode?: 'light' | 'dark' | 'system'
37
+ ) => {
38
+ try {
39
+ const response: any = await request({
40
+ url: '/setting/initial',
41
+ method: 'GET',
42
+ });
43
+
44
+ const newSettings = response.data?.setting || {};
45
+ localStorage.setItem('settings', JSON.stringify(newSettings));
46
+
47
+ const root = document.documentElement;
48
+ const effectiveTheme = themeMode || currentTheme || 'system';
49
+ let resolvedTheme: 'light' | 'dark' = 'light';
50
+
51
+ if (effectiveTheme === 'system') {
52
+ const prefersDark = window.matchMedia(
53
+ '(prefers-color-scheme: dark)'
54
+ ).matches;
55
+ resolvedTheme = prefersDark ? 'dark' : 'light';
56
+ } else {
57
+ resolvedTheme = effectiveTheme as 'light' | 'dark';
58
+ }
59
+
60
+ root.classList.remove('light', 'dark');
61
+ root.classList.add(resolvedTheme);
62
+
63
+ const existingStyle = document.getElementById('theme-custom-styles');
64
+ if (existingStyle) {
65
+ existingStyle.remove();
66
+ }
67
+
68
+ const styleTag = document.createElement('style');
69
+ styleTag.id = 'theme-custom-styles';
70
+ let cssRules = '';
71
+
72
+ const addRule = (varName: string, settingKey: string) => {
73
+ const hexColor = newSettings[settingKey];
74
+ if (
75
+ hexColor &&
76
+ typeof hexColor === 'string' &&
77
+ hexColor.startsWith('#')
78
+ ) {
79
+ cssRules += ` ${varName}: ${hexColor} !important;\n`;
80
+ }
81
+ };
82
+
83
+ if (resolvedTheme === 'light') {
84
+ cssRules += '@supports (color: lab(0% 0 0)) {\n';
85
+ cssRules += ' html.light, .light, :root.light {\n';
86
+ addRule('--primary', 'theme-primary-light');
87
+ addRule('--bprogress-color', 'theme-primary-light');
88
+ addRule('--primary-foreground', 'theme-primary-foreground-light');
89
+ addRule('--secondary', 'theme-secondary-light');
90
+ addRule('--secondary-foreground', 'theme-secondary-foreground-light');
91
+ addRule('--accent', 'theme-accent-light');
92
+ addRule('--accent-foreground', 'theme-accent-foreground-light');
93
+ addRule('--muted', 'theme-muted-light');
94
+ addRule('--muted-foreground', 'theme-muted-foreground-light');
95
+ addRule('--background', 'theme-background-light');
96
+ addRule('--foreground', 'theme-background-foreground-light');
97
+ addRule('--card', 'theme-card-light');
98
+ addRule('--card-foreground', 'theme-card-foreground-light');
99
+ } else {
100
+ cssRules += '@supports (color: lab(0% 0 0)) {\n';
101
+ cssRules += ' html.dark, .dark, :root.dark {\n';
102
+ addRule('--primary', 'theme-primary-dark');
103
+ addRule('--bprogress-color', 'theme-primary-dark');
104
+ addRule('--primary-foreground', 'theme-primary-foreground-dark');
105
+ addRule('--secondary', 'theme-secondary-dark');
106
+ addRule('--secondary-foreground', 'theme-secondary-foreground-dark');
107
+ addRule('--accent', 'theme-accent-dark');
108
+ addRule('--accent-foreground', 'theme-accent-foreground-dark');
109
+ addRule('--muted', 'theme-muted-dark');
110
+ addRule('--muted-foreground', 'theme-muted-foreground-dark');
111
+ addRule('--background', 'theme-background-dark');
112
+ addRule('--foreground', 'theme-background-foreground-dark');
113
+ addRule('--card', 'theme-card-dark');
114
+ addRule('--card-foreground', 'theme-card-foreground-dark');
115
+ }
116
+
117
+ if (newSettings['theme-radius']) {
118
+ cssRules += ` --radius: ${newSettings['theme-radius']}rem !important;\n`;
119
+ }
120
+ if (newSettings['theme-text-size']) {
121
+ cssRules += ` font-size: ${newSettings['theme-text-size']}rem !important;\n`;
122
+ }
123
+
124
+ cssRules += ' }\n';
125
+ cssRules += '}\n';
126
+
127
+ if (newSettings['theme-font']) {
128
+ cssRules += `:root {\n`;
129
+ cssRules += ` --font-sans: ${newSettings['theme-font']} !important;\n`;
130
+ cssRules += `}\n`;
131
+ cssRules += `html {\n`;
132
+ cssRules += ` font-family: ${newSettings['theme-font']} !important;\n`;
133
+ cssRules += `}\n`;
134
+ }
135
+
136
+ styleTag.textContent = cssRules;
137
+ document.head.appendChild(styleTag);
138
+ } catch (error) {
139
+ console.error('Error applying theme settings:', error);
140
+ }
141
+ };
142
+
143
+ // Busca as settings do grupo de localization
144
+ const { data: localizationSettings, isLoading } = useQuery<
145
+ PaginatedResult<Setting>
146
+ >({
147
+ queryKey: ['setting', 'localization', currentLocaleCode],
148
+ queryFn: async () => {
149
+ const response = await request<PaginatedResult<Setting>>({
150
+ url: '/setting/group/localization',
151
+ });
152
+ return response.data;
153
+ },
154
+ });
155
+
156
+ // Encontra as settings específicas
157
+ const dateFormatSetting = localizationSettings?.data?.find(
158
+ (s: Setting) => s.slug === 'date-format'
159
+ );
160
+ const timeFormatSetting = localizationSettings?.data?.find(
161
+ (s: Setting) => s.slug === 'time-format'
162
+ );
163
+ const timezoneSetting = localizationSettings?.data?.find(
164
+ (s: Setting) => s.slug === 'timezone'
165
+ );
166
+
167
+ const dateFormat = getSettingValue('date-format') || 'dd/MM/yyyy';
168
+ const timeFormat = getSettingValue('time-format') || 'HH:mm:ss';
169
+ const timezone = getSettingValue('timezone') || 'UTC';
170
+
171
+ const schema = z.object({
172
+ language: z.string().min(1, t('languageRequired')),
173
+ theme: z.string().min(1, t('themeRequired')),
174
+ });
175
+
176
+ const form = useForm({
177
+ resolver: zodResolver(schema),
178
+ defaultValues: {
179
+ language: '',
180
+ theme: '',
181
+ },
182
+ });
183
+
184
+ const handleThemeChange = async (value: 'light' | 'dark' | 'system') => {
185
+ setCurrentTheme(value);
186
+ await request({
187
+ url: '/profile/preferences',
188
+ method: 'PUT',
189
+ data: { theme: value },
190
+ });
191
+ await applyThemeSettings(value);
192
+ };
193
+
194
+ const handleLanguageChange = (code: string) => {
195
+ request({
196
+ url: '/profile/preferences',
197
+ method: 'PUT',
198
+ data: { language: code },
199
+ });
200
+ };
201
+
202
+ const handleDateFormatChange = async (value: string) => {
203
+ try {
204
+ await setSettingValue('date-format', value);
205
+ showToastHandler(
206
+ 'success',
207
+ t('dateFormatUpdated') || 'Date format updated successfully'
208
+ );
209
+ } catch (error) {
210
+ showToastHandler(
211
+ 'error',
212
+ t('dateFormatUpdateError') || 'Failed to update date format'
213
+ );
214
+ }
215
+ };
216
+
217
+ const handleTimeFormatChange = async (value: string) => {
218
+ try {
219
+ await setSettingValue('time-format', value);
220
+ showToastHandler(
221
+ 'success',
222
+ t('timeFormatUpdated') || 'Time format updated successfully'
223
+ );
224
+ } catch (error) {
225
+ showToastHandler(
226
+ 'error',
227
+ t('timeFormatUpdateError') || 'Failed to update time format'
228
+ );
229
+ }
230
+ };
231
+
232
+ const handleTimezoneChange = async (value: string) => {
233
+ try {
234
+ await setSettingValue('timezone', value);
235
+ showToastHandler(
236
+ 'success',
237
+ t('timezoneUpdated') || 'Timezone updated successfully'
238
+ );
239
+ } catch (error) {
240
+ showToastHandler(
241
+ 'error',
242
+ t('timezoneUpdateError') || 'Failed to update timezone'
243
+ );
244
+ }
245
+ };
246
+
247
+ return (
248
+ <div className="flex flex-col h-screen px-4">
249
+ <PageHeader
250
+ breadcrumbs={[
251
+ { label: 'Home', href: '/dashboard' },
252
+ { label: t('title') },
253
+ ]}
254
+ title={t('title')}
255
+ description={t('description')}
256
+ />
257
+
258
+ <div className="mt-2">
259
+ <Form {...form}>
260
+ <form className="space-y-6">
261
+ <h2 className="text-1xl font-bold text-primary mb-1">
262
+ {t('appearance')}
263
+ </h2>
264
+ <Card className="border-none bg-accent">
265
+ <CardContent className="flex-col gap-4">
266
+ <div className="flex justify-between">
267
+ <div className="flex-col">
268
+ <label className="font-semibold">{t('theme')}</label>
269
+ <p className="text-muted-foreground text-sm">
270
+ {t('setPreferredTheme')}
271
+ </p>
272
+ </div>
273
+
274
+ <div className="h-full flex items-center">
275
+ <Select
276
+ value={currentTheme}
277
+ onValueChange={handleThemeChange}
278
+ >
279
+ <SelectTrigger className="w-[180px] bg-background">
280
+ <SelectValue placeholder={t('selectTheme')} />
281
+ </SelectTrigger>
282
+ <SelectContent>
283
+ <SelectItem value="light">{t('light')}</SelectItem>
284
+ <SelectItem value="dark">{t('dark')}</SelectItem>
285
+ <SelectItem value="system">{t('system')}</SelectItem>
286
+ </SelectContent>
287
+ </Select>
288
+ </div>
289
+ </div>
290
+ </CardContent>
291
+ </Card>
292
+
293
+ <h2 className="text-1xl font-bold text-primary mb-1">
294
+ {t('localization')}
295
+ </h2>
296
+ <Card className="border-none bg-accent">
297
+ <CardContent className="flex-col gap-4">
298
+ <div className="flex justify-between">
299
+ <div className="flex-col">
300
+ <label className="font-semibold">{t('language')}</label>
301
+ <p className="text-muted-foreground text-sm">
302
+ {t('setPreferredLanguage')}
303
+ </p>
304
+ </div>
305
+
306
+ <div className="h-full flex items-center">
307
+ <LanguageSelector onChange={handleLanguageChange} />
308
+ </div>
309
+ </div>
310
+ </CardContent>
311
+ </Card>
312
+
313
+ <h2 className="text-1xl font-bold text-primary mb-1">
314
+ {t('dateAndTimeFormat')}
315
+ </h2>
316
+ <Card className="border-none bg-accent">
317
+ {isLoading && (
318
+ <CardContent className="flex h-32 w-full items-center justify-center">
319
+ <Loader className="animate-spin text-muted-foreground w-4 h-4" />
320
+ </CardContent>
321
+ )}
322
+ {!isLoading && dateFormatSetting && timeFormatSetting && (
323
+ <CardContent className="flex-col gap-4">
324
+ <div className="flex justify-between">
325
+ <div className="flex-col">
326
+ <label className="font-semibold">{t('dateFormat')}</label>
327
+ <p className="text-muted-foreground text-sm">
328
+ {t('setPreferredDateFormat')}
329
+ </p>
330
+ </div>
331
+
332
+ <div className="h-full flex items-center">
333
+ <Select
334
+ value={dateFormat}
335
+ onValueChange={handleDateFormatChange}
336
+ >
337
+ <SelectTrigger className="w-[180px] bg-background">
338
+ <SelectValue
339
+ placeholder={
340
+ t('selectDateFormat') || 'Select format'
341
+ }
342
+ />
343
+ </SelectTrigger>
344
+ <SelectContent>
345
+ {(dateFormatSetting.setting_list || []).map(
346
+ (option: SettingList) => (
347
+ <SelectItem key={option.id} value={option.value}>
348
+ {option.value}
349
+ </SelectItem>
350
+ )
351
+ )}
352
+ </SelectContent>
353
+ </Select>
354
+ </div>
355
+ </div>
356
+
357
+ <div className="flex justify-between mt-4">
358
+ <div className="flex-col">
359
+ <label className="font-semibold">{t('timeFormat')}</label>
360
+ <p className="text-muted-foreground text-sm">
361
+ {t('setPreferredTimeFormat')}
362
+ </p>
363
+ </div>
364
+
365
+ <div className="h-full flex items-center">
366
+ <Select
367
+ value={timeFormat}
368
+ onValueChange={handleTimeFormatChange}
369
+ >
370
+ <SelectTrigger className="w-[180px] bg-background">
371
+ <SelectValue
372
+ placeholder={
373
+ t('selectTimeFormat') || 'Select format'
374
+ }
375
+ />
376
+ </SelectTrigger>
377
+ <SelectContent>
378
+ {(timeFormatSetting.setting_list || []).map(
379
+ (option: SettingList) => (
380
+ <SelectItem key={option.id} value={option.value}>
381
+ {option.value}
382
+ </SelectItem>
383
+ )
384
+ )}
385
+ </SelectContent>
386
+ </Select>
387
+ </div>
388
+ </div>
389
+ </CardContent>
390
+ )}
391
+ </Card>
392
+
393
+ <h2 className="text-1xl font-bold text-primary mb-1">
394
+ {t('timezone')}
395
+ </h2>
396
+ <Card className="border-none bg-accent">
397
+ {isLoading && (
398
+ <CardContent className="flex h-32 w-full items-center justify-center">
399
+ <Loader className="animate-spin text-muted-foreground w-4 h-4" />
400
+ </CardContent>
401
+ )}
402
+ {!isLoading && timezoneSetting && (
403
+ <CardContent className="flex-col gap-4">
404
+ <div className="flex justify-between">
405
+ <div className="flex-col">
406
+ <label className="font-semibold">
407
+ {timezoneSetting.name || t('timezone')}
408
+ </label>
409
+ <p className="text-muted-foreground text-sm">
410
+ {timezoneSetting.description ||
411
+ t('setPreferredTimezone')}
412
+ </p>
413
+ </div>
414
+
415
+ <div className="h-full flex items-center">
416
+ <Select
417
+ key={timezone}
418
+ value={timezone}
419
+ onValueChange={handleTimezoneChange}
420
+ >
421
+ <SelectTrigger className="w-60 bg-background">
422
+ <SelectValue
423
+ placeholder={
424
+ t('selectTimezone') || 'Select timezone'
425
+ }
426
+ />
427
+ </SelectTrigger>
428
+ <SelectContent>
429
+ {(timezoneSetting.setting_list || []).map(
430
+ (option: SettingList) => (
431
+ <SelectItem key={option.id} value={option.value}>
432
+ {option.value}
433
+ </SelectItem>
434
+ )
435
+ )}
436
+ </SelectContent>
437
+ </Select>
438
+ </div>
439
+ </div>
440
+ </CardContent>
441
+ )}
442
+ </Card>
443
+ </form>
444
+ </Form>
445
+ </div>
446
+ </div>
447
+ );
448
+ }