@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.
- package/hedhog/frontend/app/account/2fa/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/accounts/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +356 -0
- package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +379 -0
- package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +184 -0
- package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +144 -0
- package/hedhog/frontend/app/account/components/email-request-dialog.tsx.ejs +96 -0
- package/hedhog/frontend/app/account/components/mfa-add-buttons.tsx.ejs +43 -0
- package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +115 -0
- package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +236 -0
- package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +209 -0
- package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +192 -0
- package/hedhog/frontend/app/account/components/regenerate-codes-dialog.tsx.ejs +372 -0
- package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +337 -0
- package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +393 -0
- package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +332 -0
- package/hedhog/frontend/app/account/email/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/hooks/use-mfa-methods.ts.ejs +27 -0
- package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +461 -0
- package/hedhog/frontend/app/account/layout.tsx.ejs +105 -0
- package/hedhog/frontend/app/account/lib/mfa-utils.tsx.ejs +37 -0
- package/hedhog/frontend/app/account/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/password/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/profile/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/sessions/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +490 -0
- package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +62 -0
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +316 -0
- package/hedhog/frontend/app/configurations/page.tsx.ejs +35 -0
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +351 -0
- package/hedhog/frontend/app/dashboard/[slug]/page.tsx.ejs +11 -0
- package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +62 -0
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +45 -0
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +196 -0
- package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +63 -0
- package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +516 -0
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +753 -0
- package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +516 -0
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +489 -0
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +621 -0
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -0
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +312 -0
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +1177 -0
- package/hedhog/frontend/app/preferences/page.tsx.ejs +448 -0
- package/hedhog/frontend/app/roles/menus.tsx.ejs +504 -0
- package/hedhog/frontend/app/roles/page.tsx.ejs +814 -0
- package/hedhog/frontend/app/roles/routes.tsx.ejs +397 -0
- package/hedhog/frontend/app/roles/users.tsx.ejs +306 -0
- package/hedhog/frontend/app/users/active-session.tsx.ejs +159 -0
- package/hedhog/frontend/app/users/identifiers.tsx.ejs +279 -0
- package/hedhog/frontend/app/users/page.tsx.ejs +1257 -0
- package/hedhog/frontend/app/users/permissions.tsx.ejs +155 -0
- package/hedhog/frontend/messages/en.json +1080 -0
- package/hedhog/frontend/messages/pt.json +1135 -0
- package/package.json +4 -4
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { PageHeader, PaginationFooter } from '@/components/entity-list';
|
|
4
|
+
import { Badge } from '@/components/ui/badge';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { Card, CardContent } from '@/components/ui/card';
|
|
7
|
+
import {
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogDescription,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from '@/components/ui/dialog';
|
|
14
|
+
import { Input } from '@/components/ui/input';
|
|
15
|
+
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
16
|
+
import { useDebounce } from '@/hooks/use-debounce';
|
|
17
|
+
import { formatDate } from '@/lib/format-date';
|
|
18
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
19
|
+
import { Clock, Mail, Search, User } from 'lucide-react';
|
|
20
|
+
import { useTranslations } from 'next-intl';
|
|
21
|
+
import { useEffect, useState } from 'react';
|
|
22
|
+
|
|
23
|
+
type PaginationResult<T> = {
|
|
24
|
+
data: T[];
|
|
25
|
+
total: number;
|
|
26
|
+
page: number;
|
|
27
|
+
pageSize: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type MailSent = {
|
|
31
|
+
id: number;
|
|
32
|
+
mail_id: number;
|
|
33
|
+
subject: string;
|
|
34
|
+
from: string;
|
|
35
|
+
to: string;
|
|
36
|
+
cc?: string;
|
|
37
|
+
bcc?: string;
|
|
38
|
+
body: string;
|
|
39
|
+
created_at: string;
|
|
40
|
+
updated_at: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default function MailLogPage() {
|
|
44
|
+
const t = useTranslations('core.MailLog');
|
|
45
|
+
const [logs, setLogs] = useState<MailSent[]>([]);
|
|
46
|
+
const [selectedLog, setSelectedLog] = useState<MailSent | null>(null);
|
|
47
|
+
const [isDetailDialogOpen, setIsDetailDialogOpen] = useState(false);
|
|
48
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
49
|
+
const debouncedSearch = useDebounce(searchTerm);
|
|
50
|
+
const [page, setPage] = useState(1);
|
|
51
|
+
const [pageSize, setPageSize] = useState(10);
|
|
52
|
+
const { request, getSettingValue } = useApp();
|
|
53
|
+
|
|
54
|
+
const {
|
|
55
|
+
data: { data, total },
|
|
56
|
+
refetch: refetchLogs,
|
|
57
|
+
} = useQuery<PaginationResult<MailSent>>({
|
|
58
|
+
queryKey: ['mail-sent', debouncedSearch, page, pageSize],
|
|
59
|
+
queryFn: async () => {
|
|
60
|
+
const response = await request({
|
|
61
|
+
url: '/mail-sent',
|
|
62
|
+
params: {
|
|
63
|
+
search: debouncedSearch,
|
|
64
|
+
page,
|
|
65
|
+
pageSize,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
return response.data as PaginationResult<MailSent>;
|
|
69
|
+
},
|
|
70
|
+
initialData: {
|
|
71
|
+
data: [],
|
|
72
|
+
total: 0,
|
|
73
|
+
page: 1,
|
|
74
|
+
pageSize: 10,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (data) {
|
|
80
|
+
setLogs(data);
|
|
81
|
+
}
|
|
82
|
+
}, [data]);
|
|
83
|
+
|
|
84
|
+
const handleViewDetails = (log: MailSent): void => {
|
|
85
|
+
setSelectedLog(log);
|
|
86
|
+
setIsDetailDialogOpen(true);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleSearchChange = (value: string): void => {
|
|
90
|
+
setSearchTerm(value);
|
|
91
|
+
setPage(1);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const truncateText = (text: string, maxLength: number = 100): string => {
|
|
95
|
+
if (!text) return '';
|
|
96
|
+
return text.length > maxLength
|
|
97
|
+
? text.substring(0, maxLength) + '...'
|
|
98
|
+
: text;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
refetchLogs();
|
|
103
|
+
}, [debouncedSearch, page, pageSize]);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="flex flex-col h-screen px-4">
|
|
107
|
+
<PageHeader
|
|
108
|
+
breadcrumbs={[
|
|
109
|
+
{ label: t('breadcrumbHome'), href: '/' },
|
|
110
|
+
{ label: t('breadcrumbTitle') },
|
|
111
|
+
]}
|
|
112
|
+
title={t('title')}
|
|
113
|
+
description={t('description')}
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
117
|
+
<Card className="transition-shadow hover:shadow-md p-0">
|
|
118
|
+
<CardContent className="p-4">
|
|
119
|
+
<div className="flex items-center space-x-3">
|
|
120
|
+
<div className="rounded-full bg-blue-100 p-2 dark:bg-blue-900">
|
|
121
|
+
<Mail className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
|
122
|
+
</div>
|
|
123
|
+
<div>
|
|
124
|
+
<p className="text-sm font-medium text-muted-foreground">
|
|
125
|
+
{t('totalEmails')}
|
|
126
|
+
</p>
|
|
127
|
+
<p className="text-2xl font-bold">{total || 0}</p>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</CardContent>
|
|
131
|
+
</Card>
|
|
132
|
+
|
|
133
|
+
<Card className="transition-shadow hover:shadow-md p-0">
|
|
134
|
+
<CardContent className="p-4">
|
|
135
|
+
<div className="flex items-center space-x-3">
|
|
136
|
+
<div className="rounded-full bg-green-100 p-2 dark:bg-green-900">
|
|
137
|
+
<Clock className="h-6 w-6 text-green-600 dark:text-green-400" />
|
|
138
|
+
</div>
|
|
139
|
+
<div>
|
|
140
|
+
<p className="text-sm font-medium text-muted-foreground">
|
|
141
|
+
{t('onThisPage')}
|
|
142
|
+
</p>
|
|
143
|
+
<p className="text-2xl font-bold">{logs.length}</p>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</CardContent>
|
|
147
|
+
</Card>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div className="relative my-4">
|
|
151
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
152
|
+
<Input
|
|
153
|
+
placeholder={t('searchPlaceholder')}
|
|
154
|
+
value={searchTerm}
|
|
155
|
+
onChange={(e) => handleSearchChange(e.target.value)}
|
|
156
|
+
className="pl-10"
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div className="space-y-3 mb-4">
|
|
161
|
+
{logs.length === 0 ? (
|
|
162
|
+
<Card>
|
|
163
|
+
<CardContent className="flex flex-col items-center justify-center p-12">
|
|
164
|
+
<Mail className="mb-4 h-12 w-12 text-muted-foreground" />
|
|
165
|
+
<p className="text-lg font-medium text-muted-foreground">
|
|
166
|
+
{t('noLogsFound')}
|
|
167
|
+
</p>
|
|
168
|
+
<p className="text-sm text-muted-foreground">
|
|
169
|
+
{searchTerm ? t('adjustSearch') : t('noEmailsSent')}
|
|
170
|
+
</p>
|
|
171
|
+
</CardContent>
|
|
172
|
+
</Card>
|
|
173
|
+
) : (
|
|
174
|
+
logs.map((log) => (
|
|
175
|
+
<Card
|
|
176
|
+
key={log.id}
|
|
177
|
+
onDoubleClick={() => handleViewDetails(log)}
|
|
178
|
+
className="cursor-pointer transition-shadow hover:shadow-md"
|
|
179
|
+
>
|
|
180
|
+
<CardContent className="p-6">
|
|
181
|
+
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
182
|
+
<div className="flex-1 space-y-2">
|
|
183
|
+
<div className="flex items-center gap-2">
|
|
184
|
+
<h3 className="text-lg font-semibold">{log.subject}</h3>
|
|
185
|
+
<Badge variant="outline">#{log.id}</Badge>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="flex flex-col gap-1 text-sm text-muted-foreground">
|
|
188
|
+
<div className="flex items-center gap-2">
|
|
189
|
+
<User className="h-4 w-4" />
|
|
190
|
+
<span>
|
|
191
|
+
{t('from')}: {log.from}
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="flex items-center gap-2">
|
|
195
|
+
<Mail className="h-4 w-4" />
|
|
196
|
+
<span>
|
|
197
|
+
{t('to')}: {truncateText(log.to, 50)}
|
|
198
|
+
</span>
|
|
199
|
+
</div>
|
|
200
|
+
<div className="flex items-center gap-2">
|
|
201
|
+
<Clock className="h-4 w-4" />
|
|
202
|
+
<span>
|
|
203
|
+
{formatDate(log.created_at, getSettingValue)}
|
|
204
|
+
</span>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
<Button
|
|
209
|
+
variant="outline"
|
|
210
|
+
size="sm"
|
|
211
|
+
onClick={(e) => {
|
|
212
|
+
e.stopPropagation();
|
|
213
|
+
handleViewDetails(log);
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
{t('viewDetails')}
|
|
217
|
+
</Button>
|
|
218
|
+
</div>
|
|
219
|
+
</CardContent>
|
|
220
|
+
</Card>
|
|
221
|
+
))
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<PaginationFooter
|
|
226
|
+
currentPage={page}
|
|
227
|
+
pageSize={pageSize}
|
|
228
|
+
totalItems={total}
|
|
229
|
+
onPageChange={setPage}
|
|
230
|
+
onPageSizeChange={(newSize) => {
|
|
231
|
+
setPageSize(newSize);
|
|
232
|
+
setPage(1);
|
|
233
|
+
}}
|
|
234
|
+
pageSizeOptions={[10, 20, 30, 40, 50]}
|
|
235
|
+
/>
|
|
236
|
+
|
|
237
|
+
{selectedLog && (
|
|
238
|
+
<Dialog open={isDetailDialogOpen} onOpenChange={setIsDetailDialogOpen}>
|
|
239
|
+
<DialogContent className="max-w-2xl max-h-[80vh]">
|
|
240
|
+
<DialogHeader>
|
|
241
|
+
<DialogTitle>{t('emailDetails')}</DialogTitle>
|
|
242
|
+
<DialogDescription>{t('detailsDescription')}</DialogDescription>
|
|
243
|
+
</DialogHeader>
|
|
244
|
+
<ScrollArea className="max-h-[60vh]">
|
|
245
|
+
<div className="space-y-4">
|
|
246
|
+
<div>
|
|
247
|
+
<h4 className="mb-2 text-sm font-semibold">{t('subject')}</h4>
|
|
248
|
+
<p className="rounded-md bg-muted p-3 text-sm">
|
|
249
|
+
{selectedLog.subject}
|
|
250
|
+
</p>
|
|
251
|
+
</div>
|
|
252
|
+
<div>
|
|
253
|
+
<h4 className="mb-2 text-sm font-semibold">{t('from')}</h4>
|
|
254
|
+
<p className="rounded-md bg-muted p-3 text-sm">
|
|
255
|
+
{selectedLog.from}
|
|
256
|
+
</p>
|
|
257
|
+
</div>
|
|
258
|
+
<div>
|
|
259
|
+
<h4 className="mb-2 text-sm font-semibold">{t('to')}</h4>
|
|
260
|
+
<p className="rounded-md bg-muted p-3 text-sm">
|
|
261
|
+
{selectedLog.to}
|
|
262
|
+
</p>
|
|
263
|
+
</div>
|
|
264
|
+
{selectedLog.cc && (
|
|
265
|
+
<div>
|
|
266
|
+
<h4 className="mb-2 text-sm font-semibold">{t('cc')}</h4>
|
|
267
|
+
<p className="rounded-md bg-muted p-3 text-sm">
|
|
268
|
+
{selectedLog.cc}
|
|
269
|
+
</p>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
{selectedLog.bcc && (
|
|
273
|
+
<div>
|
|
274
|
+
<h4 className="mb-2 text-sm font-semibold">{t('bcc')}</h4>
|
|
275
|
+
<p className="rounded-md bg-muted p-3 text-sm">
|
|
276
|
+
{selectedLog.bcc}
|
|
277
|
+
</p>
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
280
|
+
<div>
|
|
281
|
+
<h4 className="mb-2 text-sm font-semibold">{t('body')}</h4>
|
|
282
|
+
<div
|
|
283
|
+
className="rounded-md bg-muted p-3 text-sm"
|
|
284
|
+
dangerouslySetInnerHTML={{ __html: selectedLog.body }}
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
<div className="grid grid-cols-2 gap-4">
|
|
288
|
+
<div>
|
|
289
|
+
<h4 className="mb-2 text-sm font-semibold">
|
|
290
|
+
{t('createdAt')}
|
|
291
|
+
</h4>
|
|
292
|
+
<p className="rounded-md bg-muted p-3 text-sm">
|
|
293
|
+
{formatDate(selectedLog.created_at, getSettingValue)}
|
|
294
|
+
</p>
|
|
295
|
+
</div>
|
|
296
|
+
<div>
|
|
297
|
+
<h4 className="mb-2 text-sm font-semibold">
|
|
298
|
+
{t('updatedAt')}
|
|
299
|
+
</h4>
|
|
300
|
+
<p className="rounded-md bg-muted p-3 text-sm">
|
|
301
|
+
{formatDate(selectedLog.updated_at, getSettingValue)}
|
|
302
|
+
</p>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
</ScrollArea>
|
|
307
|
+
</DialogContent>
|
|
308
|
+
</Dialog>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
312
|
+
}
|