Skip to content

Commit 17ddafb

Browse files
authored
Merge pull request #258 from codeunia-dev/feat/adminstats
feat(admin): Add dynamic recent activities and system health fetching
2 parents 5ca107e + 2ccafa0 commit 17ddafb

File tree

3 files changed

+254
-63
lines changed

3 files changed

+254
-63
lines changed

app/admin/page.tsx

Lines changed: 36 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -87,70 +87,20 @@ const dashboardStats = [
8787
},
8888
]
8989

90-
const recentActivities = [
91-
{
92-
id: 1,
93-
type: "user_signup",
94-
message: "New user registered: akshay@gmail.com",
95-
timestamp: "2 minutes ago",
96-
status: "success",
97-
},
98-
{
99-
id: 2,
100-
type: "test_created",
101-
message: "New test 'JavaScript Fundamentals' created",
102-
timestamp: "5 minutes ago",
103-
status: "success",
104-
},
105-
{
106-
id: 3,
107-
type: "blog_published",
108-
message: "Blog post 'React 18 Features' published by Akshay",
109-
timestamp: "15 minutes ago",
110-
status: "success",
111-
},
112-
{
113-
id: 4,
114-
type: "event_created",
115-
message: "New hackathon 'Summer Challenge 2025' created",
116-
timestamp: "1 hour ago",
117-
status: "info",
118-
},
119-
{
120-
id: 5,
121-
type: "security_alert",
122-
message: "Multiple failed login attempts detected",
123-
timestamp: "2 hours ago",
124-
status: "warning",
125-
}
126-
]
90+
type Activity = {
91+
id: number
92+
type: string
93+
message: string
94+
timestamp: string
95+
status: string
96+
}
12797

128-
const systemHealth = [
129-
{
130-
service: "API Server",
131-
status: "healthy",
132-
uptime: "99.9%",
133-
responseTime: "45ms",
134-
},
135-
{
136-
service: "Database",
137-
status: "healthy",
138-
uptime: "99.8%",
139-
responseTime: "12ms",
140-
},
141-
{
142-
service: "CDN",
143-
status: "healthy",
144-
uptime: "100%",
145-
responseTime: "23ms",
146-
},
147-
{
148-
service: "Email Service",
149-
status: "warning",
150-
uptime: "98.5%",
151-
responseTime: "156ms",
152-
},
153-
]
98+
type SystemHealthItem = {
99+
service: string
100+
status: string
101+
uptime: string
102+
responseTime: string
103+
}
154104

155105
export default function AdminDashboard() {
156106
const [currentTime, setCurrentTime] = useState(new Date())
@@ -162,6 +112,8 @@ export default function AdminDashboard() {
162112
const [pageViewsChange, setPageViewsChange] = useState<string>("")
163113
const [pageViewsTrend, setPageViewsTrend] = useState<"up" | "down">("up")
164114
const [topContent, setTopContent] = useState<BlogPost[]>([])
115+
const [recentActivities, setRecentActivities] = useState<Activity[]>([])
116+
const [systemHealth, setSystemHealth] = useState<SystemHealthItem[]>([])
165117
const supabaseRef = useRef<ReturnType<typeof createClient> | null>(null)
166118
const likesChannelRef = useRef<RealtimeChannel | null>(null)
167119

@@ -249,6 +201,27 @@ export default function AdminDashboard() {
249201
}
250202
}
251203
fetchTopContentWithLikes()
204+
205+
// Fetch recent activities
206+
fetch("/api/admin/recent-activities")
207+
.then(res => res.json())
208+
.then(data => {
209+
if (data.activities) {
210+
setRecentActivities(data.activities)
211+
}
212+
})
213+
.catch(err => console.error("Failed to fetch activities:", err))
214+
215+
// Fetch system health
216+
fetch("/api/admin/system-health")
217+
.then(res => res.json())
218+
.then(data => {
219+
if (data.health) {
220+
setSystemHealth(data.health)
221+
}
222+
})
223+
.catch(err => console.error("Failed to fetch system health:", err))
224+
252225
// Setup realtime subscription
253226
const supabase = createClient()
254227
supabaseRef.current = supabase
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { NextResponse } from "next/server"
2+
import { createClient } from "@/lib/supabase/server"
3+
4+
export async function GET() {
5+
try {
6+
const supabase = await createClient()
7+
8+
// Check if user is admin
9+
const { data: { user } } = await supabase.auth.getUser()
10+
11+
if (!user) {
12+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
13+
}
14+
15+
const { data: profile } = await supabase
16+
.from("profiles")
17+
.select("is_admin")
18+
.eq("id", user.id)
19+
.single()
20+
21+
if (!profile?.is_admin) {
22+
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
23+
}
24+
25+
const activities: Array<{
26+
type: string
27+
message: string
28+
timestamp: string
29+
status: string
30+
created_at: string
31+
}> = []
32+
33+
// Fetch recent user signups (last 7 days to ensure we have data)
34+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
35+
36+
const { data: recentUsers } = await supabase
37+
.from("profiles")
38+
.select("email, created_at")
39+
.gte("created_at", sevenDaysAgo)
40+
.order("created_at", { ascending: false })
41+
.limit(3)
42+
43+
if (recentUsers) {
44+
recentUsers.forEach(user => {
45+
activities.push({
46+
type: "user_signup",
47+
message: `New user registered: ${user.email}`,
48+
timestamp: getTimeAgo(user.created_at),
49+
status: "success",
50+
created_at: user.created_at
51+
})
52+
})
53+
}
54+
55+
// Fetch recent blog posts
56+
const { data: recentBlogs } = await supabase
57+
.from("blogs")
58+
.select("title, author, date")
59+
.order("date", { ascending: false })
60+
.limit(3)
61+
62+
if (recentBlogs) {
63+
recentBlogs.forEach(blog => {
64+
activities.push({
65+
type: "blog_published",
66+
message: `Blog post "${blog.title}" published by ${blog.author}`,
67+
timestamp: getTimeAgo(blog.date),
68+
status: "success",
69+
created_at: blog.date
70+
})
71+
})
72+
}
73+
74+
// Fetch recent events
75+
const { data: recentEvents } = await supabase
76+
.from("events")
77+
.select("title, created_at")
78+
.order("created_at", { ascending: false })
79+
.limit(2)
80+
81+
if (recentEvents) {
82+
recentEvents.forEach(event => {
83+
activities.push({
84+
type: "event_created",
85+
message: `New event "${event.title}" created`,
86+
timestamp: getTimeAgo(event.created_at),
87+
status: "info",
88+
created_at: event.created_at
89+
})
90+
})
91+
}
92+
93+
// Fetch recent hackathons
94+
const { data: recentHackathons } = await supabase
95+
.from("hackathons")
96+
.select("title, created_at")
97+
.order("created_at", { ascending: false })
98+
.limit(2)
99+
100+
if (recentHackathons) {
101+
recentHackathons.forEach(hackathon => {
102+
activities.push({
103+
type: "event_created",
104+
message: `New hackathon "${hackathon.title}" created`,
105+
timestamp: getTimeAgo(hackathon.created_at),
106+
status: "info",
107+
created_at: hackathon.created_at
108+
})
109+
})
110+
}
111+
112+
// Sort all activities by timestamp and limit to 5
113+
activities.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
114+
const topActivities = activities.slice(0, 5).map((activity, index) => ({
115+
id: index + 1,
116+
type: activity.type,
117+
message: activity.message,
118+
timestamp: activity.timestamp,
119+
status: activity.status
120+
}))
121+
122+
return NextResponse.json({ activities: topActivities })
123+
} catch (error) {
124+
console.error("Recent activities error:", error)
125+
return NextResponse.json({ error: "Internal server error" }, { status: 500 })
126+
}
127+
}
128+
129+
function getTimeAgo(dateString: string): string {
130+
const date = new Date(dateString)
131+
const now = new Date()
132+
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000)
133+
134+
if (seconds < 60) return `${seconds} seconds ago`
135+
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`
136+
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`
137+
if (seconds < 604800) return `${Math.floor(seconds / 86400)} days ago`
138+
return `${Math.floor(seconds / 604800)} weeks ago`
139+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { NextResponse } from "next/server"
2+
import { createClient } from "@/lib/supabase/server"
3+
4+
export async function GET() {
5+
try {
6+
const supabase = await createClient()
7+
8+
// Check if user is admin
9+
const { data: { user } } = await supabase.auth.getUser()
10+
11+
if (!user) {
12+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
13+
}
14+
15+
const { data: profile } = await supabase
16+
.from("profiles")
17+
.select("is_admin")
18+
.eq("id", user.id)
19+
.single()
20+
21+
if (!profile?.is_admin) {
22+
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
23+
}
24+
25+
const healthChecks = []
26+
27+
// Check Database
28+
const dbStart = Date.now()
29+
const { error: dbError } = await supabase
30+
.from("profiles")
31+
.select("id")
32+
.limit(1)
33+
const dbTime = Date.now() - dbStart
34+
35+
healthChecks.push({
36+
service: "Database",
37+
status: dbError ? "error" : dbTime < 100 ? "healthy" : "warning",
38+
uptime: dbError ? "Down" : "Operational",
39+
responseTime: `${dbTime}ms`
40+
})
41+
42+
// Check API Server (this endpoint itself)
43+
const apiTime = Date.now() - dbStart
44+
healthChecks.push({
45+
service: "API Server",
46+
status: "healthy",
47+
uptime: "Operational",
48+
responseTime: `${apiTime}ms`
49+
})
50+
51+
// Check Supabase Storage
52+
const storageStart = Date.now()
53+
const { error: storageError } = await supabase.storage
54+
.from("blog-images")
55+
.list("public", { limit: 1 })
56+
const storageTime = Date.now() - storageStart
57+
58+
healthChecks.push({
59+
service: "Storage (CDN)",
60+
status: storageError ? "error" : storageTime < 300 ? "healthy" : "warning",
61+
uptime: storageError ? "Down" : "Operational",
62+
responseTime: `${storageTime}ms`
63+
})
64+
65+
// Check Email Service (Resend)
66+
const resendConfigured = !!process.env.RESEND_API_KEY
67+
healthChecks.push({
68+
service: "Email Service",
69+
status: resendConfigured ? "healthy" : "warning",
70+
uptime: resendConfigured ? "Configured" : "Not Configured",
71+
responseTime: resendConfigured ? "Ready" : "N/A"
72+
})
73+
74+
return NextResponse.json({ health: healthChecks })
75+
} catch (error) {
76+
console.error("System health error:", error)
77+
return NextResponse.json({ error: "Internal server error" }, { status: 500 })
78+
}
79+
}

0 commit comments

Comments
 (0)