Skip to content

Commit

Permalink
feat: add dashboard page
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelblijleven committed Sep 21, 2023
1 parent 2d2c965 commit 1964a2c
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 0 deletions.
67 changes: 67 additions & 0 deletions src/app/dashboard/components/stats-card-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {currentUser} from "@clerk/nextjs";
import {cache, ReactNode} from "react";
import {calculateMetrics} from "@/lib/dashboard";
import { StatsCard, StatsCardSkeleton } from "./stats-card";


const getMetrics = cache(calculateMetrics);

function Layout({children}: {children: ReactNode}) {
return (
<section>
<h2 className={"text-xl font-bold"}>Stats</h2>
<div className={"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 md:gap-4"}>
{children}
</div>
</section>
)
}

export async function StatsCardSection() {
const user = await currentUser();
const userId = user?.publicMetadata.databaseId;

if (!userId) {
return (
<Layout><span>No database user found</span></Layout>
)
}

const metrics = await getMetrics(userId)

return (
<Layout>
<StatsCard
title={"Coffee"}
subtitle={"Bags of coffee"}
value={metrics.beansCount}
/>
<StatsCard
title={"Roasters"}
subtitle={"Unique roasters"}
value={metrics.roasterCount}
/>
<StatsCard
title={"Brews"}
subtitle={"brews logged"}
value={"Coming soon"}
/>
<StatsCard
title={"Cafe brews"}
subtitle={"cafe brews logged"}
value={"Coming soon"}
/>
</Layout>
)
}

export function StatsCardSectionSkeleton() {
return (
<Layout>
<StatsCardSkeleton />
<StatsCardSkeleton />
<StatsCardSkeleton />
<StatsCardSkeleton />
</Layout>
)
}
38 changes: 38 additions & 0 deletions src/app/dashboard/components/stats-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
import {Skeleton} from "@/components/ui/skeleton";

type StatsCardProps = {
title: string;
subtitle: string;
value: string | number;
}

export function StatsCardSkeleton() {
return (
<Card>
<CardHeader className={"space-y-0 pb-2"}>
<CardTitle className={"text-sm font-medium"}>
<Skeleton className={"h-4 my-1 w-3/4"} />
</CardTitle>
</CardHeader>
<CardContent>
<Skeleton className={"h-6 w-1/2"} />
<Skeleton className={"h-3 mt-2"} />
</CardContent>
</Card>
)
}

export function StatsCard(props: StatsCardProps) {
return (
<Card>
<CardHeader className={"space-y-0 pb-2"}>
<CardTitle className={"text-sm font-medium"}>{props.title}</CardTitle>
</CardHeader>
<CardContent>
<div className={"text-2xl font-bold"}>{props.value}</div>
<p className={"text-xs text-muted-foreground"}>{props.subtitle}</p>
</CardContent>
</Card>
)
}
14 changes: 14 additions & 0 deletions src/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Title } from "@/components/layout/title";
import {ReactNode} from "react";

export default function Layout({children}: {children: ReactNode}) {
return (
<div className={"w-full max-w-7xl space-y-2 md:space-y-4"}>
<Title
title={"Dashboard"}
subtitle={"A quick glance at all your coffee stats"}
/>
{children}
</div>
)
}
17 changes: 17 additions & 0 deletions src/app/dashboard/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {Button} from "@/components/ui/button";
import {StatsCardSectionSkeleton} from "@/app/dashboard/components/stats-card-section";

export default function Loading() {
return (
<>
<section>
<div className={"space-x-2"}>
<Button disabled>Add coffee</Button>
<Button disabled>Add brew</Button>
<Button disabled>Add cafe brew</Button>
</div>
</section>
<StatsCardSectionSkeleton />
</>
)
}
21 changes: 21 additions & 0 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Button} from "@/components/ui/button";
import {StatsCardSection} from "@/app/dashboard/components/stats-card-section";


export default async function DashboardPage() {
return (
<>
<section>
<div className={"space-x-2"}>
<Button className={"hidden md:inline-block"} disabled>Add coffee</Button>
<Button className={"hidden md:inline-block"} disabled>Add brew</Button>
<Button className={"hidden md:inline-block"} disabled>Add cafe brew</Button>
<Button size={"sm"} className={"inline-block md:hidden"} disabled>Add coffee</Button>
<Button size={"sm"} className={"inline-block md:hidden"} disabled>Add brew</Button>
<Button size={"sm"} className={"inline-block md:hidden"} disabled>Add cafe brew</Button>
</div>
</section>
<StatsCardSection />
</>
)
}
15 changes: 15 additions & 0 deletions src/components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { cn } from "@/lib/utils"

function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
)
}

export { Skeleton }
31 changes: 31 additions & 0 deletions src/lib/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {db} from "@/db";
import {roasters, beans} from "@/db/schema";
import {eq, sql} from "drizzle-orm";

type Metrics = {
roasterCount: number;
beansCount: number;
}

export async function calculateMetrics(userId: number | unknown) {
if (!userId) return {
roasterCount: 0,
beansCount: 0,
}

const roasterCount = await db
.select({ count: sql<number>`count(*)`})
.from(roasters)
.where(eq(roasters.userId, userId as number));
const beansCount = await db
.select({count: sql<number>`count(*)`})
.from(beans)
.where(eq(beans.userId, userId as number));

const metrics: Metrics = {
roasterCount: roasterCount[0].count,
beansCount: beansCount[0].count,
}

return metrics;
}

0 comments on commit 1964a2c

Please sign in to comment.