Showing preview only (373K chars total). Download the full file or copy to clipboard to get everything.
Repository: shadcn-ui/taxonomy
Branch: main
Commit: 651f984e52ed
Files: 166
Total size: 337.3 KB
Directory structure:
gitextract_6s0f8trj/
├── .commitlintrc.json
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── .nvmrc
├── .prettierignore
├── LICENSE.md
├── README.md
├── app/
│ ├── (auth)/
│ │ ├── layout.tsx
│ │ ├── login/
│ │ │ └── page.tsx
│ │ └── register/
│ │ └── page.tsx
│ ├── (dashboard)/
│ │ └── dashboard/
│ │ ├── billing/
│ │ │ ├── loading.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ ├── page.tsx
│ │ └── settings/
│ │ ├── loading.tsx
│ │ └── page.tsx
│ ├── (docs)/
│ │ ├── docs/
│ │ │ ├── [[...slug]]/
│ │ │ │ └── page.tsx
│ │ │ └── layout.tsx
│ │ ├── guides/
│ │ │ ├── [...slug]/
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── (editor)/
│ │ └── editor/
│ │ ├── [postId]/
│ │ │ ├── loading.tsx
│ │ │ ├── not-found.tsx
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── (marketing)/
│ │ ├── [...slug]/
│ │ │ └── page.tsx
│ │ ├── blog/
│ │ │ ├── [...slug]/
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── pricing/
│ │ └── page.tsx
│ ├── api/
│ │ ├── auth/
│ │ │ └── [...nextauth]/
│ │ │ └── _route.ts
│ │ ├── og/
│ │ │ └── route.tsx
│ │ ├── posts/
│ │ │ ├── [postId]/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── users/
│ │ │ ├── [userId]/
│ │ │ │ └── route.ts
│ │ │ └── stripe/
│ │ │ └── route.ts
│ │ └── webhooks/
│ │ └── stripe/
│ │ └── route.ts
│ ├── layout.tsx
│ └── robots.ts
├── components/
│ ├── analytics.tsx
│ ├── billing-form.tsx
│ ├── callout.tsx
│ ├── card-skeleton.tsx
│ ├── editor.tsx
│ ├── empty-placeholder.tsx
│ ├── header.tsx
│ ├── icons.tsx
│ ├── main-nav.tsx
│ ├── mdx-card.tsx
│ ├── mdx-components.tsx
│ ├── mobile-nav.tsx
│ ├── mode-toggle.tsx
│ ├── nav.tsx
│ ├── page-header.tsx
│ ├── pager.tsx
│ ├── post-create-button.tsx
│ ├── post-item.tsx
│ ├── post-operations.tsx
│ ├── search.tsx
│ ├── shell.tsx
│ ├── sidebar-nav.tsx
│ ├── site-footer.tsx
│ ├── tailwind-indicator.tsx
│ ├── theme-provider.tsx
│ ├── toc.tsx
│ ├── ui/
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── aspect-ratio.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── calendar.tsx
│ │ ├── card.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── hover-card.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── menubar.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── radio-group.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── skeleton.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── toggle.tsx
│ │ ├── tooltip.tsx
│ │ └── use-toast.ts
│ ├── user-account-nav.tsx
│ ├── user-auth-form.tsx
│ ├── user-avatar.tsx
│ └── user-name-form.tsx
├── config/
│ ├── dashboard.ts
│ ├── docs.ts
│ ├── marketing.ts
│ ├── site.ts
│ └── subscriptions.ts
├── content/
│ ├── authors/
│ │ └── shadcn.mdx
│ ├── blog/
│ │ ├── deploying-next-apps.mdx
│ │ ├── dynamic-routing-static-regeneration.mdx
│ │ ├── preview-mode-headless-cms.mdx
│ │ └── server-client-components.mdx
│ ├── docs/
│ │ ├── documentation/
│ │ │ ├── code-blocks.mdx
│ │ │ ├── components.mdx
│ │ │ ├── index.mdx
│ │ │ └── style-guide.mdx
│ │ ├── in-progress.mdx
│ │ └── index.mdx
│ ├── guides/
│ │ ├── build-blog-using-contentlayer-mdx.mdx
│ │ └── using-next-auth-next-13.mdx
│ └── pages/
│ ├── privacy.mdx
│ └── terms.mdx
├── contentlayer.config.js
├── env.mjs
├── hooks/
│ ├── use-lock-body.ts
│ └── use-mounted.ts
├── lib/
│ ├── auth.ts
│ ├── db.ts
│ ├── exceptions.ts
│ ├── session.ts
│ ├── stripe.ts
│ ├── subscription.ts
│ ├── toc.ts
│ ├── utils.ts
│ └── validations/
│ ├── auth.ts
│ ├── og.ts
│ ├── post.ts
│ └── user.ts
├── middleware.ts
├── next.config.mjs
├── package.json
├── pages/
│ └── api/
│ └── auth/
│ └── [...nextauth].ts
├── postcss.config.js
├── prettier.config.js
├── prisma/
│ ├── migrations/
│ │ ├── 20221021182747_init/
│ │ │ └── migration.sql
│ │ ├── 20221118173244_add_stripe_columns/
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ └── schema.prisma
├── public/
│ └── site.webmanifest
├── styles/
│ ├── editor.css
│ ├── globals.css
│ └── mdx.css
├── tailwind.config.js
├── tsconfig.json
└── types/
├── index.d.ts
└── next-auth.d.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .commitlintrc.json
================================================
{
"extends": ["@commitlint/config-conventional"]
}
================================================
FILE: .editorconfig
================================================
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .eslintrc.json
================================================
{
"$schema": "https://json.schemastore.org/eslintrc",
"root": true,
"extends": [
"next/core-web-vitals",
"prettier",
"plugin:tailwindcss/recommended"
],
"plugins": ["tailwindcss"],
"rules": {
"@next/next/no-html-link-for-pages": "off",
"react/jsx-key": "off",
"tailwindcss/no-custom-classname": "off",
"tailwindcss/classnames-order": "error"
},
"settings": {
"tailwindcss": {
"callees": ["cn"],
"config": "tailwind.config.js"
},
"next": {
"rootDir": true
}
},
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser"
}
]
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.vscode
.contentlayer
================================================
FILE: .husky/commit-msg
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx commitlint --edit $1
================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx pretty-quick --staged
================================================
FILE: .nvmrc
================================================
v16.18.0
================================================
FILE: .prettierignore
================================================
dist
node_modules
.next
build
.contentlayer
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2022 shadcn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Taxonomy
An open source application built using the new router, server components and everything new in Next.js 13.
> **Warning**
> This app is a work in progress. I'm building this in public. You can follow the progress on Twitter [@shadcn](https://twitter.com/shadcn).
> See the roadmap below.
## About this project
This project as an experiment to see how a modern app (with features like authentication, subscriptions, API routes, static pages for docs ...etc) would work in Next.js 13 and server components.
**This is not a starter template.**
A few people have asked me to turn this into a starter. I think we could do that once the new features are out of beta.
## Note on Performance
> **Warning**
> This app is using the unstable releases for Next.js 13 and React 18. The new router and app dir is still in beta and not production-ready.
> **Expect some performance hits when testing the dashboard**.
> If you see something broken, you can ping me [@shadcn](https://twitter.com/shadcn).
## Features
- New `/app` dir,
- Routing, Layouts, Nested Layouts and Layout Groups
- Data Fetching, Caching and Mutation
- Loading UI
- Route handlers
- Metadata files
- Server and Client Components
- API Routes and Middlewares
- Authentication using **NextAuth.js**
- ORM using **Prisma**
- Database on **PlanetScale**
- UI Components built using **Radix UI**
- Documentation and blog using **MDX** and **Contentlayer**
- Subscriptions using **Stripe**
- Styled using **Tailwind CSS**
- Validations using **Zod**
- Written in **TypeScript**
## Roadmap
- [x] ~Add MDX support for basic pages~
- [x] ~Build marketing pages~
- [x] ~Subscriptions using Stripe~
- [x] ~Responsive styles~
- [x] ~Add OG image for blog using @vercel/og~
- [x] Dark mode
## Known Issues
A list of things not working right now:
1. ~GitHub authentication (use email)~
2. ~[Prisma: Error: ENOENT: no such file or directory, open '/var/task/.next/server/chunks/schema.prisma'](https://github.com/prisma/prisma/issues/16117)~
3. ~[Next.js 13: Client side navigation does not update head](https://github.com/vercel/next.js/issues/42414)~
4. [Cannot use opengraph-image.tsx inside catch-all routes](https://github.com/vercel/next.js/issues/48162)
## Why not tRPC, Turborepo or X?
I might add this later. For now, I want to see how far we can get using Next.js only.
If you have some suggestions, feel free to create an issue.
## Running Locally
1. Install dependencies using pnpm:
```sh
pnpm install
```
2. Copy `.env.example` to `.env.local` and update the variables.
```sh
cp .env.example .env.local
```
3. Start the development server:
```sh
pnpm dev
```
## License
Licensed under the [MIT license](https://github.com/shadcn/taxonomy/blob/main/LICENSE.md).
================================================
FILE: app/(auth)/layout.tsx
================================================
interface AuthLayoutProps {
children: React.ReactNode
}
export default function AuthLayout({ children }: AuthLayoutProps) {
return <div className="min-h-screen">{children}</div>
}
================================================
FILE: app/(auth)/login/page.tsx
================================================
import { Metadata } from "next"
import Link from "next/link"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { Icons } from "@/components/icons"
import { UserAuthForm } from "@/components/user-auth-form"
export const metadata: Metadata = {
title: "Login",
description: "Login to your account",
}
export default function LoginPage() {
return (
<div className="container flex h-screen w-screen flex-col items-center justify-center">
<Link
href="/"
className={cn(
buttonVariants({ variant: "ghost" }),
"absolute left-4 top-4 md:left-8 md:top-8"
)}
>
<>
<Icons.chevronLeft className="mr-2 h-4 w-4" />
Back
</>
</Link>
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<Icons.logo className="mx-auto h-6 w-6" />
<h1 className="text-2xl font-semibold tracking-tight">
Welcome back
</h1>
<p className="text-sm text-muted-foreground">
Enter your email to sign in to your account
</p>
</div>
<UserAuthForm />
<p className="px-8 text-center text-sm text-muted-foreground">
<Link
href="/register"
className="hover:text-brand underline underline-offset-4"
>
Don't have an account? Sign Up
</Link>
</p>
</div>
</div>
)
}
================================================
FILE: app/(auth)/register/page.tsx
================================================
import Link from "next/link"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { Icons } from "@/components/icons"
import { UserAuthForm } from "@/components/user-auth-form"
export const metadata = {
title: "Create an account",
description: "Create an account to get started.",
}
export default function RegisterPage() {
return (
<div className="container grid h-screen w-screen flex-col items-center justify-center lg:max-w-none lg:grid-cols-2 lg:px-0">
<Link
href="/login"
className={cn(
buttonVariants({ variant: "ghost" }),
"absolute right-4 top-4 md:right-8 md:top-8"
)}
>
Login
</Link>
<div className="hidden h-full bg-muted lg:block" />
<div className="lg:p-8">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<Icons.logo className="mx-auto h-6 w-6" />
<h1 className="text-2xl font-semibold tracking-tight">
Create an account
</h1>
<p className="text-sm text-muted-foreground">
Enter your email below to create your account
</p>
</div>
<UserAuthForm />
<p className="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{" "}
<Link
href="/terms"
className="hover:text-brand underline underline-offset-4"
>
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="hover:text-brand underline underline-offset-4"
>
Privacy Policy
</Link>
.
</p>
</div>
</div>
</div>
)
}
================================================
FILE: app/(dashboard)/dashboard/billing/loading.tsx
================================================
import { CardSkeleton } from "@/components/card-skeleton"
import { DashboardHeader } from "@/components/header"
import { DashboardShell } from "@/components/shell"
export default function DashboardBillingLoading() {
return (
<DashboardShell>
<DashboardHeader
heading="Billing"
text="Manage billing and your subscription plan."
/>
<div className="grid gap-10">
<CardSkeleton />
</div>
</DashboardShell>
)
}
================================================
FILE: app/(dashboard)/dashboard/billing/page.tsx
================================================
import { redirect } from "next/navigation"
import { authOptions } from "@/lib/auth"
import { getCurrentUser } from "@/lib/session"
import { stripe } from "@/lib/stripe"
import { getUserSubscriptionPlan } from "@/lib/subscription"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { BillingForm } from "@/components/billing-form"
import { DashboardHeader } from "@/components/header"
import { Icons } from "@/components/icons"
import { DashboardShell } from "@/components/shell"
export const metadata = {
title: "Billing",
description: "Manage billing and your subscription plan.",
}
export default async function BillingPage() {
const user = await getCurrentUser()
if (!user) {
redirect(authOptions?.pages?.signIn || "/login")
}
const subscriptionPlan = await getUserSubscriptionPlan(user.id)
// If user has a pro plan, check cancel status on Stripe.
let isCanceled = false
if (subscriptionPlan.isPro && subscriptionPlan.stripeSubscriptionId) {
const stripePlan = await stripe.subscriptions.retrieve(
subscriptionPlan.stripeSubscriptionId
)
isCanceled = stripePlan.cancel_at_period_end
}
return (
<DashboardShell>
<DashboardHeader
heading="Billing"
text="Manage billing and your subscription plan."
/>
<div className="grid gap-8">
<Alert className="!pl-14">
<Icons.warning />
<AlertTitle>This is a demo app.</AlertTitle>
<AlertDescription>
Taxonomy app is a demo app using a Stripe test environment. You can
find a list of test card numbers on the{" "}
<a
href="https://stripe.com/docs/testing#cards"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-8"
>
Stripe docs
</a>
.
</AlertDescription>
</Alert>
<BillingForm
subscriptionPlan={{
...subscriptionPlan,
isCanceled,
}}
/>
</div>
</DashboardShell>
)
}
================================================
FILE: app/(dashboard)/dashboard/layout.tsx
================================================
import { notFound } from "next/navigation"
import { dashboardConfig } from "@/config/dashboard"
import { getCurrentUser } from "@/lib/session"
import { MainNav } from "@/components/main-nav"
import { DashboardNav } from "@/components/nav"
import { SiteFooter } from "@/components/site-footer"
import { UserAccountNav } from "@/components/user-account-nav"
interface DashboardLayoutProps {
children?: React.ReactNode
}
export default async function DashboardLayout({
children,
}: DashboardLayoutProps) {
const user = await getCurrentUser()
if (!user) {
return notFound()
}
return (
<div className="flex min-h-screen flex-col space-y-6">
<header className="sticky top-0 z-40 border-b bg-background">
<div className="container flex h-16 items-center justify-between py-4">
<MainNav items={dashboardConfig.mainNav} />
<UserAccountNav
user={{
name: user.name,
image: user.image,
email: user.email,
}}
/>
</div>
</header>
<div className="container grid flex-1 gap-12 md:grid-cols-[200px_1fr]">
<aside className="hidden w-[200px] flex-col md:flex">
<DashboardNav items={dashboardConfig.sidebarNav} />
</aside>
<main className="flex w-full flex-1 flex-col overflow-hidden">
{children}
</main>
</div>
<SiteFooter className="border-t" />
</div>
)
}
================================================
FILE: app/(dashboard)/dashboard/loading.tsx
================================================
import { DashboardHeader } from "@/components/header"
import { PostCreateButton } from "@/components/post-create-button"
import { PostItem } from "@/components/post-item"
import { DashboardShell } from "@/components/shell"
export default function DashboardLoading() {
return (
<DashboardShell>
<DashboardHeader heading="Posts" text="Create and manage posts.">
<PostCreateButton />
</DashboardHeader>
<div className="divide-border-200 divide-y rounded-md border">
<PostItem.Skeleton />
<PostItem.Skeleton />
<PostItem.Skeleton />
<PostItem.Skeleton />
<PostItem.Skeleton />
</div>
</DashboardShell>
)
}
================================================
FILE: app/(dashboard)/dashboard/page.tsx
================================================
import { redirect } from "next/navigation"
import { authOptions } from "@/lib/auth"
import { db } from "@/lib/db"
import { getCurrentUser } from "@/lib/session"
import { EmptyPlaceholder } from "@/components/empty-placeholder"
import { DashboardHeader } from "@/components/header"
import { PostCreateButton } from "@/components/post-create-button"
import { PostItem } from "@/components/post-item"
import { DashboardShell } from "@/components/shell"
export const metadata = {
title: "Dashboard",
}
export default async function DashboardPage() {
const user = await getCurrentUser()
if (!user) {
redirect(authOptions?.pages?.signIn || "/login")
}
const posts = await db.post.findMany({
where: {
authorId: user.id,
},
select: {
id: true,
title: true,
published: true,
createdAt: true,
},
orderBy: {
updatedAt: "desc",
},
})
return (
<DashboardShell>
<DashboardHeader heading="Posts" text="Create and manage posts.">
<PostCreateButton />
</DashboardHeader>
<div>
{posts?.length ? (
<div className="divide-y divide-border rounded-md border">
{posts.map((post) => (
<PostItem key={post.id} post={post} />
))}
</div>
) : (
<EmptyPlaceholder>
<EmptyPlaceholder.Icon name="post" />
<EmptyPlaceholder.Title>No posts created</EmptyPlaceholder.Title>
<EmptyPlaceholder.Description>
You don't have any posts yet. Start creating content.
</EmptyPlaceholder.Description>
<PostCreateButton variant="outline" />
</EmptyPlaceholder>
)}
</div>
</DashboardShell>
)
}
================================================
FILE: app/(dashboard)/dashboard/settings/loading.tsx
================================================
import { Card } from "@/components/ui/card"
import { CardSkeleton } from "@/components/card-skeleton"
import { DashboardHeader } from "@/components/header"
import { DashboardShell } from "@/components/shell"
export default function DashboardSettingsLoading() {
return (
<DashboardShell>
<DashboardHeader
heading="Settings"
text="Manage account and website settings."
/>
<div className="grid gap-10">
<CardSkeleton />
</div>
</DashboardShell>
)
}
================================================
FILE: app/(dashboard)/dashboard/settings/page.tsx
================================================
import { redirect } from "next/navigation"
import { authOptions } from "@/lib/auth"
import { getCurrentUser } from "@/lib/session"
import { DashboardHeader } from "@/components/header"
import { DashboardShell } from "@/components/shell"
import { UserNameForm } from "@/components/user-name-form"
export const metadata = {
title: "Settings",
description: "Manage account and website settings.",
}
export default async function SettingsPage() {
const user = await getCurrentUser()
if (!user) {
redirect(authOptions?.pages?.signIn || "/login")
}
return (
<DashboardShell>
<DashboardHeader
heading="Settings"
text="Manage account and website settings."
/>
<div className="grid gap-10">
<UserNameForm user={{ id: user.id, name: user.name || "" }} />
</div>
</DashboardShell>
)
}
================================================
FILE: app/(docs)/docs/[[...slug]]/page.tsx
================================================
import { notFound } from "next/navigation"
import { allDocs } from "contentlayer/generated"
import { getTableOfContents } from "@/lib/toc"
import { Mdx } from "@/components/mdx-components"
import { DocsPageHeader } from "@/components/page-header"
import { DocsPager } from "@/components/pager"
import { DashboardTableOfContents } from "@/components/toc"
import "@/styles/mdx.css"
import { Metadata } from "next"
import { env } from "@/env.mjs"
import { absoluteUrl } from "@/lib/utils"
interface DocPageProps {
params: {
slug: string[]
}
}
async function getDocFromParams(params) {
const slug = params.slug?.join("/") || ""
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
if (!doc) {
null
}
return doc
}
export async function generateMetadata({
params,
}: DocPageProps): Promise<Metadata> {
const doc = await getDocFromParams(params)
if (!doc) {
return {}
}
const url = env.NEXT_PUBLIC_APP_URL
const ogUrl = new URL(`${url}/api/og`)
ogUrl.searchParams.set("heading", doc.description ?? doc.title)
ogUrl.searchParams.set("type", "Documentation")
ogUrl.searchParams.set("mode", "dark")
return {
title: doc.title,
description: doc.description,
openGraph: {
title: doc.title,
description: doc.description,
type: "article",
url: absoluteUrl(doc.slug),
images: [
{
url: ogUrl.toString(),
width: 1200,
height: 630,
alt: doc.title,
},
],
},
twitter: {
card: "summary_large_image",
title: doc.title,
description: doc.description,
images: [ogUrl.toString()],
},
}
}
export async function generateStaticParams(): Promise<
DocPageProps["params"][]
> {
return allDocs.map((doc) => ({
slug: doc.slugAsParams.split("/"),
}))
}
export default async function DocPage({ params }: DocPageProps) {
const doc = await getDocFromParams(params)
if (!doc) {
notFound()
}
const toc = await getTableOfContents(doc.body.raw)
return (
<main className="relative py-6 lg:gap-10 lg:py-10 xl:grid xl:grid-cols-[1fr_300px]">
<div className="mx-auto w-full min-w-0">
<DocsPageHeader heading={doc.title} text={doc.description} />
<Mdx code={doc.body.code} />
<hr className="my-4 md:my-6" />
<DocsPager doc={doc} />
</div>
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 max-h-[calc(var(--vh)-4rem)] overflow-y-auto pt-10">
<DashboardTableOfContents toc={toc} />
</div>
</div>
</main>
)
}
================================================
FILE: app/(docs)/docs/layout.tsx
================================================
import { docsConfig } from "@/config/docs"
import { DocsSidebarNav } from "@/components/sidebar-nav"
interface DocsLayoutProps {
children: React.ReactNode
}
export default function DocsLayout({ children }: DocsLayoutProps) {
return (
<div className="flex-1 md:grid md:grid-cols-[220px_1fr] md:gap-6 lg:grid-cols-[240px_1fr] lg:gap-10">
<aside className="fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto border-r py-6 pr-2 md:sticky md:block lg:py-10">
<DocsSidebarNav items={docsConfig.sidebarNav} />
</aside>
{children}
</div>
)
}
================================================
FILE: app/(docs)/guides/[...slug]/page.tsx
================================================
import Link from "next/link"
import { notFound } from "next/navigation"
import { allGuides } from "contentlayer/generated"
import { getTableOfContents } from "@/lib/toc"
import { Icons } from "@/components/icons"
import { Mdx } from "@/components/mdx-components"
import { DocsPageHeader } from "@/components/page-header"
import { DashboardTableOfContents } from "@/components/toc"
import "@/styles/mdx.css"
import { Metadata } from "next"
import { env } from "@/env.mjs"
import { absoluteUrl, cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
interface GuidePageProps {
params: {
slug: string[]
}
}
async function getGuideFromParams(params) {
const slug = params?.slug?.join("/")
const guide = allGuides.find((guide) => guide.slugAsParams === slug)
if (!guide) {
null
}
return guide
}
export async function generateMetadata({
params,
}: GuidePageProps): Promise<Metadata> {
const guide = await getGuideFromParams(params)
if (!guide) {
return {}
}
const url = env.NEXT_PUBLIC_APP_URL
const ogUrl = new URL(`${url}/api/og`)
ogUrl.searchParams.set("heading", guide.title)
ogUrl.searchParams.set("type", "Guide")
ogUrl.searchParams.set("mode", "dark")
return {
title: guide.title,
description: guide.description,
openGraph: {
title: guide.title,
description: guide.description,
type: "article",
url: absoluteUrl(guide.slug),
images: [
{
url: ogUrl.toString(),
width: 1200,
height: 630,
alt: guide.title,
},
],
},
twitter: {
card: "summary_large_image",
title: guide.title,
description: guide.description,
images: [ogUrl.toString()],
},
}
}
export async function generateStaticParams(): Promise<
GuidePageProps["params"][]
> {
return allGuides.map((guide) => ({
slug: guide.slugAsParams.split("/"),
}))
}
export default async function GuidePage({ params }: GuidePageProps) {
const guide = await getGuideFromParams(params)
if (!guide) {
notFound()
}
const toc = await getTableOfContents(guide.body.raw)
return (
<main className="relative py-6 lg:grid lg:grid-cols-[1fr_300px] lg:gap-10 lg:py-10 xl:gap-20">
<div>
<DocsPageHeader heading={guide.title} text={guide.description} />
<Mdx code={guide.body.code} />
<hr className="my-4" />
<div className="flex justify-center py-6 lg:py-10">
<Link
href="/guides"
className={cn(buttonVariants({ variant: "ghost" }))}
>
<Icons.chevronLeft className="mr-2 h-4 w-4" />
See all guides
</Link>
</div>
</div>
<div className="hidden text-sm lg:block">
<div className="sticky top-16 -mt-10 max-h-[calc(var(--vh)-4rem)] overflow-y-auto pt-10">
<DashboardTableOfContents toc={toc} />
</div>
</div>
</main>
)
}
================================================
FILE: app/(docs)/guides/layout.tsx
================================================
interface GuidesLayoutProps {
children: React.ReactNode
}
export default function GuidesLayout({ children }: GuidesLayoutProps) {
return <div className="mx-auto max-w-5xl">{children}</div>
}
================================================
FILE: app/(docs)/guides/page.tsx
================================================
import Link from "next/link"
import { allGuides } from "contentlayer/generated"
import { compareDesc } from "date-fns"
import { formatDate } from "@/lib/utils"
import { DocsPageHeader } from "@/components/page-header"
export const metadata = {
title: "Guides",
description:
"This section includes end-to-end guides for developing Next.js 13 apps.",
}
export default function GuidesPage() {
const guides = allGuides
.filter((guide) => guide.published)
.sort((a, b) => {
return compareDesc(new Date(a.date), new Date(b.date))
})
return (
<div className="py-6 lg:py-10">
<DocsPageHeader
heading="Guides"
text="This section includes end-to-end guides for developing Next.js 13 apps."
/>
{guides?.length ? (
<div className="grid gap-4 md:grid-cols-2 md:gap-6">
{guides.map((guide) => (
<article
key={guide._id}
className="group relative rounded-lg border p-6 shadow-md transition-shadow hover:shadow-lg"
>
{guide.featured && (
<span className="absolute right-4 top-4 rounded-full px-3 py-1 text-xs font-medium">
Featured
</span>
)}
<div className="flex flex-col justify-between space-y-4">
<div className="space-y-2">
<h2 className="text-xl font-medium tracking-tight">
{guide.title}
</h2>
{guide.description && (
<p className="text-muted-foreground">{guide.description}</p>
)}
</div>
{guide.date && (
<p className="text-sm text-muted-foreground">
{formatDate(guide.date)}
</p>
)}
</div>
<Link href={guide.slug} className="absolute inset-0">
<span className="sr-only">View</span>
</Link>
</article>
))}
</div>
) : (
<p>No guides published.</p>
)}
</div>
)
}
================================================
FILE: app/(docs)/layout.tsx
================================================
import Link from "next/link"
import { docsConfig } from "@/config/docs"
import { siteConfig } from "@/config/site"
import { Icons } from "@/components/icons"
import { MainNav } from "@/components/main-nav"
import { DocsSearch } from "@/components/search"
import { DocsSidebarNav } from "@/components/sidebar-nav"
import { SiteFooter } from "@/components/site-footer"
interface DocsLayoutProps {
children: React.ReactNode
}
export default function DocsLayout({ children }: DocsLayoutProps) {
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-40 w-full border-b bg-background">
<div className="container flex h-16 items-center space-x-4 sm:justify-between sm:space-x-0">
<MainNav items={docsConfig.mainNav}>
<DocsSidebarNav items={docsConfig.sidebarNav} />
</MainNav>
<div className="flex flex-1 items-center space-x-4 sm:justify-end">
<div className="flex-1 sm:grow-0">
<DocsSearch />
</div>
<nav className="flex space-x-4">
<Link
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
>
<Icons.gitHub className="h-7 w-7" />
<span className="sr-only">GitHub</span>
</Link>
</nav>
</div>
</div>
</header>
<div className="container flex-1">{children}</div>
<SiteFooter className="border-t" />
</div>
)
}
================================================
FILE: app/(editor)/editor/[postId]/loading.tsx
================================================
import { Skeleton } from "@/components/ui/skeleton"
export default function Loading() {
return (
<div className="grid w-full gap-10">
<div className="flex w-full items-center justify-between">
<Skeleton className="h-[38px] w-[90px]" />
<Skeleton className="h-[38px] w-[80px]" />
</div>
<div className="mx-auto w-[800px] space-y-6">
<Skeleton className="h-[50px] w-full" />
<Skeleton className="h-[20px] w-2/3" />
<Skeleton className="h-[20px] w-full" />
<Skeleton className="h-[20px] w-full" />
</div>
</div>
)
}
================================================
FILE: app/(editor)/editor/[postId]/not-found.tsx
================================================
import Link from "next/link"
import { buttonVariants } from "@/components/ui/button"
import { EmptyPlaceholder } from "@/components/empty-placeholder"
export default function NotFound() {
return (
<EmptyPlaceholder className="mx-auto max-w-[800px]">
<EmptyPlaceholder.Icon name="warning" />
<EmptyPlaceholder.Title>Uh oh! Not Found</EmptyPlaceholder.Title>
<EmptyPlaceholder.Description>
This post cound not be found. Please try again.
</EmptyPlaceholder.Description>
<Link href="/dashboard" className={buttonVariants({ variant: "ghost" })}>
Go to Dashboard
</Link>
</EmptyPlaceholder>
)
}
================================================
FILE: app/(editor)/editor/[postId]/page.tsx
================================================
import { notFound, redirect } from "next/navigation"
import { Post, User } from "@prisma/client"
import { authOptions } from "@/lib/auth"
import { db } from "@/lib/db"
import { getCurrentUser } from "@/lib/session"
import { Editor } from "@/components/editor"
async function getPostForUser(postId: Post["id"], userId: User["id"]) {
return await db.post.findFirst({
where: {
id: postId,
authorId: userId,
},
})
}
interface EditorPageProps {
params: { postId: string }
}
export default async function EditorPage({ params }: EditorPageProps) {
const user = await getCurrentUser()
if (!user) {
redirect(authOptions?.pages?.signIn || "/login")
}
const post = await getPostForUser(params.postId, user.id)
if (!post) {
notFound()
}
return (
<Editor
post={{
id: post.id,
title: post.title,
content: post.content,
published: post.published,
}}
/>
)
}
================================================
FILE: app/(editor)/editor/layout.tsx
================================================
interface EditorProps {
children?: React.ReactNode
}
export default function EditorLayout({ children }: EditorProps) {
return (
<div className="container mx-auto grid items-start gap-10 py-8">
{children}
</div>
)
}
================================================
FILE: app/(marketing)/[...slug]/page.tsx
================================================
import { notFound } from "next/navigation"
import { allPages } from "contentlayer/generated"
import { Mdx } from "@/components/mdx-components"
import "@/styles/mdx.css"
import { Metadata } from "next"
import { env } from "@/env.mjs"
import { siteConfig } from "@/config/site"
import { absoluteUrl } from "@/lib/utils"
interface PageProps {
params: {
slug: string[]
}
}
async function getPageFromParams(params) {
const slug = params?.slug?.join("/")
const page = allPages.find((page) => page.slugAsParams === slug)
if (!page) {
null
}
return page
}
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const page = await getPageFromParams(params)
if (!page) {
return {}
}
const url = env.NEXT_PUBLIC_APP_URL
const ogUrl = new URL(`${url}/api/og`)
ogUrl.searchParams.set("heading", page.title)
ogUrl.searchParams.set("type", siteConfig.name)
ogUrl.searchParams.set("mode", "light")
return {
title: page.title,
description: page.description,
openGraph: {
title: page.title,
description: page.description,
type: "article",
url: absoluteUrl(page.slug),
images: [
{
url: ogUrl.toString(),
width: 1200,
height: 630,
alt: page.title,
},
],
},
twitter: {
card: "summary_large_image",
title: page.title,
description: page.description,
images: [ogUrl.toString()],
},
}
}
export async function generateStaticParams(): Promise<PageProps["params"][]> {
return allPages.map((page) => ({
slug: page.slugAsParams.split("/"),
}))
}
export default async function PagePage({ params }: PageProps) {
const page = await getPageFromParams(params)
if (!page) {
notFound()
}
return (
<article className="container max-w-3xl py-6 lg:py-12">
<div className="space-y-4">
<h1 className="inline-block font-heading text-4xl lg:text-5xl">
{page.title}
</h1>
{page.description && (
<p className="text-xl text-muted-foreground">{page.description}</p>
)}
</div>
<hr className="my-4" />
<Mdx code={page.body.code} />
</article>
)
}
================================================
FILE: app/(marketing)/blog/[...slug]/page.tsx
================================================
import { notFound } from "next/navigation"
import { allAuthors, allPosts } from "contentlayer/generated"
import { Mdx } from "@/components/mdx-components"
import "@/styles/mdx.css"
import { Metadata } from "next"
import Image from "next/image"
import Link from "next/link"
import { env } from "@/env.mjs"
import { absoluteUrl, cn, formatDate } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { Icons } from "@/components/icons"
interface PostPageProps {
params: {
slug: string[]
}
}
async function getPostFromParams(params) {
const slug = params?.slug?.join("/")
const post = allPosts.find((post) => post.slugAsParams === slug)
if (!post) {
null
}
return post
}
export async function generateMetadata({
params,
}: PostPageProps): Promise<Metadata> {
const post = await getPostFromParams(params)
if (!post) {
return {}
}
const url = env.NEXT_PUBLIC_APP_URL
const ogUrl = new URL(`${url}/api/og`)
ogUrl.searchParams.set("heading", post.title)
ogUrl.searchParams.set("type", "Blog Post")
ogUrl.searchParams.set("mode", "dark")
return {
title: post.title,
description: post.description,
authors: post.authors.map((author) => ({
name: author,
})),
openGraph: {
title: post.title,
description: post.description,
type: "article",
url: absoluteUrl(post.slug),
images: [
{
url: ogUrl.toString(),
width: 1200,
height: 630,
alt: post.title,
},
],
},
twitter: {
card: "summary_large_image",
title: post.title,
description: post.description,
images: [ogUrl.toString()],
},
}
}
export async function generateStaticParams(): Promise<
PostPageProps["params"][]
> {
return allPosts.map((post) => ({
slug: post.slugAsParams.split("/"),
}))
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPostFromParams(params)
if (!post) {
notFound()
}
const authors = post.authors.map((author) =>
allAuthors.find(({ slug }) => slug === `/authors/${author}`)
)
return (
<article className="container relative max-w-3xl py-6 lg:py-10">
<Link
href="/blog"
className={cn(
buttonVariants({ variant: "ghost" }),
"absolute left-[-200px] top-14 hidden xl:inline-flex"
)}
>
<Icons.chevronLeft className="mr-2 h-4 w-4" />
See all posts
</Link>
<div>
{post.date && (
<time
dateTime={post.date}
className="block text-sm text-muted-foreground"
>
Published on {formatDate(post.date)}
</time>
)}
<h1 className="mt-2 inline-block font-heading text-4xl leading-tight lg:text-5xl">
{post.title}
</h1>
{authors?.length ? (
<div className="mt-4 flex space-x-4">
{authors.map((author) =>
author ? (
<Link
key={author._id}
href={`https://twitter.com/${author.twitter}`}
className="flex items-center space-x-2 text-sm"
>
<Image
src={author.avatar}
alt={author.title}
width={42}
height={42}
className="rounded-full bg-white"
/>
<div className="flex-1 text-left leading-tight">
<p className="font-medium">{author.title}</p>
<p className="text-[12px] text-muted-foreground">
@{author.twitter}
</p>
</div>
</Link>
) : null
)}
</div>
) : null}
</div>
{post.image && (
<Image
src={post.image}
alt={post.title}
width={720}
height={405}
className="my-8 rounded-md border bg-muted transition-colors"
priority
/>
)}
<Mdx code={post.body.code} />
<hr className="mt-12" />
<div className="flex justify-center py-6 lg:py-10">
<Link href="/blog" className={cn(buttonVariants({ variant: "ghost" }))}>
<Icons.chevronLeft className="mr-2 h-4 w-4" />
See all posts
</Link>
</div>
</article>
)
}
================================================
FILE: app/(marketing)/blog/page.tsx
================================================
import Image from "next/image"
import Link from "next/link"
import { allPosts } from "contentlayer/generated"
import { compareDesc } from "date-fns"
import { formatDate } from "@/lib/utils"
export const metadata = {
title: "Blog",
}
export default async function BlogPage() {
const posts = allPosts
.filter((post) => post.published)
.sort((a, b) => {
return compareDesc(new Date(a.date), new Date(b.date))
})
return (
<div className="container max-w-4xl py-6 lg:py-10">
<div className="flex flex-col items-start gap-4 md:flex-row md:justify-between md:gap-8">
<div className="flex-1 space-y-4">
<h1 className="inline-block font-heading text-4xl tracking-tight lg:text-5xl">
Blog
</h1>
<p className="text-xl text-muted-foreground">
A blog built using Contentlayer. Posts are written in MDX.
</p>
</div>
</div>
<hr className="my-8" />
{posts?.length ? (
<div className="grid gap-10 sm:grid-cols-2">
{posts.map((post, index) => (
<article
key={post._id}
className="group relative flex flex-col space-y-2"
>
{post.image && (
<Image
src={post.image}
alt={post.title}
width={804}
height={452}
className="rounded-md border bg-muted transition-colors"
priority={index <= 1}
/>
)}
<h2 className="text-2xl font-extrabold">{post.title}</h2>
{post.description && (
<p className="text-muted-foreground">{post.description}</p>
)}
{post.date && (
<p className="text-sm text-muted-foreground">
{formatDate(post.date)}
</p>
)}
<Link href={post.slug} className="absolute inset-0">
<span className="sr-only">View Article</span>
</Link>
</article>
))}
</div>
) : (
<p>No posts published.</p>
)}
</div>
)
}
================================================
FILE: app/(marketing)/layout.tsx
================================================
import Link from "next/link"
import { marketingConfig } from "@/config/marketing"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { MainNav } from "@/components/main-nav"
import { SiteFooter } from "@/components/site-footer"
interface MarketingLayoutProps {
children: React.ReactNode
}
export default async function MarketingLayout({
children,
}: MarketingLayoutProps) {
return (
<div className="flex min-h-screen flex-col">
<header className="container z-40 bg-background">
<div className="flex h-20 items-center justify-between py-6">
<MainNav items={marketingConfig.mainNav} />
<nav>
<Link
href="/login"
className={cn(
buttonVariants({ variant: "secondary", size: "sm" }),
"px-4"
)}
>
Login
</Link>
</nav>
</div>
</header>
<main className="flex-1">{children}</main>
<SiteFooter />
</div>
)
}
================================================
FILE: app/(marketing)/page.tsx
================================================
import Link from "next/link"
import { env } from "@/env.mjs"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
async function getGitHubStars(): Promise<string | null> {
try {
const response = await fetch(
"https://api.github.com/repos/shadcn/taxonomy",
{
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${env.GITHUB_ACCESS_TOKEN}`,
},
next: {
revalidate: 60,
},
}
)
if (!response?.ok) {
return null
}
const json = await response.json()
return parseInt(json["stargazers_count"]).toLocaleString()
} catch (error) {
return null
}
}
export default async function IndexPage() {
const stars = await getGitHubStars()
return (
<>
<section className="space-y-6 pb-8 pt-6 md:pb-12 md:pt-10 lg:py-32">
<div className="container flex max-w-[64rem] flex-col items-center gap-4 text-center">
<Link
href={siteConfig.links.twitter}
className="rounded-2xl bg-muted px-4 py-1.5 text-sm font-medium"
target="_blank"
>
Follow along on Twitter
</Link>
<h1 className="font-heading text-3xl sm:text-5xl md:text-6xl lg:text-7xl">
An example app built using Next.js 13 server components.
</h1>
<p className="max-w-[42rem] leading-normal text-muted-foreground sm:text-xl sm:leading-8">
I'm building a web app with Next.js 13 and open sourcing
everything. Follow along as we figure this out together.
</p>
<div className="space-x-4">
<Link href="/login" className={cn(buttonVariants({ size: "lg" }))}>
Get Started
</Link>
<Link
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
className={cn(buttonVariants({ variant: "outline", size: "lg" }))}
>
GitHub
</Link>
</div>
</div>
</section>
<section
id="features"
className="container space-y-6 bg-slate-50 py-8 dark:bg-transparent md:py-12 lg:py-24"
>
<div className="mx-auto flex max-w-[58rem] flex-col items-center space-y-4 text-center">
<h2 className="font-heading text-3xl leading-[1.1] sm:text-3xl md:text-6xl">
Features
</h2>
<p className="max-w-[85%] leading-normal text-muted-foreground sm:text-lg sm:leading-7">
This project is an experiment to see how a modern app, with features
like auth, subscriptions, API routes, and static pages would work in
Next.js 13 app dir.
</p>
</div>
<div className="mx-auto grid justify-center gap-4 sm:grid-cols-2 md:max-w-[64rem] md:grid-cols-3">
<div className="relative overflow-hidden rounded-lg border bg-background p-2">
<div className="flex h-[180px] flex-col justify-between rounded-md p-6">
<svg viewBox="0 0 24 24" className="h-12 w-12 fill-current">
<path d="M11.572 0c-.176 0-.31.001-.358.007a19.76 19.76 0 0 1-.364.033C7.443.346 4.25 2.185 2.228 5.012a11.875 11.875 0 0 0-2.119 5.243c-.096.659-.108.854-.108 1.747s.012 1.089.108 1.748c.652 4.506 3.86 8.292 8.209 9.695.779.25 1.6.422 2.534.525.363.04 1.935.04 2.299 0 1.611-.178 2.977-.577 4.323-1.264.207-.106.247-.134.219-.158-.02-.013-.9-1.193-1.955-2.62l-1.919-2.592-2.404-3.558a338.739 338.739 0 0 0-2.422-3.556c-.009-.002-.018 1.579-.023 3.51-.007 3.38-.01 3.515-.052 3.595a.426.426 0 0 1-.206.214c-.075.037-.14.044-.495.044H7.81l-.108-.068a.438.438 0 0 1-.157-.171l-.05-.106.006-4.703.007-4.705.072-.092a.645.645 0 0 1 .174-.143c.096-.047.134-.051.54-.051.478 0 .558.018.682.154.035.038 1.337 1.999 2.895 4.361a10760.433 10760.433 0 0 0 4.735 7.17l1.9 2.879.096-.063a12.317 12.317 0 0 0 2.466-2.163 11.944 11.944 0 0 0 2.824-6.134c.096-.66.108-.854.108-1.748 0-.893-.012-1.088-.108-1.747-.652-4.506-3.859-8.292-8.208-9.695a12.597 12.597 0 0 0-2.499-.523A33.119 33.119 0 0 0 11.573 0zm4.069 7.217c.347 0 .408.005.486.047a.473.473 0 0 1 .237.277c.018.06.023 1.365.018 4.304l-.006 4.218-.744-1.14-.746-1.14v-3.066c0-1.982.01-3.097.023-3.15a.478.478 0 0 1 .233-.296c.096-.05.13-.054.5-.054z" />
</svg>
<div className="space-y-2">
<h3 className="font-bold">Next.js 13</h3>
<p className="text-sm text-muted-foreground">
App dir, Routing, Layouts, Loading UI and API routes.
</p>
</div>
</div>
</div>
<div className="relative overflow-hidden rounded-lg border bg-background p-2">
<div className="flex h-[180px] flex-col justify-between rounded-md p-6">
<svg viewBox="0 0 24 24" className="h-12 w-12 fill-current">
<path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38a2.167 2.167 0 0 0-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44a23.476 23.476 0 0 0-3.107-.534A23.892 23.892 0 0 0 12.769 4.7c1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442a22.73 22.73 0 0 0-3.113.538 15.02 15.02 0 0 1-.254-1.42c-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87a25.64 25.64 0 0 1-4.412.005 26.64 26.64 0 0 1-1.183-1.86c-.372-.64-.71-1.29-1.018-1.946a25.17 25.17 0 0 1 1.013-1.954c.38-.66.773-1.286 1.18-1.868A25.245 25.245 0 0 1 12 8.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933a25.952 25.952 0 0 0-1.345-2.32zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493a23.966 23.966 0 0 0-1.1-2.98c.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98a23.142 23.142 0 0 0-1.086 2.964c-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39a25.819 25.819 0 0 0 1.341-2.338zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143a22.005 22.005 0 0 1-2.006-.386c.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295a1.185 1.185 0 0 1-.553-.132c-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" />
</svg>
<div className="space-y-2">
<h3 className="font-bold">React 18</h3>
<p className="text-sm">
Server and Client Components. Use hook.
</p>
</div>
</div>
</div>
<div className="relative overflow-hidden rounded-lg border bg-background p-2">
<div className="flex h-[180px] flex-col justify-between rounded-md p-6">
<svg viewBox="0 0 24 24" className="h-12 w-12 fill-current">
<path d="M0 12C0 5.373 5.373 0 12 0c4.873 0 9.067 2.904 10.947 7.077l-15.87 15.87a11.981 11.981 0 0 1-1.935-1.099L14.99 12H12l-8.485 8.485A11.962 11.962 0 0 1 0 12Zm12.004 12L24 12.004C23.998 18.628 18.628 23.998 12.004 24Z" />
</svg>
<div className="space-y-2">
<h3 className="font-bold">Database</h3>
<p className="text-sm text-muted-foreground">
ORM using Prisma and deployed on PlanetScale.
</p>
</div>
</div>
</div>
<div className="relative overflow-hidden rounded-lg border bg-background p-2">
<div className="flex h-[180px] flex-col justify-between rounded-md p-6">
<svg viewBox="0 0 24 24" className="h-12 w-12 fill-current">
<path d="M12.001 4.8c-3.2 0-5.2 1.6-6 4.8 1.2-1.6 2.6-2.2 4.2-1.8.913.228 1.565.89 2.288 1.624C13.666 10.618 15.027 12 18.001 12c3.2 0 5.2-1.6 6-4.8-1.2 1.6-2.6 2.2-4.2 1.8-.913-.228-1.565-.89-2.288-1.624C16.337 6.182 14.976 4.8 12.001 4.8zm-6 7.2c-3.2 0-5.2 1.6-6 4.8 1.2-1.6 2.6-2.2 4.2-1.8.913.228 1.565.89 2.288 1.624 1.177 1.194 2.538 2.576 5.512 2.576 3.2 0 5.2-1.6 6-4.8-1.2 1.6-2.6 2.2-4.2 1.8-.913-.228-1.565-.89-2.288-1.624C10.337 13.382 8.976 12 6.001 12z" />
</svg>
<div className="space-y-2">
<h3 className="font-bold">Components</h3>
<p className="text-sm text-muted-foreground">
UI components built using Radix UI and styled with Tailwind
CSS.
</p>
</div>
</div>
</div>
<div className="relative overflow-hidden rounded-lg border bg-background p-2">
<div className="flex h-[180px] flex-col justify-between rounded-md p-6">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1"
className="h-12 w-12 fill-current"
>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
</svg>
<div className="space-y-2">
<h3 className="font-bold">Authentication</h3>
<p className="text-sm text-muted-foreground">
Authentication using NextAuth.js and middlewares.
</p>
</div>
</div>
</div>
<div className="relative overflow-hidden rounded-lg border bg-background p-2">
<div className="flex h-[180px] flex-col justify-between rounded-md p-6">
<svg viewBox="0 0 24 24" className="h-12 w-12 fill-current">
<path d="M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z" />
</svg>
<div className="space-y-2">
<h3 className="font-bold">Subscriptions</h3>
<p className="text-sm text-muted-foreground">
Free and paid subscriptions using Stripe.
</p>
</div>
</div>
</div>
</div>
<div className="mx-auto text-center md:max-w-[58rem]">
<p className="leading-normal text-muted-foreground sm:text-lg sm:leading-7">
Taxonomy also includes a blog and a full-featured documentation site
built using Contentlayer and MDX.
</p>
</div>
</section>
<section id="open-source" className="container py-8 md:py-12 lg:py-24">
<div className="mx-auto flex max-w-[58rem] flex-col items-center justify-center gap-4 text-center">
<h2 className="font-heading text-3xl leading-[1.1] sm:text-3xl md:text-6xl">
Proudly Open Source
</h2>
<p className="max-w-[85%] leading-normal text-muted-foreground sm:text-lg sm:leading-7">
Taxonomy is open source and powered by open source software. <br />{" "}
The code is available on{" "}
<Link
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
className="underline underline-offset-4"
>
GitHub
</Link>
.{" "}
</p>
{stars && (
<Link
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
className="flex"
>
<div className="flex h-10 w-10 items-center justify-center space-x-2 rounded-md border border-muted bg-muted">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
className="h-5 w-5 text-foreground"
>
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path>
</svg>
</div>
<div className="flex items-center">
<div className="h-4 w-4 border-y-8 border-l-0 border-r-8 border-solid border-muted border-y-transparent"></div>
<div className="flex h-10 items-center rounded-md border border-muted bg-muted px-4 font-medium">
{stars} stars on GitHub
</div>
</div>
</Link>
)}
</div>
</section>
</>
)
}
================================================
FILE: app/(marketing)/pricing/page.tsx
================================================
import Link from "next/link"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { Icons } from "@/components/icons"
export const metadata = {
title: "Pricing",
}
export default function PricingPage() {
return (
<section className="container flex flex-col gap-6 py-8 md:max-w-[64rem] md:py-12 lg:py-24">
<div className="mx-auto flex w-full flex-col gap-4 md:max-w-[58rem]">
<h2 className="font-heading text-3xl leading-[1.1] sm:text-3xl md:text-6xl">
Simple, transparent pricing
</h2>
<p className="max-w-[85%] leading-normal text-muted-foreground sm:text-lg sm:leading-7">
Unlock all features including unlimited posts for your blog.
</p>
</div>
<div className="grid w-full items-start gap-10 rounded-lg border p-10 md:grid-cols-[1fr_200px]">
<div className="grid gap-6">
<h3 className="text-xl font-bold sm:text-2xl">
What's included in the PRO plan
</h3>
<ul className="grid gap-3 text-sm text-muted-foreground sm:grid-cols-2">
<li className="flex items-center">
<Icons.check className="mr-2 h-4 w-4" /> Unlimited Posts
</li>
<li className="flex items-center">
<Icons.check className="mr-2 h-4 w-4" /> Unlimited Users
</li>
<li className="flex items-center">
<Icons.check className="mr-2 h-4 w-4" /> Custom domain
</li>
<li className="flex items-center">
<Icons.check className="mr-2 h-4 w-4" /> Dashboard Analytics
</li>
<li className="flex items-center">
<Icons.check className="mr-2 h-4 w-4" /> Access to Discord
</li>
<li className="flex items-center">
<Icons.check className="mr-2 h-4 w-4" /> Premium Support
</li>
</ul>
</div>
<div className="flex flex-col gap-4 text-center">
<div>
<h4 className="text-7xl font-bold">$19</h4>
<p className="text-sm font-medium text-muted-foreground">
Billed Monthly
</p>
</div>
<Link href="/login" className={cn(buttonVariants({ size: "lg" }))}>
Get Started
</Link>
</div>
</div>
<div className="mx-auto flex w-full max-w-[58rem] flex-col gap-4">
<p className="max-w-[85%] leading-normal text-muted-foreground sm:leading-7">
Taxonomy is a demo app.{" "}
<strong>You can test the upgrade and won't be charged.</strong>
</p>
</div>
</section>
)
}
================================================
FILE: app/api/auth/[...nextauth]/_route.ts
================================================
import NextAuth from "next-auth"
import { authOptions } from "@/lib/auth"
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
================================================
FILE: app/api/og/route.tsx
================================================
import { ImageResponse } from "@vercel/og"
import { ogImageSchema } from "@/lib/validations/og"
export const runtime = "edge"
const interRegular = fetch(
new URL("../../../assets/fonts/Inter-Regular.ttf", import.meta.url)
).then((res) => res.arrayBuffer())
const interBold = fetch(
new URL("../../../assets/fonts/CalSans-SemiBold.ttf", import.meta.url)
).then((res) => res.arrayBuffer())
export async function GET(req: Request) {
try {
const fontRegular = await interRegular
const fontBold = await interBold
const url = new URL(req.url)
const values = ogImageSchema.parse(Object.fromEntries(url.searchParams))
const heading =
values.heading.length > 140
? `${values.heading.substring(0, 140)}...`
: values.heading
const { mode } = values
const paint = mode === "dark" ? "#fff" : "#000"
const fontSize = heading.length > 100 ? "70px" : "100px"
return new ImageResponse(
(
<div
tw="flex relative flex-col p-12 w-full h-full items-start"
style={{
color: paint,
background:
mode === "dark"
? "linear-gradient(90deg, #000 0%, #111 100%)"
: "white",
}}
>
<svg width="212" height="50" viewBox="0 0 212 50" fill="none">
<g clip-path="url(#a)" fill={paint}>
<path d="M99.715 9.784h26.128v4.823h-10.365v25.37h-5.182v-25.37h-10.58V9.784ZM56.746 9.784v4.823H35.803v7.757h16.842v4.823H35.803v7.967h20.943v4.823H30.62v-25.37h-.002V9.784h26.128ZM69.792 9.797H63.01l24.292 30.192h6.801L81.956 24.903 94.084 9.82l-6.782.01-8.742 10.856-8.768-10.89ZM76.751 31.363l-3.396-4.222L62.99 40.012h6.802l6.96-8.649Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M30.802 39.977 6.478 9.77H0v30.193h5.182V16.225l19.11 23.752h6.51Z"
/>
<path d="M127.008 39.792c-.38 0-.703-.131-.973-.394a1.267 1.267 0 0 1-.4-.959c-.004-.366.13-.681.4-.944.27-.263.593-.395.973-.395.365 0 .684.132.955.395.274.263.41.578.414.944-.004.25-.067.478-.193.682-.13.208-.295.37-.502.488a1.298 1.298 0 0 1-.674.183ZM135.853 27.073h2.296v8.847c-.003.814-.179 1.51-.523 2.094a3.477 3.477 0 0 1-1.447 1.346c-.614.311-1.334.47-2.152.47-.748 0-1.419-.135-2.016-.398a3.239 3.239 0 0 1-1.418-1.176c-.352-.519-.524-1.166-.524-1.941h2.301c.003.339.08.633.228.879.147.245.351.432.611.564.263.131.565.197.905.197.369 0 .685-.076.942-.232.256-.152.453-.38.59-.685.133-.301.203-.675.207-1.118v-8.847ZM147.598 30.533a1.67 1.67 0 0 0-.73-1.252c-.432-.301-.99-.45-1.675-.45-.481 0-.895.073-1.239.214-.345.146-.611.34-.794.585a1.423 1.423 0 0 0-.281.84c0 .264.063.492.186.683.123.193.288.356.502.487.211.135.446.246.703.336.259.09.519.166.779.228l1.197.294c.481.111.949.26 1.394.45.446.187.85.426 1.205.713.354.287.635.633.842 1.038.208.405.313.879.313 1.426 0 .737-.19 1.384-.573 1.944-.382.557-.933.993-1.657 1.308-.72.312-1.59.47-2.616.47-.99 0-1.854-.151-2.581-.456-.73-.301-1.299-.744-1.71-1.325-.41-.582-.632-1.29-.663-2.125h2.275c.032.436.172.8.411 1.094.242.29.558.505.945.65.389.142.825.215 1.306.215.502 0 .944-.076 1.327-.225.379-.149.678-.357.892-.626.218-.267.327-.582.33-.942-.003-.328-.102-.602-.292-.816-.193-.215-.459-.395-.8-.54a8.25 8.25 0 0 0-1.201-.39l-1.454-.368c-1.05-.266-1.882-.671-2.489-1.214-.611-.543-.913-1.263-.913-2.166 0-.74.203-1.391.615-1.948.407-.557.965-.99 1.671-1.298.709-.311 1.51-.463 2.401-.463.906 0 1.7.152 2.385.463.684.308 1.222.737 1.611 1.284a3.25 3.25 0 0 1 .605 1.882h-2.227Z" />
</g>
<path
d="M181.335 14.636V35h-5.528V19.727h-.119l-4.455 2.665v-4.693l5.011-3.063h5.091Zm12.136 20.642c-1.604 0-3.029-.275-4.276-.825-1.239-.557-2.214-1.322-2.923-2.297-.709-.974-1.067-2.094-1.074-3.36h5.568c.007.39.126.742.358 1.053.239.305.564.544.975.716.411.173.881.259 1.412.259.51 0 .961-.09 1.352-.269.391-.185.696-.44.915-.765.218-.325.325-.696.318-1.114a1.637 1.637 0 0 0-.378-1.094c-.252-.318-.606-.566-1.064-.745-.457-.18-.984-.269-1.581-.269h-2.068V22.75h2.068c.55 0 1.034-.09 1.452-.268.424-.18.752-.428.984-.746.239-.318.355-.683.348-1.094a1.824 1.824 0 0 0-.288-1.054 2.012 2.012 0 0 0-.835-.716c-.352-.172-.759-.258-1.223-.258-.504 0-.955.09-1.353.268a2.25 2.25 0 0 0-.924.746 1.891 1.891 0 0 0-.348 1.094h-5.29c.007-1.247.348-2.347 1.024-3.302.683-.954 1.617-1.703 2.804-2.247 1.187-.543 2.549-.815 4.087-.815 1.504 0 2.833.255 3.987.766 1.16.51 2.065 1.213 2.714 2.107.657.889.981 1.906.975 3.053.013 1.14-.378 2.075-1.174 2.804-.788.73-1.789 1.16-3.002 1.293v.159c1.644.179 2.88.683 3.708 1.511.829.822 1.237 1.856 1.223 3.102.007 1.194-.351 2.25-1.073 3.172-.716.922-1.714 1.644-2.993 2.168-1.273.524-2.741.785-4.405.785Z"
fill={paint}
/>
<rect
x="163"
y="1"
width="48"
height="48"
rx="9"
stroke={paint}
stroke-width="2"
/>
<defs>
<clipPath id="a">
<path fill={paint} d="M0 9.771h150v30.457H0z" />
</clipPath>
</defs>
</svg>
<div tw="flex flex-col flex-1 py-10">
<div
tw="flex text-xl uppercase font-bold tracking-tight"
style={{ fontFamily: "Inter", fontWeight: "normal" }}
>
{values.type}
</div>
<div
tw="flex leading-[1.1] text-[80px] font-bold"
style={{
fontFamily: "Cal Sans",
fontWeight: "bold",
marginLeft: "-3px",
fontSize,
}}
>
{heading}
</div>
</div>
<div tw="flex items-center w-full justify-between">
<div
tw="flex text-xl"
style={{ fontFamily: "Inter", fontWeight: "normal" }}
>
tx.shadcn.com
</div>
<div
tw="flex items-center text-xl"
style={{ fontFamily: "Inter", fontWeight: "normal" }}
>
<svg width="32" height="32" viewBox="0 0 48 48" fill="none">
<path
d="M30 44v-8a9.6 9.6 0 0 0-2-7c6 0 12-4 12-11 .16-2.5-.54-4.96-2-7 .56-2.3.56-4.7 0-7 0 0-2 0-6 3-5.28-1-10.72-1-16 0-4-3-6-3-6-3-.6 2.3-.6 4.7 0 7a10.806 10.806 0 0 0-2 7c0 7 6 11 12 11a9.43 9.43 0 0 0-1.7 3.3c-.34 1.2-.44 2.46-.3 3.7v8"
stroke={paint}
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M18 36c-9.02 4-10-4-14-4"
stroke={paint}
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<div tw="flex ml-2">github.com/shadcn/taxonomy</div>
</div>
</div>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: "Inter",
data: fontRegular,
weight: 400,
style: "normal",
},
{
name: "Cal Sans",
data: fontBold,
weight: 700,
style: "normal",
},
],
}
)
} catch (error) {
return new Response(`Failed to generate image`, {
status: 500,
})
}
}
================================================
FILE: app/api/posts/[postId]/route.ts
================================================
import { getServerSession } from "next-auth"
import * as z from "zod"
import { authOptions } from "@/lib/auth"
import { db } from "@/lib/db"
import { postPatchSchema } from "@/lib/validations/post"
const routeContextSchema = z.object({
params: z.object({
postId: z.string(),
}),
})
export async function DELETE(
req: Request,
context: z.infer<typeof routeContextSchema>
) {
try {
// Validate the route params.
const { params } = routeContextSchema.parse(context)
// Check if the user has access to this post.
if (!(await verifyCurrentUserHasAccessToPost(params.postId))) {
return new Response(null, { status: 403 })
}
// Delete the post.
await db.post.delete({
where: {
id: params.postId as string,
},
})
return new Response(null, { status: 204 })
} catch (error) {
if (error instanceof z.ZodError) {
return new Response(JSON.stringify(error.issues), { status: 422 })
}
return new Response(null, { status: 500 })
}
}
export async function PATCH(
req: Request,
context: z.infer<typeof routeContextSchema>
) {
try {
// Validate route params.
const { params } = routeContextSchema.parse(context)
// Check if the user has access to this post.
if (!(await verifyCurrentUserHasAccessToPost(params.postId))) {
return new Response(null, { status: 403 })
}
// Get the request body and validate it.
const json = await req.json()
const body = postPatchSchema.parse(json)
// Update the post.
// TODO: Implement sanitization for content.
await db.post.update({
where: {
id: params.postId,
},
data: {
title: body.title,
content: body.content,
},
})
return new Response(null, { status: 200 })
} catch (error) {
if (error instanceof z.ZodError) {
return new Response(JSON.stringify(error.issues), { status: 422 })
}
return new Response(null, { status: 500 })
}
}
async function verifyCurrentUserHasAccessToPost(postId: string) {
const session = await getServerSession(authOptions)
const count = await db.post.count({
where: {
id: postId,
authorId: session?.user.id,
},
})
return count > 0
}
================================================
FILE: app/api/posts/route.ts
================================================
import { getServerSession } from "next-auth/next"
import * as z from "zod"
import { authOptions } from "@/lib/auth"
import { db } from "@/lib/db"
import { RequiresProPlanError } from "@/lib/exceptions"
import { getUserSubscriptionPlan } from "@/lib/subscription"
const postCreateSchema = z.object({
title: z.string(),
content: z.string().optional(),
})
export async function GET() {
try {
const session = await getServerSession(authOptions)
if (!session) {
return new Response("Unauthorized", { status: 403 })
}
const { user } = session
const posts = await db.post.findMany({
select: {
id: true,
title: true,
published: true,
createdAt: true,
},
where: {
authorId: user.id,
},
})
return new Response(JSON.stringify(posts))
} catch (error) {
return new Response(null, { status: 500 })
}
}
export async function POST(req: Request) {
try {
const session = await getServerSession(authOptions)
if (!session) {
return new Response("Unauthorized", { status: 403 })
}
const { user } = session
const subscriptionPlan = await getUserSubscriptionPlan(user.id)
// If user is on a free plan.
// Check if user has reached limit of 3 posts.
if (!subscriptionPlan?.isPro) {
const count = await db.post.count({
where: {
authorId: user.id,
},
})
if (count >= 3) {
throw new RequiresProPlanError()
}
}
const json = await req.json()
const body = postCreateSchema.parse(json)
const post = await db.post.create({
data: {
title: body.title,
content: body.content,
authorId: session.user.id,
},
select: {
id: true,
},
})
return new Response(JSON.stringify(post))
} catch (error) {
if (error instanceof z.ZodError) {
return new Response(JSON.stringify(error.issues), { status: 422 })
}
if (error instanceof RequiresProPlanError) {
return new Response("Requires Pro Plan", { status: 402 })
}
return new Response(null, { status: 500 })
}
}
================================================
FILE: app/api/users/[userId]/route.ts
================================================
import { getServerSession } from "next-auth/next"
import { z } from "zod"
import { authOptions } from "@/lib/auth"
import { db } from "@/lib/db"
import { userNameSchema } from "@/lib/validations/user"
const routeContextSchema = z.object({
params: z.object({
userId: z.string(),
}),
})
export async function PATCH(
req: Request,
context: z.infer<typeof routeContextSchema>
) {
try {
// Validate the route context.
const { params } = routeContextSchema.parse(context)
// Ensure user is authentication and has access to this user.
const session = await getServerSession(authOptions)
if (!session?.user || params.userId !== session?.user.id) {
return new Response(null, { status: 403 })
}
// Get the request body and validate it.
const body = await req.json()
const payload = userNameSchema.parse(body)
// Update the user.
await db.user.update({
where: {
id: session.user.id,
},
data: {
name: payload.name,
},
})
return new Response(null, { status: 200 })
} catch (error) {
if (error instanceof z.ZodError) {
return new Response(JSON.stringify(error.issues), { status: 422 })
}
return new Response(null, { status: 500 })
}
}
================================================
FILE: app/api/users/stripe/route.ts
================================================
import { getServerSession } from "next-auth/next"
import { z } from "zod"
import { proPlan } from "@/config/subscriptions"
import { authOptions } from "@/lib/auth"
import { stripe } from "@/lib/stripe"
import { getUserSubscriptionPlan } from "@/lib/subscription"
import { absoluteUrl } from "@/lib/utils"
const billingUrl = absoluteUrl("/dashboard/billing")
export async function GET(req: Request) {
try {
const session = await getServerSession(authOptions)
if (!session?.user || !session?.user.email) {
return new Response(null, { status: 403 })
}
const subscriptionPlan = await getUserSubscriptionPlan(session.user.id)
// The user is on the pro plan.
// Create a portal session to manage subscription.
if (subscriptionPlan.isPro && subscriptionPlan.stripeCustomerId) {
const stripeSession = await stripe.billingPortal.sessions.create({
customer: subscriptionPlan.stripeCustomerId,
return_url: billingUrl,
})
return new Response(JSON.stringify({ url: stripeSession.url }))
}
// The user is on the free plan.
// Create a checkout session to upgrade.
const stripeSession = await stripe.checkout.sessions.create({
success_url: billingUrl,
cancel_url: billingUrl,
payment_method_types: ["card"],
mode: "subscription",
billing_address_collection: "auto",
customer_email: session.user.email,
line_items: [
{
price: proPlan.stripePriceId,
quantity: 1,
},
],
metadata: {
userId: session.user.id,
},
})
return new Response(JSON.stringify({ url: stripeSession.url }))
} catch (error) {
if (error instanceof z.ZodError) {
return new Response(JSON.stringify(error.issues), { status: 422 })
}
return new Response(null, { status: 500 })
}
}
================================================
FILE: app/api/webhooks/stripe/route.ts
================================================
import { headers } from "next/headers"
import Stripe from "stripe"
import { env } from "@/env.mjs"
import { db } from "@/lib/db"
import { stripe } from "@/lib/stripe"
export async function POST(req: Request) {
const body = await req.text()
const signature = headers().get("Stripe-Signature") as string
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
body,
signature,
env.STRIPE_WEBHOOK_SECRET
)
} catch (error) {
return new Response(`Webhook Error: ${error.message}`, { status: 400 })
}
const session = event.data.object as Stripe.Checkout.Session
if (event.type === "checkout.session.completed") {
// Retrieve the subscription details from Stripe.
const subscription = await stripe.subscriptions.retrieve(
session.subscription as string
)
// Update the user stripe into in our database.
// Since this is the initial subscription, we need to update
// the subscription id and customer id.
await db.user.update({
where: {
id: session?.metadata?.userId,
},
data: {
stripeSubscriptionId: subscription.id,
stripeCustomerId: subscription.customer as string,
stripePriceId: subscription.items.data[0].price.id,
stripeCurrentPeriodEnd: new Date(
subscription.current_period_end * 1000
),
},
})
}
if (event.type === "invoice.payment_succeeded") {
// Retrieve the subscription details from Stripe.
const subscription = await stripe.subscriptions.retrieve(
session.subscription as string
)
// Update the price id and set the new period end.
await db.user.update({
where: {
stripeSubscriptionId: subscription.id,
},
data: {
stripePriceId: subscription.items.data[0].price.id,
stripeCurrentPeriodEnd: new Date(
subscription.current_period_end * 1000
),
},
})
}
return new Response(null, { status: 200 })
}
================================================
FILE: app/layout.tsx
================================================
import { Inter as FontSans } from "next/font/google"
import localFont from "next/font/local"
import "@/styles/globals.css"
import { siteConfig } from "@/config/site"
import { absoluteUrl, cn } from "@/lib/utils"
import { Toaster } from "@/components/ui/toaster"
import { Analytics } from "@/components/analytics"
import { TailwindIndicator } from "@/components/tailwind-indicator"
import { ThemeProvider } from "@/components/theme-provider"
const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
})
// Font files can be colocated inside of `pages`
const fontHeading = localFont({
src: "../assets/fonts/CalSans-SemiBold.woff2",
variable: "--font-heading",
})
interface RootLayoutProps {
children: React.ReactNode
}
export const metadata = {
title: {
default: siteConfig.name,
template: `%s | ${siteConfig.name}`,
},
description: siteConfig.description,
keywords: [
"Next.js",
"React",
"Tailwind CSS",
"Server Components",
"Radix UI",
],
authors: [
{
name: "shadcn",
url: "https://shadcn.com",
},
],
creator: "shadcn",
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "white" },
{ media: "(prefers-color-scheme: dark)", color: "black" },
],
openGraph: {
type: "website",
locale: "en_US",
url: siteConfig.url,
title: siteConfig.name,
description: siteConfig.description,
siteName: siteConfig.name,
},
twitter: {
card: "summary_large_image",
title: siteConfig.name,
description: siteConfig.description,
images: [`${siteConfig.url}/og.jpg`],
creator: "@shadcn",
},
icons: {
icon: "/favicon.ico",
shortcut: "/favicon-16x16.png",
apple: "/apple-touch-icon.png",
},
manifest: `${siteConfig.url}/site.webmanifest`,
}
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="en" suppressHydrationWarning>
<head />
<body
className={cn(
"min-h-screen bg-background font-sans antialiased",
fontSans.variable,
fontHeading.variable
)}
>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
<Analytics />
<Toaster />
<TailwindIndicator />
</ThemeProvider>
</body>
</html>
)
}
================================================
FILE: app/robots.ts
================================================
import { MetadataRoute } from "next"
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
},
}
}
================================================
FILE: components/analytics.tsx
================================================
"use client"
import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
export function Analytics() {
return <VercelAnalytics />
}
================================================
FILE: components/billing-form.tsx
================================================
"use client"
import * as React from "react"
import { UserSubscriptionPlan } from "types"
import { cn, formatDate } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { toast } from "@/components/ui/use-toast"
import { Icons } from "@/components/icons"
interface BillingFormProps extends React.HTMLAttributes<HTMLFormElement> {
subscriptionPlan: UserSubscriptionPlan & {
isCanceled: boolean
}
}
export function BillingForm({
subscriptionPlan,
className,
...props
}: BillingFormProps) {
const [isLoading, setIsLoading] = React.useState<boolean>(false)
async function onSubmit(event) {
event.preventDefault()
setIsLoading(!isLoading)
// Get a Stripe session URL.
const response = await fetch("/api/users/stripe")
if (!response?.ok) {
return toast({
title: "Something went wrong.",
description: "Please refresh the page and try again.",
variant: "destructive",
})
}
// Redirect to the Stripe session.
// This could be a checkout page for initial upgrade.
// Or portal to manage existing subscription.
const session = await response.json()
if (session) {
window.location.href = session.url
}
}
return (
<form className={cn(className)} onSubmit={onSubmit} {...props}>
<Card>
<CardHeader>
<CardTitle>Subscription Plan</CardTitle>
<CardDescription>
You are currently on the <strong>{subscriptionPlan.name}</strong>{" "}
plan.
</CardDescription>
</CardHeader>
<CardContent>{subscriptionPlan.description}</CardContent>
<CardFooter className="flex flex-col items-start space-y-2 md:flex-row md:justify-between md:space-x-0">
<button
type="submit"
className={cn(buttonVariants())}
disabled={isLoading}
>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
{subscriptionPlan.isPro ? "Manage Subscription" : "Upgrade to PRO"}
</button>
{subscriptionPlan.isPro ? (
<p className="rounded-full text-xs font-medium">
{subscriptionPlan.isCanceled
? "Your plan will be canceled on "
: "Your plan renews on "}
{formatDate(subscriptionPlan.stripeCurrentPeriodEnd)}.
</p>
) : null}
</CardFooter>
</Card>
</form>
)
}
================================================
FILE: components/callout.tsx
================================================
import { cn } from "@/lib/utils"
interface CalloutProps {
icon?: string
children?: React.ReactNode
type?: "default" | "warning" | "danger"
}
export function Callout({
children,
icon,
type = "default",
...props
}: CalloutProps) {
return (
<div
className={cn("my-6 flex items-start rounded-md border border-l-4 p-4", {
"border-red-900 bg-red-50": type === "danger",
"border-yellow-900 bg-yellow-50": type === "warning",
})}
{...props}
>
{icon && <span className="mr-4 text-2xl">{icon}</span>}
<div>{children}</div>
</div>
)
}
================================================
FILE: components/card-skeleton.tsx
================================================
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"
export function CardSkeleton() {
return (
<Card>
<CardHeader className="gap-2">
<Skeleton className="h-5 w-1/5" />
<Skeleton className="h-4 w-4/5" />
</CardHeader>
<CardContent className="h-10" />
<CardFooter>
<Skeleton className="h-8 w-[120px]" />
</CardFooter>
</Card>
)
}
================================================
FILE: components/editor.tsx
================================================
"use client"
import * as React from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import EditorJS from "@editorjs/editorjs"
import { zodResolver } from "@hookform/resolvers/zod"
import { Post } from "@prisma/client"
import { useForm } from "react-hook-form"
import TextareaAutosize from "react-textarea-autosize"
import * as z from "zod"
import "@/styles/editor.css"
import { cn } from "@/lib/utils"
import { postPatchSchema } from "@/lib/validations/post"
import { buttonVariants } from "@/components/ui/button"
import { toast } from "@/components/ui/use-toast"
import { Icons } from "@/components/icons"
interface EditorProps {
post: Pick<Post, "id" | "title" | "content" | "published">
}
type FormData = z.infer<typeof postPatchSchema>
export function Editor({ post }: EditorProps) {
const { register, handleSubmit } = useForm<FormData>({
resolver: zodResolver(postPatchSchema),
})
const ref = React.useRef<EditorJS>()
const router = useRouter()
const [isSaving, setIsSaving] = React.useState<boolean>(false)
const [isMounted, setIsMounted] = React.useState<boolean>(false)
const initializeEditor = React.useCallback(async () => {
const EditorJS = (await import("@editorjs/editorjs")).default
const Header = (await import("@editorjs/header")).default
const Embed = (await import("@editorjs/embed")).default
const Table = (await import("@editorjs/table")).default
const List = (await import("@editorjs/list")).default
const Code = (await import("@editorjs/code")).default
const LinkTool = (await import("@editorjs/link")).default
const InlineCode = (await import("@editorjs/inline-code")).default
const body = postPatchSchema.parse(post)
if (!ref.current) {
const editor = new EditorJS({
holder: "editor",
onReady() {
ref.current = editor
},
placeholder: "Type here to write your post...",
inlineToolbar: true,
data: body.content,
tools: {
header: Header,
linkTool: LinkTool,
list: List,
code: Code,
inlineCode: InlineCode,
table: Table,
embed: Embed,
},
})
}
}, [post])
React.useEffect(() => {
if (typeof window !== "undefined") {
setIsMounted(true)
}
}, [])
React.useEffect(() => {
if (isMounted) {
initializeEditor()
return () => {
ref.current?.destroy()
ref.current = undefined
}
}
}, [isMounted, initializeEditor])
async function onSubmit(data: FormData) {
setIsSaving(true)
const blocks = await ref.current?.save()
const response = await fetch(`/api/posts/${post.id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: data.title,
content: blocks,
}),
})
setIsSaving(false)
if (!response?.ok) {
return toast({
title: "Something went wrong.",
description: "Your post was not saved. Please try again.",
variant: "destructive",
})
}
router.refresh()
return toast({
description: "Your post has been saved.",
})
}
if (!isMounted) {
return null
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid w-full gap-10">
<div className="flex w-full items-center justify-between">
<div className="flex items-center space-x-10">
<Link
href="/dashboard"
className={cn(buttonVariants({ variant: "ghost" }))}
>
<>
<Icons.chevronLeft className="mr-2 h-4 w-4" />
Back
</>
</Link>
<p className="text-sm text-muted-foreground">
{post.published ? "Published" : "Draft"}
</p>
</div>
<button type="submit" className={cn(buttonVariants())}>
{isSaving && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
<span>Save</span>
</button>
</div>
<div className="prose prose-stone mx-auto w-[800px] dark:prose-invert">
<TextareaAutosize
autoFocus
id="title"
defaultValue={post.title}
placeholder="Post title"
className="w-full resize-none appearance-none overflow-hidden bg-transparent text-5xl font-bold focus:outline-none"
{...register("title")}
/>
<div id="editor" className="min-h-[500px]" />
<p className="text-sm text-gray-500">
Use{" "}
<kbd className="rounded-md border bg-muted px-1 text-xs uppercase">
Tab
</kbd>{" "}
to open the command menu.
</p>
</div>
</div>
</form>
)
}
================================================
FILE: components/empty-placeholder.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
interface EmptyPlaceholderProps extends React.HTMLAttributes<HTMLDivElement> {}
export function EmptyPlaceholder({
className,
children,
...props
}: EmptyPlaceholderProps) {
return (
<div
className={cn(
"flex min-h-[400px] flex-col items-center justify-center rounded-md border border-dashed p-8 text-center animate-in fade-in-50",
className
)}
{...props}
>
<div className="mx-auto flex max-w-[420px] flex-col items-center justify-center text-center">
{children}
</div>
</div>
)
}
interface EmptyPlaceholderIconProps
extends Partial<React.SVGProps<SVGSVGElement>> {
name: keyof typeof Icons
}
EmptyPlaceholder.Icon = function EmptyPlaceHolderIcon({
name,
className,
...props
}: EmptyPlaceholderIconProps) {
const Icon = Icons[name]
if (!Icon) {
return null
}
return (
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-muted">
<Icon className={cn("h-10 w-10", className)} {...props} />
</div>
)
}
interface EmptyPlacholderTitleProps
extends React.HTMLAttributes<HTMLHeadingElement> {}
EmptyPlaceholder.Title = function EmptyPlaceholderTitle({
className,
...props
}: EmptyPlacholderTitleProps) {
return (
<h2 className={cn("mt-6 text-xl font-semibold", className)} {...props} />
)
}
interface EmptyPlacholderDescriptionProps
extends React.HTMLAttributes<HTMLParagraphElement> {}
EmptyPlaceholder.Description = function EmptyPlaceholderDescription({
className,
...props
}: EmptyPlacholderDescriptionProps) {
return (
<p
className={cn(
"mb-8 mt-2 text-center text-sm font-normal leading-6 text-muted-foreground",
className
)}
{...props}
/>
)
}
================================================
FILE: components/header.tsx
================================================
interface DashboardHeaderProps {
heading: string
text?: string
children?: React.ReactNode
}
export function DashboardHeader({
heading,
text,
children,
}: DashboardHeaderProps) {
return (
<div className="flex items-center justify-between px-2">
<div className="grid gap-1">
<h1 className="font-heading text-3xl md:text-4xl">{heading}</h1>
{text && <p className="text-lg text-muted-foreground">{text}</p>}
</div>
{children}
</div>
)
}
================================================
FILE: components/icons.tsx
================================================
import {
AlertTriangle,
ArrowRight,
Check,
ChevronLeft,
ChevronRight,
Command,
CreditCard,
File,
FileText,
HelpCircle,
Image,
Laptop,
Loader2,
LucideProps,
Moon,
MoreVertical,
Pizza,
Plus,
Settings,
SunMedium,
Trash,
Twitter,
User,
X,
type Icon as LucideIcon,
} from "lucide-react"
export type Icon = LucideIcon
export const Icons = {
logo: Command,
close: X,
spinner: Loader2,
chevronLeft: ChevronLeft,
chevronRight: ChevronRight,
trash: Trash,
post: FileText,
page: File,
media: Image,
settings: Settings,
billing: CreditCard,
ellipsis: MoreVertical,
add: Plus,
warning: AlertTriangle,
user: User,
arrowRight: ArrowRight,
help: HelpCircle,
pizza: Pizza,
sun: SunMedium,
moon: Moon,
laptop: Laptop,
gitHub: ({ ...props }: LucideProps) => (
<svg
aria-hidden="true"
focusable="false"
data-prefix="fab"
data-icon="github"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512"
{...props}
>
<path
fill="currentColor"
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
></path>
</svg>
),
twitter: Twitter,
check: Check,
}
================================================
FILE: components/main-nav.tsx
================================================
"use client"
import * as React from "react"
import Link from "next/link"
import { useSelectedLayoutSegment } from "next/navigation"
import { MainNavItem } from "types"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { MobileNav } from "@/components/mobile-nav"
interface MainNavProps {
items?: MainNavItem[]
children?: React.ReactNode
}
export function MainNav({ items, children }: MainNavProps) {
const segment = useSelectedLayoutSegment()
const [showMobileMenu, setShowMobileMenu] = React.useState<boolean>(false)
return (
<div className="flex gap-6 md:gap-10">
<Link href="/" className="hidden items-center space-x-2 md:flex">
<Icons.logo />
<span className="hidden font-bold sm:inline-block">
{siteConfig.name}
</span>
</Link>
{items?.length ? (
<nav className="hidden gap-6 md:flex">
{items?.map((item, index) => (
<Link
key={index}
href={item.disabled ? "#" : item.href}
className={cn(
"flex items-center text-lg font-medium transition-colors hover:text-foreground/80 sm:text-sm",
item.href.startsWith(`/${segment}`)
? "text-foreground"
: "text-foreground/60",
item.disabled && "cursor-not-allowed opacity-80"
)}
>
{item.title}
</Link>
))}
</nav>
) : null}
<button
className="flex items-center space-x-2 md:hidden"
onClick={() => setShowMobileMenu(!showMobileMenu)}
>
{showMobileMenu ? <Icons.close /> : <Icons.logo />}
<span className="font-bold">Menu</span>
</button>
{showMobileMenu && items && (
<MobileNav items={items}>{children}</MobileNav>
)}
</div>
)
}
================================================
FILE: components/mdx-card.tsx
================================================
import Link from "next/link"
import { cn } from "@/lib/utils"
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
href?: string
disabled?: boolean
}
export function MdxCard({
href,
className,
children,
disabled,
...props
}: CardProps) {
return (
<div
className={cn(
"group relative rounded-lg border p-6 shadow-md transition-shadow hover:shadow-lg",
disabled && "cursor-not-allowed opacity-60",
className
)}
{...props}
>
<div className="flex flex-col justify-between space-y-4">
<div className="space-y-2 [&>h3]:!mt-0 [&>h4]:!mt-0 [&>p]:text-muted-foreground">
{children}
</div>
</div>
{href && (
<Link href={disabled ? "#" : href} className="absolute inset-0">
<span className="sr-only">View</span>
</Link>
)}
</div>
)
}
================================================
FILE: components/mdx-components.tsx
================================================
import * as React from "react"
import Image from "next/image"
import { useMDXComponent } from "next-contentlayer/hooks"
import { cn } from "@/lib/utils"
import { Callout } from "@/components/callout"
import { MdxCard } from "@/components/mdx-card"
const components = {
h1: ({ className, ...props }) => (
<h1
className={cn(
"mt-2 scroll-m-20 text-4xl font-bold tracking-tight",
className
)}
{...props}
/>
),
h2: ({ className, ...props }) => (
<h2
className={cn(
"mt-10 scroll-m-20 border-b pb-1 text-3xl font-semibold tracking-tight first:mt-0",
className
)}
{...props}
/>
),
h3: ({ className, ...props }) => (
<h3
className={cn(
"mt-8 scroll-m-20 text-2xl font-semibold tracking-tight",
className
)}
{...props}
/>
),
h4: ({ className, ...props }) => (
<h4
className={cn(
"mt-8 scroll-m-20 text-xl font-semibold tracking-tight",
className
)}
{...props}
/>
),
h5: ({ className, ...props }) => (
<h5
className={cn(
"mt-8 scroll-m-20 text-lg font-semibold tracking-tight",
className
)}
{...props}
/>
),
h6: ({ className, ...props }) => (
<h6
className={cn(
"mt-8 scroll-m-20 text-base font-semibold tracking-tight",
className
)}
{...props}
/>
),
a: ({ className, ...props }) => (
<a
className={cn("font-medium underline underline-offset-4", className)}
{...props}
/>
),
p: ({ className, ...props }) => (
<p
className={cn("leading-7 [&:not(:first-child)]:mt-6", className)}
{...props}
/>
),
ul: ({ className, ...props }) => (
<ul className={cn("my-6 ml-6 list-disc", className)} {...props} />
),
ol: ({ className, ...props }) => (
<ol className={cn("my-6 ml-6 list-decimal", className)} {...props} />
),
li: ({ className, ...props }) => (
<li className={cn("mt-2", className)} {...props} />
),
blockquote: ({ className, ...props }) => (
<blockquote
className={cn(
"mt-6 border-l-2 pl-6 italic [&>*]:text-muted-foreground",
className
)}
{...props}
/>
),
img: ({
className,
alt,
...props
}: React.ImgHTMLAttributes<HTMLImageElement>) => (
// eslint-disable-next-line @next/next/no-img-element
<img className={cn("rounded-md border", className)} alt={alt} {...props} />
),
hr: ({ ...props }) => <hr className="my-4 md:my-8" {...props} />,
table: ({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) => (
<div className="my-6 w-full overflow-y-auto">
<table className={cn("w-full", className)} {...props} />
</div>
),
tr: ({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>) => (
<tr
className={cn("m-0 border-t p-0 even:bg-muted", className)}
{...props}
/>
),
th: ({ className, ...props }) => (
<th
className={cn(
"border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",
className
)}
{...props}
/>
),
td: ({ className, ...props }) => (
<td
className={cn(
"border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",
className
)}
{...props}
/>
),
pre: ({ className, ...props }) => (
<pre
className={cn(
"mb-4 mt-6 overflow-x-auto rounded-lg border bg-black py-4",
className
)}
{...props}
/>
),
code: ({ className, ...props }) => (
<code
className={cn(
"relative rounded border px-[0.3rem] py-[0.2rem] font-mono text-sm",
className
)}
{...props}
/>
),
Image,
Callout,
Card: MdxCard,
}
interface MdxProps {
code: string
}
export function Mdx({ code }: MdxProps) {
const Component = useMDXComponent(code)
return (
<div className="mdx">
<Component components={components} />
</div>
)
}
================================================
FILE: components/mobile-nav.tsx
================================================
import * as React from "react"
import Link from "next/link"
import { MainNavItem } from "types"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { useLockBody } from "@/hooks/use-lock-body"
import { Icons } from "@/components/icons"
interface MobileNavProps {
items: MainNavItem[]
children?: React.ReactNode
}
export function MobileNav({ items, children }: MobileNavProps) {
useLockBody()
return (
<div
className={cn(
"fixed inset-0 top-16 z-50 grid h-[calc(100vh-4rem)] grid-flow-row auto-rows-max overflow-auto p-6 pb-32 shadow-md animate-in slide-in-from-bottom-80 md:hidden"
)}
>
<div className="relative z-20 grid gap-6 rounded-md bg-popover p-4 text-popover-foreground shadow-md">
<Link href="/" className="flex items-center space-x-2">
<Icons.logo />
<span className="font-bold">{siteConfig.name}</span>
</Link>
<nav className="grid grid-flow-row auto-rows-max text-sm">
{items.map((item, index) => (
<Link
key={index}
href={item.disabled ? "#" : item.href}
className={cn(
"flex w-full items-center rounded-md p-2 text-sm font-medium hover:underline",
item.disabled && "cursor-not-allowed opacity-60"
)}
>
{item.title}
</Link>
))}
</nav>
{children}
</div>
</div>
)
}
================================================
FILE: components/mode-toggle.tsx
================================================
"use client"
import * as React from "react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Icons } from "@/components/icons"
export function ModeToggle() {
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 w-8 px-0">
<Icons.sun className="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Icons.moon className="absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
<Icons.sun className="mr-2 h-4 w-4" />
<span>Light</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
<Icons.moon className="mr-2 h-4 w-4" />
<span>Dark</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
<Icons.laptop className="mr-2 h-4 w-4" />
<span>System</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
================================================
FILE: components/nav.tsx
================================================
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { SidebarNavItem } from "types"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
interface DashboardNavProps {
items: SidebarNavItem[]
}
export function DashboardNav({ items }: DashboardNavProps) {
const path = usePathname()
if (!items?.length) {
return null
}
return (
<nav className="grid items-start gap-2">
{items.map((item, index) => {
const Icon = Icons[item.icon || "arrowRight"]
return (
item.href && (
<Link key={index} href={item.disabled ? "/" : item.href}>
<span
className={cn(
"group flex items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground",
path === item.href ? "bg-accent" : "transparent",
item.disabled && "cursor-not-allowed opacity-80"
)}
>
<Icon className="mr-2 h-4 w-4" />
<span>{item.title}</span>
</span>
</Link>
)
)
})}
</nav>
)
}
================================================
FILE: components/page-header.tsx
================================================
import { cn } from "@/lib/utils"
interface DocsPageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
heading: string
text?: string
}
export function DocsPageHeader({
heading,
text,
className,
...props
}: DocsPageHeaderProps) {
return (
<>
<div className={cn("space-y-4", className)} {...props}>
<h1 className="inline-block font-heading text-4xl lg:text-5xl">
{heading}
</h1>
{text && <p className="text-xl text-muted-foreground">{text}</p>}
</div>
<hr className="my-4" />
</>
)
}
================================================
FILE: components/pager.tsx
================================================
import Link from "next/link"
import { Doc } from "contentlayer/generated"
import { docsConfig } from "@/config/docs"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { Icons } from "@/components/icons"
interface DocsPagerProps {
doc: Doc
}
export function DocsPager({ doc }: DocsPagerProps) {
const pager = getPagerForDoc(doc)
if (!pager) {
return null
}
return (
<div className="flex flex-row items-center justify-between">
{pager?.prev && (
<Link
href={pager.prev.href}
className={cn(buttonVariants({ variant: "ghost" }))}
>
<Icons.chevronLeft className="mr-2 h-4 w-4" />
{pager.prev.title}
</Link>
)}
{pager?.next && (
<Link
href={pager.next.href}
className={cn(buttonVariants({ variant: "ghost" }), "ml-auto")}
>
{pager.next.title}
<Icons.chevronRight className="ml-2 h-4 w-4" />
</Link>
)}
</div>
)
}
export function getPagerForDoc(doc: Doc) {
const flattenedLinks = [null, ...flatten(docsConfig.sidebarNav), null]
const activeIndex = flattenedLinks.findIndex(
(link) => doc.slug === link?.href
)
const prev = activeIndex !== 0 ? flattenedLinks[activeIndex - 1] : null
const next =
activeIndex !== flattenedLinks.length - 1
? flattenedLinks[activeIndex + 1]
: null
return {
prev,
next,
}
}
export function flatten(links: { items? }[]) {
return links.reduce((flat, link) => {
return flat.concat(link.items ? flatten(link.items) : link)
}, [])
}
================================================
FILE: components/post-create-button.tsx
================================================
"use client"
import * as React from "react"
import { useRouter } from "next/navigation"
import { cn } from "@/lib/utils"
import { ButtonProps, buttonVariants } from "@/components/ui/button"
import { toast } from "@/components/ui/use-toast"
import { Icons } from "@/components/icons"
interface PostCreateButtonProps extends ButtonProps {}
export function PostCreateButton({
className,
variant,
...props
}: PostCreateButtonProps) {
const router = useRouter()
const [isLoading, setIsLoading] = React.useState<boolean>(false)
async function onClick() {
setIsLoading(true)
const response = await fetch("/api/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "Untitled Post",
}),
})
setIsLoading(false)
if (!response?.ok) {
if (response.status === 402) {
return toast({
title: "Limit of 3 posts reached.",
description: "Please upgrade to the PRO plan.",
variant: "destructive",
})
}
return toast({
title: "Something went wrong.",
description: "Your post was not created. Please try again.",
variant: "destructive",
})
}
const post = await response.json()
// This forces a cache invalidation.
router.refresh()
router.push(`/editor/${post.id}`)
}
return (
<button
onClick={onClick}
className={cn(
buttonVariants({ variant }),
{
"cursor-not-allowed opacity-60": isLoading,
},
className
)}
disabled={isLoading}
{...props}
>
{isLoading ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : (
<Icons.add className="mr-2 h-4 w-4" />
)}
New post
</button>
)
}
================================================
FILE: components/post-item.tsx
================================================
import Link from "next/link"
import { Post } from "@prisma/client"
import { formatDate } from "@/lib/utils"
import { Skeleton } from "@/components/ui/skeleton"
import { PostOperations } from "@/components/post-operations"
interface PostItemProps {
post: Pick<Post, "id" | "title" | "published" | "createdAt">
}
export function PostItem({ post }: PostItemProps) {
return (
<div className="flex items-center justify-between p-4">
<div className="grid gap-1">
<Link
href={`/editor/${post.id}`}
className="font-semibold hover:underline"
>
{post.title}
</Link>
<div>
<p className="text-sm text-muted-foreground">
{formatDate(post.createdAt?.toDateString())}
</p>
</div>
</div>
<PostOperations post={{ id: post.id, title: post.title }} />
</div>
)
}
PostItem.Skeleton = function PostItemSkeleton() {
return (
<div className="p-4">
<div className="space-y-3">
<Skeleton className="h-5 w-2/5" />
<Skeleton className="h-4 w-4/5" />
</div>
</div>
)
}
================================================
FILE: components/post-operations.tsx
================================================
"use client"
import * as React from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { Post } from "@prisma/client"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { toast } from "@/components/ui/use-toast"
import { Icons } from "@/components/icons"
async function deletePost(postId: string) {
const response = await fetch(`/api/posts/${postId}`, {
method: "DELETE",
})
if (!response?.ok) {
toast({
title: "Something went wrong.",
description: "Your post was not deleted. Please try again.",
variant: "destructive",
})
}
return true
}
interface PostOperationsProps {
post: Pick<Post, "id" | "title">
}
export function PostOperations({ post }: PostOperationsProps) {
const router = useRouter()
const [showDeleteAlert, setShowDeleteAlert] = React.useState<boolean>(false)
const [isDeleteLoading, setIsDeleteLoading] = React.useState<boolean>(false)
return (
<>
<DropdownMenu>
<DropdownMenuTrigger className="flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted">
<Icons.ellipsis className="h-4 w-4" />
<span className="sr-only">Open</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Link href={`/editor/${post.id}`} className="flex w-full">
Edit
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="flex cursor-pointer items-center text-destructive focus:text-destructive"
onSelect={() => setShowDeleteAlert(true)}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AlertDialog open={showDeleteAlert} onOpenChange={setShowDeleteAlert}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want to delete this post?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async (event) => {
event.preventDefault()
setIsDeleteLoading(true)
const deleted = await deletePost(post.id)
if (deleted) {
setIsDeleteLoading(false)
setShowDeleteAlert(false)
router.refresh()
}
}}
className="bg-red-600 focus:ring-red-600"
>
{isDeleteLoading ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : (
<Icons.trash className="mr-2 h-4 w-4" />
)}
<span>Delete</span>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}
================================================
FILE: components/search.tsx
================================================
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Input } from "@/components/ui/input"
import { toast } from "@/components/ui/use-toast"
interface DocsSearchProps extends React.HTMLAttributes<HTMLFormElement> {}
export function DocsSearch({ className, ...props }: DocsSearchProps) {
function onSubmit(event: React.SyntheticEvent) {
event.preventDefault()
return toast({
title: "Not implemented",
description: "We're still working on the search.",
})
}
return (
<form
onSubmit={onSubmit}
className={cn("relative w-full", className)}
{...props}
>
<Input
type="search"
placeholder="Search documentation..."
className="h-8 w-full sm:w-64 sm:pr-12"
/>
<kbd className="pointer-events-none absolute right-1.5 top-1.5 hidden h-5 select-none items-center gap-1 rounded border bg-background px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100 sm:flex">
<span className="text-xs">⌘</span>K
</kbd>
</form>
)
}
================================================
FILE: components/shell.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
interface DashboardShellProps extends React.HTMLAttributes<HTMLDivElement> {}
export function DashboardShell({
children,
className,
...props
}: DashboardShellProps) {
return (
<div className={cn("grid items-start gap-8", className)} {...props}>
{children}
</div>
)
}
================================================
FILE: components/sidebar-nav.tsx
================================================
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { SidebarNavItem } from "types"
import { cn } from "@/lib/utils"
export interface DocsSidebarNavProps {
items: SidebarNavItem[]
}
export function DocsSidebarNav({ items }: DocsSidebarNavProps) {
const pathname = usePathname()
return items.length ? (
<div className="w-full">
{items.map((item, index) => (
<div key={index} className={cn("pb-8")}>
<h4 className="mb-1 rounded-md px-2 py-1 text-sm font-medium">
{item.title}
</h4>
{item.items ? (
<DocsSidebarNavItems items={item.items} pathname={pathname} />
) : null}
</div>
))}
</div>
) : null
}
interface DocsSidebarNavItemsProps {
items: SidebarNavItem[]
pathname: string | null
}
export function DocsSidebarNavItems({
items,
pathname,
}: DocsSidebarNavItemsProps) {
return items?.length ? (
<div className="grid grid-flow-row auto-rows-max text-sm">
{items.map((item, index) =>
!item.disabled && item.href ? (
<Link
key={index}
href={item.href}
className={cn(
"flex w-full items-center rounded-md p-2 hover:underline",
{
"bg-muted": pathname === item.href,
}
)}
target={item.external ? "_blank" : ""}
rel={item.external ? "noreferrer" : ""}
>
{item.title}
</Link>
) : (
<span className="flex w-full cursor-not-allowed items-center rounded-md p-2 opacity-60">
{item.title}
</span>
)
)}
</div>
) : null
}
================================================
FILE: components/site-footer.tsx
================================================
import * as React from "react"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { ModeToggle } from "@/components/mode-toggle"
export function SiteFooter({ className }: React.HTMLAttributes<HTMLElement>) {
return (
<footer className={cn(className)}>
<div className="container flex flex-col items-center justify-between gap-4 py-10 md:h-24 md:flex-row md:py-0">
<div className="flex flex-col items-center gap-4 px-8 md:flex-row md:gap-2 md:px-0">
<Icons.logo />
<p className="text-center text-sm leading-loose md:text-left">
Built by{" "}
<a
href={siteConfig.links.twitter}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
shadcn
</a>
. Hosted on{" "}
<a
href="https://vercel.com"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
Vercel
</a>
. Illustrations by{" "}
<a
href="https://popsy.co"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
Popsy
</a>
. The source code is available on{" "}
<a
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
GitHub
</a>
.
</p>
</div>
<ModeToggle />
</div>
</footer>
)
}
================================================
FILE: components/tailwind-indicator.tsx
================================================
export function TailwindIndicator() {
if (process.env.NODE_ENV === "production") return null
return (
<div className="fixed bottom-1 left-1 z-50 flex h-6 w-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
<div className="block sm:hidden">xs</div>
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">
sm
</div>
<div className="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div>
<div className="hidden lg:block xl:hidden 2xl:hidden">lg</div>
<div className="hidden xl:block 2xl:hidden">xl</div>
<div className="hidden 2xl:block">2xl</div>
</div>
)
}
================================================
FILE: components/theme-provider.tsx
================================================
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
================================================
FILE: components/toc.tsx
================================================
"use client"
import * as React from "react"
import { TableOfContents } from "@/lib/toc"
import { cn } from "@/lib/utils"
import { useMounted } from "@/hooks/use-mounted"
interface TocProps {
toc: TableOfContents
}
export function DashboardTableOfContents({ toc }: TocProps) {
const itemIds = React.useMemo(
() =>
toc.items
? toc.items
.flatMap((item) => [item.url, item?.items?.map((item) => item.url)])
.flat()
.filter(Boolean)
.map((id) => id?.split("#")[1])
: [],
[toc]
)
const activeHeading = useActiveItem(itemIds)
const mounted = useMounted()
if (!toc?.items) {
return null
}
return mounted ? (
<div className="space-y-2">
<p className="font-medium">On This Page</p>
<Tree tree={toc} activeItem={activeHeading} />
</div>
) : null
}
function useActiveItem(itemIds: (string | undefined)[]) {
const [activeId, setActiveId] = React.useState<string>("")
React.useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveId(entry.target.id)
}
})
},
{ rootMargin: `0% 0% -80% 0%` }
)
itemIds?.forEach((id) => {
if (!id) {
return
}
const element = document.getElementById(id)
if (element) {
observer.observe(element)
}
})
return () => {
itemIds?.forEach((id) => {
if (!id) {
return
}
const element = document.getElementById(id)
if (element) {
observer.unobserve(element)
}
})
}
}, [itemIds])
return activeId
}
interface TreeProps {
tree: TableOfContents
level?: number
activeItem?: string | null
}
function Tree({ tree, level = 1, activeItem }: TreeProps) {
return tree?.items?.length && level < 3 ? (
<ul className={cn("m-0 list-none", { "pl-4": level !== 1 })}>
{tree.items.map((item, index) => {
return (
<li key={index} className={cn("mt-0 pt-2")}>
<a
href={item.url}
className={cn(
"inline-block no-underline",
item.url === `#${activeItem}`
? "font-medium text-primary"
: "text-sm text-muted-foreground"
)}
>
{item.title}
</a>
{item.items?.length ? (
<Tree tree={item} level={level + 1} activeItem={activeItem} />
) : null}
</li>
)
})}
</ul>
) : null
}
================================================
FILE: components/ui/accordion.tsx
================================================
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className={cn(
"overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
className
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
================================================
FILE: components/ui/alert-dialog.tsx
================================================
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = ({
className,
children,
...props
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
<AlertDialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
{children}
</div>
</AlertDialogPrimitive.Portal>
)
AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-opacity animate-in fade-in",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed z-50 grid w-full max-w-lg scale-100 gap-4 border bg-background p-6 opacity-100 shadow-lg animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
================================================
FILE: components/ui/alert.tsx
================================================
import * as React from "react"
import { VariantProps, cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }
================================================
FILE: components/ui/aspect-ratio.tsx
================================================
"use client"
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
const AspectRatio = AspectRatioPrimitive.Root
export { AspectRatio }
================================================
FILE: components/ui/avatar.tsx
================================================
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }
================================================
FILE: components/ui/badge.tsx
================================================
import * as React from "react"
import { VariantProps, cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"bg-primary hover:bg-primary/80 border-transparent text-primary-foreground",
secondary:
"bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground",
destructive:
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }
================================================
FILE: components/ui/button.tsx
================================================
import * as React from "react"
import { VariantProps, cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary",
},
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
================================================
FILE: components/ui/calendar.tsx
================================================
"use client"
import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
export type CalendarProps = React.ComponentProps<typeof DayPicker>
function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: "text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn(
buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
),
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside: "text-muted-foreground opacity-50",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
)
}
Calendar.displayName = "Calendar"
export { Calendar }
================================================
FILE: components/ui/card.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(" flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
================================================
FILE: components/ui/checkbox.tsx
================================================
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-primary")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }
================================================
FILE: components/ui/collapsible.tsx
================================================
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
================================================
FILE: components/ui/command.tsx
================================================
"use client"
import * as React from "react"
import { DialogProps } from "@radix-ui/react-dialog"
import { Command as CommandPrimitive } from "cmdk"
import { Search } from "lucide-react"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent } from "@/components/ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-2xl">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"placeholder:text-foreground-muted flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}
================================================
FILE: components/ui/context-menu.tsx
================================================
"use client"
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const ContextMenu = ContextMenuPrimitive.Root
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
const ContextMenuGroup = ContextMenuPrimitive.Group
const ContextMenuPortal = ContextMenuPrimitive.Portal
const ContextMenuSub = ContextMenuPrimitive.Sub
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
))
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in slide-in-from-left-1",
className
)}
{...props}
/>
))
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80",
className
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
))
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
))
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
))
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
const ContextMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
ContextMenuShortcut.displayName = "ContextMenuShortcut"
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}
================================================
FILE: components/ui/dialog.tsx
================================================
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
className,
children,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
{children}
</div>
</DialogPrimitive.Portal>
)
DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
================================================
FILE: components/ui/dropdown-menu.tsx
================================================
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"text-on-popover z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
================================================
FILE: components/ui/hover-card.tsx
================================================
"use client"
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils"
const HoverCard = HoverCardPrimitive.Root
const HoverCardTrigger = HoverCardPrimitive.Trigger
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none animate-in zoom-in-90",
className
)}
{...props}
/>
))
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
export { HoverCard, HoverCardTrigger, HoverCardContent }
================================================
FILE: components/ui/input.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
================================================
FILE: components/ui/label.tsx
================================================
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { VariantProps, cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
================================================
FILE: components/ui/menubar.tsx
================================================
"use client"
import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const MenubarMenu = MenubarPrimitive.Menu
const MenubarGroup = MenubarPrimitive.Group
const MenubarPortal = MenubarPrimitive.Portal
const MenubarSub = MenubarPrimitive.Sub
const MenubarRadioGroup = MenubarPrimitive.RadioGroup
const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
"flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
className
)}
{...props}
/>
))
Menubar.displayName = MenubarPrimitive.Root.displayName
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className
)}
{...props}
/>
))
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
))
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
)}
{...props}
/>
))
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(
(
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
ref
) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in slide-in-from-top-1",
className
)}
{...props}
/>
</MenubarPrimitive.Portal>
)
)
MenubarContent.displayName = MenubarPrimitive.Content.displayName
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarItem.displayName = MenubarPrimitive.Item.displayName
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
))
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
))
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
MenubarShortcut.displayname = "MenubarShortcut"
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
}
================================================
FILE: components/ui/navigation-menu.tsx
================================================
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:bg-accent focus:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none bg-background hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent/50 data-[active]:bg-accent/50 h-10 py-2 px-4 group w-max"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}
================================================
FILE: components/ui/popover.tsx
================================================
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent }
================================================
FILE: components/ui/progress.tsx
================================================
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }
================================================
FILE: components/ui/radio-group.tsx
================================================
"use client"
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, children, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"h-4 w-4 rounded-full border border-input ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-primary text-primary" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem }
================================================
FILE: components/ui/scroll-area.tsx
================================================
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }
================================================
FILE: components/ui/select.tsx
================================================
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-80",
position === "popper" && "translate-y-1",
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
}
================================================
FILE: components/ui/separator.tsx
================================================
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }
================================================
FILE: components/ui/sheet.tsx
================================================
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { VariantProps, cva } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const portalVariants = cva("fixed inset-0 z-50 flex", {
variants: {
position: {
top: "items-start",
bottom: "items-end",
left: "justify-start",
right: "justify-end",
},
},
defaultVariants: { position: "right" },
})
interface SheetPortalProps
extends SheetPrimitive.DialogPortalProps,
VariantProps<typeof portalVariants> {}
const SheetPortal = ({
position,
className,
children,
...props
}: SheetPortalProps) => (
<SheetPrimitive.Portal className={cn(className)} {...props}>
<div className={portalVariants({ position })}>{children}</div>
</SheetPrimitive.Portal>
)
SheetPortal.displayName = SheetPrimitive.Portal.displayName
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 scale-100 gap-4 bg-background p-6 opacity-100 shadow-lg border",
{
variants: {
position: {
top: "animate-in slide-in-from-top w-full duration-300",
bottom: "animate-in slide-in-from-bottom w-full duration-300",
left: "animate-in slide-in-from-left h-full duration-300",
right: "animate-in slide-in-from-right h-full duration-300",
},
size: {
content: "",
default: "",
sm: "",
lg: "",
xl: "",
full: "",
},
},
compoundVariants: [
{
position: ["top", "bottom"],
size: "content",
class: "max-h-screen",
},
{
position: ["top", "bottom"],
size: "default",
class: "h-1/3",
},
{
position: ["top", "bottom"],
size: "sm",
class: "h-1/4",
},
{
position: ["top", "bottom"],
size: "lg",
class: "h-1/2",
},
{
position: ["top", "bottom"],
size: "xl",
class: "h-5/6",
},
{
position: ["top", "bottom"],
size: "full",
class: "h-screen",
},
{
position: ["right", "left"],
size: "content",
class: "max-w-screen",
},
{
position: ["right", "left"],
size: "default",
class: "w-1/3",
},
{
position: ["right", "left"],
size: "sm",
class: "w-1/4",
},
{
position: ["right", "left"],
size: "lg",
class: "w-1/2",
},
{
position: ["right", "left"],
size: "xl",
class: "w-5/6",
},
{
position: ["right", "left"],
size: "full",
class: "w-screen",
},
],
defaultVariants: {
position: "right",
size: "default",
},
}
)
export interface DialogContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof shee
gitextract_6s0f8trj/
├── .commitlintrc.json
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── .nvmrc
├── .prettierignore
├── LICENSE.md
├── README.md
├── app/
│ ├── (auth)/
│ │ ├── layout.tsx
│ │ ├── login/
│ │ │ └── page.tsx
│ │ └── register/
│ │ └── page.tsx
│ ├── (dashboard)/
│ │ └── dashboard/
│ │ ├── billing/
│ │ │ ├── loading.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ ├── page.tsx
│ │ └── settings/
│ │ ├── loading.tsx
│ │ └── page.tsx
│ ├── (docs)/
│ │ ├── docs/
│ │ │ ├── [[...slug]]/
│ │ │ │ └── page.tsx
│ │ │ └── layout.tsx
│ │ ├── guides/
│ │ │ ├── [...slug]/
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── (editor)/
│ │ └── editor/
│ │ ├── [postId]/
│ │ │ ├── loading.tsx
│ │ │ ├── not-found.tsx
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── (marketing)/
│ │ ├── [...slug]/
│ │ │ └── page.tsx
│ │ ├── blog/
│ │ │ ├── [...slug]/
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── pricing/
│ │ └── page.tsx
│ ├── api/
│ │ ├── auth/
│ │ │ └── [...nextauth]/
│ │ │ └── _route.ts
│ │ ├── og/
│ │ │ └── route.tsx
│ │ ├── posts/
│ │ │ ├── [postId]/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── users/
│ │ │ ├── [userId]/
│ │ │ │ └── route.ts
│ │ │ └── stripe/
│ │ │ └── route.ts
│ │ └── webhooks/
│ │ └── stripe/
│ │ └── route.ts
│ ├── layout.tsx
│ └── robots.ts
├── components/
│ ├── analytics.tsx
│ ├── billing-form.tsx
│ ├── callout.tsx
│ ├── card-skeleton.tsx
│ ├── editor.tsx
│ ├── empty-placeholder.tsx
│ ├── header.tsx
│ ├── icons.tsx
│ ├── main-nav.tsx
│ ├── mdx-card.tsx
│ ├── mdx-components.tsx
│ ├── mobile-nav.tsx
│ ├── mode-toggle.tsx
│ ├── nav.tsx
│ ├── page-header.tsx
│ ├── pager.tsx
│ ├── post-create-button.tsx
│ ├── post-item.tsx
│ ├── post-operations.tsx
│ ├── search.tsx
│ ├── shell.tsx
│ ├── sidebar-nav.tsx
│ ├── site-footer.tsx
│ ├── tailwind-indicator.tsx
│ ├── theme-provider.tsx
│ ├── toc.tsx
│ ├── ui/
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── aspect-ratio.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── calendar.tsx
│ │ ├── card.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── hover-card.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── menubar.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── radio-group.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── skeleton.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── toggle.tsx
│ │ ├── tooltip.tsx
│ │ └── use-toast.ts
│ ├── user-account-nav.tsx
│ ├── user-auth-form.tsx
│ ├── user-avatar.tsx
│ └── user-name-form.tsx
├── config/
│ ├── dashboard.ts
│ ├── docs.ts
│ ├── marketing.ts
│ ├── site.ts
│ └── subscriptions.ts
├── content/
│ ├── authors/
│ │ └── shadcn.mdx
│ ├── blog/
│ │ ├── deploying-next-apps.mdx
│ │ ├── dynamic-routing-static-regeneration.mdx
│ │ ├── preview-mode-headless-cms.mdx
│ │ └── server-client-components.mdx
│ ├── docs/
│ │ ├── documentation/
│ │ │ ├── code-blocks.mdx
│ │ │ ├── components.mdx
│ │ │ ├── index.mdx
│ │ │ └── style-guide.mdx
│ │ ├── in-progress.mdx
│ │ └── index.mdx
│ ├── guides/
│ │ ├── build-blog-using-contentlayer-mdx.mdx
│ │ └── using-next-auth-next-13.mdx
│ └── pages/
│ ├── privacy.mdx
│ └── terms.mdx
├── contentlayer.config.js
├── env.mjs
├── hooks/
│ ├── use-lock-body.ts
│ └── use-mounted.ts
├── lib/
│ ├── auth.ts
│ ├── db.ts
│ ├── exceptions.ts
│ ├── session.ts
│ ├── stripe.ts
│ ├── subscription.ts
│ ├── toc.ts
│ ├── utils.ts
│ └── validations/
│ ├── auth.ts
│ ├── og.ts
│ ├── post.ts
│ └── user.ts
├── middleware.ts
├── next.config.mjs
├── package.json
├── pages/
│ └── api/
│ └── auth/
│ └── [...nextauth].ts
├── postcss.config.js
├── prettier.config.js
├── prisma/
│ ├── migrations/
│ │ ├── 20221021182747_init/
│ │ │ └── migration.sql
│ │ ├── 20221118173244_add_stripe_columns/
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ └── schema.prisma
├── public/
│ └── site.webmanifest
├── styles/
│ ├── editor.css
│ ├── globals.css
│ └── mdx.css
├── tailwind.config.js
├── tsconfig.json
└── types/
├── index.d.ts
└── next-auth.d.ts
SYMBOL INDEX (196 symbols across 89 files)
FILE: app/(auth)/layout.tsx
type AuthLayoutProps (line 1) | interface AuthLayoutProps {
function AuthLayout (line 5) | function AuthLayout({ children }: AuthLayoutProps) {
FILE: app/(auth)/login/page.tsx
function LoginPage (line 14) | function LoginPage() {
FILE: app/(auth)/register/page.tsx
function RegisterPage (line 13) | function RegisterPage() {
FILE: app/(dashboard)/dashboard/billing/loading.tsx
function DashboardBillingLoading (line 5) | function DashboardBillingLoading() {
FILE: app/(dashboard)/dashboard/billing/page.tsx
function BillingPage (line 25) | async function BillingPage() {
FILE: app/(dashboard)/dashboard/layout.tsx
type DashboardLayoutProps (line 10) | interface DashboardLayoutProps {
function DashboardLayout (line 14) | async function DashboardLayout({
FILE: app/(dashboard)/dashboard/loading.tsx
function DashboardLoading (line 6) | function DashboardLoading() {
FILE: app/(dashboard)/dashboard/page.tsx
function DashboardPage (line 16) | async function DashboardPage() {
FILE: app/(dashboard)/dashboard/settings/loading.tsx
function DashboardSettingsLoading (line 6) | function DashboardSettingsLoading() {
FILE: app/(dashboard)/dashboard/settings/page.tsx
function SettingsPage (line 14) | async function SettingsPage() {
FILE: app/(docs)/docs/[[...slug]]/page.tsx
type DocPageProps (line 16) | interface DocPageProps {
function getDocFromParams (line 22) | async function getDocFromParams(params) {
function generateMetadata (line 33) | async function generateMetadata({
function generateStaticParams (line 75) | async function generateStaticParams(): Promise<
function DocPage (line 83) | async function DocPage({ params }: DocPageProps) {
FILE: app/(docs)/docs/layout.tsx
type DocsLayoutProps (line 4) | interface DocsLayoutProps {
function DocsLayout (line 8) | function DocsLayout({ children }: DocsLayoutProps) {
FILE: app/(docs)/guides/[...slug]/page.tsx
type GuidePageProps (line 18) | interface GuidePageProps {
function getGuideFromParams (line 24) | async function getGuideFromParams(params) {
function generateMetadata (line 35) | async function generateMetadata({
function generateStaticParams (line 77) | async function generateStaticParams(): Promise<
function GuidePage (line 85) | async function GuidePage({ params }: GuidePageProps) {
FILE: app/(docs)/guides/layout.tsx
type GuidesLayoutProps (line 1) | interface GuidesLayoutProps {
function GuidesLayout (line 5) | function GuidesLayout({ children }: GuidesLayoutProps) {
FILE: app/(docs)/guides/page.tsx
function GuidesPage (line 14) | function GuidesPage() {
FILE: app/(docs)/layout.tsx
type DocsLayoutProps (line 11) | interface DocsLayoutProps {
function DocsLayout (line 15) | function DocsLayout({ children }: DocsLayoutProps) {
FILE: app/(editor)/editor/[postId]/loading.tsx
function Loading (line 3) | function Loading() {
FILE: app/(editor)/editor/[postId]/not-found.tsx
function NotFound (line 6) | function NotFound() {
FILE: app/(editor)/editor/[postId]/page.tsx
function getPostForUser (line 9) | async function getPostForUser(postId: Post["id"], userId: User["id"]) {
type EditorPageProps (line 18) | interface EditorPageProps {
function EditorPage (line 22) | async function EditorPage({ params }: EditorPageProps) {
FILE: app/(editor)/editor/layout.tsx
type EditorProps (line 1) | interface EditorProps {
function EditorLayout (line 5) | function EditorLayout({ children }: EditorProps) {
FILE: app/(marketing)/[...slug]/page.tsx
type PageProps (line 13) | interface PageProps {
function getPageFromParams (line 19) | async function getPageFromParams(params) {
function generateMetadata (line 30) | async function generateMetadata({
function generateStaticParams (line 72) | async function generateStaticParams(): Promise<PageProps["params"][]> {
function PagePage (line 78) | async function PagePage({ params }: PageProps) {
FILE: app/(marketing)/blog/[...slug]/page.tsx
type PostPageProps (line 16) | interface PostPageProps {
function getPostFromParams (line 22) | async function getPostFromParams(params) {
function generateMetadata (line 33) | async function generateMetadata({
function generateStaticParams (line 78) | async function generateStaticParams(): Promise<
function PostPage (line 86) | async function PostPage({ params }: PostPageProps) {
FILE: app/(marketing)/blog/page.tsx
function BlogPage (line 12) | async function BlogPage() {
FILE: app/(marketing)/layout.tsx
type MarketingLayoutProps (line 9) | interface MarketingLayoutProps {
function MarketingLayout (line 13) | async function MarketingLayout({
FILE: app/(marketing)/page.tsx
function getGitHubStars (line 8) | async function getGitHubStars(): Promise<string | null> {
function IndexPage (line 35) | async function IndexPage() {
FILE: app/(marketing)/pricing/page.tsx
function PricingPage (line 11) | function PricingPage() {
FILE: app/api/og/route.tsx
function GET (line 15) | async function GET(req: Request) {
FILE: app/api/posts/[postId]/route.ts
function DELETE (line 14) | async function DELETE(
function PATCH (line 44) | async function PATCH(
function verifyCurrentUserHasAccessToPost (line 83) | async function verifyCurrentUserHasAccessToPost(postId: string) {
FILE: app/api/posts/route.ts
function GET (line 14) | async function GET() {
function POST (line 41) | async function POST(req: Request) {
FILE: app/api/users/[userId]/route.ts
function PATCH (line 14) | async function PATCH(
FILE: app/api/users/stripe/route.ts
function GET (line 12) | async function GET(req: Request) {
FILE: app/api/webhooks/stripe/route.ts
function POST (line 8) | async function POST(req: Request) {
FILE: app/layout.tsx
type RootLayoutProps (line 23) | interface RootLayoutProps {
function RootLayout (line 74) | function RootLayout({ children }: RootLayoutProps) {
FILE: app/robots.ts
function robots (line 3) | function robots(): MetadataRoute.Robots {
FILE: components/analytics.tsx
function Analytics (line 5) | function Analytics() {
FILE: components/billing-form.tsx
type BillingFormProps (line 19) | interface BillingFormProps extends React.HTMLAttributes<HTMLFormElement> {
function BillingForm (line 25) | function BillingForm({
FILE: components/callout.tsx
type CalloutProps (line 3) | interface CalloutProps {
function Callout (line 9) | function Callout({
FILE: components/card-skeleton.tsx
function CardSkeleton (line 4) | function CardSkeleton() {
FILE: components/editor.tsx
type EditorProps (line 20) | interface EditorProps {
type FormData (line 24) | type FormData = z.infer<typeof postPatchSchema>
function Editor (line 26) | function Editor({ post }: EditorProps) {
FILE: components/empty-placeholder.tsx
type EmptyPlaceholderProps (line 6) | interface EmptyPlaceholderProps extends React.HTMLAttributes<HTMLDivElem...
function EmptyPlaceholder (line 8) | function EmptyPlaceholder({
type EmptyPlaceholderIconProps (line 28) | interface EmptyPlaceholderIconProps
type EmptyPlacholderTitleProps (line 51) | interface EmptyPlacholderTitleProps
type EmptyPlacholderDescriptionProps (line 63) | interface EmptyPlacholderDescriptionProps
FILE: components/header.tsx
type DashboardHeaderProps (line 1) | interface DashboardHeaderProps {
function DashboardHeader (line 7) | function DashboardHeader({
FILE: components/icons.tsx
type Icon (line 29) | type Icon = LucideIcon
FILE: components/main-nav.tsx
type MainNavProps (line 13) | interface MainNavProps {
function MainNav (line 18) | function MainNav({ items, children }: MainNavProps) {
FILE: components/mdx-card.tsx
type CardProps (line 5) | interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
function MdxCard (line 10) | function MdxCard({
FILE: components/mdx-components.tsx
type MdxProps (line 155) | interface MdxProps {
function Mdx (line 159) | function Mdx({ code }: MdxProps) {
FILE: components/mobile-nav.tsx
type MobileNavProps (line 10) | interface MobileNavProps {
function MobileNav (line 15) | function MobileNav({ items, children }: MobileNavProps) {
FILE: components/mode-toggle.tsx
function ModeToggle (line 15) | function ModeToggle() {
FILE: components/nav.tsx
type DashboardNavProps (line 10) | interface DashboardNavProps {
function DashboardNav (line 14) | function DashboardNav({ items }: DashboardNavProps) {
FILE: components/page-header.tsx
type DocsPageHeaderProps (line 3) | interface DocsPageHeaderProps extends React.HTMLAttributes<HTMLDivElemen...
function DocsPageHeader (line 8) | function DocsPageHeader({
FILE: components/pager.tsx
type DocsPagerProps (line 9) | interface DocsPagerProps {
function DocsPager (line 13) | function DocsPager({ doc }: DocsPagerProps) {
function getPagerForDoc (line 44) | function getPagerForDoc(doc: Doc) {
function flatten (line 60) | function flatten(links: { items? }[]) {
FILE: components/post-create-button.tsx
type PostCreateButtonProps (line 11) | interface PostCreateButtonProps extends ButtonProps {}
function PostCreateButton (line 13) | function PostCreateButton({
FILE: components/post-item.tsx
type PostItemProps (line 8) | interface PostItemProps {
function PostItem (line 12) | function PostItem({ post }: PostItemProps) {
FILE: components/post-operations.tsx
function deletePost (line 28) | async function deletePost(postId: string) {
type PostOperationsProps (line 44) | interface PostOperationsProps {
function PostOperations (line 48) | function PostOperations({ post }: PostOperationsProps) {
FILE: components/search.tsx
type DocsSearchProps (line 9) | interface DocsSearchProps extends React.HTMLAttributes<HTMLFormElement> {}
function DocsSearch (line 11) | function DocsSearch({ className, ...props }: DocsSearchProps) {
FILE: components/shell.tsx
type DashboardShellProps (line 5) | interface DashboardShellProps extends React.HTMLAttributes<HTMLDivElemen...
function DashboardShell (line 7) | function DashboardShell({
FILE: components/sidebar-nav.tsx
type DocsSidebarNavProps (line 9) | interface DocsSidebarNavProps {
function DocsSidebarNav (line 13) | function DocsSidebarNav({ items }: DocsSidebarNavProps) {
type DocsSidebarNavItemsProps (line 32) | interface DocsSidebarNavItemsProps {
function DocsSidebarNavItems (line 37) | function DocsSidebarNavItems({
FILE: components/site-footer.tsx
function SiteFooter (line 8) | function SiteFooter({ className }: React.HTMLAttributes<HTMLElement>) {
FILE: components/tailwind-indicator.tsx
function TailwindIndicator (line 1) | function TailwindIndicator() {
FILE: components/theme-provider.tsx
function ThemeProvider (line 7) | function ThemeProvider({ children, ...props }: ThemeProviderProps) {
FILE: components/toc.tsx
type TocProps (line 9) | interface TocProps {
function DashboardTableOfContents (line 13) | function DashboardTableOfContents({ toc }: TocProps) {
function useActiveItem (line 40) | function useActiveItem(itemIds: (string | undefined)[]) {
type TreeProps (line 83) | interface TreeProps {
function Tree (line 89) | function Tree({ tree, level = 1, activeItem }: TreeProps) {
FILE: components/ui/badge.tsx
type BadgeProps (line 26) | interface BadgeProps
function Badge (line 30) | function Badge({ className, variant, ...props }: BadgeProps) {
FILE: components/ui/button.tsx
type ButtonProps (line 34) | interface ButtonProps
FILE: components/ui/calendar.tsx
type CalendarProps (line 10) | type CalendarProps = React.ComponentProps<typeof DayPicker>
function Calendar (line 12) | function Calendar({
FILE: components/ui/command.tsx
type CommandDialogProps (line 26) | interface CommandDialogProps extends DialogProps {}
FILE: components/ui/input.tsx
type InputProps (line 5) | interface InputProps
FILE: components/ui/sheet.tsx
type SheetPortalProps (line 26) | interface SheetPortalProps
type DialogContentProps (line 145) | interface DialogContentProps
FILE: components/ui/skeleton.tsx
function Skeleton (line 3) | function Skeleton({
FILE: components/ui/textarea.tsx
type TextareaProps (line 5) | interface TextareaProps
FILE: components/ui/toast.tsx
type ToastProps (line 113) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement (line 115) | type ToastActionElement = React.ReactElement<typeof ToastAction>
FILE: components/ui/toaster.tsx
function Toaster (line 13) | function Toaster() {
FILE: components/ui/use-toast.ts
constant TOAST_LIMIT (line 6) | const TOAST_LIMIT = 1
constant TOAST_REMOVE_DELAY (line 7) | const TOAST_REMOVE_DELAY = 1000000
type ToasterToast (line 9) | type ToasterToast = ToastProps & {
function genId (line 25) | function genId() {
type ActionType (line 30) | type ActionType = typeof actionTypes
type Action (line 32) | type Action =
type State (line 50) | interface State {
function dispatch (line 131) | function dispatch(action: Action) {
type Toast (line 138) | interface Toast extends Omit<ToasterToast, "id"> {}
function toast (line 140) | function toast({ ...props }: Toast) {
function useToast (line 169) | function useToast() {
FILE: components/user-account-nav.tsx
type UserAccountNavProps (line 16) | interface UserAccountNavProps extends React.HTMLAttributes<HTMLDivElemen...
function UserAccountNav (line 20) | function UserAccountNav({ user }: UserAccountNavProps) {
FILE: components/user-auth-form.tsx
type UserAuthFormProps (line 18) | interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}
type FormData (line 20) | type FormData = z.infer<typeof userAuthSchema>
function UserAuthForm (line 22) | function UserAuthForm({ className, ...props }: UserAuthFormProps) {
FILE: components/user-avatar.tsx
type UserAvatarProps (line 7) | interface UserAvatarProps extends AvatarProps {
function UserAvatar (line 11) | function UserAvatar({ user, ...props }: UserAvatarProps) {
FILE: components/user-name-form.tsx
type UserNameFormProps (line 26) | interface UserNameFormProps extends React.HTMLAttributes<HTMLFormElement> {
type FormData (line 30) | type FormData = z.infer<typeof userNameSchema>
function UserNameForm (line 32) | function UserNameForm({ user, className, ...props }: UserNameFormProps) {
FILE: contentlayer.config.js
method onVisitLine (line 155) | onVisitLine(node) {
method onVisitHighlightedLine (line 162) | onVisitHighlightedLine(node) {
method onVisitHighlightedWord (line 165) | onVisitHighlightedWord(node) {
FILE: hooks/use-lock-body.ts
function useLockBody (line 4) | function useLockBody() {
FILE: hooks/use-mounted.ts
function useMounted (line 3) | function useMounted() {
FILE: lib/auth.ts
method session (line 73) | async session({ token, session }) {
method jwt (line 83) | async jwt({ token, user }) {
FILE: lib/exceptions.ts
class RequiresProPlanError (line 1) | class RequiresProPlanError extends Error {
method constructor (line 2) | constructor(message = "This action requires a pro plan") {
FILE: lib/session.ts
function getCurrentUser (line 5) | async function getCurrentUser() {
FILE: lib/subscription.ts
function getUserSubscriptionPlan (line 7) | async function getUserSubscriptionPlan(
FILE: lib/toc.ts
function flattenNode (line 10) | function flattenNode(node) {
type Item (line 19) | interface Item {
type Items (line 25) | interface Items {
function getItems (line 29) | function getItems(node, current): Items {
type TableOfContents (line 71) | type TableOfContents = Items
function getTableOfContents (line 73) | async function getTableOfContents(
FILE: lib/utils.ts
function cn (line 6) | function cn(...inputs: ClassValue[]) {
function formatDate (line 10) | function formatDate(input: string | number): string {
function absoluteUrl (line 19) | function absoluteUrl(path: string) {
FILE: middleware.ts
method authorized (line 34) | async authorized() {
FILE: prisma/migrations/20221021182747_init/migration.sql
type `accounts` (line 2) | CREATE TABLE `accounts` (
type `sessions` (line 23) | CREATE TABLE `sessions` (
type `users` (line 34) | CREATE TABLE `users` (
type `verification_tokens` (line 48) | CREATE TABLE `verification_tokens` (
type `posts` (line 58) | CREATE TABLE `posts` (
FILE: prisma/migrations/20221118173244_add_stripe_columns/migration.sql
type `users_stripe_customer_id_key` (line 24) | CREATE UNIQUE INDEX `users_stripe_customer_id_key` ON `users`(`stripe_cu...
type `users_stripe_subscription_id_key` (line 27) | CREATE UNIQUE INDEX `users_stripe_subscription_id_key` ON `users`(`strip...
FILE: types/index.d.ts
type NavItem (line 6) | type NavItem = {
type MainNavItem (line 12) | type MainNavItem = NavItem
type SidebarNavItem (line 14) | type SidebarNavItem = {
type SiteConfig (line 30) | type SiteConfig = {
type DocsConfig (line 41) | type DocsConfig = {
type MarketingConfig (line 46) | type MarketingConfig = {
type DashboardConfig (line 50) | type DashboardConfig = {
type SubscriptionPlan (line 55) | type SubscriptionPlan = {
type UserSubscriptionPlan (line 61) | type UserSubscriptionPlan = SubscriptionPlan &
FILE: types/next-auth.d.ts
type UserId (line 4) | type UserId = string
type JWT (line 7) | interface JWT {
type Session (line 13) | interface Session {
Condensed preview — 166 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (370K chars).
[
{
"path": ".commitlintrc.json",
"chars": 53,
"preview": "{\n \"extends\": [\"@commitlint/config-conventional\"]\n}\n"
},
{
"path": ".editorconfig",
"chars": 166,
"preview": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_n"
},
{
"path": ".eslintrc.json",
"chars": 653,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/eslintrc\",\n \"root\": true,\n \"extends\": [\n \"next/core-web-vitals\",\n \""
},
{
"path": ".gitignore",
"chars": 412,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": ".husky/commit-msg",
"chars": 78,
"preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx commitlint --edit $1\n"
},
{
"path": ".husky/pre-commit",
"chars": 79,
"preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx pretty-quick --staged\n"
},
{
"path": ".nvmrc",
"chars": 9,
"preview": "v16.18.0\n"
},
{
"path": ".prettierignore",
"chars": 43,
"preview": "dist\nnode_modules\n.next\nbuild\n.contentlayer"
},
{
"path": "LICENSE.md",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2022 shadcn\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 2754,
"preview": "# Taxonomy\n\nAn open source application built using the new router, server components and everything new in Next.js 13.\n\n"
},
{
"path": "app/(auth)/layout.tsx",
"chars": 185,
"preview": "interface AuthLayoutProps {\n children: React.ReactNode\n}\n\nexport default function AuthLayout({ children }: AuthLayoutPr"
},
{
"path": "app/(auth)/login/page.tsx",
"chars": 1559,
"preview": "import { Metadata } from \"next\"\nimport Link from \"next/link\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants }"
},
{
"path": "app/(auth)/register/page.tsx",
"chars": 1913,
"preview": "import Link from \"next/link\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\ni"
},
{
"path": "app/(dashboard)/dashboard/billing/loading.tsx",
"chars": 467,
"preview": "import { CardSkeleton } from \"@/components/card-skeleton\"\nimport { DashboardHeader } from \"@/components/header\"\nimport {"
},
{
"path": "app/(dashboard)/dashboard/billing/page.tsx",
"chars": 2238,
"preview": "import { redirect } from \"next/navigation\"\n\nimport { authOptions } from \"@/lib/auth\"\nimport { getCurrentUser } from \"@/l"
},
{
"path": "app/(dashboard)/dashboard/layout.tsx",
"chars": 1464,
"preview": "import { notFound } from \"next/navigation\"\n\nimport { dashboardConfig } from \"@/config/dashboard\"\nimport { getCurrentUser"
},
{
"path": "app/(dashboard)/dashboard/loading.tsx",
"chars": 687,
"preview": "import { DashboardHeader } from \"@/components/header\"\nimport { PostCreateButton } from \"@/components/post-create-button\""
},
{
"path": "app/(dashboard)/dashboard/page.tsx",
"chars": 1755,
"preview": "import { redirect } from \"next/navigation\"\n\nimport { authOptions } from \"@/lib/auth\"\nimport { db } from \"@/lib/db\"\nimpor"
},
{
"path": "app/(dashboard)/dashboard/settings/loading.tsx",
"chars": 507,
"preview": "import { Card } from \"@/components/ui/card\"\nimport { CardSkeleton } from \"@/components/card-skeleton\"\nimport { Dashboard"
},
{
"path": "app/(dashboard)/dashboard/settings/page.tsx",
"chars": 853,
"preview": "import { redirect } from \"next/navigation\"\n\nimport { authOptions } from \"@/lib/auth\"\nimport { getCurrentUser } from \"@/l"
},
{
"path": "app/(docs)/docs/[[...slug]]/page.tsx",
"chars": 2617,
"preview": "import { notFound } from \"next/navigation\"\nimport { allDocs } from \"contentlayer/generated\"\n\nimport { getTableOfContents"
},
{
"path": "app/(docs)/docs/layout.tsx",
"chars": 605,
"preview": "import { docsConfig } from \"@/config/docs\"\nimport { DocsSidebarNav } from \"@/components/sidebar-nav\"\n\ninterface DocsLayo"
},
{
"path": "app/(docs)/guides/[...slug]/page.tsx",
"chars": 2974,
"preview": "import Link from \"next/link\"\nimport { notFound } from \"next/navigation\"\nimport { allGuides } from \"contentlayer/generate"
},
{
"path": "app/(docs)/guides/layout.tsx",
"chars": 196,
"preview": "interface GuidesLayoutProps {\n children: React.ReactNode\n}\n\nexport default function GuidesLayout({ children }: GuidesLa"
},
{
"path": "app/(docs)/guides/page.tsx",
"chars": 2133,
"preview": "import Link from \"next/link\"\nimport { allGuides } from \"contentlayer/generated\"\nimport { compareDesc } from \"date-fns\"\n\n"
},
{
"path": "app/(docs)/layout.tsx",
"chars": 1541,
"preview": "import Link from \"next/link\"\n\nimport { docsConfig } from \"@/config/docs\"\nimport { siteConfig } from \"@/config/site\"\nimpo"
},
{
"path": "app/(editor)/editor/[postId]/loading.tsx",
"chars": 598,
"preview": "import { Skeleton } from \"@/components/ui/skeleton\"\n\nexport default function Loading() {\n return (\n <div className=\""
},
{
"path": "app/(editor)/editor/[postId]/not-found.tsx",
"chars": 656,
"preview": "import Link from \"next/link\"\n\nimport { buttonVariants } from \"@/components/ui/button\"\nimport { EmptyPlaceholder } from \""
},
{
"path": "app/(editor)/editor/[postId]/page.tsx",
"chars": 952,
"preview": "import { notFound, redirect } from \"next/navigation\"\nimport { Post, User } from \"@prisma/client\"\n\nimport { authOptions }"
},
{
"path": "app/(editor)/editor/layout.tsx",
"chars": 236,
"preview": "interface EditorProps {\n children?: React.ReactNode\n}\n\nexport default function EditorLayout({ children }: EditorProps) "
},
{
"path": "app/(marketing)/[...slug]/page.tsx",
"chars": 2236,
"preview": "import { notFound } from \"next/navigation\"\nimport { allPages } from \"contentlayer/generated\"\n\nimport { Mdx } from \"@/com"
},
{
"path": "app/(marketing)/blog/[...slug]/page.tsx",
"chars": 4448,
"preview": "import { notFound } from \"next/navigation\"\nimport { allAuthors, allPosts } from \"contentlayer/generated\"\n\nimport { Mdx }"
},
{
"path": "app/(marketing)/blog/page.tsx",
"chars": 2196,
"preview": "import Image from \"next/image\"\nimport Link from \"next/link\"\nimport { allPosts } from \"contentlayer/generated\"\nimport { c"
},
{
"path": "app/(marketing)/layout.tsx",
"chars": 1056,
"preview": "import Link from \"next/link\"\n\nimport { marketingConfig } from \"@/config/marketing\"\nimport { cn } from \"@/lib/utils\"\nimpo"
},
{
"path": "app/(marketing)/page.tsx",
"chars": 14811,
"preview": "import Link from \"next/link\"\n\nimport { env } from \"@/env.mjs\"\nimport { siteConfig } from \"@/config/site\"\nimport { cn } f"
},
{
"path": "app/(marketing)/pricing/page.tsx",
"chars": 2699,
"preview": "import Link from \"next/link\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\ni"
},
{
"path": "app/api/auth/[...nextauth]/_route.ts",
"chars": 158,
"preview": "import NextAuth from \"next-auth\"\n\nimport { authOptions } from \"@/lib/auth\"\n\nconst handler = NextAuth(authOptions)\n\nexpor"
},
{
"path": "app/api/og/route.tsx",
"chars": 7555,
"preview": "import { ImageResponse } from \"@vercel/og\"\n\nimport { ogImageSchema } from \"@/lib/validations/og\"\n\nexport const runtime ="
},
{
"path": "app/api/posts/[postId]/route.ts",
"chars": 2250,
"preview": "import { getServerSession } from \"next-auth\"\nimport * as z from \"zod\"\n\nimport { authOptions } from \"@/lib/auth\"\nimport {"
},
{
"path": "app/api/posts/route.ts",
"chars": 2155,
"preview": "import { getServerSession } from \"next-auth/next\"\nimport * as z from \"zod\"\n\nimport { authOptions } from \"@/lib/auth\"\nimp"
},
{
"path": "app/api/users/[userId]/route.ts",
"chars": 1263,
"preview": "import { getServerSession } from \"next-auth/next\"\nimport { z } from \"zod\"\n\nimport { authOptions } from \"@/lib/auth\"\nimpo"
},
{
"path": "app/api/users/stripe/route.ts",
"chars": 1858,
"preview": "import { getServerSession } from \"next-auth/next\"\nimport { z } from \"zod\"\n\nimport { proPlan } from \"@/config/subscriptio"
},
{
"path": "app/api/webhooks/stripe/route.ts",
"chars": 1996,
"preview": "import { headers } from \"next/headers\"\nimport Stripe from \"stripe\"\n\nimport { env } from \"@/env.mjs\"\nimport { db } from \""
},
{
"path": "app/layout.tsx",
"chars": 2351,
"preview": "import { Inter as FontSans } from \"next/font/google\"\nimport localFont from \"next/font/local\"\n\nimport \"@/styles/globals.c"
},
{
"path": "app/robots.ts",
"chars": 172,
"preview": "import { MetadataRoute } from \"next\"\n\nexport default function robots(): MetadataRoute.Robots {\n return {\n rules: {\n "
},
{
"path": "components/analytics.tsx",
"chars": 147,
"preview": "\"use client\"\n\nimport { Analytics as VercelAnalytics } from \"@vercel/analytics/react\"\n\nexport function Analytics() {\n re"
},
{
"path": "components/billing-form.tsx",
"chars": 2626,
"preview": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { UserSubscriptionPlan } from \"types\"\nimport { cn, formatDate } fro"
},
{
"path": "components/callout.tsx",
"chars": 602,
"preview": "import { cn } from \"@/lib/utils\"\n\ninterface CalloutProps {\n icon?: string\n children?: React.ReactNode\n type?: \"defaul"
},
{
"path": "components/card-skeleton.tsx",
"chars": 475,
"preview": "import { Card, CardContent, CardFooter, CardHeader } from \"@/components/ui/card\"\nimport { Skeleton } from \"@/components/"
},
{
"path": "components/editor.tsx",
"chars": 4920,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport Link from \"next/link\"\nimport { useRouter } from \"next/navigation\"\nim"
},
{
"path": "components/empty-placeholder.tsx",
"chars": 1862,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Icons } from \"@/components/icons\"\n\ninterface E"
},
{
"path": "components/header.tsx",
"chars": 492,
"preview": "interface DashboardHeaderProps {\n heading: string\n text?: string\n children?: React.ReactNode\n}\n\nexport function Dashb"
},
{
"path": "components/icons.tsx",
"chars": 2489,
"preview": "import {\n AlertTriangle,\n ArrowRight,\n Check,\n ChevronLeft,\n ChevronRight,\n Command,\n CreditCard,\n File,\n FileT"
},
{
"path": "components/main-nav.tsx",
"chars": 1925,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport Link from \"next/link\"\nimport { useSelectedLayoutSegment } from \"next"
},
{
"path": "components/mdx-card.tsx",
"chars": 888,
"preview": "import Link from \"next/link\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface CardProps extends React.HTMLAttributes<HTMLDiv"
},
{
"path": "components/mdx-components.tsx",
"chars": 4051,
"preview": "import * as React from \"react\"\nimport Image from \"next/image\"\nimport { useMDXComponent } from \"next-contentlayer/hooks\"\n"
},
{
"path": "components/mobile-nav.tsx",
"chars": 1484,
"preview": "import * as React from \"react\"\nimport Link from \"next/link\"\n\nimport { MainNavItem } from \"types\"\nimport { siteConfig } f"
},
{
"path": "components/mode-toggle.tsx",
"chars": 1417,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { useTheme } from \"next-themes\"\n\nimport { Button } from \"@/component"
},
{
"path": "components/nav.tsx",
"chars": 1194,
"preview": "\"use client\"\n\nimport Link from \"next/link\"\nimport { usePathname } from \"next/navigation\"\n\nimport { SidebarNavItem } from"
},
{
"path": "components/page-header.tsx",
"chars": 567,
"preview": "import { cn } from \"@/lib/utils\"\n\ninterface DocsPageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n heading"
},
{
"path": "components/pager.tsx",
"chars": 1629,
"preview": "import Link from \"next/link\"\nimport { Doc } from \"contentlayer/generated\"\n\nimport { docsConfig } from \"@/config/docs\"\nim"
},
{
"path": "components/post-create-button.tsx",
"chars": 1847,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { useRouter } from \"next/navigation\"\n\nimport { cn } from \"@/lib/util"
},
{
"path": "components/post-item.tsx",
"chars": 1119,
"preview": "import Link from \"next/link\"\nimport { Post } from \"@prisma/client\"\n\nimport { formatDate } from \"@/lib/utils\"\nimport { Sk"
},
{
"path": "components/post-operations.tsx",
"chars": 3441,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport Link from \"next/link\"\nimport { useRouter } from \"next/navigation\"\nim"
},
{
"path": "components/search.tsx",
"chars": 1081,
"preview": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Input } from \"@/components/ui/in"
},
{
"path": "components/shell.tsx",
"chars": 358,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface DashboardShellProps extends React.HTMLAttrib"
},
{
"path": "components/sidebar-nav.tsx",
"chars": 1726,
"preview": "\"use client\"\n\nimport Link from \"next/link\"\nimport { usePathname } from \"next/navigation\"\n\nimport { SidebarNavItem } from"
},
{
"path": "components/site-footer.tsx",
"chars": 1833,
"preview": "import * as React from \"react\"\n\nimport { siteConfig } from \"@/config/site\"\nimport { cn } from \"@/lib/utils\"\nimport { Ico"
},
{
"path": "components/tailwind-indicator.tsx",
"chars": 682,
"preview": "export function TailwindIndicator() {\n if (process.env.NODE_ENV === \"production\") return null\n\n return (\n <div clas"
},
{
"path": "components/theme-provider.tsx",
"chars": 322,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\"\nimport { "
},
{
"path": "components/toc.tsx",
"chars": 2653,
"preview": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { TableOfContents } from \"@/lib/toc\"\nimport { cn } from \"@/lib/util"
},
{
"path": "components/ui/accordion.tsx",
"chars": 1999,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\"\nimport { Ch"
},
{
"path": "components/ui/alert-dialog.tsx",
"chars": 4392,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\"\n\nimpor"
},
{
"path": "components/ui/alert.tsx",
"chars": 1600,
"preview": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/u"
},
{
"path": "components/ui/aspect-ratio.tsx",
"chars": 154,
"preview": "\"use client\"\n\nimport * as AspectRatioPrimitive from \"@radix-ui/react-aspect-ratio\"\n\nconst AspectRatio = AspectRatioPrimi"
},
{
"path": "components/ui/avatar.tsx",
"chars": 1419,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\n\nimport { cn } fr"
},
{
"path": "components/ui/badge.tsx",
"chars": 1123,
"preview": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/u"
},
{
"path": "components/ui/button.tsx",
"chars": 1648,
"preview": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/u"
},
{
"path": "components/ui/calendar.tsx",
"chars": 2363,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { DayPicker"
},
{
"path": "components/ui/card.tsx",
"chars": 1877,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n Rea"
},
{
"path": "components/ui/checkbox.tsx",
"chars": 1027,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Chec"
},
{
"path": "components/ui/collapsible.tsx",
"chars": 329,
"preview": "\"use client\"\n\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\"\n\nconst Collapsible = CollapsiblePrimit"
},
{
"path": "components/ui/command.tsx",
"chars": 4863,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { DialogProps } from \"@radix-ui/react-dialog\"\nimport { Command as Co"
},
{
"path": "components/ui/context-menu.tsx",
"chars": 6618,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ContextMenuPrimitive from \"@radix-ui/react-context-menu\"\nimport"
},
{
"path": "components/ui/dialog.tsx",
"chars": 3841,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from"
},
{
"path": "components/ui/dropdown-menu.tsx",
"chars": 6961,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimpo"
},
{
"path": "components/ui/hover-card.tsx",
"chars": 883,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as HoverCardPrimitive from \"@radix-ui/react-hover-card\"\n\nimport { "
},
{
"path": "components/ui/input.tsx",
"chars": 825,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLA"
},
{
"path": "components/ui/label.tsx",
"chars": 719,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { VariantPro"
},
{
"path": "components/ui/menubar.tsx",
"chars": 7554,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as MenubarPrimitive from \"@radix-ui/react-menubar\"\nimport { Check,"
},
{
"path": "components/ui/navigation-menu.tsx",
"chars": 5036,
"preview": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva }"
},
{
"path": "components/ui/popover.tsx",
"chars": 1074,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } "
},
{
"path": "components/ui/progress.tsx",
"chars": 791,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\"\n\nimport { cn "
},
{
"path": "components/ui/radio-group.tsx",
"chars": 1438,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\"\nimport {"
},
{
"path": "components/ui/scroll-area.tsx",
"chars": 1647,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport "
},
{
"path": "components/ui/select.tsx",
"chars": 3931,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, C"
},
{
"path": "components/ui/separator.tsx",
"chars": 770,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { c"
},
{
"path": "components/ui/sheet.tsx",
"chars": 5913,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\"\nimport { VariantPr"
},
{
"path": "components/ui/skeleton.tsx",
"chars": 261,
"preview": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) {"
},
{
"path": "components/ui/slider.tsx",
"chars": 1091,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\n\nimport { cn } fr"
},
{
"path": "components/ui/switch.tsx",
"chars": 1162,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\"\n\nimport { cn } f"
},
{
"path": "components/ui/tabs.tsx",
"chars": 1897,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \""
},
{
"path": "components/ui/textarea.tsx",
"chars": 765,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface TextareaProps\n extends React.Textare"
},
{
"path": "components/ui/toast.tsx",
"chars": 4830,
"preview": "import * as React from \"react\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { VariantProps, cva } fro"
},
{
"path": "components/ui/toaster.tsx",
"chars": 794,
"preview": "\"use client\"\n\nimport {\n Toast,\n ToastClose,\n ToastDescription,\n ToastProvider,\n ToastTitle,\n ToastViewport,\n} from"
},
{
"path": "components/ui/toggle.tsx",
"chars": 1444,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { VariantP"
},
{
"path": "components/ui/tooltip.tsx",
"chars": 1054,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } "
},
{
"path": "components/ui/use-toast.ts",
"chars": 3936,
"preview": "// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport { ToastActionElement, type ToastProps } fr"
},
{
"path": "components/user-account-nav.tsx",
"chars": 1965,
"preview": "\"use client\"\n\nimport Link from \"next/link\"\nimport { User } from \"next-auth\"\nimport { signOut } from \"next-auth/react\"\n\ni"
},
{
"path": "components/user-auth-form.tsx",
"chars": 3660,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { useSearchParams } from \"next/navigation\"\nimport { zodResolver } fr"
},
{
"path": "components/user-avatar.tsx",
"chars": 671,
"preview": "import { User } from \"@prisma/client\"\nimport { AvatarProps } from \"@radix-ui/react-avatar\"\n\nimport { Avatar, AvatarFallb"
},
{
"path": "components/user-name-form.tsx",
"chars": 3081,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { useRouter } from \"next/navigation\"\nimport { zodResolver } from \"@h"
},
{
"path": "config/dashboard.ts",
"chars": 551,
"preview": "import { DashboardConfig } from \"types\"\n\nexport const dashboardConfig: DashboardConfig = {\n mainNav: [\n {\n titl"
},
{
"path": "config/docs.ts",
"chars": 2807,
"preview": "import { DocsConfig } from \"types\"\n\nexport const docsConfig: DocsConfig = {\n mainNav: [\n {\n title: \"Documentati"
},
{
"path": "config/marketing.ts",
"chars": 355,
"preview": "import { MarketingConfig } from \"types\"\n\nexport const marketingConfig: MarketingConfig = {\n mainNav: [\n {\n titl"
},
{
"path": "config/site.ts",
"chars": 411,
"preview": "import { SiteConfig } from \"types\"\n\nexport const siteConfig: SiteConfig = {\n name: \"Taxonomy\",\n description:\n \"An o"
},
{
"path": "config/subscriptions.ts",
"chars": 429,
"preview": "import { SubscriptionPlan } from \"types\"\nimport { env } from \"@/env.mjs\"\n\nexport const freePlan: SubscriptionPlan = {\n "
},
{
"path": "content/authors/shadcn.mdx",
"chars": 73,
"preview": "---\ntitle: shadcn\navatar: /images/avatars/shadcn.png\ntwitter: shadcn\n---\n"
},
{
"path": "content/blog/deploying-next-apps.mdx",
"chars": 11212,
"preview": "---\ntitle: Deploying Next.js Apps\ndescription: How to deploy your Next.js apps on Vercel.\nimage: /images/blog/blog-post-"
},
{
"path": "content/blog/dynamic-routing-static-regeneration.mdx",
"chars": 11251,
"preview": "---\ntitle: Dynamic Routing and Static Regeneration\ndescription: How to use incremental static regeneration using dynamic"
},
{
"path": "content/blog/preview-mode-headless-cms.mdx",
"chars": 11228,
"preview": "---\ntitle: Preview Mode for Headless CMS\ndescription: How to implement preview mode in your headless CMS.\ndate: \"2023-04"
},
{
"path": "content/blog/server-client-components.mdx",
"chars": 11271,
"preview": "---\ntitle: Server and Client Components\ndescription: React Server Components allow developers to build applications that"
},
{
"path": "content/docs/documentation/code-blocks.mdx",
"chars": 1481,
"preview": "---\ntitle: Code Blocks\ndescription: Advanced code blocks with highlighting, file names and more.\n---\n\nThe code blocks on"
},
{
"path": "content/docs/documentation/components.mdx",
"chars": 2871,
"preview": "---\ntitle: Components\ndescription: Use React components in Markdown using MDX.\n---\n\nThe following components are availab"
},
{
"path": "content/docs/documentation/index.mdx",
"chars": 1242,
"preview": "---\ntitle: Documentation\ndescription: Build your documentation site using Contentlayer and MDX.\n---\n\nTaxonomy includes a"
},
{
"path": "content/docs/documentation/style-guide.mdx",
"chars": 11308,
"preview": "---\ntitle: Style Guide\ndescription: Testing the MDX style guide with Tailwind Typography\n---\n\n<Callout>\n\n- The text belo"
},
{
"path": "content/docs/in-progress.mdx",
"chars": 267,
"preview": "---\ntitle: Not Implemented\ndescription: This page is in progress.\n---\n\n<Callout>\n\nThis site is a work in progress. If yo"
},
{
"path": "content/docs/index.mdx",
"chars": 963,
"preview": "---\ntitle: Documentation\ndescription: Welcome to the Taxonomy documentation.\n---\n\nThis is the documentation for the Taxo"
},
{
"path": "content/guides/build-blog-using-contentlayer-mdx.mdx",
"chars": 11385,
"preview": "---\ntitle: Build a blog using ContentLayer and MDX.\ndescription: Learn how to use ContentLayer to build a blog with Next"
},
{
"path": "content/guides/using-next-auth-next-13.mdx",
"chars": 11361,
"preview": "---\ntitle: Using NextAuth.js with Next.13\ndescription: How to use NextAuth.js in server components.\ndate: 2022-11-23\n---"
},
{
"path": "content/pages/privacy.mdx",
"chars": 1468,
"preview": "---\ntitle: Privacy\ndescription: The Privacy Policy for Taxonomy App.\n---\n\nBlandit libero volutpat sed cras ornare arcu. "
},
{
"path": "content/pages/terms.mdx",
"chars": 2316,
"preview": "---\ntitle: Terms & Conditions\ndescription: Read our terms and conditions.\n---\n\nLorem ipsumMagna fermentum iaculis eu non"
},
{
"path": "contentlayer.config.js",
"chars": 3790,
"preview": "import { defineDocumentType, makeSource } from \"contentlayer/source-files\"\nimport rehypeAutolinkHeadings from \"rehype-au"
},
{
"path": "env.mjs",
"chars": 1674,
"preview": "import { createEnv } from \"@t3-oss/env-nextjs\"\nimport { z } from \"zod\"\n\nexport const env = createEnv({\n server: {\n /"
},
{
"path": "hooks/use-lock-body.ts",
"chars": 371,
"preview": "import * as React from \"react\"\n\n// @see https://usehooks.com/useLockBodyScroll.\nexport function useLockBody() {\n React."
},
{
"path": "hooks/use-mounted.ts",
"chars": 194,
"preview": "import * as React from \"react\"\n\nexport function useMounted() {\n const [mounted, setMounted] = React.useState(false)\n\n "
},
{
"path": "lib/auth.ts",
"chars": 2783,
"preview": "import { PrismaAdapter } from \"@next-auth/prisma-adapter\"\nimport { NextAuthOptions } from \"next-auth\"\nimport EmailProvid"
},
{
"path": "lib/db.ts",
"chars": 384,
"preview": "import { PrismaClient } from \"@prisma/client\"\n\ndeclare global {\n // eslint-disable-next-line no-var\n var cachedPrisma:"
},
{
"path": "lib/exceptions.ts",
"chars": 136,
"preview": "export class RequiresProPlanError extends Error {\n constructor(message = \"This action requires a pro plan\") {\n super"
},
{
"path": "lib/session.ts",
"chars": 214,
"preview": "import { getServerSession } from \"next-auth/next\"\n\nimport { authOptions } from \"@/lib/auth\"\n\nexport async function getCu"
},
{
"path": "lib/stripe.ts",
"chars": 168,
"preview": "import Stripe from \"stripe\"\n\nimport { env } from \"@/env.mjs\"\n\nexport const stripe = new Stripe(env.STRIPE_API_KEY, {\n a"
},
{
"path": "lib/subscription.ts",
"chars": 901,
"preview": "// @ts-nocheck\n// TODO: Fix this when we turn strict mode on.\nimport { UserSubscriptionPlan } from \"types\"\nimport { free"
},
{
"path": "lib/toc.ts",
"chars": 1529,
"preview": "// @ts-nocheck\n// TODO: Fix this when we turn strict mode on.\n\nimport { toc } from \"mdast-util-toc\"\nimport { remark } fr"
},
{
"path": "lib/utils.ts",
"chars": 490,
"preview": "import { ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nimport { env } from \"@/env.mjs\"\n\nexpor"
},
{
"path": "lib/validations/auth.ts",
"chars": 99,
"preview": "import * as z from \"zod\"\n\nexport const userAuthSchema = z.object({\n email: z.string().email(),\n})\n"
},
{
"path": "lib/validations/og.ts",
"chars": 163,
"preview": "import * as z from \"zod\"\n\nexport const ogImageSchema = z.object({\n heading: z.string(),\n type: z.string(),\n mode: z.e"
},
{
"path": "lib/validations/post.ts",
"chars": 208,
"preview": "import * as z from \"zod\"\n\nexport const postPatchSchema = z.object({\n title: z.string().min(3).max(128).optional(),\n\n /"
},
{
"path": "lib/validations/user.ts",
"chars": 105,
"preview": "import * as z from \"zod\"\n\nexport const userNameSchema = z.object({\n name: z.string().min(3).max(32),\n})\n"
},
{
"path": "middleware.ts",
"chars": 1152,
"preview": "import { getToken } from \"next-auth/jwt\"\nimport { withAuth } from \"next-auth/middleware\"\nimport { NextResponse } from \"n"
},
{
"path": "next.config.mjs",
"chars": 372,
"preview": "import { withContentlayer } from \"next-contentlayer\"\n\nimport \"./env.mjs\"\n\n/** @type {import('next').NextConfig} */\nconst"
},
{
"path": "package.json",
"chars": 4039,
"preview": "{\n \"name\": \"taxonomy\",\n \"version\": \"0.2.0\",\n \"private\": true,\n \"author\": {\n \"name\": \"shadcn\",\n \"url\": \"https:/"
},
{
"path": "pages/api/auth/[...nextauth].ts",
"chars": 132,
"preview": "import NextAuth from \"next-auth\"\n\nimport { authOptions } from \"@/lib/auth\"\n\n// @see ./lib/auth\nexport default NextAuth(a"
},
{
"path": "postcss.config.js",
"chars": 82,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}\n"
},
{
"path": "prettier.config.js",
"chars": 840,
"preview": "/** @type {import('prettier').Config} */\nmodule.exports = {\n endOfLine: \"lf\",\n semi: false,\n singleQuote: false,\n ta"
},
{
"path": "prisma/migrations/20221021182747_init/migration.sql",
"chars": 2860,
"preview": "-- CreateTable\nCREATE TABLE `accounts` (\n `id` VARCHAR(191) NOT NULL,\n `userId` VARCHAR(191) NOT NULL,\n `type` "
},
{
"path": "prisma/migrations/20221118173244_add_stripe_columns/migration.sql",
"chars": 1056,
"preview": "/*\n Warnings:\n\n - A unique constraint covering the columns `[stripe_customer_id]` on the table `users` will be added. "
},
{
"path": "prisma/migrations/migration_lock.toml",
"chars": 121,
"preview": "# Please do not edit this file manually\n# It should be added in your version-control system (i.e. Git)\nprovider = \"mysql"
},
{
"path": "prisma/schema.prisma",
"chars": 2396,
"preview": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator clien"
},
{
"path": "public/site.webmanifest",
"chars": 376,
"preview": "{\n \"name\": \"Taxonomy\",\n \"short_name\": \"Taxonomy\",\n \"icons\": [\n {\n \"src\": \"/android-chrome-192x192.png\",\n "
},
{
"path": "styles/editor.css",
"chars": 1506,
"preview": ".dark .ce-block--selected .ce-block__content,\n.dark .ce-inline-toolbar,\n.dark .codex-editor--narrow .ce-toolbox,\n.dark ."
},
{
"path": "styles/globals.css",
"chars": 1615,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --f"
},
{
"path": "styles/mdx.css",
"chars": 911,
"preview": "[data-rehype-pretty-code-fragment] code {\n @apply grid min-w-full break-words rounded-none border-0 bg-transparent p-0 "
},
{
"path": "tailwind.config.js",
"chars": 2370,
"preview": "const { fontFamily } = require(\"tailwindcss/defaultTheme\")\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports ="
},
{
"path": "tsconfig.json",
"chars": 803,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"sk"
},
{
"path": "types/index.d.ts",
"chars": 1131,
"preview": "import { User } from \"@prisma/client\"\nimport type { Icon } from \"lucide-react\"\n\nimport { Icons } from \"@/components/icon"
},
{
"path": "types/next-auth.d.ts",
"chars": 264,
"preview": "import { User } from \"next-auth\"\nimport { JWT } from \"next-auth/jwt\"\n\ntype UserId = string\n\ndeclare module \"next-auth/jw"
}
]
About this extraction
This page contains the full source code of the shadcn-ui/taxonomy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 166 files (337.3 KB), approximately 92.6k tokens, and a symbol index with 196 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.