mirror of
https://github.com/shadcn-ui/taxonomy
synced 2026-05-24 01:38:28 +00:00
feat: implement new dashboard design
This commit is contained in:
parent
ae7cb2f01a
commit
f21f4b2855
11 changed files with 47 additions and 59 deletions
|
|
@ -1,10 +1,11 @@
|
|||
import { headers } from "next/headers"
|
||||
import { notFound } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
|
||||
import { getSession } from "@/lib/session"
|
||||
import { DashboardBranding } from "@/components/dashboard-branding"
|
||||
import { DashboardNav } from "@/components/dashboard-nav"
|
||||
import { UserAccountNav } from "@/components/user-account-nav"
|
||||
import { notFound } from "next/navigation"
|
||||
import { Icons } from "@/components/icons"
|
||||
|
||||
interface DashboardLayoutProps {
|
||||
children?: React.ReactNode
|
||||
|
|
@ -30,25 +31,28 @@ export default async function DashboardLayout({
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
<aside className="hidden w-14 flex-col border-r border-slate-100 bg-slate-50 py-4 md:flex lg:w-56 lg:flex-shrink-0 lg:px-4">
|
||||
<div className="flex flex-1 flex-col space-y-4">
|
||||
<DashboardBranding />
|
||||
<DashboardNav />
|
||||
</div>
|
||||
<UserAccountNav
|
||||
user={{
|
||||
name: user.name,
|
||||
image: user.image,
|
||||
email: user.email,
|
||||
}}
|
||||
/>
|
||||
<div className="mx-auto flex h-screen max-w-[1440px] flex-col space-y-6 overflow-hidden px-6">
|
||||
<header className="flex h-[64px] items-center justify-between pl-2">
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<Icons.logo />
|
||||
<span className="text-lg font-bold">Taxonomy</span>
|
||||
</Link>
|
||||
<UserAccountNav
|
||||
user={{
|
||||
name: user.name,
|
||||
image: user.image,
|
||||
email: user.email,
|
||||
}}
|
||||
/>
|
||||
</header>
|
||||
<div className="grid grid-cols-[200px_1fr] gap-12">
|
||||
<aside className="flex w-[200px] flex-col">
|
||||
<DashboardNav />
|
||||
</aside>
|
||||
<main className="flex w-0 flex-1 flex-col overflow-hidden px-12 py-10">
|
||||
<main className="flex w-full flex-1 flex-col overflow-hidden">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { headers } from "next/headers"
|
||||
import { User } from "@prisma/client"
|
||||
|
||||
import { db } from "@/lib/db"
|
||||
import { getSession } from "@/lib/session"
|
||||
|
|
@ -8,14 +9,10 @@ import { DashboardShell } from "@/components/dashboard-shell"
|
|||
import { PostItem } from "@/components/post-item"
|
||||
import { EmptyPlaceholder } from "@/components/empty-placeholder"
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
async function getPosts() {
|
||||
const session = await getSession(headers().get("cookie"))
|
||||
|
||||
async function getPostsForUser(userId: User["id"]) {
|
||||
return await db.post.findMany({
|
||||
where: {
|
||||
authorId: session?.user.id,
|
||||
authorId: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
|
@ -30,7 +27,8 @@ async function getPosts() {
|
|||
}
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const posts = await getPosts()
|
||||
const session = await getSession(headers().get("cookie"))
|
||||
const posts = await getPostsForUser(session?.user.id)
|
||||
|
||||
return (
|
||||
<DashboardShell>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { EmptyPlaceholder } from "@/components/empty-placeholder"
|
|||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<EmptyPlaceholder>
|
||||
<EmptyPlaceholder className="mx-auto max-w-[800px]">
|
||||
<EmptyPlaceholder.Icon name="warning" />
|
||||
<EmptyPlaceholder.Title>Uh oh! Not Found</EmptyPlaceholder.Title>
|
||||
<EmptyPlaceholder.Description>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import { notFound } from "next/navigation"
|
||||
import { headers } from "next/headers"
|
||||
import { Post, User } from "@prisma/client"
|
||||
|
||||
import { Editor } from "@/components/editor"
|
||||
import { db } from "@/lib/db"
|
||||
import { getSession } from "@/lib/session"
|
||||
|
||||
async function getPost(postId: string) {
|
||||
async function getPostForUser(postId: Post["id"], userId: User["id"]) {
|
||||
return await db.post.findFirst({
|
||||
where: {
|
||||
id: postId,
|
||||
authorId: userId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -16,7 +20,8 @@ interface EditorPageProps {
|
|||
}
|
||||
|
||||
export default async function EditorPage({ params }: EditorPageProps) {
|
||||
const post = await getPost(params.postId)
|
||||
const session = await getSession(headers().get("cookie"))
|
||||
const post = await getPostForUser(params.postId, session?.user.id)
|
||||
|
||||
if (!post) {
|
||||
notFound()
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import { Icons } from "./icons"
|
||||
|
||||
export function DashboardBranding() {
|
||||
return (
|
||||
<header className="flex items-center space-x-2 px-3">
|
||||
<Icons.logo />
|
||||
<span className="font-bold">Taxonomy</span>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
|
@ -12,10 +12,10 @@ export function DashboardHeader({
|
|||
return (
|
||||
<div className="flex justify-between px-2">
|
||||
<div className="grid gap-1">
|
||||
<h1 className="text-xl font-bold tracking-wide text-black">
|
||||
<h1 className="text-2xl font-bold tracking-wide text-slate-900">
|
||||
{heading}
|
||||
</h1>
|
||||
{text && <p className="text-sm text-neutral-500">{text}</p>}
|
||||
{text && <p className="text-neutral-500">{text}</p>}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export function DashboardNav() {
|
|||
const path = usePathname()
|
||||
|
||||
return (
|
||||
<nav className="grid items-start gap-1">
|
||||
<nav className="grid items-start gap-2">
|
||||
{navigationItems.map((navigationItem, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
|
|
@ -50,7 +50,7 @@ export function DashboardNav() {
|
|||
>
|
||||
<span
|
||||
className={clsx(
|
||||
"group flex items-center rounded-md px-3 py-2 text-sm font-medium text-slate-600 hover:bg-slate-100",
|
||||
"group flex items-center rounded-md px-3 py-2 text-sm font-medium text-slate-800 hover:bg-slate-100",
|
||||
path === navigationItem.href ? "bg-slate-200" : "transparent",
|
||||
navigationItem.disabled && "cursor-not-allowed opacity-50"
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import Image, { ImageProps } from "next/image"
|
||||
|
||||
type AvatarProps = AvatarPrimitive.AvatarProps
|
||||
|
||||
|
|
@ -9,7 +8,7 @@ export function Avatar({ className, ...props }: AvatarProps) {
|
|||
return (
|
||||
<AvatarPrimitive.Root
|
||||
className={cn(
|
||||
"flex h-[48px] w-[48px] items-center justify-center overflow-hidden rounded-full bg-slate-100",
|
||||
"flex h-[32px] w-[32px] items-center justify-center overflow-hidden rounded-full bg-slate-100",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ DropdownMenu.Content = React.forwardRef<
|
|||
ref={ref}
|
||||
align="end"
|
||||
className={cn(
|
||||
"overflow-hidden rounded-md bg-white shadow-md animate-in slide-in-from-top-1 md:w-32",
|
||||
"overflow-hidden rounded-md border border-slate-50 bg-white shadow-md animate-in slide-in-from-top-1 md:w-32",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
import { User } from "next-auth"
|
||||
import { signOut } from "next-auth/react"
|
||||
import Link from "next/link"
|
||||
|
||||
import { DropdownMenu } from "@/components/ui/dropdown"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { UserAvatar } from "./user-avatar"
|
||||
import Link from "next/link"
|
||||
import { UserAvatar } from "@/components/user-avatar"
|
||||
|
||||
interface UserAccountNavProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
user: Pick<User, "name" | "image" | "email">
|
||||
|
|
@ -15,20 +14,13 @@ interface UserAccountNavProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||
export function UserAccountNav({ user }: UserAccountNavProps) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger className="flex items-center gap-2 overflow-hidden rounded-md border bg-white p-2 px-2 hover:bg-slate-100 focus:ring-2 focus:ring-brand-900 focus:ring-offset-2 focus-visible:outline-none">
|
||||
<DropdownMenu.Trigger className="flex items-center gap-2 overflow-hidden focus:ring-2 focus:ring-brand-900 focus:ring-offset-2 focus-visible:outline-none">
|
||||
<UserAvatar user={{ name: user.name, image: user.image }} />
|
||||
<div className="flex flex-1 flex-col items-start">
|
||||
{user.name && <p className="text-sm font-medium">{user.name}</p>}
|
||||
<p className="rounded-md bg-brand px-2 py-[2px] text-[10px] uppercase text-white">
|
||||
Pro
|
||||
</p>
|
||||
</div>
|
||||
<Icons.ellipsis className="h-4 w-4" />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="md:w-[240px]" align="start">
|
||||
<DropdownMenu.Content className="mt-2 md:w-[240px]" align="end">
|
||||
<div className="flex items-center justify-start gap-2 p-4">
|
||||
<div className="flex flex-col leading-none">
|
||||
<div className="flex flex-col space-y-1 leading-none">
|
||||
{user.name && <p className="font-medium">{user.name}</p>}
|
||||
{user.email && (
|
||||
<p className="w-[200px] truncate text-sm text-slate-600">
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export function UserAvatar({ user, ...props }: UserAvatarProps) {
|
|||
<Avatar.Image alt="Picture" src={user.image} />
|
||||
<Avatar.Fallback>
|
||||
<span className="sr-only">{user.name}</span>
|
||||
<Icons.user className="h-6 w-6" />
|
||||
<Icons.user className="h-4 w-4" />
|
||||
</Avatar.Fallback>
|
||||
</Avatar>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue