Added main dashboard
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
|
import { StatsCard } from "@/components/dashboard/stats-card"
|
||||||
import { getSession } from "@/lib/auth/session"
|
import { getSession } from "@/lib/auth/session"
|
||||||
|
import { getDashboardStats } from "@/lib/dashboard/stats"
|
||||||
|
import { LinkIcon, MousePointerClick, TrendingUp } from "lucide-react"
|
||||||
|
|
||||||
export default async function Dashboard() {
|
export default async function Dashboard() {
|
||||||
const { session, redirect } = await getSession()
|
const { session, redirect } = await getSession()
|
||||||
@@ -7,10 +10,42 @@ export default async function Dashboard() {
|
|||||||
redirect("/sign-in")
|
redirect("/sign-in")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stats = await getDashboardStats()
|
||||||
|
|
||||||
|
// Determine the most visited URL display value
|
||||||
|
const mostVisitedDisplay = stats.mostVisitedUrl
|
||||||
|
? `${stats.mostVisitedUrl.title} (${stats.mostVisitedUrl.visitCount})`
|
||||||
|
: "No URLs"
|
||||||
|
|
||||||
|
const mostVisitedDescription = stats.mostVisitedUrl
|
||||||
|
? `/${stats.mostVisitedUrl.slug} - ${stats.mostVisitedUrl.visitCount} visits`
|
||||||
|
: "Create your first shortened URL"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<h1 className="text-2xl font-bold text-gray-900 mb-4">Dashboard</h1>
|
<h1 className="text-2xl font-bold text-gray-900 mb-4">Dashboard</h1>
|
||||||
<p className="text-gray-600">Welcome to your dashboard! Use the sidebar to navigate to different sections.</p>
|
<p className="text-gray-600 mb-6">Welcome to your dashboard! Use the sidebar to navigate to different sections.</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
|
||||||
|
<StatsCard
|
||||||
|
title="Shortened URLs"
|
||||||
|
value={stats.totalUrls}
|
||||||
|
icon={<LinkIcon className="h-4 w-4 text-muted-foreground" />}
|
||||||
|
description="Total number of shortened links"
|
||||||
|
/>
|
||||||
|
<StatsCard
|
||||||
|
title="Total Visits"
|
||||||
|
value={stats.totalVisits}
|
||||||
|
icon={<MousePointerClick className="h-4 w-4 text-muted-foreground" />}
|
||||||
|
description="Combined visits across all links"
|
||||||
|
/>
|
||||||
|
<StatsCard
|
||||||
|
title="Most Visited URL"
|
||||||
|
value={mostVisitedDisplay}
|
||||||
|
icon={<TrendingUp className="h-4 w-4 text-muted-foreground" />}
|
||||||
|
description={mostVisitedDescription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/components/dashboard/stats-card.tsx
Normal file
25
src/components/dashboard/stats-card.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
|
||||||
|
interface StatsCardProps {
|
||||||
|
title: string
|
||||||
|
value: number | string
|
||||||
|
icon?: React.ReactNode
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatsCard({ title, value, icon, description }: StatsCardProps) {
|
||||||
|
const displayValue = typeof value === "number" ? value.toLocaleString() : value
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium">{title}</CardTitle>
|
||||||
|
{icon}
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold">{displayValue}</div>
|
||||||
|
{description && <p className="text-xs text-muted-foreground">{description}</p>}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
40
src/lib/dashboard/stats.ts
Normal file
40
src/lib/dashboard/stats.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { count, desc, eq } from "drizzle-orm"
|
||||||
|
import { db } from "../drizzle/db"
|
||||||
|
import { urls, visits } from "../drizzle/schema"
|
||||||
|
|
||||||
|
export async function getDashboardStats() {
|
||||||
|
try {
|
||||||
|
// Get count of shortened URLs
|
||||||
|
const [urlsCount] = await db.select({ count: count() }).from(urls)
|
||||||
|
|
||||||
|
// Get count of total visits
|
||||||
|
const [visitsCount] = await db.select({ count: count() }).from(visits)
|
||||||
|
|
||||||
|
// Get most visited URL
|
||||||
|
const mostVisitedUrl = await db
|
||||||
|
.select({
|
||||||
|
id: urls.id,
|
||||||
|
title: urls.title,
|
||||||
|
slug: urls.slug,
|
||||||
|
visitCount: count(visits.id)
|
||||||
|
})
|
||||||
|
.from(urls)
|
||||||
|
.leftJoin(visits, eq(urls.id, visits.urlId))
|
||||||
|
.groupBy(urls.id, urls.title, urls.slug)
|
||||||
|
.orderBy(desc(count(visits.id)))
|
||||||
|
.limit(1)
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalUrls: urlsCount.count,
|
||||||
|
totalVisits: visitsCount.count,
|
||||||
|
mostVisitedUrl: mostVisitedUrl[0] || null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch dashboard stats:", error)
|
||||||
|
return {
|
||||||
|
totalUrls: 0,
|
||||||
|
totalVisits: 0,
|
||||||
|
mostVisitedUrl: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user