Skip to content

Commit eb03e17

Browse files
authored
Update to new shadcn/ui design. (#54)
1 parent 086ca56 commit eb03e17

40 files changed

+2974
-1162
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
This is a starter template using the following stack:
1414

15-
- Framework - [Next.js 14](https://nextjs.org/)
15+
- Framework - [Next.js (App Router)](https://nextjs.org)
1616
- Language - [TypeScript](https://www.typescriptlang.org)
1717
- Auth - [Auth.js](https://authjs.dev)
1818
- Database - [Postgres](https://vercel.com/postgres)

app/(dashboard)/actions.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use server';
2+
3+
import { deleteProductById } from '@/lib/db';
4+
import { revalidatePath } from 'next/cache';
5+
6+
export async function deleteProduct(formData: FormData) {
7+
// let id = Number(formData.get('id'));
8+
// await deleteProductById(id);
9+
// revalidatePath('/');
10+
}

app/(dashboard)/customers/page.tsx

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {
2+
Card,
3+
CardContent,
4+
CardDescription,
5+
CardHeader,
6+
CardTitle
7+
} from '@/components/ui/card';
8+
9+
export default function CustomersPage() {
10+
return (
11+
<Card>
12+
<CardHeader>
13+
<CardTitle>Customers</CardTitle>
14+
<CardDescription>View all customers and their orders.</CardDescription>
15+
</CardHeader>
16+
<CardContent></CardContent>
17+
</Card>
18+
);
19+
}
File renamed without changes.

app/(dashboard)/layout.tsx

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import Link from 'next/link';
2+
import {
3+
Home,
4+
LineChart,
5+
Package,
6+
Package2,
7+
PanelLeft,
8+
Settings,
9+
ShoppingCart,
10+
Users2
11+
} from 'lucide-react';
12+
13+
import {
14+
Breadcrumb,
15+
BreadcrumbItem,
16+
BreadcrumbLink,
17+
BreadcrumbList,
18+
BreadcrumbPage,
19+
BreadcrumbSeparator
20+
} from '@/components/ui/breadcrumb';
21+
import { Button } from '@/components/ui/button';
22+
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
23+
import {
24+
Tooltip,
25+
TooltipContent,
26+
TooltipTrigger
27+
} from '@/components/ui/tooltip';
28+
import { Analytics } from '@vercel/analytics/react';
29+
import { User } from './user';
30+
import { VercelLogo } from '@/components/icons';
31+
import Providers from './providers';
32+
import { NavItem } from './nav-item';
33+
import { SearchInput } from './search';
34+
35+
export default function DashboardLayout({
36+
children
37+
}: {
38+
children: React.ReactNode;
39+
}) {
40+
return (
41+
<Providers>
42+
<main className="flex min-h-screen w-full flex-col bg-muted/40">
43+
<DesktopNav />
44+
<div className="flex flex-col sm:gap-4 sm:py-4 sm:pl-14">
45+
<header className="sticky top-0 z-30 flex h-14 items-center gap-4 border-b bg-background px-4 sm:static sm:h-auto sm:border-0 sm:bg-transparent sm:px-6">
46+
<MobileNav />
47+
<DashboardBreadcrumb />
48+
<SearchInput />
49+
<User />
50+
</header>
51+
<main className="grid flex-1 items-start gap-2 p-4 sm:px-6 sm:py-0 md:gap-4 bg-muted/40">
52+
{children}
53+
</main>
54+
</div>
55+
<Analytics />
56+
</main>
57+
</Providers>
58+
);
59+
}
60+
61+
function DesktopNav() {
62+
return (
63+
<aside className="fixed inset-y-0 left-0 z-10 hidden w-14 flex-col border-r bg-background sm:flex">
64+
<nav className="flex flex-col items-center gap-4 px-2 sm:py-5">
65+
<Link
66+
href="https://vercel.com/templates/next.js/admin-dashboard-tailwind-postgres-react-nextjs"
67+
className="group flex h-9 w-9 shrink-0 items-center justify-center gap-2 rounded-full bg-primary text-lg font-semibold text-primary-foreground md:h-8 md:w-8 md:text-base"
68+
>
69+
<VercelLogo className="h-3 w-3 transition-all group-hover:scale-110" />
70+
<span className="sr-only">Acme Inc</span>
71+
</Link>
72+
73+
<NavItem href="#" label="Dashboard">
74+
<Home className="h-5 w-5" />
75+
</NavItem>
76+
77+
<NavItem href="#" label="Orders">
78+
<ShoppingCart className="h-5 w-5" />
79+
</NavItem>
80+
81+
<NavItem href="/" label="Products">
82+
<Package className="h-5 w-5" />
83+
</NavItem>
84+
85+
<NavItem href="/customers" label="Customers">
86+
<Users2 className="h-5 w-5" />
87+
</NavItem>
88+
89+
<NavItem href="#" label="Analytics">
90+
<LineChart className="h-5 w-5" />
91+
</NavItem>
92+
</nav>
93+
<nav className="mt-auto flex flex-col items-center gap-4 px-2 sm:py-5">
94+
<Tooltip>
95+
<TooltipTrigger asChild>
96+
<Link
97+
href="#"
98+
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
99+
>
100+
<Settings className="h-5 w-5" />
101+
<span className="sr-only">Settings</span>
102+
</Link>
103+
</TooltipTrigger>
104+
<TooltipContent side="right">Settings</TooltipContent>
105+
</Tooltip>
106+
</nav>
107+
</aside>
108+
);
109+
}
110+
111+
function MobileNav() {
112+
return (
113+
<Sheet>
114+
<SheetTrigger asChild>
115+
<Button size="icon" variant="outline" className="sm:hidden">
116+
<PanelLeft className="h-5 w-5" />
117+
<span className="sr-only">Toggle Menu</span>
118+
</Button>
119+
</SheetTrigger>
120+
<SheetContent side="left" className="sm:max-w-xs">
121+
<nav className="grid gap-6 text-lg font-medium">
122+
<Link
123+
href="#"
124+
className="group flex h-10 w-10 shrink-0 items-center justify-center gap-2 rounded-full bg-primary text-lg font-semibold text-primary-foreground md:text-base"
125+
>
126+
<Package2 className="h-5 w-5 transition-all group-hover:scale-110" />
127+
<span className="sr-only">Vercel</span>
128+
</Link>
129+
<Link
130+
href="#"
131+
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
132+
>
133+
<Home className="h-5 w-5" />
134+
Dashboard
135+
</Link>
136+
<Link
137+
href="#"
138+
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
139+
>
140+
<ShoppingCart className="h-5 w-5" />
141+
Orders
142+
</Link>
143+
<Link
144+
href="#"
145+
className="flex items-center gap-4 px-2.5 text-foreground"
146+
>
147+
<Package className="h-5 w-5" />
148+
Products
149+
</Link>
150+
<Link
151+
href="#"
152+
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
153+
>
154+
<Users2 className="h-5 w-5" />
155+
Customers
156+
</Link>
157+
<Link
158+
href="#"
159+
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
160+
>
161+
<LineChart className="h-5 w-5" />
162+
Settings
163+
</Link>
164+
</nav>
165+
</SheetContent>
166+
</Sheet>
167+
);
168+
}
169+
170+
function DashboardBreadcrumb() {
171+
return (
172+
<Breadcrumb className="hidden md:flex">
173+
<BreadcrumbList>
174+
<BreadcrumbItem>
175+
<BreadcrumbLink asChild>
176+
<Link href="#">Dashboard</Link>
177+
</BreadcrumbLink>
178+
</BreadcrumbItem>
179+
<BreadcrumbSeparator />
180+
<BreadcrumbItem>
181+
<BreadcrumbLink asChild>
182+
<Link href="#">Products</Link>
183+
</BreadcrumbLink>
184+
</BreadcrumbItem>
185+
<BreadcrumbSeparator />
186+
<BreadcrumbItem>
187+
<BreadcrumbPage>All Products</BreadcrumbPage>
188+
</BreadcrumbItem>
189+
</BreadcrumbList>
190+
</Breadcrumb>
191+
);
192+
}

app/(dashboard)/nav-item.tsx

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use client';
2+
3+
import {
4+
Tooltip,
5+
TooltipContent,
6+
TooltipTrigger
7+
} from '@/components/ui/tooltip';
8+
import clsx from 'clsx';
9+
import Link from 'next/link';
10+
import { usePathname } from 'next/navigation';
11+
12+
export function NavItem({
13+
href,
14+
label,
15+
children
16+
}: {
17+
href: string;
18+
label: string;
19+
children: React.ReactNode;
20+
}) {
21+
const pathname = usePathname();
22+
23+
return (
24+
<Tooltip>
25+
<TooltipTrigger asChild>
26+
<Link
27+
href={href}
28+
className={clsx(
29+
'flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8',
30+
{
31+
'bg-accent text-black': pathname === href
32+
}
33+
)}
34+
>
35+
{children}
36+
<span className="sr-only">{label}</span>
37+
</Link>
38+
</TooltipTrigger>
39+
<TooltipContent side="right">{label}</TooltipContent>
40+
</Tooltip>
41+
);
42+
}

app/(dashboard)/page.tsx

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
2+
import { File, PlusCircle } from 'lucide-react';
3+
import { Button } from '@/components/ui/button';
4+
import { ProductsTable } from './products-table';
5+
import { getProducts } from '@/lib/db';
6+
7+
export default async function ProductsPage({
8+
searchParams
9+
}: {
10+
searchParams: { q: string; offset: string };
11+
}) {
12+
const search = searchParams.q ?? '';
13+
const offset = searchParams.offset ?? 0;
14+
const { products, newOffset, totalProducts } = await getProducts(
15+
search,
16+
Number(offset)
17+
);
18+
19+
return (
20+
<Tabs defaultValue="all">
21+
<div className="flex items-center">
22+
<TabsList>
23+
<TabsTrigger value="all">All</TabsTrigger>
24+
<TabsTrigger value="active">Active</TabsTrigger>
25+
<TabsTrigger value="draft">Draft</TabsTrigger>
26+
<TabsTrigger value="archived" className="hidden sm:flex">
27+
Archived
28+
</TabsTrigger>
29+
</TabsList>
30+
<div className="ml-auto flex items-center gap-2">
31+
<Button size="sm" variant="outline" className="h-8 gap-1">
32+
<File className="h-3.5 w-3.5" />
33+
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
34+
Export
35+
</span>
36+
</Button>
37+
<Button size="sm" className="h-8 gap-1">
38+
<PlusCircle className="h-3.5 w-3.5" />
39+
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
40+
Add Product
41+
</span>
42+
</Button>
43+
</div>
44+
</div>
45+
<TabsContent value="all">
46+
<ProductsTable
47+
products={products}
48+
offset={newOffset ?? 0}
49+
totalProducts={totalProducts}
50+
/>
51+
</TabsContent>
52+
</Tabs>
53+
);
54+
}

app/(dashboard)/product.tsx

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Image from 'next/image';
2+
import { Badge } from '@/components/ui/badge';
3+
import { Button } from '@/components/ui/button';
4+
import {
5+
DropdownMenu,
6+
DropdownMenuContent,
7+
DropdownMenuItem,
8+
DropdownMenuLabel,
9+
DropdownMenuTrigger
10+
} from '@/components/ui/dropdown-menu';
11+
import { MoreHorizontal } from 'lucide-react';
12+
import { TableCell, TableRow } from '@/components/ui/table';
13+
import { SelectProduct } from '@/lib/db';
14+
import { deleteProduct } from './actions';
15+
16+
export function Product({ product }: { product: SelectProduct }) {
17+
return (
18+
<TableRow>
19+
<TableCell className="hidden sm:table-cell">
20+
<Image
21+
alt="Product image"
22+
className="aspect-square rounded-md object-cover"
23+
height="64"
24+
src={product.imageUrl}
25+
width="64"
26+
/>
27+
</TableCell>
28+
<TableCell className="font-medium">{product.name}</TableCell>
29+
<TableCell>
30+
<Badge variant="outline" className="capitalize">
31+
{product.status}
32+
</Badge>
33+
</TableCell>
34+
<TableCell className="hidden md:table-cell">{`$${product.price}`}</TableCell>
35+
<TableCell className="hidden md:table-cell">{product.stock}</TableCell>
36+
<TableCell className="hidden md:table-cell">
37+
{product.availableAt.toLocaleDateString()}
38+
</TableCell>
39+
<TableCell>
40+
<DropdownMenu>
41+
<DropdownMenuTrigger asChild>
42+
<Button aria-haspopup="true" size="icon" variant="ghost">
43+
<MoreHorizontal className="h-4 w-4" />
44+
<span className="sr-only">Toggle menu</span>
45+
</Button>
46+
</DropdownMenuTrigger>
47+
<DropdownMenuContent align="end">
48+
<DropdownMenuLabel>Actions</DropdownMenuLabel>
49+
<DropdownMenuItem>Edit</DropdownMenuItem>
50+
<DropdownMenuItem>
51+
<form action={deleteProduct}>
52+
<button type="submit">Delete</button>
53+
</form>
54+
</DropdownMenuItem>
55+
</DropdownMenuContent>
56+
</DropdownMenu>
57+
</TableCell>
58+
</TableRow>
59+
);
60+
}

0 commit comments

Comments
 (0)