Skip to content

Commit

Permalink
feat: scaffold ui for dashboard page
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphico committed May 18, 2024
1 parent ee7c226 commit 6f2e78d
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 14 deletions.
33 changes: 33 additions & 0 deletions src/app/(app)/dashboard/_components/workshop-actions-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Icons } from "@/components/icons"

export function WorkshopActionsDropdown() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="icon" variant="ghost" className="size-8">
<Icons.circleEllipsis />
<span className="sr-only">Create or Join a Workshop</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Icons.plus className="mr-2 size-4" aria-hidden="true" />
Create Workshop
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.video className="mr-2 size-4" aria-hidden="true" />
Join Workshop
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
39 changes: 27 additions & 12 deletions src/app/(app)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import { type Metadata } from "next"
import { redirect } from "next/navigation"
import { env } from "@/env"

import { redirects } from "@/config/constants"
import { validateRequest } from "@/lib/lucia/validate-request"
import { Button } from "@/components/ui/button"
import { EmptyShell } from "@/components/empty-shell"
import { Icons } from "@/components/icons"
import { PageHeader, PageHeaderHeading } from "@/components/page-header"
import { Shell } from "@/components/shell"

import { WorkshopActionsDropdown } from "./_components/workshop-actions-dropdown"

export const metadata: Metadata = {
metadataBase: new URL(env.NEXT_PUBLIC_APP_URL),
title: "Dashboard",
description: "Welcome to the dashboard",
}

export default async function DashboardPage() {
const { user } = await validateRequest()
export default function DashboardPage() {
return (
<Shell className="max-w-6xl">
<div className="flex items-center justify-between">
<PageHeader>
<PageHeaderHeading>Upcoming</PageHeaderHeading>
</PageHeader>

if (!user) {
return redirect(redirects.toLogin)
}
<WorkshopActionsDropdown />
</div>

return (
<div>
<h1 className="text-lg">{user.username}</h1>
</div>
<EmptyShell
title="No Upcoming Workshops"
description="Looks like you don't have any workshops scheduled yet"
icon="empty"
>
<Button size="sm">
<Icons.plus className="mr-2 size-4" aria-hidden="true" />
Create Workshop
</Button>
</EmptyShell>
</Shell>
)
}
40 changes: 40 additions & 0 deletions src/components/empty-shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { cn } from "@/lib/utils"

import { Icons } from "./icons"

interface MessageShellProps extends React.HTMLAttributes<HTMLDivElement> {
title: string
description?: string
icon?: keyof typeof Icons
}

export function EmptyShell({
title,
description,
icon = "empty",
className,
children,
...props
}: MessageShellProps) {
const Icon = Icons[icon]

return (
<div
className={cn(
"mx-auto flex h-[50vh] w-full max-w-lg flex-col items-center justify-center space-y-2 text-center",
className
)}
{...props}
>
<Icon className="size-16" aria-hidden="true" />

<h3 className="text-lg font-semibold sm:text-xl">{title}</h3>
{description && (
<p className="pb-1 text-sm text-muted-foreground sm:text-base">
{description}
</p>
)}
{children}
</div>
)
}
12 changes: 10 additions & 2 deletions src/components/icons.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import {
AlignLeft,
ChevronLeft,
CircleEllipsis,
LayoutDashboard,
LogOut,
MoveRight,
PackageOpen,
Plus,
RotateCcw,
TriangleAlert,
Video,
type LucideProps,
} from "lucide-react"

export const Icons = {
menu: AlignLeft,
video: Video,
plus: Plus,
circleEllipsis: CircleEllipsis,
empty: PackageOpen,
menu: LayoutDashboard,
logOut: LogOut,
arrowRight: MoveRight,
chevronLeft: ChevronLeft,
Expand Down
39 changes: 39 additions & 0 deletions src/components/page-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Balancer from "react-wrap-balancer"

import { cn } from "@/lib/utils"

export function PageHeader({
className,
children,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<section className={cn("grid gap-1", className)} {...props}>
{children}
</section>
)
}

export function PageHeaderHeading({
className,
...props
}: React.HTMLAttributes<HTMLHeadElement>) {
return (
<h1
className={cn("text-lg font-semibold sm:text-xl", className)}
{...props}
/>
)
}

export function PageHeaderDescription({
className,
...props
}: React.HTMLAttributes<HTMLParagraphElement>) {
return (
<Balancer
className={cn("max-w-lg text-muted-foreground", className)}
{...props}
/>
)
}
39 changes: 39 additions & 0 deletions src/components/shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Original source:
* @see https://github.com/sadmann7/skateshop/tree/main/src/components/shell.tsx
*/

import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const shellVariants = cva("grid items-center gap-8 pb-8 pt-6 lg:py-6", {
variants: {
variant: {
default: "container",
centered: "container flex h-dvh max-w-2xl flex-col justify-center",
},
},
defaultVariants: {
variant: "default",
},
})

interface ShellProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof shellVariants> {
as?: React.ElementType
}

function Shell({
className,
as: Comp = "section",
variant,
...props
}: ShellProps) {
return (
<Comp className={cn(shellVariants({ variant }), className)} {...props} />
)
}

export { Shell, shellVariants }

0 comments on commit 6f2e78d

Please sign in to comment.