johndoe 2/2/2025
{
"name": "markdown",
"type": "registry:block",
"registryDependencies": [],
"dependencies": [
"sugar-high",
"next-mdx-remote"
],
"devDependencies": [],
"tailwind": {
"config": {}
},
"cssVars": {},
"files": [
{
"path": "md-helper.ts",
"content": "import fs from 'fs'\nimport path from 'path'\n\ntype Metadata = {\n title: string\n publishedAt: string\n summary: string\n image?: string\n}\n\nfunction parseFrontmatter(fileContent: string) {\n let frontmatterRegex = /---\\s*([\\s\\S]*?)\\s*---/\n let match = frontmatterRegex.exec(fileContent)\n let frontMatterBlock = match![1]\n let content = fileContent.replace(frontmatterRegex, '').trim()\n let frontMatterLines = frontMatterBlock.trim().split('\\n')\n let metadata: Partial<Metadata> = {}\n\n frontMatterLines.forEach((line) => {\n let [key, ...valueArr] = line.split(': ')\n let value = valueArr.join(': ').trim()\n value = value.replace(/^['\"](.*)['\"]$/, '$1') // Remove quotes\n metadata[key.trim() as keyof Metadata] = value\n })\n\n return { metadata: metadata as Metadata, content }\n}\n\nfunction getMDXFiles(dir) {\n return fs.readdirSync(dir).filter((file) => path.extname(file) === '.mdx')\n}\n\nfunction readMDXFile(filePath) {\n let rawContent = fs.readFileSync(filePath, 'utf-8')\n return parseFrontmatter(rawContent)\n}\n\nfunction getMDXData(dir) {\n let mdxFiles = getMDXFiles(dir)\n return mdxFiles.map((file) => {\n let { metadata, content } = readMDXFile(path.join(dir, file))\n let slug = path.basename(file, path.extname(file))\n\n return {\n metadata,\n slug,\n content,\n }\n })\n}\n\nexport function getBlogPosts() {\n return getMDXData(path.join(process.cwd(), 'app', 'blog', 'posts'))\n}\n\nexport function formatDate(date: string, includeRelative = false) {\n let currentDate = new Date()\n if (!date.includes('T')) {\n date = `${date}T00:00:00`\n }\n let targetDate = new Date(date)\n\n let yearsAgo = currentDate.getFullYear() - targetDate.getFullYear()\n let monthsAgo = currentDate.getMonth() - targetDate.getMonth()\n let daysAgo = currentDate.getDate() - targetDate.getDate()\n\n let formattedDate = ''\n\n if (yearsAgo > 0) {\n formattedDate = `${yearsAgo}y ago`\n } else if (monthsAgo > 0) {\n formattedDate = `${monthsAgo}mo ago`\n } else if (daysAgo > 0) {\n formattedDate = `${daysAgo}d ago`\n } else {\n formattedDate = 'Today'\n }\n\n let fullDate = targetDate.toLocaleString('en-us', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n })\n\n if (!includeRelative) {\n return fullDate\n }\n\n return `${fullDate} (${formattedDate})`\n}\n",
"type": "registry:lib"
},
{
"path": "mdx.tsx",
"content": "import Link from 'next/link'\nimport Image from 'next/image'\nimport { MDXRemote } from 'next-mdx-remote/rsc'\nimport { highlight } from 'sugar-high'\nimport React from 'react'\n\nfunction Table({ data }) {\n let headers = data.headers.map((header, index) => (\n <th key={index}>{header}</th>\n ))\n let rows = data.rows.map((row, index) => (\n <tr key={index}>\n {row.map((cell, cellIndex) => (\n <td key={cellIndex}>{cell}</td>\n ))}\n </tr>\n ))\n\n return (\n <table>\n <thead>\n <tr>{headers}</tr>\n </thead>\n <tbody>{rows}</tbody>\n </table>\n )\n}\n\nfunction CustomLink(props) {\n let href = props.href\n\n if (href.startsWith('/')) {\n return (\n <Link href={href} {...props}>\n {props.children}\n </Link>\n )\n }\n\n if (href.startsWith('#')) {\n return <a {...props} />\n }\n\n return <a target=\"_blank\" rel=\"noopener noreferrer\" {...props} />\n}\n\nfunction RoundedImage(props) {\n return <Image alt={props.alt} className=\"rounded-lg\" {...props} />\n}\n\nfunction Code({ children, ...props }) {\n let codeHTML = highlight(children)\n return <code dangerouslySetInnerHTML={{ __html: codeHTML }} {...props} />\n}\n\nfunction slugify(str) {\n return str\n .toString()\n .toLowerCase()\n .trim() // Remove whitespace from both ends of a string\n .replace(/\\s+/g, '-') // Replace spaces with -\n .replace(/&/g, '-and-') // Replace & with 'and'\n .replace(/[^\\w\\-]+/g, '') // Remove all non-word characters except for -\n .replace(/\\-\\-+/g, '-') // Replace multiple - with single -\n}\n\nfunction createHeading(level) {\n const Heading = ({ children }) => {\n let slug = slugify(children)\n return React.createElement(\n `h${level}`,\n { id: slug },\n [\n React.createElement('a', {\n href: `#${slug}`,\n key: `link-${slug}`,\n className: 'anchor',\n }),\n ],\n children\n )\n }\n\n Heading.displayName = `Heading${level}`\n\n return Heading\n}\n\nlet components = {\n h1: createHeading(1),\n h2: createHeading(2),\n h3: createHeading(3),\n h4: createHeading(4),\n h5: createHeading(5),\n h6: createHeading(6),\n Image: RoundedImage,\n a: CustomLink,\n code: Code,\n Table,\n}\n\nexport function CustomMDX(props) {\n return (\n <MDXRemote\n {...props}\n components={{ ...components, ...(props.components || {}) }}\n />\n )\n}\n",
"type": "registry:component"
}
]
}johndoe 10/26/2024
{
"name": "shadcn-clerk-template",
"type": "registry:block",
"dependencies": [
"@clerk/elements",
"@clerk/nextjs",
"@radix-ui/react-label",
"@radix-ui/react-slot"
],
"devDependencies": [],
"registryDependencies": [
"button",
"card",
"input",
"label"
],
"files": [
{
"target": "./app/(auth)/layout.tsx",
"path": "./app/(auth)/layout.tsx",
"content": "export default function Layout({children}:{children:React.ReactNode}) {\n return (\n <div className=\"min-h-screen flex place-items-center\">\n {children}\n </div>\n )\n}",
"type": "registry:example"
},
{
"target": "./app/(auth)/sign-in/[...sign-in]/page.tsx",
"path": "./app/(auth)/sign-in/[...sign-in]/page.tsx",
"content": "'use client'\nimport * as Clerk from '@clerk/elements/common'\nimport * as SignIn from '@clerk/elements/sign-in'\nimport Link from 'next/link'\nimport { Button } from '@/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Label } from '@/components/ui/label'\nimport { Icons } from '@/components/ui/icons'\nimport { cn } from '@/lib/utils'\n\nexport default function SignInPage() {\n return (\n <div className=\"grid w-full grow items-center px-4 sm:justify-center\">\n <SignIn.Root>\n <Clerk.Loading>\n {(isGlobalLoading) => (\n <>\n <SignIn.Step name=\"start\">\n <Card className=\"w-full sm:w-96\">\n <CardHeader>\n <CardTitle>Sign in to Acme Co</CardTitle>\n <CardDescription>Welcome back! Please sign in to continue</CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-y-4\">\n <div className=\"grid grid-cols-2 gap-x-4\">\n <Clerk.Connection name=\"github\" asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n type=\"button\"\n disabled={isGlobalLoading}\n >\n <Clerk.Loading scope=\"provider:github\">\n {(isLoading) =>\n isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n <>\n <Icons.gitHub className=\"mr-2 size-4\" />\n GitHub\n </>\n )\n }\n </Clerk.Loading>\n </Button>\n </Clerk.Connection>\n <Clerk.Connection name=\"google\" asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n type=\"button\"\n disabled={isGlobalLoading}\n >\n <Clerk.Loading scope=\"provider:google\">\n {(isLoading) =>\n isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n <>\n <Icons.google className=\"mr-2 size-4\" />\n Google\n </>\n )\n }\n </Clerk.Loading>\n </Button>\n </Clerk.Connection>\n </div>\n <p className=\"flex items-center gap-x-3 text-sm text-muted-foreground before:h-px before:flex-1 before:bg-border after:h-px after:flex-1 after:bg-border\">\n or\n </p>\n <Clerk.Field name=\"identifier\" className=\"space-y-2\">\n <Clerk.Label asChild>\n <Label>Email address</Label>\n </Clerk.Label>\n <Clerk.Input type=\"email\" required asChild>\n <Input />\n </Clerk.Input>\n <Clerk.FieldError className=\"block text-sm text-destructive\" />\n </Clerk.Field>\n </CardContent>\n <CardFooter>\n <div className=\"grid w-full gap-y-4\">\n <SignIn.Action submit asChild>\n <Button disabled={isGlobalLoading}>\n <Clerk.Loading>\n {(isLoading) => {\n return isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n 'Continue'\n )\n }}\n </Clerk.Loading>\n </Button>\n </SignIn.Action>\n\n <Button variant=\"link\" size=\"sm\" asChild>\n <Link href=\"/sign-up\">Don't have an account? Sign up</Link>\n </Button>\n </div>\n </CardFooter>\n </Card>\n </SignIn.Step>\n\n <SignIn.Step name=\"choose-strategy\">\n <Card className=\"w-full sm:w-96\">\n <CardHeader>\n <CardTitle>Use another method</CardTitle>\n <CardDescription>\n Facing issues? You can use any of these methods to sign in.\n </CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-y-4\">\n <SignIn.SupportedStrategy name=\"email_code\" asChild>\n <Button type=\"button\" variant=\"link\" disabled={isGlobalLoading}>\n Email code\n </Button>\n </SignIn.SupportedStrategy>\n <SignIn.SupportedStrategy name=\"password\" asChild>\n <Button type=\"button\" variant=\"link\" disabled={isGlobalLoading}>\n Password\n </Button>\n </SignIn.SupportedStrategy>\n </CardContent>\n <CardFooter>\n <div className=\"grid w-full gap-y-4\">\n <SignIn.Action navigate=\"previous\" asChild>\n <Button disabled={isGlobalLoading}>\n <Clerk.Loading>\n {(isLoading) => {\n return isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n 'Go back'\n )\n }}\n </Clerk.Loading>\n </Button>\n </SignIn.Action>\n </div>\n </CardFooter>\n </Card>\n </SignIn.Step>\n\n <SignIn.Step name=\"verifications\">\n <SignIn.Strategy name=\"password\">\n <Card className=\"w-full sm:w-96\">\n <CardHeader>\n <CardTitle>Check your email</CardTitle>\n <CardDescription>\n Enter the verification code sent to your email\n </CardDescription>\n <p className=\"text-sm text-muted-foreground\">\n Welcome back <SignIn.SafeIdentifier />\n </p>\n </CardHeader>\n <CardContent className=\"grid gap-y-4\">\n <Clerk.Field name=\"password\" className=\"space-y-2\">\n <Clerk.Label asChild>\n <Label>Password</Label>\n </Clerk.Label>\n <Clerk.Input type=\"password\" asChild>\n <Input />\n </Clerk.Input>\n <Clerk.FieldError className=\"block text-sm text-destructive\" />\n </Clerk.Field>\n </CardContent>\n <CardFooter>\n <div className=\"grid w-full gap-y-4\">\n <SignIn.Action submit asChild>\n <Button disabled={isGlobalLoading}>\n <Clerk.Loading>\n {(isLoading) => {\n return isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n 'Continue'\n )\n }}\n </Clerk.Loading>\n </Button>\n </SignIn.Action>\n <SignIn.Action navigate=\"choose-strategy\" asChild>\n <Button type=\"button\" size=\"sm\" variant=\"link\">\n Use another method\n </Button>\n </SignIn.Action>\n </div>\n </CardFooter>\n </Card>\n </SignIn.Strategy>\n\n <SignIn.Strategy name=\"email_code\">\n <Card className=\"w-full sm:w-96\">\n <CardHeader>\n <CardTitle>Check your email</CardTitle>\n <CardDescription>\n Enter the verification code sent to your email\n </CardDescription>\n <p className=\"text-sm text-muted-foreground\">\n Welcome back <SignIn.SafeIdentifier />\n </p>\n </CardHeader>\n <CardContent className=\"grid gap-y-4\">\n <Clerk.Field name=\"code\">\n <Clerk.Label className=\"sr-only\">Email verification code</Clerk.Label>\n <div className=\"grid gap-y-2 items-center justify-center\">\n <div className=\"flex justify-center text-center\">\n <Clerk.Input\n type=\"otp\"\n autoSubmit\n className=\"flex justify-center has-[:disabled]:opacity-50\"\n render={({ value, status }) => {\n return (\n <div\n data-status={status}\n className=\"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md data-[status=selected]:ring-1 data-[status=selected]:ring-ring data-[status=cursor]:ring-1 data-[status=cursor]:ring-ring\"\n >\n {value}\n </div>\n )\n }}\n />\n </div>\n <Clerk.FieldError className=\"block text-sm text-destructive text-center\" />\n <SignIn.Action\n asChild\n resend\n className=\"text-muted-foreground\"\n fallback={({ resendableAfter }) => (\n <Button variant=\"link\" size=\"sm\" disabled>\n Didn't receive a code? Resend (\n <span className=\"tabular-nums\">{resendableAfter}</span>)\n </Button>\n )}\n >\n <Button variant=\"link\" size=\"sm\">\n Didn't receive a code? Resend\n </Button>\n </SignIn.Action>\n </div>\n </Clerk.Field>\n </CardContent>\n <CardFooter>\n <div className=\"grid w-full gap-y-4\">\n <SignIn.Action submit asChild>\n <Button disabled={isGlobalLoading}>\n <Clerk.Loading>\n {(isLoading) => {\n return isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n 'Continue'\n )\n }}\n </Clerk.Loading>\n </Button>\n </SignIn.Action>\n <SignIn.Action navigate=\"choose-strategy\" asChild>\n <Button size=\"sm\" variant=\"link\">\n Use another method\n </Button>\n </SignIn.Action>\n </div>\n </CardFooter>\n </Card>\n </SignIn.Strategy>\n </SignIn.Step>\n </>\n )}\n </Clerk.Loading>\n </SignIn.Root>\n </div>\n )\n}",
"type": "registry:example"
},
{
"target": "./app/(auth)/sign-up/[...sign-up]/page.tsx",
"path": "./app/(auth)/sign-up/[...sign-up]/page.tsx",
"content": "'use client'\nimport * as Clerk from '@clerk/elements/common'\nimport * as SignUp from '@clerk/elements/sign-up'\nimport Link from 'next/link'\nimport { Button } from '@/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Label } from '@/components/ui/label'\nimport { Icons } from '@/components/ui/icons'\nimport { cn } from '@/lib/utils'\n\nexport default function SignUpPage() {\n return (\n <div className=\"grid w-full grow items-center px-4 sm:justify-center\">\n <SignUp.Root>\n <Clerk.Loading>\n {(isGlobalLoading) => (\n <>\n <SignUp.Step name=\"start\">\n <Card className=\"w-full sm:w-96\">\n <CardHeader>\n <CardTitle>Create your account</CardTitle>\n <CardDescription>\n Welcome! Please fill in the details to get started.\n </CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-y-4\">\n <div className=\"grid grid-cols-2 gap-x-4\">\n <Clerk.Connection name=\"github\" asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n type=\"button\"\n disabled={isGlobalLoading}\n >\n <Clerk.Loading scope=\"provider:github\">\n {(isLoading) =>\n isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n <>\n <Icons.gitHub className=\"mr-2 size-4\" />\n GitHub\n </>\n )\n }\n </Clerk.Loading>\n </Button>\n </Clerk.Connection>\n <Clerk.Connection name=\"google\" asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n type=\"button\"\n disabled={isGlobalLoading}\n >\n <Clerk.Loading scope=\"provider:google\">\n {(isLoading) =>\n isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n <>\n <Icons.google className=\"mr-2 size-4\" />\n Google\n </>\n )\n }\n </Clerk.Loading>\n </Button>\n </Clerk.Connection>\n </div>\n <p className=\"flex items-center gap-x-3 text-sm text-muted-foreground before:h-px before:flex-1 before:bg-border after:h-px after:flex-1 after:bg-border\">\n or\n </p>\n <Clerk.Field name=\"emailAddress\" className=\"space-y-2\">\n <Clerk.Label asChild>\n <Label>Email address</Label>\n </Clerk.Label>\n <Clerk.Input type=\"email\" required asChild>\n <Input />\n </Clerk.Input>\n <Clerk.FieldError className=\"block text-sm text-destructive\" />\n </Clerk.Field>\n <Clerk.Field name=\"password\" className=\"space-y-2\">\n <Clerk.Label asChild>\n <Label>Password</Label>\n </Clerk.Label>\n <Clerk.Input type=\"password\" required asChild>\n <Input />\n </Clerk.Input>\n <Clerk.FieldError className=\"block text-sm text-destructive\" />\n </Clerk.Field>\n </CardContent>\n <CardFooter>\n <div className=\"grid w-full gap-y-4\">\n <SignUp.Captcha className=\"empty:hidden\" />\n <SignUp.Action submit asChild>\n <Button disabled={isGlobalLoading}>\n <Clerk.Loading>\n {(isLoading) => {\n return isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n 'Continue'\n )\n }}\n </Clerk.Loading>\n </Button>\n </SignUp.Action>\n <Button variant=\"link\" size=\"sm\" asChild>\n <Link href=\"/sign-in\">Already have an account? Sign in</Link>\n </Button>\n </div>\n </CardFooter>\n </Card>\n </SignUp.Step>\n\n <SignUp.Step name=\"continue\">\n <Card className=\"w-full sm:w-96\">\n <CardHeader>\n <CardTitle>Continue registration</CardTitle>\n </CardHeader>\n <CardContent>\n <Clerk.Field name=\"username\" className=\"space-y-2\">\n <Clerk.Label>\n <Label>Username</Label>\n </Clerk.Label>\n <Clerk.Input type=\"text\" required asChild>\n <Input />\n </Clerk.Input>\n <Clerk.FieldError className=\"block text-sm text-destructive\" />\n </Clerk.Field>\n </CardContent>\n <CardFooter>\n <div className=\"grid w-full gap-y-4\">\n <SignUp.Action submit asChild>\n <Button disabled={isGlobalLoading}>\n <Clerk.Loading>\n {(isLoading) => {\n return isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n 'Continue'\n )\n }}\n </Clerk.Loading>\n </Button>\n </SignUp.Action>\n </div>\n </CardFooter>\n </Card>\n </SignUp.Step>\n\n <SignUp.Step name=\"verifications\">\n <SignUp.Strategy name=\"email_code\">\n <Card className=\"w-full sm:w-96\">\n <CardHeader>\n <CardTitle>Verify your email</CardTitle>\n <CardDescription>\n Use the verification link sent to your email address\n </CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-y-4\">\n <div className=\"grid items-center justify-center gap-y-2\">\n <Clerk.Field name=\"code\" className=\"space-y-2\">\n <Clerk.Label className=\"sr-only\">Email address</Clerk.Label>\n <div className=\"flex justify-center text-center\">\n <Clerk.Input\n type=\"otp\"\n className=\"flex justify-center has-[:disabled]:opacity-50\"\n autoSubmit\n render={({ value, status }) => {\n return (\n <div\n data-status={status}\n className={cn(\n 'relative flex size-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',\n {\n 'z-10 ring-2 ring-ring ring-offset-background':\n status === 'cursor' || status === 'selected',\n },\n )}\n >\n {value}\n {status === 'cursor' && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"animate-caret-blink h-4 w-px bg-foreground duration-1000\" />\n </div>\n )}\n </div>\n )\n }}\n />\n </div>\n <Clerk.FieldError className=\"block text-center text-sm text-destructive\" />\n </Clerk.Field>\n <SignUp.Action\n asChild\n resend\n className=\"text-muted-foreground\"\n fallback={({ resendableAfter }) => (\n <Button variant=\"link\" size=\"sm\" disabled>\n Didn't receive a code? Resend (\n <span className=\"tabular-nums\">{resendableAfter}</span>)\n </Button>\n )}\n >\n <Button type=\"button\" variant=\"link\" size=\"sm\">\n Didn't receive a code? Resend\n </Button>\n </SignUp.Action>\n </div>\n </CardContent>\n <CardFooter>\n <div className=\"grid w-full gap-y-4\">\n <SignUp.Action submit asChild>\n <Button disabled={isGlobalLoading}>\n <Clerk.Loading>\n {(isLoading) => {\n return isLoading ? (\n <Icons.spinner className=\"size-4 animate-spin\" />\n ) : (\n 'Continue'\n )\n }}\n </Clerk.Loading>\n </Button>\n </SignUp.Action>\n </div>\n </CardFooter>\n </Card>\n </SignUp.Strategy>\n </SignUp.Step>\n </>\n )}\n </Clerk.Loading>\n </SignUp.Root>\n </div>\n )\n}",
"type": "registry:example"
},
{
"target": "./app/_components/navbar.tsx",
"path": "./app/_components/navbar.tsx",
"content": "import { SignedOut, SignInButton, SignedIn, UserButton } from \"@clerk/nextjs\";\nimport Link from \"next/link\";\n\nexport default function Navbar() {\n return (\n <header className=\"flex justify-between absolute top-0 mx-2 w-[min(500px,90%)] left-1/2 -translate-x-1/2 place-items-center rounded-full shadow-sm px-4 py-1 border border-black/10\">\n <span className=\"font-extrabold\">Acme</span>\n <div className=\"grid place-items-center\">\n <SignedOut>\n <Link href={\"/sign-in/_\"}>Sign In</Link>\n </SignedOut>\n <SignedIn>\n <UserButton />\n </SignedIn>\n </div>\n </header>\n )\n}",
"type": "registry:example"
},
{
"target": "./app/layout.tsx",
"path": "./app/layout.tsx",
"content": "import type { Metadata } from \"next\";\nimport localFont from \"next/font/local\";\nimport \"./globals.css\";\nimport { ClerkProvider } from '@clerk/nextjs'\n\nconst geistSans = localFont({\n src: \"./fonts/GeistVF.woff\",\n variable: \"--font-geist-sans\",\n weight: \"100 900\",\n});\nconst geistMono = localFont({\n src: \"./fonts/GeistMonoVF.woff\",\n variable: \"--font-geist-mono\",\n weight: \"100 900\",\n});\n\nexport const metadata: Metadata = {\n title: \"Create Next App\",\n description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n children,\n}: Readonly<{\n children: React.ReactNode;\n}>) {\n return (\n <ClerkProvider>\n <html lang=\"en\">\n <body\n className={`${geistSans.variable} ${geistMono.variable} antialiased`}\n >\n {children}\n </body>\n </html>\n </ClerkProvider>\n );\n}\n",
"type": "registry:example"
},
{
"target": "./app/page.tsx",
"path": "./app/page.tsx",
"content": "import Image from \"next/image\";\nimport Navbar from \"./_components/navbar\";\n\nexport default function Home() {\n return (\n <div className=\"grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]\">\n <main className=\"flex flex-col gap-8 row-start-2 items-center sm:items-start\">\n <Navbar />\n <Image\n className=\"dark:invert\"\n src=\"https://nextjs.org/icons/next.svg\"\n alt=\"Next.js logo\"\n width={180}\n height={38}\n priority\n />\n <ol className=\"list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]\">\n <li className=\"mb-2\">\n Get started by editing{\" \"}\n <code className=\"bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold\">\n app/page.tsx\n </code>\n .\n </li>\n <li>Save and see your changes instantly.</li>\n </ol>\n\n <div className=\"flex gap-4 items-center flex-col sm:flex-row\">\n <a\n className=\"rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5\"\n href=\"https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <Image\n className=\"dark:invert\"\n src=\"https://nextjs.org/icons/vercel.svg\"\n alt=\"Vercel logomark\"\n width={20}\n height={20}\n />\n Deploy now\n </a>\n <a\n className=\"rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44\"\n href=\"https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Read our docs\n </a>\n </div>\n </main>\n <footer className=\"row-start-3 flex gap-6 flex-wrap items-center justify-center\">\n <a\n className=\"flex items-center gap-2 hover:underline hover:underline-offset-4\"\n href=\"https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <Image\n aria-hidden\n src=\"https://nextjs.org/icons/file.svg\"\n alt=\"File icon\"\n width={16}\n height={16}\n />\n Learn\n </a>\n <a\n className=\"flex items-center gap-2 hover:underline hover:underline-offset-4\"\n href=\"https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <Image\n aria-hidden\n src=\"https://nextjs.org/icons/window.svg\"\n alt=\"Window icon\"\n width={16}\n height={16}\n />\n Examples\n </a>\n <a\n className=\"flex items-center gap-2 hover:underline hover:underline-offset-4\"\n href=\"https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <Image\n aria-hidden\n src=\"https://nextjs.org/icons/globe.svg\"\n alt=\"Globe icon\"\n width={16}\n height={16}\n />\n Go to nextjs.org →\n </a>\n </footer>\n </div>\n );\n}\n",
"type": "registry:example"
},
{
"path": "./components/ui/icons.tsx",
"content": "type IconProps = React.HTMLAttributes<SVGElement>\n\nexport const Icons = {\n logo: (props: IconProps) => (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" {...props}>\n <rect width=\"256\" height=\"256\" fill=\"none\" />\n <line\n x1=\"208\"\n y1=\"128\"\n x2=\"128\"\n y2=\"208\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"32\"\n />\n <line\n x1=\"192\"\n y1=\"40\"\n x2=\"40\"\n y2=\"192\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"32\"\n />\n </svg>\n ),\n twitter: (props: IconProps) => (\n <svg\n {...props}\n height=\"23\"\n viewBox=\"0 0 1200 1227\"\n width=\"23\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z\" />\n </svg>\n ),\n gitHub: (props: IconProps) => (\n <svg viewBox=\"0 0 438.549 438.549\" {...props}>\n <path\n fill=\"currentColor\"\n d=\"M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z\"\n ></path>\n </svg>\n ),\n radix: (props: IconProps) => (\n <svg viewBox=\"0 0 25 25\" fill=\"none\" {...props}>\n <path\n d=\"M12 25C7.58173 25 4 21.4183 4 17C4 12.5817 7.58173 9 12 9V25Z\"\n fill=\"currentcolor\"\n ></path>\n <path d=\"M12 0H4V8H12V0Z\" fill=\"currentcolor\"></path>\n <path\n d=\"M17 8C19.2091 8 21 6.20914 21 4C21 1.79086 19.2091 0 17 0C14.7909 0 13 1.79086 13 4C13 6.20914 14.7909 8 17 8Z\"\n fill=\"currentcolor\"\n ></path>\n </svg>\n ),\n aria: (props: IconProps) => (\n <svg role=\"img\" viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M13.966 22.624l-1.69-4.281H8.122l3.892-9.144 5.662 13.425zM8.884 1.376H0v21.248zm15.116 0h-8.884L24 22.624Z\" />\n </svg>\n ),\n npm: (props: IconProps) => (\n <svg viewBox=\"0 0 24 24\" {...props}>\n <path\n d=\"M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z\"\n fill=\"currentColor\"\n />\n </svg>\n ),\n yarn: (props: IconProps) => (\n <svg viewBox=\"0 0 24 24\" {...props}>\n <path\n d=\"M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z\"\n fill=\"currentColor\"\n />\n </svg>\n ),\n pnpm: (props: IconProps) => (\n <svg viewBox=\"0 0 24 24\" {...props}>\n <path\n d=\"M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z\"\n fill=\"currentColor\"\n />\n </svg>\n ),\n react: (props: IconProps) => (\n <svg viewBox=\"0 0 24 24\" {...props}>\n <path\n 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.38-.318-.184-.688-.277-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.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.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.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.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.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.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.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.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.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.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.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.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.295-.22-.005-.406-.05-.553-.132-.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\"\n fill=\"currentColor\"\n />\n </svg>\n ),\n tailwind: (props: IconProps) => (\n <svg viewBox=\"0 0 24 24\" {...props}>\n <path\n d=\"M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z\"\n fill=\"currentColor\"\n />\n </svg>\n ),\n google: (props: IconProps) => (\n <svg role=\"img\" viewBox=\"0 0 24 24\" {...props}>\n <path\n fill=\"currentColor\"\n d=\"M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z\"\n />\n </svg>\n ),\n apple: (props: IconProps) => (\n <svg role=\"img\" viewBox=\"0 0 24 24\" {...props}>\n <path\n d=\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\"\n fill=\"currentColor\"\n />\n </svg>\n ),\n paypal: (props: IconProps) => (\n <svg role=\"img\" viewBox=\"0 0 24 24\" {...props}>\n <path\n d=\"M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.565-5.946.36-1.847.174-3.388-.777-4.471z\"\n fill=\"currentColor\"\n />\n </svg>\n ),\n spinner: (props: IconProps) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n </svg>\n ),\n}",
"type": "registry:ui"
},
{
"target": "~/middleware.ts",
"path": "./middleware.ts",
"content": "import { clerkMiddleware } from '@clerk/nextjs/server'\n\nexport default clerkMiddleware()\n\nexport const config = {\n matcher: [\n // Skip Next.js internals and all static files, unless found in search params\n '/((?!_next|[^?]*\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',\n // Always run for API routes\n '/(api|trpc)(.*)',\n ],\n}",
"type": "registry:example"
}
],
"tailwind": {},
"cssVars": {},
"meta": {}
}johndoe 10/26/2024
{
"name": "dark-mode",
"type": "registry:block",
"dependencies": [
"@radix-ui/react-dropdown-menu",
"@radix-ui/react-slot",
"next-themes"
],
"devDependencies": [],
"registryDependencies": [
"button",
"dropdown-menu"
],
"files": [
{
"target": "./app/layout.tsx",
"path": "./app/layout.tsx",
"content": "import type { Metadata } from \"next\";\nimport localFont from \"next/font/local\";\nimport \"./globals.css\";\nimport { ThemeProvider } from \"@/components/theme-provider\";\n\nconst geistSans = localFont({\n src: \"./fonts/GeistVF.woff\",\n variable: \"--font-geist-sans\",\n weight: \"100 900\",\n});\nconst geistMono = localFont({\n src: \"./fonts/GeistMonoVF.woff\",\n variable: \"--font-geist-mono\",\n weight: \"100 900\",\n});\n\nexport const metadata: Metadata = {\n title: \"Create Next App\",\n description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n children,\n}: Readonly<{\n children: React.ReactNode;\n}>) {\n return (\n <html lang=\"en\" suppressHydrationWarning>\n <body\n className={`${geistSans.variable} ${geistMono.variable} antialiased`}\n >\n <ThemeProvider\n attribute=\"class\"\n defaultTheme=\"system\"\n enableSystem\n disableTransitionOnChange\n >\n {children}\n </ThemeProvider>\n </body>\n </html>\n );\n}\n",
"type": "registry:example"
},
{
"target": "./app/page.tsx",
"path": "./app/page.tsx",
"content": "import Image from \"next/image\";\nimport { ModeToggle } from \"@/components/theme-toggle\";\n\nexport default function Home() {\n return (\n <div className=\"grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]\">\n <main className=\"flex flex-col gap-8 row-start-2 items-center sm:items-start\">\n <ModeToggle />\n <Image\n className=\"dark:invert\"\n src=\"https://nextjs.org/icons/next.svg\"\n alt=\"Next.js logo\"\n width={180}\n height={38}\n priority\n />\n <ol className=\"list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]\">\n <li className=\"mb-2\">\n Get started by editing{\" \"}\n <code className=\"bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold\">\n app/page.tsx\n </code>\n .\n </li>\n <li>Save and see your changes instantly.</li>\n </ol>\n\n <div className=\"flex gap-4 items-center flex-col sm:flex-row\">\n <a\n className=\"rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5\"\n href=\"https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <Image\n className=\"dark:invert\"\n src=\"https://nextjs.org/icons/vercel.svg\"\n alt=\"Vercel logomark\"\n width={20}\n height={20}\n />\n Deploy now\n </a>\n <a\n className=\"rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44\"\n href=\"https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Read our docs\n </a>\n </div>\n </main>\n <footer className=\"row-start-3 flex gap-6 flex-wrap items-center justify-center\">\n <a\n className=\"flex items-center gap-2 hover:underline hover:underline-offset-4\"\n href=\"https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <Image\n aria-hidden\n src=\"https://nextjs.org/icons/file.svg\"\n alt=\"File icon\"\n width={16}\n height={16}\n />\n Learn\n </a>\n <a\n className=\"flex items-center gap-2 hover:underline hover:underline-offset-4\"\n href=\"https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <Image\n aria-hidden\n src=\"https://nextjs.org/icons/window.svg\"\n alt=\"Window icon\"\n width={16}\n height={16}\n />\n Examples\n </a>\n <a\n className=\"flex items-center gap-2 hover:underline hover:underline-offset-4\"\n href=\"https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <Image\n aria-hidden\n src=\"https://nextjs.org/icons/globe.svg\"\n alt=\"Globe icon\"\n width={16}\n height={16}\n />\n Go to nextjs.org →\n </a>\n </footer>\n </div>\n );\n}\n",
"type": "registry:example"
},
{
"path": "./components/theme-provider.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\"\nimport { type ThemeProviderProps } from \"next-themes/dist/types\"\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n return <NextThemesProvider {...props}>{children}</NextThemesProvider>\n}\n",
"type": "registry:block"
},
{
"path": "./components/theme-toggle.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Moon, Sun } from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\nexport function ModeToggle() {\n const { setTheme } = useTheme()\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"icon\">\n <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n <span className=\"sr-only\">Toggle theme</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => setTheme(\"light\")}>\n Light\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => setTheme(\"dark\")}>\n Dark\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => setTheme(\"system\")}>\n System\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n",
"type": "registry:block"
}
],
"tailwind": {},
"cssVars": {},
"meta": {}
}johndoe 10/9/2024
{
"name": "nextjs-darkmode-prisma-starter",
"type": "registry:block",
"dependencies": [
"@hookform/resolvers",
"@next/swc-wasm-nodejs",
"@prisma/client",
"@radix-ui/react-accordion",
"@radix-ui/react-alert-dialog",
"@radix-ui/react-aspect-ratio",
"@radix-ui/react-avatar",
"@radix-ui/react-checkbox",
"@radix-ui/react-collapsible",
"@radix-ui/react-context-menu",
"@radix-ui/react-dialog",
"@radix-ui/react-dropdown-menu",
"@radix-ui/react-hover-card",
"@radix-ui/react-label",
"@radix-ui/react-menubar",
"@radix-ui/react-navigation-menu",
"@radix-ui/react-popover",
"@radix-ui/react-progress",
"@radix-ui/react-radio-group",
"@radix-ui/react-scroll-area",
"@radix-ui/react-select",
"@radix-ui/react-separator",
"@radix-ui/react-slider",
"@radix-ui/react-slot",
"@radix-ui/react-switch",
"@radix-ui/react-tabs",
"@radix-ui/react-toast",
"@radix-ui/react-toggle",
"@radix-ui/react-toggle-group",
"@radix-ui/react-tooltip",
"@types/node",
"@types/react",
"@types/react-dom",
"autoprefixer",
"cmdk",
"date-fns",
"embla-carousel-react",
"eslint",
"eslint-config-next",
"input-otp",
"next-themes",
"postcss",
"react-day-picker",
"react-hook-form",
"react-resizable-panels",
"recharts",
"sonner",
"tailwindcss",
"typescript",
"vaul",
"zod"
],
"devDependencies": [
"prisma"
],
"registryDependencies": [
"accordion",
"alert-dialog",
"alert",
"aspect-ratio",
"avatar",
"badge",
"breadcrumb",
"button",
"calendar",
"card",
"carousel",
"chart",
"checkbox",
"collapsible",
"command",
"context-menu",
"dialog",
"drawer",
"dropdown-menu",
"form",
"hover-card",
"input-otp",
"input",
"label",
"menubar",
"navigation-menu",
"pagination",
"popover",
"progress",
"radio-group",
"resizable",
"scroll-area",
"select",
"separator",
"sheet",
"skeleton",
"slider",
"sonner",
"switch",
"table",
"tabs",
"textarea",
"toast",
"toggle-group",
"toggle",
"tooltip"
],
"files": [
{
"target": "./app/globals.css",
"path": "./app/globals.css",
"content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n:root {\n --foreground-rgb: 0, 0, 0;\n --background-start-rgb: 214, 219, 220;\n --background-end-rgb: 255, 255, 255;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --foreground-rgb: 255, 255, 255;\n --background-start-rgb: 0, 0, 0;\n --background-end-rgb: 0, 0, 0;\n }\n}\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 0 0% 3.9%;\n --card: 0 0% 100%;\n --card-foreground: 0 0% 3.9%;\n --popover: 0 0% 100%;\n --popover-foreground: 0 0% 3.9%;\n --primary: 0 0% 9%;\n --primary-foreground: 0 0% 98%;\n --secondary: 0 0% 96.1%;\n --secondary-foreground: 0 0% 9%;\n --muted: 0 0% 96.1%;\n --muted-foreground: 0 0% 45.1%;\n --accent: 0 0% 96.1%;\n --accent-foreground: 0 0% 9%;\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 0 0% 98%;\n --border: 0 0% 89.8%;\n --input: 0 0% 89.8%;\n --ring: 0 0% 3.9%;\n --chart-1: 12 76% 61%;\n --chart-2: 173 58% 39%;\n --chart-3: 197 37% 24%;\n --chart-4: 43 74% 66%;\n --chart-5: 27 87% 67%;\n --radius: 0.5rem;\n }\n .dark {\n --background: 0 0% 3.9%;\n --foreground: 0 0% 98%;\n --card: 0 0% 3.9%;\n --card-foreground: 0 0% 98%;\n --popover: 0 0% 3.9%;\n --popover-foreground: 0 0% 98%;\n --primary: 0 0% 98%;\n --primary-foreground: 0 0% 9%;\n --secondary: 0 0% 14.9%;\n --secondary-foreground: 0 0% 98%;\n --muted: 0 0% 14.9%;\n --muted-foreground: 0 0% 63.9%;\n --accent: 0 0% 14.9%;\n --accent-foreground: 0 0% 98%;\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 0% 98%;\n --border: 0 0% 14.9%;\n --input: 0 0% 14.9%;\n --ring: 0 0% 83.1%;\n --chart-1: 220 70% 50%;\n --chart-2: 160 60% 45%;\n --chart-3: 30 80% 55%;\n --chart-4: 280 65% 60%;\n --chart-5: 340 75% 55%;\n }\n}\n\n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}\n",
"type": "registry:example"
},
{
"target": "./app/layout.tsx",
"path": "./app/layout.tsx",
"content": "import './globals.css';\nimport type { Metadata } from 'next';\nimport { Inter } from 'next/font/google';\nimport { ThemeProvider } from '@/components/theme-provider';\nimport Navbar from '@/components/navbar';\n\nconst inter = Inter({ subsets: ['latin'] });\n\nexport const metadata: Metadata = {\n title: 'Next.js Starter with shadcn/ui',\n description: 'A Next.js starter with shadcn/ui, dark mode, and Prisma',\n};\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}) {\n return (\n <html lang=\"en\" suppressHydrationWarning>\n <body className={inter.className}>\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n <Navbar />\n <main className=\"container mx-auto px-4 py-8\">\n {children}\n </main>\n </ThemeProvider>\n </body>\n </html>\n );\n}",
"type": "registry:example"
},
{
"target": "./app/page.tsx",
"path": "./app/page.tsx",
"content": "import { Button } from \"@/components/ui/button\";\n\nexport default function Home() {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen py-2\">\n <h1 className=\"text-4xl font-bold mb-4\">Welcome to Next.js with shadcn/ui</h1>\n <p className=\"text-xl mb-4\">This starter includes dark mode, a simple navbar, and Prisma integration.</p>\n <Button>Get Started</Button>\n </div>\n );\n}",
"type": "registry:example"
},
{
"path": "./components/navbar.tsx",
"content": "\"use client\";\n\nimport Link from 'next/link';\nimport { useTheme } from 'next-themes';\nimport { Button } from '@/components/ui/button';\nimport { Moon, Sun } from 'lucide-react';\n\nexport default function Navbar() {\n const { theme, setTheme } = useTheme();\n\n return (\n <nav className=\"bg-background border-b\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"flex items-center justify-between h-16\">\n <div className=\"flex items-center\">\n <Link href=\"/\" className=\"text-2xl font-bold\">\n Logo\n </Link>\n <div className=\"hidden md:block ml-10\">\n <div className=\"flex items-baseline space-x-4\">\n <Link href=\"/\" className=\"text-foreground hover:text-primary px-3 py-2 rounded-md text-sm font-medium\">\n Home\n </Link>\n <Link href=\"/about\" className=\"text-foreground hover:text-primary px-3 py-2 rounded-md text-sm font-medium\">\n About\n </Link>\n <Link href=\"/contact\" className=\"text-foreground hover:text-primary px-3 py-2 rounded-md text-sm font-medium\">\n Contact\n </Link>\n </div>\n </div>\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Toggle Theme\"\n className=\"ml-auto\"\n onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}\n >\n <Sun className=\"h-6 w-6 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n <Moon className=\"absolute h-6 w-6 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n <span className=\"sr-only\">Toggle Theme</span>\n </Button>\n </div>\n </div>\n </nav>\n );\n}",
"type": "registry:block"
},
{
"path": "./components/theme-provider.tsx",
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\nimport { type ThemeProviderProps } from \"next-themes/dist/types\";\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n return <NextThemesProvider {...props}>{children}</NextThemesProvider>;\n}",
"type": "registry:block"
},
{
"target": "~/prisma/schema.prisma",
"path": "./prisma/schema.prisma",
"content": "generator client {\n provider = \"prisma-client-js\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n email String @unique\n name String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}",
"type": "registry:example"
}
],
"tailwind": {
"config": {}
},
"cssVars": {},
"meta": {}
}johndoe 9/30/2024
{
"name": "framer motion resize panel",
"type": "registry:block",
"registryDependencies": [],
"dependencies": [
"framer-motion"
],
"devDependencies": [],
"tailwind": {
"config": {}
},
"cssVars": {},
"files": [
{
"path": "framer-resize.tsx",
"content": "\"use client\";\n\nimport { useState } from \"react\";\nimport * as ResizablePanel from \"./resizable-panel\";\n\nexport default function Page() {\n let [state, setState] = useState<\"form\" | \"success\">(\"form\");\n\n return (\n <ResizablePanel.Root value={state}>\n <ResizablePanel.Content value=\"form\">\n <p>Reset password</p>\n <label>\n Email address\n <input type=\"text\" />\n </label>\n <button\n onClick={async () => {\n await sendEmail();\n setState(\"success\");\n }}\n >\n Reset your password\n </button>\n </ResizablePanel.Content>\n <ResizablePanel.Content value=\"success\">\n <p>Email sent!</p>\n </ResizablePanel.Content>\n </ResizablePanel.Root>\n );\n}\n",
"type": "registry:component"
},
{
"path": "resizable-panel.tsx",
"content": "// resizable-panel.tsx\n\n\"use client\";\n\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { ComponentProps, ReactNode, createContext, useContext } from \"react\";\nimport useMeasure from \"react-use-measure\";\n\nlet PanelContext = createContext({ value: \"\" });\n\nexport function Root({\n children,\n value,\n ...rest\n}: {\n children: ReactNode;\n value: string;\n} & ComponentProps<\"div\">) {\n let [ref, bounds] = useMeasure();\n\n return (\n <motion.div\n animate={{ height: bounds.height > 0 ? bounds.height : undefined }}\n transition={{ type: \"spring\", bounce: 0, duration: 0.8 }}\n style={{ overflow: \"hidden\", position: \"relative\" }}\n >\n <div ref={ref}>\n <PanelContext.Provider value={{ value }}>\n <div {...rest}>{children}</div>\n </PanelContext.Provider>\n </div>\n </motion.div>\n );\n}\n\nexport function Content({\n value,\n children,\n ...rest\n}: {\n value: string;\n children: ReactNode;\n} & ComponentProps<\"div\">) {\n let panelContext = useContext(PanelContext);\n let isActive = panelContext.value === value;\n\n return (\n <AnimatePresence mode=\"popLayout\" initial={false}>\n {isActive && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{\n opacity: 1,\n transition: {\n type: \"ease\",\n ease: \"easeInOut\",\n duration: 0.3,\n delay: 0.2,\n },\n }}\n exit={{\n opacity: 0,\n transition: {\n type: \"ease\",\n ease: \"easeInOut\",\n duration: 0.2,\n },\n }}\n >\n <div {...rest}>{children}</div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n",
"type": "registry:component"
}
]
}johndoe 9/26/2024
{
"name": "nextjs-lucia-turso-template",
"type": "registry:block",
"dependencies": [
"@hookform/resolvers",
"@libsql/client",
"@lucia-auth/adapter-drizzle",
"@radix-ui/react-avatar",
"@radix-ui/react-dialog",
"@radix-ui/react-dropdown-menu",
"@radix-ui/react-label",
"@radix-ui/react-select",
"@radix-ui/react-slot",
"@radix-ui/react-switch",
"@radix-ui/react-tabs",
"bcrypt",
"dotenv",
"drizzle-orm",
"lucia",
"nanoid",
"next-themes",
"react-hook-form",
"zod"
],
"devDependencies": [
"@types/bcrypt",
"drizzle-kit"
],
"registryDependencies": [
"avatar",
"button",
"card",
"dropdown-menu",
"form",
"input",
"label",
"select",
"sheet",
"switch",
"tabs"
],
"files": [
{
"target": "~/.env.example",
"path": "./.env.example",
"content": "TURSO_CONNECTION_URL=\nTURSO_AUTH_TOKEN=",
"type": "registry:example"
},
{
"target": "~/.eslintrc.json",
"path": "./.eslintrc.json",
"content": "{\n \"extends\": [\n \"next/core-web-vitals\",\n \"next/typescript\"\n ],\n \"rules\": {\n \"@typescript-eslint/no-empty-interface\": \"off\",\n \"@typescript-eslint/no-unused-vars\": \"off\",\n \"@typescript-eslint/no-empty-object-type\": \"off\"\n }\n}",
"type": "registry:example"
},
{
"target": "./app/globals.css",
"path": "./app/globals.css",
"content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n\nbody {\n font-family: Arial, Helvetica, sans-serif;\n}\n\n@layer utilities {\n .text-balance {\n text-wrap: balance;\n }\n}\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 240 10% 3.9%;\n --card: 0 0% 100%;\n --card-foreground: 240 10% 3.9%;\n --popover: 0 0% 100%;\n --popover-foreground: 240 10% 3.9%;\n --primary: 240 5.9% 10%;\n --primary-foreground: 0 0% 98%;\n --secondary: 240 4.8% 95.9%;\n --secondary-foreground: 240 5.9% 10%;\n --muted: 240 4.8% 95.9%;\n --muted-foreground: 240 3.8% 46.1%;\n --accent: 240 4.8% 95.9%;\n --accent-foreground: 240 5.9% 10%;\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 0 0% 98%;\n --border: 240 5.9% 90%;\n --input: 240 5.9% 90%;\n --ring: 240 10% 3.9%;\n --chart-1: 12 76% 61%;\n --chart-2: 173 58% 39%;\n --chart-3: 197 37% 24%;\n --chart-4: 43 74% 66%;\n --chart-5: 27 87% 67%;\n --radius: 0.5rem;\n }\n .dark {\n --background: 240 10% 3.9%;\n --foreground: 0 0% 98%;\n --card: 240 10% 3.9%;\n --card-foreground: 0 0% 98%;\n --popover: 240 10% 3.9%;\n --popover-foreground: 0 0% 98%;\n --primary: 0 0% 98%;\n --primary-foreground: 240 5.9% 10%;\n --secondary: 240 3.7% 15.9%;\n --secondary-foreground: 0 0% 98%;\n --muted: 240 3.7% 15.9%;\n --muted-foreground: 240 5% 64.9%;\n --accent: 240 3.7% 15.9%;\n --accent-foreground: 0 0% 98%;\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 0% 98%;\n --border: 240 3.7% 15.9%;\n --input: 240 3.7% 15.9%;\n --ring: 240 4.9% 83.9%;\n --chart-1: 220 70% 50%;\n --chart-2: 160 60% 45%;\n --chart-3: 30 80% 55%;\n --chart-4: 280 65% 60%;\n --chart-5: 340 75% 55%;\n }\n}\n\n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}\n",
"type": "registry:example"
},
{
"target": "./app/layout.tsx",
"path": "./app/layout.tsx",
"content": "import type { Metadata } from \"next\";\nimport localFont from \"next/font/local\";\nimport \"./globals.css\";\nimport { ThemeProvider } from \"@/components/theme-provider\";\nimport Navbar from \"@/components/navbar\";\n\nconst geistSans = localFont({\n src: \"./fonts/GeistVF.woff\",\n variable: \"--font-geist-sans\",\n weight: \"100 900\",\n});\nconst geistMono = localFont({\n src: \"./fonts/GeistMonoVF.woff\",\n variable: \"--font-geist-mono\",\n weight: \"100 900\",\n});\n\nexport const metadata: Metadata = {\n title: \"Create Next App\",\n description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n children,\n}: Readonly<{\n children: React.ReactNode;\n}>) {\n return (\n <html lang=\"en\">\n <body\n className={`${geistSans.variable} ${geistMono.variable} antialiased`}\n >\n <ThemeProvider\n attribute=\"class\"\n defaultTheme=\"system\"\n enableSystem\n disableTransitionOnChange\n >\n <Navbar />\n {children}\n </ThemeProvider>\n </body>\n </html>\n );\n}\n",
"type": "registry:example"
},
{
"target": "./app/login/login-form.tsx",
"path": "./app/login/login-form.tsx",
"content": "'use client'\n\nimport Link from \"next/link\"\nimport { useForm } from \"react-hook-form\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\"\n\nconst loginSchema = z.object({\n email: z.string().email({ message: \"Invalid email address\" }),\n password: z.string().min(8, { message: \"Password must be at least 8 characters\" }),\n})\n\ntype LoginFormValues = z.infer<typeof loginSchema>\n\nexport function LoginForm() {\n const form = useForm<LoginFormValues>({\n resolver: zodResolver(loginSchema),\n defaultValues: {\n email: \"\",\n password: \"\",\n },\n })\n\n function onSubmit(values: LoginFormValues) {\n // Handle form submission\n console.log(values)\n }\n\n return (\n <Card className=\"mx-auto max-w-sm\">\n <CardHeader>\n <CardTitle className=\"text-2xl\">Login</CardTitle>\n <CardDescription>\n Enter your email below to login to your account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"grid gap-4\">\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Email</FormLabel>\n <FormControl>\n <Input\n type=\"email\"\n placeholder=\"[email protected]\"\n {...field}\n />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name=\"password\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex items-center\">\n <FormLabel>Password</FormLabel>\n <Link href=\"#\" className=\"ml-auto inline-block text-sm underline\">\n Forgot your password?\n </Link>\n </div>\n <FormControl>\n <Input type=\"password\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" className=\"w-full\">\n Login\n </Button>\n <Button type=\"button\" variant=\"outline\" className=\"w-full\">\n Login with Google\n </Button>\n </form>\n </Form>\n <div className=\"mt-4 text-center text-sm\">\n Don't have an account?{\" \"}\n <Link href=\"/signup\" className=\"underline\">\n Sign up\n </Link>\n </div>\n </CardContent>\n </Card>\n )\n}\n",
"type": "registry:example"
},
{
"target": "./app/login/page.tsx",
"path": "./app/login/page.tsx",
"content": "import { LoginForm } from \"./login-form\"\n\nexport default function Page() {\n return (\n <div className=\"flex h-screen w-full items-center justify-center px-4\">\n <LoginForm />\n </div>\n )\n}\n",
"type": "registry:example"
},
{
"target": "./app/settings/page.tsx",
"path": "./app/settings/page.tsx",
"content": "import ProfileSettings from \"./profile-settings\";\n\nexport default function Page() {\n return (\n <div className=\"\">\n <ProfileSettings />\n </div>\n )\n}",
"type": "registry:example"
},
{
"target": "./app/settings/profile-settings.tsx",
"path": "./app/settings/profile-settings.tsx",
"content": "'use client'\n\nimport { useState } from \"react\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { Switch } from \"@/components/ui/switch\"\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\"\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from \"@/components/ui/card\"\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Bell, Lock, User } from \"lucide-react\"\n\nexport default function ProfileSettings() {\n const [name, setName] = useState(\"John Doe\")\n const [email, setEmail] = useState(\"[email protected]\")\n\n return (\n <div className=\"container mx-auto py-10 max-w-3xl\">\n <h1 className=\"text-3xl font-bold mb-8\">Account Settings</h1>\n <div className=\"flex items-center space-x-6 mb-8\">\n <Avatar className=\"h-24 w-24\">\n <AvatarImage src=\"/placeholder.svg?height=96&width=96\" alt=\"@johndoe\" />\n <AvatarFallback>JD</AvatarFallback>\n </Avatar>\n <div>\n <h2 className=\"text-2xl font-semibold\">{name}</h2>\n <p className=\"text-muted-foreground\">{email}</p>\n </div>\n </div>\n <Tabs defaultValue=\"personal\" className=\"space-y-6\">\n <TabsList className=\"grid w-full grid-cols-3\">\n <TabsTrigger value=\"personal\">\n <User className=\"mr-2 h-4 w-4\" />\n Personal Info\n </TabsTrigger>\n <TabsTrigger value=\"security\">\n <Lock className=\"mr-2 h-4 w-4\" />\n Security\n </TabsTrigger>\n <TabsTrigger value=\"notifications\">\n <Bell className=\"mr-2 h-4 w-4\" />\n Notifications\n </TabsTrigger>\n </TabsList>\n <TabsContent value=\"personal\">\n <Card>\n <CardHeader>\n <CardTitle>Personal Information</CardTitle>\n <CardDescription>Update your personal details here.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-6\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"name\">Name</Label>\n <Input id=\"name\" value={name} onChange={(e) => setName(e.target.value)} />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" value={email} onChange={(e) => setEmail(e.target.value)} />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"bio\">Bio</Label>\n <Input id=\"bio\" placeholder=\"Tell us about yourself\" />\n </div>\n </CardContent>\n <CardFooter>\n <Button>Save Changes</Button>\n </CardFooter>\n </Card>\n </TabsContent>\n <TabsContent value=\"security\">\n <Card>\n <CardHeader>\n <CardTitle>Security Settings</CardTitle>\n <CardDescription>Manage your account's security.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-6\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"current-password\">Current Password</Label>\n <Input id=\"current-password\" type=\"password\" />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"new-password\">New Password</Label>\n <Input id=\"new-password\" type=\"password\" />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"confirm-password\">Confirm New Password</Label>\n <Input id=\"confirm-password\" type=\"password\" />\n </div>\n <div className=\"flex items-center space-x-2\">\n <Switch id=\"2fa\" />\n <Label htmlFor=\"2fa\">Enable Two-Factor Authentication</Label>\n </div>\n </CardContent>\n <CardFooter>\n <Button>Update Security Settings</Button>\n </CardFooter>\n </Card>\n </TabsContent>\n <TabsContent value=\"notifications\">\n <Card>\n <CardHeader>\n <CardTitle>Notification Preferences</CardTitle>\n <CardDescription>Manage how you receive notifications.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-6\">\n <div className=\"flex items-center justify-between\">\n <div className=\"space-y-0.5\">\n <Label htmlFor=\"email-notifications\">Email Notifications</Label>\n <p className=\"text-sm text-muted-foreground\">Receive notifications via email</p>\n </div>\n <Switch id=\"email-notifications\" />\n </div>\n <div className=\"flex items-center justify-between\">\n <div className=\"space-y-0.5\">\n <Label htmlFor=\"push-notifications\">Push Notifications</Label>\n <p className=\"text-sm text-muted-foreground\">Receive push notifications on your device</p>\n </div>\n <Switch id=\"push-notifications\" />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"notification-frequency\">Notification Frequency</Label>\n <Select>\n <SelectTrigger id=\"notification-frequency\">\n <SelectValue placeholder=\"Select frequency\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"real-time\">Real-time</SelectItem>\n <SelectItem value=\"daily\">Daily Digest</SelectItem>\n <SelectItem value=\"weekly\">Weekly Summary</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </CardContent>\n <CardFooter>\n <Button>Save Notification Settings</Button>\n </CardFooter>\n </Card>\n </TabsContent>\n </Tabs>\n </div>\n )\n}",
"type": "registry:example"
},
{
"target": "./app/signup/page.tsx",
"path": "./app/signup/page.tsx",
"content": "import { SignupForm } from \"./signup-form\"\n\nexport default function Page() {\n return <div className=\"flex place-items-center min-h-screen\">\n <SignupForm />\n </div>\n}",
"type": "registry:example"
},
{
"target": "./app/signup/signup-form.tsx",
"path": "./app/signup/signup-form.tsx",
"content": "'use client'\n\nimport Link from \"next/link\"\nimport { useForm } from \"react-hook-form\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport * as z from \"zod\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\"\n\nconst signupSchema = z.object({\n firstName: z.string().min(1, { message: \"First name is required\" }),\n lastName: z.string().min(1, { message: \"Last name is required\" }),\n email: z.string().email({ message: \"Invalid email address\" }),\n password: z.string().min(8, { message: \"Password must be at least 8 characters\" }),\n})\n\ntype SignupFormValues = z.infer<typeof signupSchema>\n\nexport const description =\n \"A sign up form with first name, last name, email and password inside a card. There's an option to sign up with GitHub and a link to login if you already have an account\"\n\nexport function SignupForm() {\n const form = useForm<SignupFormValues>({\n resolver: zodResolver(signupSchema),\n defaultValues: {\n firstName: \"\",\n lastName: \"\",\n email: \"\",\n password: \"\",\n },\n })\n\n function onSubmit(values: SignupFormValues) {\n // Handle form submission\n console.log(values)\n }\n\n return (\n <Card className=\"mx-auto max-w-sm\">\n <CardHeader>\n <CardTitle className=\"text-xl\">Sign Up</CardTitle>\n <CardDescription>\n Enter your information to create an account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"grid gap-4\">\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField\n control={form.control}\n name=\"firstName\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>First name</FormLabel>\n <FormControl>\n <Input placeholder=\"Max\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name=\"lastName\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Last name</FormLabel>\n <FormControl>\n <Input placeholder=\"Robinson\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n </div>\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Email</FormLabel>\n <FormControl>\n <Input\n type=\"email\"\n placeholder=\"[email protected]\"\n {...field}\n />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name=\"password\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Password</FormLabel>\n <FormControl>\n <Input type=\"password\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" className=\"w-full\">\n Create an account\n </Button>\n <Button variant=\"outline\" className=\"w-full\">\n Sign up with GitHub\n </Button>\n </form>\n </Form>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{\" \"}\n <Link href=\"/login\" className=\"underline\">\n Sign in\n </Link>\n </div>\n </CardContent>\n </Card>\n )\n}\n",
"type": "registry:example"
},
{
"path": "./components/auth-avatar.tsx",
"content": "import { validateRequest } from \"@/lib/lucia\";\nimport { Button } from \"./ui/button\";\nimport Link from \"next/link\";\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from \"./ui/dropdown-menu\";\nimport { logout } from \"@/lib/lucia-actions\";\nimport { Avatar, AvatarFallback } from \"./ui/avatar\";\n\nexport async function AuthAvatar() {\n const { user } = await validateRequest()\n\n if (!user) {\n return <Link href=\"/login\">\n <Button>Login</Button>\n </Link>\n }\n\n async function handleLogout() {\n 'use server'\n await logout()\n }\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger>\n <Avatar>\n {/* <AvatarImage src=\"https://github.com/shadcn.png\" /> */}\n <AvatarFallback className=\"bg-secondary p-2 rounded-full\">{user.username.slice(0, 2).toUpperCase()}</AvatarFallback>\n </Avatar>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\" w-40\">\n <DropdownMenuItem>\n\n <form action={handleLogout} className=\"w-full\">\n <button type=\"submit\" className=\"w-full\">Logout</button>\n </form>\n {/* <a href=\"/api/logout\">Logout</a> */}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n",
"type": "registry:block"
},
{
"path": "./components/navbar.tsx",
"content": "import React from 'react';\nimport { Button } from \"@/components/ui/button\";\nimport {\n Sheet,\n SheetContent,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Menu } from \"lucide-react\";\nimport { ModeToggle } from './theme-toggle';\nimport Link from 'next/link';\n\nconst Navbar = () => {\n const navItems = [\n { name: 'Home', href: '/' },\n { name: 'Settings', href: '/settings' },\n ];\n\n return (\n <nav className=\" p-4\">\n <div className=\"container mx-auto flex justify-between items-center\">\n <a href=\"/\" className=\" font-bold text-xl\">Logo</a>\n\n {/* Desktop Menu */}\n <div className=\"hidden md:flex space-x-4\">\n {navItems.map((item) => (\n <Button key={item.name} variant=\"ghost\" className=\"\" asChild>\n <a href={item.href}>{item.name}</a>\n </Button>\n ))}\n <Link href={\"/login\"}>\n <Button>Login</Button>\n </Link>\n <ModeToggle />\n </div>\n\n {/* Mobile Menu */}\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"md:hidden\">\n <Menu className=\"h-6 w-6\" />\n </Button>\n </SheetTrigger>\n <SheetContent>\n <div className=\"flex flex-col space-y-4 mt-4\">\n {navItems.map((item) => (\n <Button key={item.name} variant=\"ghost\" asChild>\n <a href={item.href}>{item.name}</a>\n </Button>\n ))}\n <Button asChild>\n <Link href={\"/login\"}>\n Login\n </Link>\n </Button>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </nav>\n );\n};\n\nexport default Navbar;",
"type": "registry:block"
},
{
"path": "./components/theme-provider.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\"\nimport { type ThemeProviderProps } from \"next-themes/dist/types\"\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n return <NextThemesProvider {...props}>{children}</NextThemesProvider>\n}\n",
"type": "registry:block"
},
{
"path": "./components/theme-toggle.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Moon, Sun } from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\nexport function ModeToggle() {\n const { setTheme } = useTheme()\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"icon\">\n <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n <span className=\"sr-only\">Toggle theme</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => setTheme(\"light\")}>\n Light\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => setTheme(\"dark\")}>\n Dark\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => setTheme(\"system\")}>\n System\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n",
"type": "registry:block"
},
{
"target": "~/drizzle.config.ts",
"path": "./drizzle.config.ts",
"content": "import { config } from 'dotenv';\nimport { defineConfig } from 'drizzle-kit';\n\nconfig({ path: '.env' });\n\nexport default defineConfig({\n schema: './lib/schema.ts',\n out: './migrations',\n dialect: 'sqlite',\n driver: 'turso',\n dbCredentials: {\n url: process.env.TURSO_CONNECTION_URL!,\n authToken: process.env.TURSO_AUTH_TOKEN!,\n },\n});",
"type": "registry:example"
},
{
"path": "./lib/captcha.ts",
"content": "export async function verifyCaptchaToken(token: string) {\n const verifyUrl = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';\n\n // Make a request to verify the CAPTCHA token\n const response = await fetch(verifyUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n secret: process.env.CAPTCHA_KEY!,\n response: token,\n }),\n });\n\n const data = await response.json();\n\n return data.success as boolean; // Return true if CAPTCHA verification is successful, false otherwise\n}",
"type": "registry:lib"
},
{
"path": "./lib/db.ts",
"content": "import { config } from 'dotenv';\nimport { drizzle } from 'drizzle-orm/libsql';\nimport { createClient } from '@libsql/client';\nimport { DrizzleSQLiteAdapter } from '@lucia-auth/adapter-drizzle';\nimport { sessionTable, userTable } from './schema';\n\nconfig({ path: '.env' }); // or .env.local\n\nconst client = createClient({\n url: process.env.TURSO_CONNECTION_URL!,\n authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\nexport const db = drizzle(client);\nexport const adapter = new DrizzleSQLiteAdapter(db, sessionTable, userTable);",
"type": "registry:lib"
},
{
"path": "./lib/hash.ts",
"content": "import bcrypt from 'bcrypt'\n\nexport async function hashPassword(plainPassword:string) {\n const saltRounds = 10;\n try {\n const hash = await bcrypt.hash(plainPassword, saltRounds);\n return hash;\n } catch (error) {\n console.error('Error hashing password:', error);\n throw error;\n }\n}\n\n// Function to verify a password\nexport async function verifyPassword(plainPassword:string, storedHash:string) {\n try {\n const match = await bcrypt.compare(plainPassword, storedHash);\n return match; // true if match, false otherwise\n } catch (error) {\n console.error('Error verifying password:', error);\n throw error;\n }\n}",
"type": "registry:lib"
},
{
"path": "./lib/lucia-actions.ts",
"content": "'use server'\n\nimport { db } from \"@/lib/db\";\nimport { verifyPassword, hashPassword } from '@/lib/hash'\nimport { cookies } from \"next/headers\";\nimport { lucia, validateRequest } from \"@/lib/lucia\";\nimport { redirect } from \"next/navigation\";\nimport { verifyCaptchaToken } from \"@/lib/captcha\";\nimport { userTable } from \"@/lib/schema\";\nimport { eq } from \"drizzle-orm\";\nimport { nanoid } from \"nanoid\";\n\n\nexport async function signup({\n username,\n password,\n email,\n captchaToken\n}: {\n username: string,\n password: string,\n email: string,\n captchaToken?: string\n}) {\n\n console.log('signup', username, password, email, captchaToken)\n\n // if (!captchaToken) {\n // return {\n // error: \"Invalid captcha\"\n // };\n // }\n\n // const captchaResult = await verifyCaptchaToken(captchaToken);\n\n // if (!captchaResult) {\n // return {\n // error: \"Invalid captcha\"\n // };\n // }\n\n\n // username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _\n // keep in mind some database (e.g. mysql) are case insensitive\n if (\n typeof username !== \"string\" ||\n username.length < 3 ||\n username.length > 31\n ) {\n return {\n error: \"Invalid username\"\n };\n }\n\n if (typeof password !== \"string\" || password.length < 6 || password.length > 255) {\n return {\n error: \"Invalid password\"\n };\n }\n\n const passwordHash = await hashPassword(password);\n\n // check if username is used\n const existingUser = await db.select().from(userTable).where(eq(userTable.username, username))\n\n if (existingUser.length > 0) {\n console.log('username is taken')\n\n return {\n error: \"Username is taken\"\n };\n }\n\n\n\n try {\n const user = await db.insert(userTable).values({\n id: nanoid(),\n username: username,\n password: passwordHash\n }).returning();\n\n const session = await lucia.createSession(user[0].id, {});\n const sessionCookie = lucia.createSessionCookie(session.id);\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n console.log('user created!');\n\n } catch (error) {\n return {\n error: \"Failed to create user\"\n };\n }\n\n\n}\n\n\nexport async function login({\n username,\n password,\n captchaToken\n}: {\n username: string,\n password: string,\n captchaToken?: string\n}) {\n\n // if (!captchaToken) {\n // return {\n // error: \"Invalid captcha\"\n // };\n // }\n\n // const captchaResult = await verifyCaptchaToken(captchaToken);\n\n // if (!captchaResult) {\n // return {\n // error: \"Invalid captcha\"\n // };\n // }\n\n if (\n typeof username !== \"string\" ||\n username.length < 3 ||\n username.length > 31\n ) {\n return {\n error: \"Invalid username\"\n };\n }\n\n if (typeof password !== \"string\" || password.length < 6 || password.length > 255) {\n return {\n error: \"Invalid password\"\n };\n }\n\n\n const existingUser = await db.select().from(userTable).where(eq(userTable.username, username))\n\n\n if (existingUser.length === 0) {\n // NOTE:\n // Returning immediately allows malicious actors to figure out valid usernames from response times,\n // allowing them to only focus on guessing passwords in brute-force attacks.\n // As a preventive measure, you may want to hash passwords even for invalid usernames.\n // However, valid usernames can be already be revealed with the signup page among other methods.\n // It will also be much more resource intensive.\n // Since protecting against this is non-trivial,\n // it is crucial your implementation is protected against brute-force attacks with login throttling etc.\n // If usernames are public, you may outright tell the user that the username is invalid.\n return {\n error: \"Incorrect username or password\"\n };\n }\n\n const validPassword = await verifyPassword(password, existingUser[0].password);\n\n if (!validPassword) {\n return {\n error: \"Incorrect username or password\"\n };\n }\n\n\n const session = await lucia.createSession(existingUser[0].id, {});\n const sessionCookie = lucia.createSessionCookie(session.id);\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n return redirect(\"/\");\n}\n\n\n\nexport async function logout() {\n\n const { session } = await validateRequest();\n if (!session) {\n return {\n error: \"Unauthorized\"\n };\n }\n\n await lucia.invalidateSession(session.id);\n\n const sessionCookie = lucia.createBlankSessionCookie();\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n redirect(\"/login\")\n}",
"type": "registry:lib"
},
{
"path": "./lib/lucia.ts",
"content": "import { Lucia, Session, User } from \"lucia\";\nimport { db } from \"@/lib/db\";\nimport { cache } from \"react\";\nimport { cookies } from \"next/headers\";\nimport { adapter } from \"./db\";\n\nexport const lucia = new Lucia(adapter, {\n sessionCookie: {\n expires: false,\n attributes: {\n secure: process.env.NODE_ENV === \"production\"\n }\n },\n getUserAttributes: (attributes) => {\n return {\n // attributes has the type of DatabaseUserAttributes\n username: attributes.username,\n id: attributes.id\n };\n }\n});\n\ndeclare module \"lucia\" {\n interface Register {\n Lucia: typeof lucia;\n DatabaseUserAttributes: DatabaseUserAttributes;\n }\n}\n\ninterface DatabaseUserAttributes {\n username: string;\n id: string;\n}\n\nexport const validateRequest = cache(\n async (): Promise<{ user: User; session: Session } | { user: null; session: null }> => {\n const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null;\n if (!sessionId) {\n return {\n user: null,\n session: null\n };\n }\n\n const result = await lucia.validateSession(sessionId);\n // next.js throws when you attempt to set cookie when rendering page\n try {\n if (result.session && result.session.fresh) {\n const sessionCookie = lucia.createSessionCookie(result.session.id);\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n }\n if (!result.session) {\n const sessionCookie = lucia.createBlankSessionCookie();\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n }\n } catch { }\n return result;\n }\n);",
"type": "registry:lib"
},
{
"path": "./lib/schema.ts",
"content": "import { sqliteTable, text, integer } from \"drizzle-orm/sqlite-core\";\n\nexport const userTable = sqliteTable(\"user\", {\n id: text(\"id\").primaryKey(),\n username: text(\"username\").notNull(),\n password: text(\"password\").notNull()\n});\n\nexport const sessionTable = sqliteTable(\"session\", {\n id: text(\"id\").primaryKey(),\n userId: text(\"user_id\")\n .notNull()\n .references(() => userTable.id, { onDelete: \"cascade\" }),\n expiresAt: integer(\"expires_at\").notNull()\n});\n\n",
"type": "registry:lib"
}
],
"tailwind": {},
"cssVars": {},
"meta": {}
}johndoe 9/23/2024
{
"name": "lucia-nextjs-drizzle",
"type": "registry:block",
"registryDependencies": [],
"dependencies": [
"lucia",
"bcrypt",
"drizzle-orm",
"nanoid",
"@libsql/client",
"@lucia-auth/adapter-drizzle"
],
"devDependencies": [],
"tailwind": {
"config": {}
},
"cssVars": {},
"files": [
{
"path": "captcha.ts",
"content": "export async function verifyCaptchaToken(token: string) {\n const verifyUrl = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';\n\n // Make a request to verify the CAPTCHA token\n const response = await fetch(verifyUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n secret: process.env.CAPTCHA_KEY!,\n response: token,\n }),\n });\n\n const data = await response.json();\n\n return data.success as boolean; // Return true if CAPTCHA verification is successful, false otherwise\n}",
"type": "registry:lib"
},
{
"path": "db.ts",
"content": "import { config } from 'dotenv';\nimport { drizzle } from 'drizzle-orm/libsql';\nimport { createClient } from '@libsql/client';\nimport { DrizzleSQLiteAdapter } from '@lucia-auth/adapter-drizzle';\nimport { sessionTable, userTable } from './schema';\n\nconfig({ path: '.env' }); // or .env.local\n\nconst client = createClient({\n url: process.env.TURSO_CONNECTION_URL!,\n authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\nexport const db = drizzle(client);\nexport const adapter = new DrizzleSQLiteAdapter(db, sessionTable, userTable);",
"type": "registry:lib"
},
{
"path": "hash.ts",
"content": "import bcrypt from 'bcrypt'\n\nexport async function hashPassword(plainPassword:string) {\n const saltRounds = 10;\n try {\n const hash = await bcrypt.hash(plainPassword, saltRounds);\n return hash;\n } catch (error) {\n console.error('Error hashing password:', error);\n throw error;\n }\n}\n\n// Function to verify a password\nexport async function verifyPassword(plainPassword:string, storedHash:string) {\n try {\n const match = await bcrypt.compare(plainPassword, storedHash);\n return match; // true if match, false otherwise\n } catch (error) {\n console.error('Error verifying password:', error);\n throw error;\n }\n}",
"type": "registry:lib"
},
{
"path": "lucia-actions.ts",
"content": "'use server'\n\nimport { db } from \"@/lib/db\";\nimport { verifyPassword, hashPassword } from '@/lib/hash'\nimport { cookies } from \"next/headers\";\nimport { lucia, validateRequest } from \"@/lib/lucia\";\nimport { redirect } from \"next/navigation\";\nimport { verifyCaptchaToken } from \"@/lib/captcha\";\nimport { userTable } from \"@/lib/schema\";\nimport { eq } from \"drizzle-orm\";\nimport { nanoid } from \"nanoid\";\n\n\nexport async function signup({\n username,\n password,\n email,\n captchaToken\n}: {\n username: string,\n password: string,\n email: string,\n captchaToken?: string\n}) {\n\n console.log('signup', username, password, email, captchaToken)\n\n // if (!captchaToken) {\n // return {\n // error: \"Invalid captcha\"\n // };\n // }\n\n // const captchaResult = await verifyCaptchaToken(captchaToken);\n\n // if (!captchaResult) {\n // return {\n // error: \"Invalid captcha\"\n // };\n // }\n\n\n // username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _\n // keep in mind some database (e.g. mysql) are case insensitive\n if (\n typeof username !== \"string\" ||\n username.length < 3 ||\n username.length > 31\n ) {\n return {\n error: \"Invalid username\"\n };\n }\n\n if (typeof password !== \"string\" || password.length < 6 || password.length > 255) {\n return {\n error: \"Invalid password\"\n };\n }\n\n const passwordHash = await hashPassword(password);\n\n // check if username is used\n const existingUser = await db.select().from(userTable).where(eq(userTable.username, username))\n\n if (existingUser.length > 0) {\n console.log('username is taken')\n\n return {\n error: \"Username is taken\"\n };\n }\n\n\n\n try {\n const user = await db.insert(userTable).values({\n id: nanoid(),\n username: username,\n password: passwordHash\n }).returning();\n\n const session = await lucia.createSession(user[0].id, {});\n const sessionCookie = lucia.createSessionCookie(session.id);\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n console.log('user created!');\n\n } catch (error) {\n return {\n error: \"Failed to create user\"\n };\n }\n\n\n}\n\n\nexport async function login({\n username,\n password,\n captchaToken\n}: {\n username: string,\n password: string,\n captchaToken?: string\n}) {\n\n // if (!captchaToken) {\n // return {\n // error: \"Invalid captcha\"\n // };\n // }\n\n // const captchaResult = await verifyCaptchaToken(captchaToken);\n\n // if (!captchaResult) {\n // return {\n // error: \"Invalid captcha\"\n // };\n // }\n\n if (\n typeof username !== \"string\" ||\n username.length < 3 ||\n username.length > 31\n ) {\n return {\n error: \"Invalid username\"\n };\n }\n\n if (typeof password !== \"string\" || password.length < 6 || password.length > 255) {\n return {\n error: \"Invalid password\"\n };\n }\n\n\n const existingUser = await db.select().from(userTable).where(eq(userTable.username, username))\n\n\n if (existingUser.length === 0) {\n // NOTE:\n // Returning immediately allows malicious actors to figure out valid usernames from response times,\n // allowing them to only focus on guessing passwords in brute-force attacks.\n // As a preventive measure, you may want to hash passwords even for invalid usernames.\n // However, valid usernames can be already be revealed with the signup page among other methods.\n // It will also be much more resource intensive.\n // Since protecting against this is non-trivial,\n // it is crucial your implementation is protected against brute-force attacks with login throttling etc.\n // If usernames are public, you may outright tell the user that the username is invalid.\n return {\n error: \"Incorrect username or password\"\n };\n }\n\n const validPassword = await verifyPassword(password, existingUser[0].password);\n\n if (!validPassword) {\n return {\n error: \"Incorrect username or password\"\n };\n }\n\n\n const session = await lucia.createSession(existingUser[0].id, {});\n const sessionCookie = lucia.createSessionCookie(session.id);\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n return redirect(\"/\");\n}\n\n\n\nexport async function logout() {\n\n const { session } = await validateRequest();\n if (!session) {\n return {\n error: \"Unauthorized\"\n };\n }\n\n await lucia.invalidateSession(session.id);\n\n const sessionCookie = lucia.createBlankSessionCookie();\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n redirect(\"/login\")\n}",
"type": "registry:lib"
},
{
"path": "lucia.ts",
"content": "import { Lucia, Session, User } from \"lucia\";\nimport { PrismaAdapter } from \"@lucia-auth/adapter-prisma\";\nimport { db } from \"@/lib/db\";\nimport { cache } from \"react\";\nimport { cookies } from \"next/headers\";\nimport { adapter } from \"./db\";\n\nexport const lucia = new Lucia(adapter, {\n sessionCookie: {\n expires: false,\n attributes: {\n secure: process.env.NODE_ENV === \"production\"\n }\n },\n getUserAttributes: (attributes) => {\n return {\n // attributes has the type of DatabaseUserAttributes\n username: attributes.username,\n id: attributes.id\n };\n }\n});\n\ndeclare module \"lucia\" {\n interface Register {\n Lucia: typeof lucia;\n DatabaseUserAttributes: DatabaseUserAttributes;\n }\n}\n\ninterface DatabaseUserAttributes {\n username: string;\n id: string;\n}\n\nexport const validateRequest = cache(\n async (): Promise<{ user: User; session: Session } | { user: null; session: null }> => {\n const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null;\n if (!sessionId) {\n return {\n user: null,\n session: null\n };\n }\n\n const result = await lucia.validateSession(sessionId);\n // next.js throws when you attempt to set cookie when rendering page\n try {\n if (result.session && result.session.fresh) {\n const sessionCookie = lucia.createSessionCookie(result.session.id);\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n }\n if (!result.session) {\n const sessionCookie = lucia.createBlankSessionCookie();\n cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);\n }\n } catch { }\n return result;\n }\n);",
"type": "registry:lib"
},
{
"path": "schema.ts",
"content": "import { sqliteTable, text, integer } from \"drizzle-orm/sqlite-core\";\n\nexport const userTable = sqliteTable(\"user\", {\n id: text(\"id\").primaryKey(),\n username: text(\"username\").notNull(),\n password: text(\"password\").notNull()\n});\n\nexport const sessionTable = sqliteTable(\"session\", {\n id: text(\"id\").primaryKey(),\n userId: text(\"user_id\")\n .notNull()\n .references(() => userTable.id, { onDelete: \"cascade\" }),\n expiresAt: integer(\"expires_at\").notNull()\n});\n\n",
"type": "registry:lib"
}
]
}johndoe 9/23/2024
{
"name": "profile-settings-page",
"type": "registry:block",
"registryDependencies": [
"button",
"input",
"label",
"switch",
"tabs",
"card",
"select",
"avatar"
],
"dependencies": [
"lucide-react"
],
"devDependencies": [],
"tailwind": {
"config": {}
},
"cssVars": {},
"files": [
{
"target": "app/settings/page.tsx",
"path": "page.tsx",
"content": "import ProfileSettings from \"@/components/profile-settings\";\n\nexport default function Page() {\n return (\n <div className=\"\">\n <ProfileSettings />\n </div>\n )\n}",
"type": "registry:page"
},
{
"path": "profile-settings.tsx",
"content": "'use client'\n\nimport { useState } from \"react\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { Switch } from \"@/components/ui/switch\"\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\"\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from \"@/components/ui/card\"\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Bell, Lock, User } from \"lucide-react\"\n\nexport default function ProfileSettings() {\n const [name, setName] = useState(\"John Doe\")\n const [email, setEmail] = useState(\"[email protected]\")\n\n return (\n <div className=\"container mx-auto py-10 max-w-3xl\">\n <h1 className=\"text-3xl font-bold mb-8\">Account Settings</h1>\n <div className=\"flex items-center space-x-6 mb-8\">\n <Avatar className=\"h-24 w-24\">\n <AvatarImage src=\"/placeholder.svg?height=96&width=96\" alt=\"@johndoe\" />\n <AvatarFallback>JD</AvatarFallback>\n </Avatar>\n <div>\n <h2 className=\"text-2xl font-semibold\">{name}</h2>\n <p className=\"text-muted-foreground\">{email}</p>\n </div>\n </div>\n <Tabs defaultValue=\"personal\" className=\"space-y-6\">\n <TabsList className=\"grid w-full grid-cols-3\">\n <TabsTrigger value=\"personal\">\n <User className=\"mr-2 h-4 w-4\" />\n Personal Info\n </TabsTrigger>\n <TabsTrigger value=\"security\">\n <Lock className=\"mr-2 h-4 w-4\" />\n Security\n </TabsTrigger>\n <TabsTrigger value=\"notifications\">\n <Bell className=\"mr-2 h-4 w-4\" />\n Notifications\n </TabsTrigger>\n </TabsList>\n <TabsContent value=\"personal\">\n <Card>\n <CardHeader>\n <CardTitle>Personal Information</CardTitle>\n <CardDescription>Update your personal details here.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-6\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"name\">Name</Label>\n <Input id=\"name\" value={name} onChange={(e) => setName(e.target.value)} />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" value={email} onChange={(e) => setEmail(e.target.value)} />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"bio\">Bio</Label>\n <Input id=\"bio\" placeholder=\"Tell us about yourself\" />\n </div>\n </CardContent>\n <CardFooter>\n <Button>Save Changes</Button>\n </CardFooter>\n </Card>\n </TabsContent>\n <TabsContent value=\"security\">\n <Card>\n <CardHeader>\n <CardTitle>Security Settings</CardTitle>\n <CardDescription>Manage your account's security.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-6\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"current-password\">Current Password</Label>\n <Input id=\"current-password\" type=\"password\" />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"new-password\">New Password</Label>\n <Input id=\"new-password\" type=\"password\" />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"confirm-password\">Confirm New Password</Label>\n <Input id=\"confirm-password\" type=\"password\" />\n </div>\n <div className=\"flex items-center space-x-2\">\n <Switch id=\"2fa\" />\n <Label htmlFor=\"2fa\">Enable Two-Factor Authentication</Label>\n </div>\n </CardContent>\n <CardFooter>\n <Button>Update Security Settings</Button>\n </CardFooter>\n </Card>\n </TabsContent>\n <TabsContent value=\"notifications\">\n <Card>\n <CardHeader>\n <CardTitle>Notification Preferences</CardTitle>\n <CardDescription>Manage how you receive notifications.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-6\">\n <div className=\"flex items-center justify-between\">\n <div className=\"space-y-0.5\">\n <Label htmlFor=\"email-notifications\">Email Notifications</Label>\n <p className=\"text-sm text-muted-foreground\">Receive notifications via email</p>\n </div>\n <Switch id=\"email-notifications\" />\n </div>\n <div className=\"flex items-center justify-between\">\n <div className=\"space-y-0.5\">\n <Label htmlFor=\"push-notifications\">Push Notifications</Label>\n <p className=\"text-sm text-muted-foreground\">Receive push notifications on your device</p>\n </div>\n <Switch id=\"push-notifications\" />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"notification-frequency\">Notification Frequency</Label>\n <Select>\n <SelectTrigger id=\"notification-frequency\">\n <SelectValue placeholder=\"Select frequency\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"real-time\">Real-time</SelectItem>\n <SelectItem value=\"daily\">Daily Digest</SelectItem>\n <SelectItem value=\"weekly\">Weekly Summary</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </CardContent>\n <CardFooter>\n <Button>Save Notification Settings</Button>\n </CardFooter>\n </Card>\n </TabsContent>\n </Tabs>\n </div>\n )\n}",
"type": "registry:component"
}
]
}johndoe 9/23/2024
{
"name": "navbar",
"type": "registry:block",
"registryDependencies": [
"button",
"sheet",
"dropdown-menu"
],
"dependencies": [
"next-themes",
"lucide-react"
],
"devDependencies": [],
"tailwind": {
"config": {}
},
"cssVars": {},
"files": [
{
"path": "theme-provider.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\"\nimport { type ThemeProviderProps } from \"next-themes/dist/types\"\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n return <NextThemesProvider {...props}>{children}</NextThemesProvider>\n}\n",
"type": "registry:component"
},
{
"path": "theme-toggle.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Moon, Sun } from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\nexport function ModeToggle() {\n const { setTheme } = useTheme()\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"icon\">\n <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n <span className=\"sr-only\">Toggle theme</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => setTheme(\"light\")}>\n Light\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => setTheme(\"dark\")}>\n Dark\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => setTheme(\"system\")}>\n System\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n",
"type": "registry:component"
},
{
"path": "navbar.tsx",
"content": "import React from 'react';\nimport { Button } from \"@/components/ui/button\";\nimport {\n Sheet,\n SheetContent,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Menu } from \"lucide-react\";\nimport { ModeToggle } from './theme-toggle';\nimport Link from 'next/link';\n\nconst Navbar = () => {\n const navItems = [\n { name: 'Home', href: '/' },\n { name: 'About', href: '/about' },\n { name: 'Services', href: '/services' },\n { name: 'Contact', href: '/contact' }\n ];\n\n return (\n <nav className=\" p-4\">\n <div className=\"container mx-auto flex justify-between items-center\">\n <a href=\"/\" className=\" font-bold text-xl\">Logo</a>\n\n {/* Desktop Menu */}\n <div className=\"hidden md:flex space-x-4\">\n {navItems.map((item) => (\n <Button key={item.name} variant=\"ghost\" className=\"\" asChild>\n <a href={item.href}>{item.name}</a>\n </Button>\n ))}\n <Link href={\"/login\"}>\n <Button>Login</Button>\n </Link>\n <ModeToggle />\n </div>\n\n {/* Mobile Menu */}\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"md:hidden\">\n <Menu className=\"h-6 w-6\" />\n </Button>\n </SheetTrigger>\n <SheetContent>\n <div className=\"flex flex-col space-y-4 mt-4\">\n {navItems.map((item) => (\n <Button key={item.name} variant=\"ghost\" asChild>\n <a href={item.href}>{item.name}</a>\n </Button>\n ))}\n <Button asChild>\n <Link href={\"/login\"}>\n Login\n </Link>\n </Button>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </nav>\n );\n};\n\nexport default Navbar;",
"type": "registry:component"
},
{
"target": "app/layout.tsx",
"path": "layout.tsx",
"content": "import type { Metadata } from \"next\";\nimport localFont from \"next/font/local\";\nimport \"./globals.css\";\nimport { ThemeProvider } from \"@/components/theme-provider\";\nimport Navbar from \"@/components/navbar\";\n\nconst geistSans = localFont({\n src: \"./fonts/GeistVF.woff\",\n variable: \"--font-geist-sans\",\n weight: \"100 900\",\n});\nconst geistMono = localFont({\n src: \"./fonts/GeistMonoVF.woff\",\n variable: \"--font-geist-mono\",\n weight: \"100 900\",\n});\n\nexport const metadata: Metadata = {\n title: \"Create Next App\",\n description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n children,\n}: Readonly<{\n children: React.ReactNode;\n}>) {\n return (\n <html lang=\"en\">\n <body\n className={`${geistSans.variable} ${geistMono.variable} antialiased`}\n >\n <ThemeProvider\n attribute=\"class\"\n defaultTheme=\"system\"\n enableSystem\n disableTransitionOnChange\n >\n <Navbar />\n {children}\n </ThemeProvider>\n </body>\n </html>\n );\n}\n",
"type": "registry:page"
},
{
"path": "auth-avatar.tsx",
"content": "import { Avatar, AvatarImage, AvatarFallback } from \"@radix-ui/react-avatar\";\nimport { validateRequest } from \"@/lib/lucia\";\nimport { Button } from \"./ui/button\";\nimport Link from \"next/link\";\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from \"./ui/dropdown-menu\";\nimport { logout } from \"@/lib/lucia-actions\";\n\nexport async function AuthAvatar() {\n const { user } = await validateRequest()\n\n if (!user) {\n return <Link href=\"/login\">\n <Button>Login</Button>\n </Link>\n }\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger>\n <Avatar>\n {/* <AvatarImage src=\"https://github.com/shadcn.png\" /> */}\n <AvatarFallback className=\"bg-secondary p-2 rounded-full\">{user.username.slice(0, 2).toUpperCase()}</AvatarFallback>\n </Avatar>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\" w-40\">\n <DropdownMenuItem>\n\n <form action={logout} className=\"w-full\">\n <button type=\"submit\" className=\"w-full\">Logout</button>\n </form>\n {/* <a href=\"/api/logout\">Logout</a> */}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n",
"type": "registry:component"
}
]
}