@xk2800/nextjs-template 0.5.3
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/README.md +339 -0
- package/components/auth/authCard.tsx +92 -0
- package/components/auth/authShell.tsx +46 -0
- package/components/auth/deviceCheck.tsx +52 -0
- package/components/auth/emailPasswordLogin.tsx +136 -0
- package/components/auth/emailPasswordSignup.tsx +178 -0
- package/components/auth/forgotPasswordForm.tsx +66 -0
- package/components/auth/loginErrorRecovery.tsx +117 -0
- package/components/auth/logoutButtons.tsx +26 -0
- package/components/auth/oneTap.tsx +50 -0
- package/components/auth/passkeyLogin.tsx +50 -0
- package/components/auth/resetPasswordForm.tsx +112 -0
- package/components/auth/socialLogin.tsx +51 -0
- package/components/auth/twoFactorVerifyForm.tsx +100 -0
- package/components/dashboard/accountDetailsCard.tsx +58 -0
- package/components/dashboard/activityLogsCard.tsx +141 -0
- package/components/dashboard/activityLogsCardSkeleton.tsx +20 -0
- package/components/dashboard/activityLogsSection.tsx +16 -0
- package/components/dashboard/admin/activityLogsSection.tsx +13 -0
- package/components/dashboard/admin/adminActivityLogsSkeleton.tsx +34 -0
- package/components/dashboard/admin/adminActivityLogsTable.tsx +319 -0
- package/components/dashboard/admin/adminStatsGrid.tsx +30 -0
- package/components/dashboard/admin/adminStatsGridSkeleton.tsx +16 -0
- package/components/dashboard/admin/adminUserActivityLogCard.tsx +53 -0
- package/components/dashboard/admin/adminUserActivityLogTable.tsx +69 -0
- package/components/dashboard/admin/adminUserLoginInfoCard.tsx +50 -0
- package/components/dashboard/admin/adminUserLoginInfoTable.tsx +63 -0
- package/components/dashboard/admin/adminUserSessionsCard.tsx +225 -0
- package/components/dashboard/admin/adminUserStatusCard.tsx +57 -0
- package/components/dashboard/admin/adminUsersSkeleton.tsx +30 -0
- package/components/dashboard/admin/adminUsersTable.tsx +596 -0
- package/components/dashboard/admin/deviceThrottleCard.tsx +59 -0
- package/components/dashboard/admin/deviceThrottleTable.tsx +93 -0
- package/components/dashboard/admin/impersonationLogCard.tsx +73 -0
- package/components/dashboard/admin/newDeviceCard.tsx +35 -0
- package/components/dashboard/admin/newDeviceTable.tsx +71 -0
- package/components/dashboard/admin/revokeAllSessionsButton.tsx +77 -0
- package/components/dashboard/admin/sharedDeviceCard.tsx +42 -0
- package/components/dashboard/admin/sharedDeviceTable.tsx +75 -0
- package/components/dashboard/admin/systemSettingsForm.tsx +200 -0
- package/components/dashboard/admin/systemSettingsSection.tsx +9 -0
- package/components/dashboard/admin/systemSettingsSkeleton.tsx +29 -0
- package/components/dashboard/admin/usersSection.tsx +13 -0
- package/components/dashboard/adminSection.tsx +31 -0
- package/components/dashboard/appSidebar.tsx +170 -0
- package/components/dashboard/dashboardHeader.tsx +85 -0
- package/components/dashboard/heartbeat.tsx +22 -0
- package/components/dashboard/impersonationBanner.tsx +50 -0
- package/components/dashboard/passkeysCard.tsx +187 -0
- package/components/dashboard/profileCard.tsx +42 -0
- package/components/dashboard/sectionErrorFallback.tsx +18 -0
- package/components/dashboard/sessionsCard.tsx +194 -0
- package/components/dashboard/sessionsCardSkeleton.tsx +20 -0
- package/components/dashboard/sessionsSection.tsx +22 -0
- package/components/dashboard/twoFactorCard.tsx +344 -0
- package/components/dashboard/verifyEmailButton.tsx +32 -0
- package/components/email/email-template-2.tsx +44 -0
- package/components/email/email-template-new-device.tsx +80 -0
- package/components/email/email-template-passkey-change.tsx +81 -0
- package/components/email/email-template-reset-password.tsx +56 -0
- package/components/email/email-template-verify-email.tsx +55 -0
- package/components/email/email-template-welcome.tsx +53 -0
- package/components/email/email-template.tsx +33 -0
- package/components/email/send-email-button.tsx +61 -0
- package/components/providers/theme-provider.tsx +9 -0
- package/components/theme/theme-toggle.tsx +53 -0
- package/components/ui/alert-dialog.tsx +141 -0
- package/components/ui/avatar.tsx +53 -0
- package/components/ui/badge.tsx +46 -0
- package/components/ui/button.tsx +64 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/data-table.tsx +302 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/dropdown-menu.tsx +203 -0
- package/components/ui/input.tsx +21 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/password-input.tsx +38 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/sheet.tsx +143 -0
- package/components/ui/sidebar.tsx +726 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/switch.tsx +35 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +57 -0
- package/dist/activity/logger.d.mts +18 -0
- package/dist/activity/logger.d.ts +18 -0
- package/dist/activity/logger.js +289 -0
- package/dist/activity/logger.js.map +1 -0
- package/dist/activity/logger.mjs +7 -0
- package/dist/activity/logger.mjs.map +1 -0
- package/dist/activity/queries.d.mts +98 -0
- package/dist/activity/queries.d.ts +98 -0
- package/dist/activity/queries.js +372 -0
- package/dist/activity/queries.js.map +1 -0
- package/dist/activity/queries.mjs +124 -0
- package/dist/activity/queries.mjs.map +1 -0
- package/dist/admin/queries.d.mts +68 -0
- package/dist/admin/queries.d.ts +68 -0
- package/dist/admin/queries.js +450 -0
- package/dist/admin/queries.js.map +1 -0
- package/dist/admin/queries.mjs +143 -0
- package/dist/admin/queries.mjs.map +1 -0
- package/dist/auth/helpers.d.mts +111 -0
- package/dist/auth/helpers.d.ts +111 -0
- package/dist/auth/helpers.js +1023 -0
- package/dist/auth/helpers.js.map +1 -0
- package/dist/auth/helpers.mjs +67 -0
- package/dist/auth/helpers.mjs.map +1 -0
- package/dist/auth/index.d.mts +6589 -0
- package/dist/auth/index.d.ts +6589 -0
- package/dist/auth/index.js +968 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/index.mjs +10 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth-client/index.d.mts +4864 -0
- package/dist/auth-client/index.d.ts +4864 -0
- package/dist/auth-client/index.js +55 -0
- package/dist/auth-client/index.js.map +1 -0
- package/dist/auth-client/index.mjs +49 -0
- package/dist/auth-client/index.mjs.map +1 -0
- package/dist/chunk-2FW7C3KS.mjs +556 -0
- package/dist/chunk-2FW7C3KS.mjs.map +1 -0
- package/dist/chunk-ENGYO4K7.mjs +189 -0
- package/dist/chunk-ENGYO4K7.mjs.map +1 -0
- package/dist/chunk-GTPTVNY2.mjs +40 -0
- package/dist/chunk-GTPTVNY2.mjs.map +1 -0
- package/dist/chunk-HYKUB3A2.mjs +30 -0
- package/dist/chunk-HYKUB3A2.mjs.map +1 -0
- package/dist/chunk-JIYHBWAU.mjs +79 -0
- package/dist/chunk-JIYHBWAU.mjs.map +1 -0
- package/dist/chunk-K6OJCSJS.mjs +41 -0
- package/dist/chunk-K6OJCSJS.mjs.map +1 -0
- package/dist/chunk-N6ECRCHY.mjs +61 -0
- package/dist/chunk-N6ECRCHY.mjs.map +1 -0
- package/dist/chunk-YN4436PK.mjs +33 -0
- package/dist/chunk-YN4436PK.mjs.map +1 -0
- package/dist/config/env.d.mts +15 -0
- package/dist/config/env.d.ts +15 -0
- package/dist/config/env.js +42 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/env.mjs +4 -0
- package/dist/config/env.mjs.map +1 -0
- package/dist/db/index.d.mts +10 -0
- package/dist/db/index.d.ts +10 -0
- package/dist/db/index.js +252 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/index.mjs +6 -0
- package/dist/db/index.mjs.map +1 -0
- package/dist/db/schema.d.mts +2 -0
- package/dist/db/schema.d.ts +2 -0
- package/dist/db/schema.js +186 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/schema.mjs +4 -0
- package/dist/db/schema.mjs.map +1 -0
- package/dist/schema-CgkDv4wJ.d.mts +1758 -0
- package/dist/schema-CgkDv4wJ.d.ts +1758 -0
- package/dist/sessions/queries.d.mts +34 -0
- package/dist/sessions/queries.d.ts +34 -0
- package/dist/sessions/queries.js +268 -0
- package/dist/sessions/queries.js.map +1 -0
- package/dist/sessions/queries.mjs +23 -0
- package/dist/sessions/queries.mjs.map +1 -0
- package/dist/settings/queries.d.mts +20 -0
- package/dist/settings/queries.d.ts +20 -0
- package/dist/settings/queries.js +344 -0
- package/dist/settings/queries.js.map +1 -0
- package/dist/settings/queries.mjs +7 -0
- package/dist/settings/queries.mjs.map +1 -0
- package/dist/types/auth/loginSchema.d.mts +8 -0
- package/dist/types/auth/loginSchema.d.ts +8 -0
- package/dist/types/auth/loginSchema.js +18 -0
- package/dist/types/auth/loginSchema.js.map +1 -0
- package/dist/types/auth/loginSchema.mjs +16 -0
- package/dist/types/auth/loginSchema.mjs.map +1 -0
- package/dist/types/auth/signupSchema.d.mts +9 -0
- package/dist/types/auth/signupSchema.d.ts +9 -0
- package/dist/types/auth/signupSchema.js +21 -0
- package/dist/types/auth/signupSchema.js.map +1 -0
- package/dist/types/auth/signupSchema.mjs +19 -0
- package/dist/types/auth/signupSchema.mjs.map +1 -0
- package/dist/users/queries.d.mts +71 -0
- package/dist/users/queries.d.ts +71 -0
- package/dist/users/queries.js +293 -0
- package/dist/users/queries.js.map +1 -0
- package/dist/users/queries.mjs +47 -0
- package/dist/users/queries.mjs.map +1 -0
- package/lib/auth-client.ts +58 -0
- package/lib/cookie-names.ts +13 -0
- package/lib/device-fingerprint.ts +21 -0
- package/lib/formatters.ts +53 -0
- package/lib/recalculate-columns.ts +75 -0
- package/lib/request-info.ts +78 -0
- package/lib/utils.ts +6 -0
- package/package.json +301 -0
- package/styles/theme.css +124 -0
- package/types/auth/loginSchema.ts +11 -0
- package/types/auth/signupSchema.ts +14 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
import { useRouter } from 'next/navigation'
|
|
5
|
+
import { Button } from '../ui/button'
|
|
6
|
+
import { Input } from '../ui/input'
|
|
7
|
+
import { Label } from '../ui/label'
|
|
8
|
+
import { Checkbox } from '../ui/checkbox'
|
|
9
|
+
import { authClient } from '../../lib/auth-client'
|
|
10
|
+
import { toast } from 'sonner'
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
callbackUrl: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Second-factor challenge, shown after a correct password when the account
|
|
17
|
+
// has TOTP enabled. `signIn.email` returns `twoFactorRedirect: true` instead
|
|
18
|
+
// of a session and sets a short-lived cookie; verifying here finalizes it.
|
|
19
|
+
const TwoFactorVerifyForm = ({ callbackUrl }: Props) => {
|
|
20
|
+
const router = useRouter()
|
|
21
|
+
const [mode, setMode] = useState<'totp' | 'backup'>('totp')
|
|
22
|
+
const [code, setCode] = useState('')
|
|
23
|
+
const [trustDevice, setTrustDevice] = useState(false)
|
|
24
|
+
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
25
|
+
|
|
26
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
27
|
+
e.preventDefault()
|
|
28
|
+
const trimmed = code.trim()
|
|
29
|
+
if (!trimmed) return
|
|
30
|
+
|
|
31
|
+
setIsSubmitting(true)
|
|
32
|
+
try {
|
|
33
|
+
const { error } =
|
|
34
|
+
mode === 'totp'
|
|
35
|
+
? await authClient.twoFactor.verifyTotp({ code: trimmed, trustDevice })
|
|
36
|
+
: await authClient.twoFactor.verifyBackupCode({ code: trimmed, trustDevice })
|
|
37
|
+
|
|
38
|
+
if (error) {
|
|
39
|
+
toast.error(error.message || 'Invalid code')
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
router.push(callbackUrl)
|
|
43
|
+
router.refresh()
|
|
44
|
+
} catch (err) {
|
|
45
|
+
toast.error(err instanceof Error ? err.message : 'Failed to verify code')
|
|
46
|
+
} finally {
|
|
47
|
+
setIsSubmitting(false)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div>
|
|
53
|
+
<h1 className="text-3xl font-bold tracking-tight">Two-factor authentication</h1>
|
|
54
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
55
|
+
{mode === 'totp'
|
|
56
|
+
? 'Enter the 6-digit code from your authenticator app.'
|
|
57
|
+
: 'Enter one of your backup codes.'}
|
|
58
|
+
</p>
|
|
59
|
+
|
|
60
|
+
<form onSubmit={handleSubmit} className="mt-8 flex flex-col gap-4">
|
|
61
|
+
<div className="flex flex-col gap-2">
|
|
62
|
+
<Label htmlFor="code">{mode === 'totp' ? 'Authenticator code' : 'Backup code'}</Label>
|
|
63
|
+
<Input
|
|
64
|
+
id="code"
|
|
65
|
+
inputMode={mode === 'totp' ? 'numeric' : 'text'}
|
|
66
|
+
autoComplete="one-time-code"
|
|
67
|
+
autoFocus
|
|
68
|
+
value={code}
|
|
69
|
+
onChange={(e) => setCode(e.target.value)}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
74
|
+
<Checkbox
|
|
75
|
+
checked={trustDevice}
|
|
76
|
+
onCheckedChange={(v) => setTrustDevice(v === true)}
|
|
77
|
+
/>
|
|
78
|
+
Trust this device for 30 days
|
|
79
|
+
</label>
|
|
80
|
+
|
|
81
|
+
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
|
82
|
+
{isSubmitting ? 'Verifying...' : 'Verify'}
|
|
83
|
+
</Button>
|
|
84
|
+
</form>
|
|
85
|
+
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
onClick={() => {
|
|
89
|
+
setMode((m) => (m === 'totp' ? 'backup' : 'totp'))
|
|
90
|
+
setCode('')
|
|
91
|
+
}}
|
|
92
|
+
className="mt-6 text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
|
|
93
|
+
>
|
|
94
|
+
{mode === 'totp' ? 'Use a backup code instead' : 'Use your authenticator app instead'}
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default TwoFactorVerifyForm
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
|
|
2
|
+
import { Badge } from "../ui/badge"
|
|
3
|
+
import { Separator } from "../ui/separator"
|
|
4
|
+
import { formatDate } from "../../lib/formatters"
|
|
5
|
+
import VerifyEmailButton from "./verifyEmailButton"
|
|
6
|
+
|
|
7
|
+
type User = {
|
|
8
|
+
id: string
|
|
9
|
+
name: string
|
|
10
|
+
email: string
|
|
11
|
+
emailVerified: boolean
|
|
12
|
+
role: string | null | undefined
|
|
13
|
+
createdAt: Date
|
|
14
|
+
updatedAt: Date
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function AccountDetailsCard({ user }: { user: User }) {
|
|
18
|
+
return (
|
|
19
|
+
<Card>
|
|
20
|
+
<CardHeader>
|
|
21
|
+
<CardTitle>Account Details</CardTitle>
|
|
22
|
+
</CardHeader>
|
|
23
|
+
<CardContent className="space-y-4">
|
|
24
|
+
<div>
|
|
25
|
+
<p className="text-sm font-medium text-gray-600">Role</p>
|
|
26
|
+
<p className="text-base capitalize">{user.role || 'user'}</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<Separator />
|
|
30
|
+
|
|
31
|
+
<div>
|
|
32
|
+
<p className="text-sm font-medium text-gray-600">Email Status</p>
|
|
33
|
+
<div className="flex items-center gap-2 mt-1 flex-wrap">
|
|
34
|
+
<p className="text-base">{user.email}</p>
|
|
35
|
+
<Badge variant={user.emailVerified ? 'default' : 'destructive'}>
|
|
36
|
+
{user.emailVerified ? 'Verified' : 'Unverified'}
|
|
37
|
+
</Badge>
|
|
38
|
+
{!user.emailVerified && <VerifyEmailButton email={user.email} />}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<Separator />
|
|
43
|
+
|
|
44
|
+
<div>
|
|
45
|
+
<p className="text-sm font-medium text-gray-600">Member Since</p>
|
|
46
|
+
<p className="text-base">{formatDate(user.createdAt)}</p>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<Separator />
|
|
50
|
+
|
|
51
|
+
<div>
|
|
52
|
+
<p className="text-sm font-medium text-gray-600">Last Updated</p>
|
|
53
|
+
<p className="text-base">{formatDate(user.updatedAt)}</p>
|
|
54
|
+
</div>
|
|
55
|
+
</CardContent>
|
|
56
|
+
</Card>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../ui/card'
|
|
5
|
+
import { Badge } from '../ui/badge'
|
|
6
|
+
import { Skeleton } from '../ui/skeleton'
|
|
7
|
+
import { DataTable, type Column } from '../ui/data-table'
|
|
8
|
+
import { formatDateTime, actionBadgeVariant } from '../../lib/formatters'
|
|
9
|
+
|
|
10
|
+
interface ActivityLog {
|
|
11
|
+
id: string
|
|
12
|
+
action: string
|
|
13
|
+
description: string
|
|
14
|
+
ipAddress: string | null
|
|
15
|
+
userAgent: string | null
|
|
16
|
+
deviceLabel?: string
|
|
17
|
+
location?: string
|
|
18
|
+
createdAt: Date | string
|
|
19
|
+
userId: string
|
|
20
|
+
userName?: string | null
|
|
21
|
+
userEmail?: string | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ActivityLogsCardProps {
|
|
25
|
+
isAdmin?: boolean
|
|
26
|
+
initialLogs?: ActivityLog[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const baseColumns: Column<ActivityLog>[] = [
|
|
30
|
+
{
|
|
31
|
+
name: 'action',
|
|
32
|
+
title: 'Action',
|
|
33
|
+
sticky: true,
|
|
34
|
+
renderer: (log) => (
|
|
35
|
+
<Badge variant={actionBadgeVariant[log.action] || 'outline'}>
|
|
36
|
+
{log.action.replace(/_/g, ' ')}
|
|
37
|
+
</Badge>
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'description',
|
|
42
|
+
title: 'Description',
|
|
43
|
+
renderer: (log) => <span className="text-sm max-w-xs truncate block">{log.description}</span>,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'deviceLabel',
|
|
47
|
+
title: 'Device',
|
|
48
|
+
renderer: (log) => <span className="text-sm">{log.deviceLabel || 'N/A'}</span>,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'location',
|
|
52
|
+
title: 'Location',
|
|
53
|
+
renderer: (log) => <span className="text-sm">{log.location || 'N/A'}</span>,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'ipAddress',
|
|
57
|
+
title: 'IP Address',
|
|
58
|
+
renderer: (log) => <span className="text-sm font-mono">{log.ipAddress || 'N/A'}</span>,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'createdAt',
|
|
62
|
+
title: 'Time',
|
|
63
|
+
renderer: (log) => (
|
|
64
|
+
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
65
|
+
{formatDateTime(log.createdAt)}
|
|
66
|
+
</span>
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
const userColumn: Column<ActivityLog> = {
|
|
72
|
+
name: 'user',
|
|
73
|
+
title: 'User',
|
|
74
|
+
renderer: (log) => (
|
|
75
|
+
<div className="text-sm">
|
|
76
|
+
<div className="font-medium">{log.userName || 'N/A'}</div>
|
|
77
|
+
<div className="text-xs text-gray-500">{log.userEmail}</div>
|
|
78
|
+
</div>
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default function ActivityLogsCard({ isAdmin = false, initialLogs = [] }: ActivityLogsCardProps) {
|
|
83
|
+
const [logs, setLogs] = useState<ActivityLog[]>(initialLogs)
|
|
84
|
+
const [loading, setLoading] = useState(false)
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const fetchLogs = async () => {
|
|
88
|
+
try {
|
|
89
|
+
const params = new URLSearchParams({
|
|
90
|
+
limit: '20',
|
|
91
|
+
offset: '0',
|
|
92
|
+
})
|
|
93
|
+
const response = await fetch(`/api/activity-logs?${params}`)
|
|
94
|
+
if (!response.ok) throw new Error('Failed to fetch logs')
|
|
95
|
+
const data = await response.json()
|
|
96
|
+
setLogs(data.logs)
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Failed to fetch activity logs:', error)
|
|
99
|
+
} finally {
|
|
100
|
+
setLoading(false)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fetchLogs()
|
|
105
|
+
// Refresh logs every 30 seconds
|
|
106
|
+
const interval = setInterval(fetchLogs, 30000)
|
|
107
|
+
return () => clearInterval(interval)
|
|
108
|
+
}, [])
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Card>
|
|
112
|
+
<CardHeader>
|
|
113
|
+
<CardTitle>Activity Logs</CardTitle>
|
|
114
|
+
<CardDescription>
|
|
115
|
+
{isAdmin ? 'Recent activity from all users' : 'Your recent activity'}
|
|
116
|
+
</CardDescription>
|
|
117
|
+
</CardHeader>
|
|
118
|
+
<CardContent>
|
|
119
|
+
{loading ? (
|
|
120
|
+
<div className="space-y-4">
|
|
121
|
+
{[...Array(5)].map((_, i) => (
|
|
122
|
+
<Skeleton key={i} className="h-16 w-full" />
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
) : logs.length === 0 ? (
|
|
126
|
+
<p className="text-center text-gray-500 py-8">No activity logs found</p>
|
|
127
|
+
) : (
|
|
128
|
+
<DataTable
|
|
129
|
+
columns={
|
|
130
|
+
isAdmin
|
|
131
|
+
? [baseColumns[0], userColumn, ...baseColumns.slice(1)]
|
|
132
|
+
: baseColumns
|
|
133
|
+
}
|
|
134
|
+
data={logs}
|
|
135
|
+
getRowId={(log) => log.id}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
</CardContent>
|
|
139
|
+
</Card>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../ui/card"
|
|
2
|
+
import { Skeleton } from "../ui/skeleton"
|
|
3
|
+
|
|
4
|
+
export default function ActivityLogsCardSkeleton() {
|
|
5
|
+
return (
|
|
6
|
+
<Card>
|
|
7
|
+
<CardHeader>
|
|
8
|
+
<CardTitle>Activity Logs</CardTitle>
|
|
9
|
+
<CardDescription>Loading...</CardDescription>
|
|
10
|
+
</CardHeader>
|
|
11
|
+
<CardContent>
|
|
12
|
+
<div className="space-y-4">
|
|
13
|
+
{[...Array(5)].map((_, i) => (
|
|
14
|
+
<Skeleton key={i} className="h-16 w-full" />
|
|
15
|
+
))}
|
|
16
|
+
</div>
|
|
17
|
+
</CardContent>
|
|
18
|
+
</Card>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getUserActivityLogs, getAllActivityLogs } from "@xk2800/nextjs-template/activity/queries"
|
|
2
|
+
import ActivityLogsCard from "./activityLogsCard"
|
|
3
|
+
|
|
4
|
+
export default async function ActivityLogsSection({
|
|
5
|
+
userId,
|
|
6
|
+
isAdmin,
|
|
7
|
+
}: {
|
|
8
|
+
userId: string
|
|
9
|
+
isAdmin: boolean
|
|
10
|
+
}) {
|
|
11
|
+
const logs = isAdmin
|
|
12
|
+
? await getAllActivityLogs(20, 0)
|
|
13
|
+
: await getUserActivityLogs(userId, 20)
|
|
14
|
+
|
|
15
|
+
return <ActivityLogsCard initialLogs={logs} isAdmin={isAdmin} />
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getActivityLogsFiltered } from "@xk2800/nextjs-template/activity/queries"
|
|
2
|
+
import AdminActivityLogsTable from "./adminActivityLogsTable"
|
|
3
|
+
|
|
4
|
+
export default async function ActivityLogsSection() {
|
|
5
|
+
const { logs, total, page, limit, pages } = await getActivityLogsFiltered({ page: 1, limit: 20 })
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<AdminActivityLogsTable
|
|
9
|
+
initialLogs={logs}
|
|
10
|
+
initialPagination={{ page, limit, total, pages }}
|
|
11
|
+
/>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../../ui/card"
|
|
2
|
+
import { Skeleton } from "../../ui/skeleton"
|
|
3
|
+
|
|
4
|
+
export default function AdminActivityLogsSkeleton() {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
<Card>
|
|
8
|
+
<CardHeader>
|
|
9
|
+
<CardTitle>Filters</CardTitle>
|
|
10
|
+
<CardDescription>Search and narrow down activity events</CardDescription>
|
|
11
|
+
</CardHeader>
|
|
12
|
+
<CardContent>
|
|
13
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
|
|
14
|
+
{[...Array(5)].map((_, i) => (
|
|
15
|
+
<Skeleton key={i} className="h-9 w-full" />
|
|
16
|
+
))}
|
|
17
|
+
</div>
|
|
18
|
+
</CardContent>
|
|
19
|
+
</Card>
|
|
20
|
+
|
|
21
|
+
<Card>
|
|
22
|
+
<CardHeader>
|
|
23
|
+
<CardTitle>Events</CardTitle>
|
|
24
|
+
<CardDescription>Loading...</CardDescription>
|
|
25
|
+
</CardHeader>
|
|
26
|
+
<CardContent className="space-y-4">
|
|
27
|
+
{[...Array(5)].map((_, i) => (
|
|
28
|
+
<Skeleton key={i} className="h-16 w-full" />
|
|
29
|
+
))}
|
|
30
|
+
</CardContent>
|
|
31
|
+
</Card>
|
|
32
|
+
</>
|
|
33
|
+
)
|
|
34
|
+
}
|