@hed-hog/core 0.0.297 → 0.0.298

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 (60) hide show
  1. package/dist/auth/auth.controller.d.ts +10 -10
  2. package/dist/auth/auth.service.d.ts +10 -10
  3. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +14 -0
  4. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  5. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +9 -0
  6. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  7. package/dist/dashboard/dashboard-core/dashboard-core.module.d.ts.map +1 -1
  8. package/dist/dashboard/dashboard-core/dashboard-core.module.js +6 -1
  9. package/dist/dashboard/dashboard-core/dashboard-core.module.js.map +1 -1
  10. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +173 -1
  11. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  12. package/dist/dashboard/dashboard-core/dashboard-core.service.js +531 -5
  13. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  14. package/dist/file/file.controller.d.ts.map +1 -1
  15. package/dist/file/file.controller.js +16 -0
  16. package/dist/file/file.controller.js.map +1 -1
  17. package/dist/file/file.service.d.ts +7 -1
  18. package/dist/file/file.service.d.ts.map +1 -1
  19. package/dist/file/file.service.js +38 -1
  20. package/dist/file/file.service.js.map +1 -1
  21. package/dist/file/provider/s3.provider.d.ts +1 -0
  22. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  23. package/dist/file/provider/s3.provider.js +38 -29
  24. package/dist/file/provider/s3.provider.js.map +1 -1
  25. package/dist/oauth/oauth.service.d.ts.map +1 -1
  26. package/dist/oauth/oauth.service.js +2 -1
  27. package/dist/oauth/oauth.service.js.map +1 -1
  28. package/dist/user/constants/user.constants.d.ts +1 -0
  29. package/dist/user/constants/user.constants.d.ts.map +1 -1
  30. package/dist/user/constants/user.constants.js +2 -1
  31. package/dist/user/constants/user.constants.js.map +1 -1
  32. package/dist/user/user.controller.d.ts +10 -10
  33. package/dist/user/user.service.d.ts +30 -30
  34. package/dist/user/user.service.d.ts.map +1 -1
  35. package/dist/user/user.service.js +2 -1
  36. package/dist/user/user.service.js.map +1 -1
  37. package/hedhog/data/dashboard_item.yaml +11 -11
  38. package/hedhog/data/route.yaml +8 -0
  39. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +76 -15
  40. package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +85 -61
  41. package/hedhog/frontend/app/dashboard/components/widgets/locale-config.tsx.ejs +139 -280
  42. package/hedhog/frontend/app/dashboard/components/widgets/mail-config.tsx.ejs +161 -407
  43. package/hedhog/frontend/app/dashboard/components/widgets/oauth-config.tsx.ejs +150 -271
  44. package/hedhog/frontend/app/dashboard/components/widgets/profile-card.tsx.ejs +3 -3
  45. package/hedhog/frontend/app/dashboard/components/widgets/storage-config.tsx.ejs +161 -305
  46. package/hedhog/frontend/app/dashboard/components/widgets/theme-config.tsx.ejs +184 -246
  47. package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +12 -14
  48. package/hedhog/frontend/messages/en.json +90 -0
  49. package/hedhog/frontend/messages/pt.json +90 -0
  50. package/hedhog/table/mail_sent_user.yaml +75 -0
  51. package/package.json +5 -5
  52. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
  53. package/src/dashboard/dashboard-core/dashboard-core.module.ts +6 -1
  54. package/src/dashboard/dashboard-core/dashboard-core.service.ts +766 -3
  55. package/src/file/file.controller.ts +37 -13
  56. package/src/file/file.service.ts +47 -5
  57. package/src/file/provider/s3.provider.ts +39 -29
  58. package/src/oauth/oauth.service.ts +8 -7
  59. package/src/user/constants/user.constants.ts +1 -0
  60. package/src/user/user.service.ts +2 -1
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { Button } from '@/components/ui/button';
3
+ import { Badge } from '@/components/ui/badge';
4
4
  import {
5
5
  Card,
6
6
  CardContent,
@@ -8,268 +8,206 @@ import {
8
8
  CardHeader,
9
9
  CardTitle,
10
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';
11
+ import { useWidgetData } from '@/hooks/use-widget-data';
12
+ import type {
13
+ DashboardCoreConfigOverviewData,
14
+ ThemeConfigWidgetData,
15
+ } from '@/types/widget-data';
16
+ import { Palette } from 'lucide-react';
17
+ import { useTranslations } from 'next-intl';
18
+ import { WidgetWrapper } from '../widget-wrapper';
15
19
 
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
- ];
20
+ const emptyPaletteMode = {
21
+ primary: null,
22
+ primaryForeground: null,
23
+ secondary: null,
24
+ secondaryForeground: null,
25
+ accent: null,
26
+ accentForeground: null,
27
+ muted: null,
28
+ mutedForeground: null,
29
+ background: null,
30
+ backgroundForeground: null,
31
+ card: null,
32
+ cardForeground: null,
33
+ };
30
34
 
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);
35
+ const defaultThemeConfigData: ThemeConfigWidgetData = {
36
+ status: {
37
+ isConfigured: false,
38
+ configuredTokenCount: 0,
39
+ },
40
+ branding: {
41
+ systemName: null,
42
+ systemSlogan: null,
43
+ iconUrl: null,
44
+ imageUrl: null,
45
+ },
46
+ presentation: {
47
+ mode: null,
48
+ font: null,
49
+ textSize: null,
50
+ radius: null,
51
+ },
52
+ palette: {
53
+ light: emptyPaletteMode,
54
+ dark: emptyPaletteMode,
55
+ },
56
+ };
39
57
 
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
- }
58
+ interface ThemeConfigProps {
59
+ widget?: { name?: string };
60
+ onRemove?: () => void;
61
+ }
62
+
63
+ export default function ThemeConfig({ widget, onRemove }: ThemeConfigProps) {
64
+ const t = useTranslations('core.DashboardPage.themeConfig');
65
+
66
+ const { data, isLoading, isError, isAccessDenied } = useWidgetData<
67
+ DashboardCoreConfigOverviewData,
68
+ ThemeConfigWidgetData
69
+ >({
70
+ endpoint: '/dashboard-core/config/overview',
71
+ queryKey: 'dashboard-core-config-overview',
72
+ select: (d) => d.themeConfig,
73
+ });
74
+
75
+ const themeData = data ?? defaultThemeConfigData;
51
76
 
52
77
  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
- )}
78
+ <WidgetWrapper
79
+ isLoading={isLoading}
80
+ isError={isError}
81
+ isAccessDenied={isAccessDenied}
82
+ widgetName={widget?.name ?? t('title')}
83
+ onRemove={onRemove}
84
+ >
85
+ <Card className="flex h-full min-h-0 flex-col overflow-hidden">
86
+ <CardHeader className="shrink-0">
87
+ <div className="flex items-center justify-between gap-3">
88
+ <div className="flex items-center gap-3">
89
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-rose-50">
90
+ <Palette className="h-5 w-5 text-rose-600" />
91
+ </div>
92
+ <div>
93
+ <CardTitle className="text-base">{t('title')}</CardTitle>
94
+ <CardDescription>{t('description')}</CardDescription>
95
+ </div>
89
96
  </div>
90
- <div>
91
- <span
92
- className="text-sm font-bold"
93
- style={{ color: primaryColor }}
97
+ <Badge
98
+ variant="secondary"
99
+ className={
100
+ themeData.status.isConfigured
101
+ ? 'bg-emerald-50 text-emerald-700'
102
+ : 'bg-amber-50 text-amber-700'
103
+ }
104
+ >
105
+ {themeData.status.isConfigured ? t('configured') : t('pending')}
106
+ </Badge>
107
+ </div>
108
+ </CardHeader>
109
+ <CardContent className="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden pt-0">
110
+ <div className="rounded-lg border bg-muted/30 p-4">
111
+ <div className="flex items-center gap-3 rounded-lg border bg-background p-3">
112
+ <div
113
+ className="flex h-10 w-10 items-center justify-center rounded-lg text-sm font-bold text-white"
114
+ style={{
115
+ backgroundColor: themeData.palette.light.primary || '#64748b',
116
+ }}
94
117
  >
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
118
+ {(themeData.branding.systemName || 'S').charAt(0).toUpperCase()}
119
+ </div>
120
+ <div className="min-w-0">
121
+ <p className="truncate text-sm font-semibold">
122
+ {themeData.branding.systemName || t('notSet')}
123
+ </p>
124
+ <p className="truncate text-xs text-muted-foreground">
125
+ {themeData.branding.systemSlogan || t('notSet')}
126
+ </p>
109
127
  </div>
110
- )}
128
+ <Badge variant="outline" className="ml-auto text-[10px]">
129
+ {themeData.presentation.mode || t('notSet')}
130
+ </Badge>
131
+ </div>
111
132
  </div>
112
- </div>
113
133
 
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
- )}
134
+ <div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
135
+ <div className="rounded-lg border p-3 text-center">
136
+ <p className="text-[11px] text-muted-foreground">{t('tokens')}</p>
137
+ <p className="mt-1 text-lg font-semibold">
138
+ {themeData.status.configuredTokenCount}
139
+ </p>
141
140
  </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
- )}
141
+ <div className="rounded-lg border p-3 text-center">
142
+ <p className="text-[11px] text-muted-foreground">{t('font')}</p>
143
+ <p className="mt-1 truncate text-sm font-medium">
144
+ {themeData.presentation.font || t('notSet')}
145
+ </p>
146
+ </div>
147
+ <div className="rounded-lg border p-3 text-center">
148
+ <p className="text-[11px] text-muted-foreground">
149
+ {t('textSize')}
150
+ </p>
151
+ <p className="mt-1 truncate text-sm font-medium">
152
+ {themeData.presentation.textSize || t('notSet')}
153
+ </p>
154
+ </div>
155
+ <div className="rounded-lg border p-3 text-center">
156
+ <p className="text-[11px] text-muted-foreground">{t('radius')}</p>
157
+ <p className="mt-1 truncate text-sm font-medium">
158
+ {themeData.presentation.radius || t('notSet')}
159
+ </p>
175
160
  </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
161
  </div>
206
- </div>
207
162
 
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
- />
163
+ <div className="grid gap-2 sm:grid-cols-2">
164
+ <div className="rounded-lg border p-3">
165
+ <div className="mb-2 flex items-center justify-between">
166
+ <p className="text-xs text-muted-foreground">
167
+ {t('lightPalette')}
168
+ </p>
169
+ <Badge variant="outline" className="text-[10px]">
170
+ {themeData.palette.light.primary || t('notSet')}
171
+ </Badge>
172
+ </div>
173
+ <div className="flex items-center gap-2">
174
+ <span
175
+ className="h-5 w-5 rounded-full border"
176
+ style={{
177
+ backgroundColor:
178
+ themeData.palette.light.primary || 'transparent',
179
+ }}
180
+ />
181
+ <span className="text-xs text-muted-foreground">
182
+ {t('primary')}
183
+ </span>
184
+ </div>
223
185
  </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
- >
186
+ <div className="rounded-lg border p-3">
187
+ <div className="mb-2 flex items-center justify-between">
188
+ <p className="text-xs text-muted-foreground">
189
+ {t('darkPalette')}
190
+ </p>
191
+ <Badge variant="outline" className="text-[10px]">
192
+ {themeData.palette.dark.primary || t('notSet')}
193
+ </Badge>
194
+ </div>
195
+ <div className="flex items-center gap-2">
244
196
  <span
245
- className="h-5 w-5 rounded-full"
246
- style={{ backgroundColor: c.value }}
197
+ className="h-5 w-5 rounded-full border"
198
+ style={{
199
+ backgroundColor:
200
+ themeData.palette.dark.primary || 'transparent',
201
+ }}
247
202
  />
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>
203
+ <span className="text-xs text-muted-foreground">
204
+ {t('primary')}
205
+ </span>
206
+ </div>
207
+ </div>
270
208
  </div>
271
- </div>
272
- </CardContent>
273
- </Card>
209
+ </CardContent>
210
+ </Card>
211
+ </WidgetWrapper>
274
212
  );
275
213
  }
@@ -53,46 +53,44 @@ function RolesContent({ roles }: { roles: RoleData[] }) {
53
53
 
54
54
  return (
55
55
  <Card className="flex h-full min-h-0 flex-col overflow-hidden">
56
- <CardHeader className="shrink-0 pb-3">
57
- <div className="flex items-center gap-2">
58
- <Crown className="h-4 w-4 text-amber-600 dark:text-amber-400 sm:h-5 sm:w-5" />
56
+ <CardHeader className="shrink-0 space-y-1 pb-2">
57
+ <div className="flex items-center gap-1.5">
58
+ <Crown className="h-4 w-4 text-amber-600 dark:text-amber-400" />
59
59
  <div>
60
- <CardTitle className="text-sm font-semibold sm:text-base">
60
+ <CardTitle className="text-sm font-semibold">
61
61
  {t('title')}
62
62
  </CardTitle>
63
- <CardDescription className="text-xs sm:text-sm">
63
+ <CardDescription className="text-xs leading-tight">
64
64
  {t('description')}
65
65
  </CardDescription>
66
66
  </div>
67
67
  </div>
68
68
  </CardHeader>
69
69
  <CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
70
- <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
70
+ <div className="grid content-start items-start grid-cols-1 gap-1.5 md:grid-cols-2">
71
71
  {roles.map((role, index) => {
72
72
  const style = levelStyles[index % levelStyles.length]!;
73
73
  return (
74
74
  <div
75
75
  key={role.id}
76
- className={`flex items-center gap-2.5 rounded-xl border p-2.5 transition-all duration-200 hover:shadow-sm sm:gap-3 sm:p-3 ${style.border} ${style.bg}`}
76
+ className={`flex h-fit self-start items-center gap-2 rounded-lg border p-2 transition-all duration-200 hover:shadow-sm ${style.border} ${style.bg}`}
77
77
  >
78
78
  <div
79
- className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-9 sm:w-9 ${style.iconBg}`}
79
+ className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-md ${style.iconBg}`}
80
80
  >
81
- <ShieldCheck
82
- className={`h-3.5 w-3.5 sm:h-4 sm:w-4 ${style.iconColor}`}
83
- />
81
+ <ShieldCheck className={`h-3.5 w-3.5 ${style.iconColor}`} />
84
82
  </div>
85
83
  <div className="flex min-w-0 flex-1 flex-col gap-0.5">
86
- <span className="truncate text-[13px] font-medium text-foreground sm:text-sm">
84
+ <span className="truncate text-xs font-medium text-foreground sm:text-[13px]">
87
85
  {role.name}
88
86
  </span>
89
- <span className="truncate text-[11px] text-muted-foreground sm:text-xs">
87
+ <span className="truncate text-[10px] text-muted-foreground sm:text-[11px]">
90
88
  {role.slug}
91
89
  </span>
92
90
  </div>
93
91
  <Badge
94
92
  variant="secondary"
95
- className={`shrink-0 text-[10px] ${style.badge}`}
93
+ className={`shrink-0 px-1.5 py-0 text-[10px] ${style.badge}`}
96
94
  >
97
95
  {t('active')}
98
96
  </Badge>
@@ -1026,6 +1026,96 @@
1026
1026
  "hoursAgo": "{count}h ago",
1027
1027
  "daysAgo": "{count} day ago",
1028
1028
  "daysAgoPlural": "{count} days ago"
1029
+ },
1030
+ "emailNotifications": {
1031
+ "title": "Email Notifications",
1032
+ "description": "System email flow in the last 14 days",
1033
+ "received": "Received",
1034
+ "read": "Read",
1035
+ "unread": "Unread",
1036
+ "error": "Error",
1037
+ "receivedShort": "Recv.",
1038
+ "readShort": "Read",
1039
+ "unreadShort": "Unrd.",
1040
+ "errorShort": "Err."
1041
+ },
1042
+ "localeConfig": {
1043
+ "title": "Localization",
1044
+ "description": "Locale and regional format summary",
1045
+ "configured": "Configured",
1046
+ "pending": "Pending",
1047
+ "enabled": "Enabled",
1048
+ "disabled": "Disabled",
1049
+ "total": "Total",
1050
+ "timezone": "Timezone",
1051
+ "dateFormat": "Date format",
1052
+ "timeFormat": "Time format",
1053
+ "enabledLocales": "Enabled locales",
1054
+ "noneEnabled": "No locale enabled",
1055
+ "notSet": "Not set"
1056
+ },
1057
+ "mailConfig": {
1058
+ "title": "Mail Configuration",
1059
+ "description": "Provider and delivery overview",
1060
+ "configured": "Configured",
1061
+ "pending": "Pending",
1062
+ "templates": "Templates",
1063
+ "sentTotal": "Sent total",
1064
+ "sent30Days": "Sent (30d)",
1065
+ "sender": "Default sender",
1066
+ "providers": "Providers",
1067
+ "selectedProvider": "Selected: {provider}",
1068
+ "selected": "Selected",
1069
+ "missingKeys": "Missing keys: {count}",
1070
+ "allKeysPresent": "All required keys present",
1071
+ "noProviders": "No providers found",
1072
+ "none": "None",
1073
+ "notSet": "Not set"
1074
+ },
1075
+ "oauthConfig": {
1076
+ "title": "OAuth Providers",
1077
+ "description": "Social login readiness summary",
1078
+ "configured": "Configured",
1079
+ "pending": "Pending",
1080
+ "enabled": "Enabled",
1081
+ "disabled": "Disabled",
1082
+ "configuredProviders": "Configured",
1083
+ "connectedAccounts": "Connected",
1084
+ "scopesCount": "Scopes: {count}",
1085
+ "connectedUsers": "Users: {count}",
1086
+ "missingKeys": "Missing keys: {count}",
1087
+ "allKeysPresent": "All required keys present",
1088
+ "noProviders": "No providers found"
1089
+ },
1090
+ "storageConfig": {
1091
+ "title": "Storage Profiles",
1092
+ "description": "Active providers and profile health",
1093
+ "configured": "Configured",
1094
+ "pending": "Pending",
1095
+ "profiles": "Profiles",
1096
+ "active": "Active",
1097
+ "providers": "Providers",
1098
+ "defaultProfile": "Default profile",
1099
+ "defaults": "defaults",
1100
+ "providerSummary": "{active} active of {total}",
1101
+ "defaultTag": "Default",
1102
+ "inactive": "Inactive",
1103
+ "noProfiles": "No profiles found",
1104
+ "notSet": "Not set"
1105
+ },
1106
+ "themeConfig": {
1107
+ "title": "Theme and Branding",
1108
+ "description": "Visual identity configuration snapshot",
1109
+ "configured": "Configured",
1110
+ "pending": "Pending",
1111
+ "tokens": "Tokens",
1112
+ "font": "Font",
1113
+ "textSize": "Text size",
1114
+ "radius": "Radius",
1115
+ "lightPalette": "Light palette",
1116
+ "darkPalette": "Dark palette",
1117
+ "primary": "Primary",
1118
+ "notSet": "Not set"
1029
1119
  }
1030
1120
  },
1031
1121
  "HomePage": {