-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2d2c965
commit 1964a2c
Showing
7 changed files
with
203 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 /> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 /> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |