├── .DS_Store ├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── api │ └── auth │ │ ├── [...nextauth] │ │ └── route.ts │ │ └── register │ │ └── route.ts ├── favicon.ico ├── layout.tsx ├── login │ └── page.tsx ├── opengraph-image.png ├── page.tsx ├── protected │ └── page.tsx └── register │ └── page.tsx ├── components.json ├── components ├── form.tsx ├── header │ └── header.tsx ├── interface │ └── INavLink.ts ├── loading-dots.module.css ├── loading-dots.tsx ├── sign-out.tsx └── ui │ ├── avatar.tsx │ ├── button.tsx │ ├── command.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── input.tsx │ ├── label.tsx │ ├── nav │ ├── nav-elements.tsx │ ├── profile-switcher.tsx │ └── user-nav.tsx │ ├── popover.tsx │ └── select.tsx ├── lib ├── prisma.ts ├── shadcn-plugin.ts └── utils.ts ├── middleware.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prisma └── schema.prisma ├── public ├── logo.png └── vercel.svg ├── styles └── globals.css ├── tailwind.config.ts └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dinuda/next.js-14-postgres-prisma-shadcn-template/cd0a89811b541068421c27302567338c486df998/.DS_Store -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Create a Postgres database 2 | POSTGRES_PRISMA_URL= 3 | POSTGRES_URL_NON_POOLING= 4 | 5 | # Generate one with this command: openssl rand -base64 32 6 | NEXTAUTH_SECRET= 7 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | 35 | # vercel 36 | .vercel 37 | 38 | 39 | # env files 40 | .env.local 41 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

Next.js Prisma PostgreSQL Auth Starter with Shadcn

5 | 6 |

7 | 8 |

9 | This is a Next.js starter kit that uses Next-Auth for simple email + password login
10 | Prisma as the ORM, and Postgres database to persist the data. This application uses Shadcn for UI components, and Tailwind CSS for styling. It has integrated theming support, with support for multiple themes with a custom plugin. 11 | 12 |
13 | 14 | ## Configure the Database 15 | 16 | - create a `.env` file in the root of the project 17 | 18 | ``` 19 | # Create a Postgres database 20 | POSTGRES_PRISMA_URL= 21 | POSTGRES_URL_NON_POOLING= 22 | 23 | # Generate one with this command: openssl rand -base64 32 24 | NEXTAUTH_SECRET= 25 | ``` 26 | 27 | First, run the development server: 28 | 29 | ```bash 30 | npm run dev 31 | ``` 32 | 33 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 34 | 35 | ## Theming with Shadcn 36 | 37 | This starter kit uses Shadcn for UI components, and Tailwind CSS for styling. It has integrated theming support, with support for multiple themes with a custom plugin. 38 | 39 | ### Creating a Theme 40 | 41 | To create a theme, add to `lib/shadcn-plugin.ts`: 42 | 43 | ```ts 44 | 45 | - add colors to `:root` object 46 | ` 47 | "--brown-dark-1": "355 45% 31%", 48 | "--magenta-dark-1": "200 55% 37%", 49 | "--purple-dark-1": "261 51% 51%", 50 | "--dark-green-1": "145 58% 55%", 51 | 52 | 53 | - configure the `theme` object 54 | 55 | "dark-1": "hsl(var(--brown-dark-1))", 56 | "dark-2": "hsl(var(--magenta-dark-1))", 57 | "dark-3": "hsl(var(--purple-dark-1))", 58 | "dark-4": "hsl(var(--dark-green-1))", 59 | ``` 60 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth, { type NextAuthOptions } from "next-auth"; 2 | import CredentialsProvider from "next-auth/providers/credentials"; 3 | import prisma from "@/lib/prisma"; 4 | import { compare } from "bcrypt"; 5 | 6 | export const authOptions: NextAuthOptions = { 7 | providers: [ 8 | CredentialsProvider({ 9 | credentials: { 10 | email: { label: "Email", type: "email" }, 11 | password: { label: "Password", type: "password" } 12 | }, 13 | async authorize(credentials) { 14 | const { email, password } = credentials ?? {} 15 | if (!email || !password) { 16 | throw new Error("Missing username or password"); 17 | } 18 | const user = await prisma.user.findUnique({ 19 | where: { 20 | email, 21 | }, 22 | }); 23 | // if user doesn't exist or password doesn't match 24 | if (!user || !(await compare(password, user.password))) { 25 | throw new Error("Invalid username or password"); 26 | } 27 | return user as any; 28 | }, 29 | }), 30 | ], 31 | }; 32 | 33 | const handler = NextAuth(authOptions); 34 | 35 | export { handler as GET, handler as POST }; 36 | -------------------------------------------------------------------------------- /app/api/auth/register/route.ts: -------------------------------------------------------------------------------- 1 | import prisma from "@/lib/prisma"; 2 | import { NextApiRequest, NextApiResponse } from "next"; 3 | import { hash } from "bcrypt"; 4 | import { NextResponse } from "next/server"; 5 | 6 | export async function POST(req: Request) { 7 | const { email, password } = await req.json(); 8 | const exists = await prisma.user.findUnique({ 9 | where: { 10 | email, 11 | }, 12 | }); 13 | if (exists) { 14 | return NextResponse.json({ error: "User already exists" }, { status: 400 }); 15 | } else { 16 | const user = await prisma.user.create({ 17 | data: { 18 | email, 19 | password: await hash(password, 10), 20 | }, 21 | }); 22 | return NextResponse.json(user); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dinuda/next.js-14-postgres-prisma-shadcn-template/cd0a89811b541068421c27302567338c486df998/app/favicon.ico -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | // These styles apply to every route in the application 2 | import "@/styles/globals.css"; 3 | import { Metadata } from "next"; 4 | import { Inter } from "next/font/google"; 5 | import { Toaster } from "react-hot-toast"; 6 | import { Suspense } from "react"; 7 | import Header from "@/components/header/header"; 8 | 9 | const inter = Inter({ 10 | variable: "--font-inter", 11 | subsets: ["latin"], 12 | }); 13 | 14 | const title = "Next.js Prisma Postgres Auth Starter"; 15 | const description = 16 | "This is a Next.js starter kit that uses Next-Auth for simple email + password login and a Postgres database to persist the data."; 17 | 18 | export const metadata: Metadata = { 19 | title, 20 | description, 21 | twitter: { 22 | card: "summary_large_image", 23 | title, 24 | description, 25 | }, 26 | metadataBase: new URL("https://nextjs-postgres-auth.vercel.app"), 27 | creator: "shadcn", 28 | themeColor: [ 29 | { media: "(prefers-color-scheme: light)", color: "white" }, 30 | { media: "(prefers-color-scheme: dark)", color: "black" }, 31 | ], 32 | }; 33 | 34 | export default async function RootLayout({ 35 | children, 36 | }: { 37 | children: React.ReactNode; 38 | }) { 39 | return ( 40 | 41 | 42 | 43 | 44 |

45 | 46 | {children} 47 | 48 | 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Form from "@/components/form"; 3 | import Link from "next/link"; 4 | 5 | export default function Login() { 6 | return ( 7 |
8 |
9 |
10 | 11 | Logo 19 | 20 |

Sign In

21 |

22 | Use your email and password to sign in 23 |

24 |
25 |
26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dinuda/next.js-14-postgres-prisma-shadcn-template/cd0a89811b541068421c27302567338c486df998/app/opengraph-image.png -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | 4 | export default function Home() { 5 | return ( 6 |
7 |
8 | Platforms on Vercel 15 |
16 |

17 | Next.js Prisma PostgreSQL Auth Starter 18 |

19 |

20 | This is a{" "} 21 | 27 | Next.js 28 | {" "} 29 | starter kit that uses{" "} 30 | 36 | Next-Auth 37 | {" "} 38 | for simple email + password login and a{" "} 39 | 45 | Vercel Postgres 46 | {" "} 47 | database to persist the data. 48 |

49 |
50 |
51 | 56 | Protected Page 57 | 58 |

·

59 | 65 | GitHub 66 | 67 |

·

68 | 74 | 1-click Deploy to Vercel 75 | 76 |
77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /app/protected/page.tsx: -------------------------------------------------------------------------------- 1 | import SignOut from "@/components/sign-out"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 | {/* */} 14 | 15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /app/register/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Form from "@/components/form"; 3 | import Link from "next/link"; 4 | 5 | export default function Login() { 6 | return ( 7 |
8 |
9 |
10 | 11 | Logo 19 | 20 |

Sign Up

21 |

22 | Create an account with your email and password 23 |

24 |
25 | 26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /components/form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { signIn } from "next-auth/react"; 5 | import LoadingDots from "@/components/loading-dots"; 6 | import toast from "react-hot-toast"; 7 | import Link from "next/link"; 8 | import { useRouter } from "next/navigation"; 9 | 10 | export default function Form({ type }: { type: "login" | "register" }) { 11 | const [loading, setLoading] = useState(false); 12 | const router = useRouter(); 13 | 14 | return ( 15 | { 17 | e.preventDefault(); 18 | setLoading(true); 19 | if (type === "login") { 20 | signIn("credentials", { 21 | redirect: false, 22 | email: e.currentTarget.email.value, 23 | password: e.currentTarget.password.value, 24 | // @ts-ignore 25 | }).then(({ error }) => { 26 | if (error) { 27 | setLoading(false); 28 | toast.error(error); 29 | } else { 30 | router.refresh(); 31 | router.push("/protected"); 32 | } 33 | }); 34 | } else { 35 | fetch("/api/auth/register", { 36 | method: "POST", 37 | headers: { 38 | "Content-Type": "application/json", 39 | }, 40 | body: JSON.stringify({ 41 | email: e.currentTarget.email.value, 42 | password: e.currentTarget.password.value, 43 | }), 44 | }).then(async (res) => { 45 | setLoading(false); 46 | if (res.status === 200) { 47 | toast.success("Account created! Redirecting to login..."); 48 | setTimeout(() => { 49 | router.push("/login"); 50 | }, 2000); 51 | } else { 52 | const { error } = await res.json(); 53 | toast.error(error); 54 | } 55 | }); 56 | } 57 | }} 58 | className="flex flex-col space-y-4 bg-gray-50 px-4 py-8 sm:px-16" 59 | > 60 |
61 | 67 | 76 |
77 |
78 | 84 | 91 |
92 | 106 | {type === "login" ? ( 107 |

108 | Don't have an account?{" "} 109 | 110 | Sign up 111 | {" "} 112 | for free. 113 |

114 | ) : ( 115 |

116 | Already have an account?{" "} 117 | 118 | Sign in 119 | {" "} 120 | instead. 121 |

122 | )} 123 | 124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /components/header/header.tsx: -------------------------------------------------------------------------------- 1 | import { getServerSession } from "next-auth/next"; 2 | import ProfileSwitcher from "../ui/nav/profile-switcher"; 3 | import { UserNav } from "../ui/nav/user-nav"; 4 | import { NavElements } from "../ui/nav/nav-elements"; 5 | import { INavLink } from "../interface/INavLink"; 6 | import { Button } from "../ui/button"; 7 | import Link from "next/link"; 8 | import Router from "next/navigation"; 9 | import Image from "next/image"; 10 | 11 | export default async function Header() { 12 | const navigation = [ 13 | { key: "Home", value: "" }, 14 | { key: "About", value: "features" }, 15 | { key: "Pricing", value: "pricing" }, 16 | ] as INavLink[]; 17 | 18 | const session = await getServerSession(); 19 | 20 | return ( 21 | <> 22 |
23 |
24 |
25 | {(session && session.user && ( 26 | <> 27 | 28 |
29 | 30 |
31 |
32 | 33 |
34 | 35 | )) || ( 36 | <> 37 |
38 | logo 39 |
40 |
41 | 42 |
43 |
44 | 45 | 48 | 49 |
50 | 51 | )} 52 |
53 |
54 |
55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /components/interface/INavLink.ts: -------------------------------------------------------------------------------- 1 | export interface INavLink { 2 | key: string; 3 | value: string; 4 | } -------------------------------------------------------------------------------- /components/loading-dots.module.css: -------------------------------------------------------------------------------- 1 | .loading { 2 | display: inline-flex; 3 | align-items: center; 4 | } 5 | 6 | .loading .spacer { 7 | margin-right: 2px; 8 | } 9 | 10 | .loading span { 11 | animation-name: blink; 12 | animation-duration: 1.4s; 13 | animation-iteration-count: infinite; 14 | animation-fill-mode: both; 15 | width: 5px; 16 | height: 5px; 17 | border-radius: 50%; 18 | display: inline-block; 19 | margin: 0 1px; 20 | } 21 | 22 | .loading span:nth-of-type(2) { 23 | animation-delay: 0.2s; 24 | } 25 | 26 | .loading span:nth-of-type(3) { 27 | animation-delay: 0.4s; 28 | } 29 | 30 | @keyframes blink { 31 | 0% { 32 | opacity: 0.2; 33 | } 34 | 20% { 35 | opacity: 1; 36 | } 37 | 100% { 38 | opacity: 0.2; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /components/loading-dots.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./loading-dots.module.css"; 2 | 3 | const LoadingDots = ({ color = "#000" }: { color?: string }) => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default LoadingDots; 14 | -------------------------------------------------------------------------------- /components/sign-out.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { signOut } from "next-auth/react"; 3 | 4 | export default function SignOut() { 5 | return ( 6 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | brown: "bg-dark-1 text-primary-foreground hover:bg-dark-1/90", 22 | }, 23 | size: { 24 | default: "h-10 px-4 py-2", 25 | sm: "h-9 rounded-md px-3", 26 | lg: "h-11 rounded-md px-8", 27 | icon: "h-10 w-10", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { type DialogProps } from "@radix-ui/react-dialog" 5 | import { Command as CommandPrimitive } from "cmdk" 6 | import { Search } from "lucide-react" 7 | 8 | import { cn } from "@/lib/utils" 9 | import { Dialog, DialogContent } from "components/ui/dialog" 10 | 11 | const Command = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 23 | )) 24 | Command.displayName = CommandPrimitive.displayName 25 | 26 | interface CommandDialogProps extends DialogProps {} 27 | 28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => { 29 | return ( 30 | 31 | 32 | 33 | {children} 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | const CommandInput = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 |
45 | 46 | 54 |
55 | )) 56 | 57 | CommandInput.displayName = CommandPrimitive.Input.displayName 58 | 59 | const CommandList = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, ...props }, ref) => ( 63 | 68 | )) 69 | 70 | CommandList.displayName = CommandPrimitive.List.displayName 71 | 72 | const CommandEmpty = React.forwardRef< 73 | React.ElementRef, 74 | React.ComponentPropsWithoutRef 75 | >((props, ref) => ( 76 | 81 | )) 82 | 83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName 84 | 85 | const CommandGroup = React.forwardRef< 86 | React.ElementRef, 87 | React.ComponentPropsWithoutRef 88 | >(({ className, ...props }, ref) => ( 89 | 97 | )) 98 | 99 | CommandGroup.displayName = CommandPrimitive.Group.displayName 100 | 101 | const CommandSeparator = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName 112 | 113 | const CommandItem = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 125 | )) 126 | 127 | CommandItem.displayName = CommandPrimitive.Item.displayName 128 | 129 | const CommandShortcut = ({ 130 | className, 131 | ...props 132 | }: React.HTMLAttributes) => { 133 | return ( 134 | 141 | ) 142 | } 143 | CommandShortcut.displayName = "CommandShortcut" 144 | 145 | export { 146 | Command, 147 | CommandDialog, 148 | CommandInput, 149 | CommandList, 150 | CommandEmpty, 151 | CommandGroup, 152 | CommandItem, 153 | CommandShortcut, 154 | CommandSeparator, 155 | } 156 | -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = DialogPrimitive.Portal 14 | 15 | const DialogClose = DialogPrimitive.Close 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )) 54 | DialogContent.displayName = DialogPrimitive.Content.displayName 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ) 68 | DialogHeader.displayName = "DialogHeader" 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ) 82 | DialogFooter.displayName = "DialogFooter" 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )) 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogClose, 116 | DialogTrigger, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { Check, ChevronRight, Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef & { 24 | inset?: boolean 25 | } 26 | >(({ className, inset, children, ...props }, ref) => ( 27 | 36 | {children} 37 | 38 | 39 | )) 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )) 56 | DropdownMenuSubContent.displayName = 57 | DropdownMenuPrimitive.SubContent.displayName 58 | 59 | const DropdownMenuContent = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, sideOffset = 4, ...props }, ref) => ( 63 | 64 | 73 | 74 | )) 75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 76 | 77 | const DropdownMenuItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef & { 80 | inset?: boolean 81 | } 82 | >(({ className, inset, ...props }, ref) => ( 83 | 92 | )) 93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 94 | 95 | const DropdownMenuCheckboxItem = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, children, checked, ...props }, ref) => ( 99 | 108 | 109 | 110 | 111 | 112 | 113 | {children} 114 | 115 | )) 116 | DropdownMenuCheckboxItem.displayName = 117 | DropdownMenuPrimitive.CheckboxItem.displayName 118 | 119 | const DropdownMenuRadioItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )) 139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 140 | 141 | const DropdownMenuLabel = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef & { 144 | inset?: boolean 145 | } 146 | >(({ className, inset, ...props }, ref) => ( 147 | 156 | )) 157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 158 | 159 | const DropdownMenuSeparator = React.forwardRef< 160 | React.ElementRef, 161 | React.ComponentPropsWithoutRef 162 | >(({ className, ...props }, ref) => ( 163 | 168 | )) 169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 170 | 171 | const DropdownMenuShortcut = ({ 172 | className, 173 | ...props 174 | }: React.HTMLAttributes) => { 175 | return ( 176 | 180 | ) 181 | } 182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 183 | 184 | export { 185 | DropdownMenu, 186 | DropdownMenuTrigger, 187 | DropdownMenuContent, 188 | DropdownMenuItem, 189 | DropdownMenuCheckboxItem, 190 | DropdownMenuRadioItem, 191 | DropdownMenuLabel, 192 | DropdownMenuSeparator, 193 | DropdownMenuShortcut, 194 | DropdownMenuGroup, 195 | DropdownMenuPortal, 196 | DropdownMenuSub, 197 | DropdownMenuSubContent, 198 | DropdownMenuSubTrigger, 199 | DropdownMenuRadioGroup, 200 | } 201 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /components/ui/nav/nav-elements.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { INavLink } from "../../interface/INavLink"; 3 | 4 | 5 | export function NavElements({ navigationLinks }: { navigationLinks: INavLink[] }) { 6 | return ( 7 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/ui/nav/profile-switcher.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { 5 | CaretSortIcon, 6 | CheckIcon, 7 | PlusCircledIcon, 8 | } from "@radix-ui/react-icons" 9 | 10 | import { cn } from "@/lib/utils" 11 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" 12 | 13 | import { Button } from "@/components/ui/button" 14 | import { 15 | Command, 16 | CommandEmpty, 17 | CommandGroup, 18 | CommandInput, 19 | CommandItem, 20 | CommandList, 21 | CommandSeparator, 22 | } from "@/components/ui/command" 23 | import { 24 | Dialog, 25 | DialogContent, 26 | DialogDescription, 27 | DialogFooter, 28 | DialogHeader, 29 | DialogTitle, 30 | DialogTrigger, 31 | } from "@/components/ui/dialog" 32 | import { Input } from "@/components/ui/input" 33 | import { Label } from "@/components/ui/label" 34 | import { 35 | Popover, 36 | PopoverContent, 37 | PopoverTrigger, 38 | } from "@/components/ui/popover" 39 | import { 40 | Select, 41 | SelectContent, 42 | SelectItem, 43 | SelectTrigger, 44 | SelectValue, 45 | } from "@/components/ui/select" 46 | 47 | const groups = [ 48 | { 49 | label: "Personal Account", 50 | teams: [ 51 | { 52 | label: "Alicia Koch", 53 | value: "personal", 54 | }, 55 | ], 56 | }, 57 | { 58 | label: "Teams", 59 | teams: [ 60 | { 61 | label: "Acme Inc.", 62 | value: "acme-inc", 63 | }, 64 | { 65 | label: "Monsters Inc.", 66 | value: "monsters", 67 | }, 68 | ], 69 | }, 70 | ] 71 | 72 | type Team = (typeof groups)[number]["teams"][number] 73 | 74 | type PopoverTriggerProps = React.ComponentPropsWithoutRef 75 | 76 | interface ProfileSwitcherProps extends PopoverTriggerProps {} 77 | 78 | export default function ProfileSwitcher({ className }: ProfileSwitcherProps & { session: any }) { 79 | const [open, setOpen] = React.useState(false) 80 | const [showNewTeamDialog, setShowNewTeamDialog] = React.useState(false) 81 | const [selectedTeam, setSelectedTeam] = React.useState( 82 | groups[0].teams[0] 83 | ) 84 | 85 | return ( 86 | 87 | 88 | 89 | 106 | 107 | 108 | 109 | 110 | 111 | No team found. 112 | {groups.map((group) => ( 113 | 114 | {group.teams.map((team) => ( 115 | { 118 | setSelectedTeam(team) 119 | setOpen(false) 120 | }} 121 | className="text-sm" 122 | > 123 | 124 | 129 | SC 130 | 131 | {team.label} 132 | 140 | 141 | ))} 142 | 143 | ))} 144 | 145 | 146 | 147 | 148 | 149 | { 151 | setOpen(false) 152 | setShowNewTeamDialog(true) 153 | }} 154 | > 155 | 156 | Create Team 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | Create team 167 | 168 | Add a new team to manage products and customers. 169 | 170 | 171 |
172 |
173 |
174 | 175 | 176 |
177 |
178 | 179 | 198 |
199 |
200 |
201 | 202 | 205 | 206 | 207 |
208 |
209 | ) 210 | } -------------------------------------------------------------------------------- /components/ui/nav/user-nav.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Avatar, 3 | AvatarFallback, 4 | AvatarImage, 5 | } from "@/components/ui/avatar" 6 | import { Button } from "@/components/ui/button" 7 | import { 8 | DropdownMenu, 9 | DropdownMenuContent, 10 | DropdownMenuGroup, 11 | DropdownMenuItem, 12 | DropdownMenuLabel, 13 | DropdownMenuSeparator, 14 | DropdownMenuShortcut, 15 | DropdownMenuTrigger, 16 | } from "@/components/ui/dropdown-menu" 17 | 18 | export function UserNav() { 19 | return ( 20 | 21 | 22 | 28 | 29 | 30 | 31 |
32 |

shadcn

33 |

34 | m@example.com 35 |

36 |
37 |
38 | 39 | 40 | 41 | Profile 42 | ⇧⌘P 43 | 44 | 45 | Billing 46 | ⌘B 47 | 48 | 49 | Settings 50 | ⌘S 51 | 52 | New Team 53 | 54 | 55 | 56 | Log out 57 | ⇧⌘Q 58 | 59 |
60 |
61 | ) 62 | } -------------------------------------------------------------------------------- /components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )) 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 30 | 31 | export { Popover, PopoverTrigger, PopoverContent } 32 | -------------------------------------------------------------------------------- /components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { Check, ChevronDown, ChevronUp } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Select = SelectPrimitive.Root 10 | 11 | const SelectGroup = SelectPrimitive.Group 12 | 13 | const SelectValue = SelectPrimitive.Value 14 | 15 | const SelectTrigger = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | span]:line-clamp-1", 23 | className 24 | )} 25 | {...props} 26 | > 27 | {children} 28 | 29 | 30 | 31 | 32 | )) 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 34 | 35 | const SelectScrollUpButton = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | 48 | 49 | )) 50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 51 | 52 | const SelectScrollDownButton = React.forwardRef< 53 | React.ElementRef, 54 | React.ComponentPropsWithoutRef 55 | >(({ className, ...props }, ref) => ( 56 | 64 | 65 | 66 | )) 67 | SelectScrollDownButton.displayName = 68 | SelectPrimitive.ScrollDownButton.displayName 69 | 70 | const SelectContent = React.forwardRef< 71 | React.ElementRef, 72 | React.ComponentPropsWithoutRef 73 | >(({ className, children, position = "popper", ...props }, ref) => ( 74 | 75 | 86 | 87 | 94 | {children} 95 | 96 | 97 | 98 | 99 | )) 100 | SelectContent.displayName = SelectPrimitive.Content.displayName 101 | 102 | const SelectLabel = React.forwardRef< 103 | React.ElementRef, 104 | React.ComponentPropsWithoutRef 105 | >(({ className, ...props }, ref) => ( 106 | 111 | )) 112 | SelectLabel.displayName = SelectPrimitive.Label.displayName 113 | 114 | const SelectItem = React.forwardRef< 115 | React.ElementRef, 116 | React.ComponentPropsWithoutRef 117 | >(({ className, children, ...props }, ref) => ( 118 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | {children} 133 | 134 | )) 135 | SelectItem.displayName = SelectPrimitive.Item.displayName 136 | 137 | const SelectSeparator = React.forwardRef< 138 | React.ElementRef, 139 | React.ComponentPropsWithoutRef 140 | >(({ className, ...props }, ref) => ( 141 | 146 | )) 147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 148 | 149 | export { 150 | Select, 151 | SelectGroup, 152 | SelectValue, 153 | SelectTrigger, 154 | SelectContent, 155 | SelectLabel, 156 | SelectItem, 157 | SelectSeparator, 158 | SelectScrollUpButton, 159 | SelectScrollDownButton, 160 | } 161 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | var prisma: PrismaClient | undefined; 5 | } 6 | 7 | const prisma = global.prisma || new PrismaClient(); 8 | 9 | if (process.env.NODE_ENV === "development") global.prisma = prisma; 10 | 11 | export default prisma; 12 | -------------------------------------------------------------------------------- /lib/shadcn-plugin.ts: -------------------------------------------------------------------------------- 1 | import plugin from "tailwindcss/plugin"; 2 | import AnimatePlugin from "tailwindcss-animate"; 3 | 4 | export const shadcnPlugin = plugin( 5 | function ({ addBase }) { 6 | addBase({ 7 | ":root": { 8 | "--background": "0 0% 100%", 9 | "--foreground": "222.2 84% 4.9%", 10 | "--card": "0 0% 100%", 11 | "--card-foreground": "222.2 84% 4.9%", 12 | "--popover": "0 0% 100%", 13 | "--popover-foreground": "222.2 84% 4.9%", 14 | "--primary": "222.2 47.4% 11.2%", 15 | "--primary-foreground": "210 40% 98%", 16 | "--secondary": "210 40% 96.1%", 17 | "--secondary-foreground": "222.2 47.4% 11.2%", 18 | "--muted": "210 40% 96.1%", 19 | "--muted-foreground": "215.4 16.3% 46.9%", 20 | "--accent": "210 40% 96.1%", 21 | "--accent-foreground": "222.2 47.4% 11.2%", 22 | "--destructive": "0 84.2% 60.2%", 23 | "--destructive-foreground": "210 40% 98%", 24 | "--border": "214.3 31.8% 91.4%", 25 | "--input": "214.3 31.8% 91.4%", 26 | "--ring": "222.2 84% 4.9%", 27 | "--radius": "0.5rem", 28 | // custom color for theme 29 | "--brown-dark-1": "355 45% 31%", 30 | "--magenta-dark-1": "200 55% 37%", 31 | "--purple-dark-1": "261 51% 51%", 32 | "--dark-green-1": "145 58% 55%", 33 | }, 34 | ".dark": { 35 | "--background": "222.2 84% 4.9%", 36 | "--foreground": "210 40% 98%", 37 | "--card": "222.2 84% 4.9%", 38 | "--card-foreground": "210 40% 98%", 39 | "--popover": "222.2 84% 4.9%", 40 | "--popover-foreground": "210 40% 98%", 41 | "--primary": "210 40% 98%", 42 | "--primary-foreground": "222.2 47.4% 11.2%", 43 | "--secondary": "217.2 32.6% 17.5%", 44 | "--secondary-foreground": "210 40% 98%", 45 | "--muted": "217.2 32.6% 17.5%", 46 | "--muted-foreground": "215 20.2% 65.1%", 47 | "--accent": "217.2 32.6% 17.5%", 48 | "--accent-foreground": "210 40% 98%", 49 | "--destructive": "0 62.8% 30.6%", 50 | "--destructive-foreground": "210 40% 98%", 51 | "--border": "217.2 32.6% 17.5%", 52 | "--input": "217.2 32.6% 17.5%", 53 | "--ring": "212.7 26.8% 83.9%", 54 | }, 55 | }); 56 | 57 | addBase({ 58 | "*": { 59 | "@apply border-border": {}, 60 | }, 61 | body: { 62 | "@apply bg-background text-foreground": {}, 63 | }, 64 | }); 65 | }, 66 | { 67 | theme: { 68 | container: { 69 | center: true, 70 | padding: "2rem", 71 | screens: { 72 | "2xl": "1400px", 73 | }, 74 | }, 75 | extend: { 76 | colors: { 77 | border: "hsl(var(--border))", 78 | input: "hsl(var(--input))", 79 | ring: "hsl(var(--ring))", 80 | background: "hsl(var(--background))", 81 | foreground: "hsl(var(--foreground))", 82 | primary: { 83 | DEFAULT: "hsl(var(--primary))", 84 | foreground: "hsl(var(--primary-foreground))", 85 | }, 86 | secondary: { 87 | DEFAULT: "hsl(var(--secondary))", 88 | foreground: "hsl(var(--secondary-foreground))", 89 | }, 90 | destructive: { 91 | DEFAULT: "hsl(var(--destructive))", 92 | foreground: "hsl(var(--destructive-foreground))", 93 | }, 94 | muted: { 95 | DEFAULT: "hsl(var(--muted))", 96 | foreground: "hsl(var(--muted-foreground))", 97 | }, 98 | accent: { 99 | DEFAULT: "hsl(var(--accent))", 100 | foreground: "hsl(var(--accent-foreground))", 101 | }, 102 | popover: { 103 | DEFAULT: "hsl(var(--popover))", 104 | foreground: "hsl(var(--popover-foreground))", 105 | }, 106 | card: { 107 | DEFAULT: "hsl(var(--card))", 108 | foreground: "hsl(var(--card-foreground))", 109 | }, 110 | // add your custom colors here 111 | "dark-1": "hsl(var(--brown-dark-1))", 112 | "dark-2": "hsl(var(--magenta-dark-1))", 113 | "dark-3": "hsl(var(--purple-dark-1))", 114 | "dark-4": "hsl(var(--dark-green-1))", 115 | }, 116 | borderRadius: { 117 | lg: "var(--radius)", 118 | md: "calc(var(--radius) - 2px)", 119 | sm: "calc(var(--radius) - 4px)", 120 | }, 121 | keyframes: { 122 | "accordion-down": { 123 | from: { height: "0" }, 124 | to: { height: "var(--radix-accordion-content-height)" }, 125 | }, 126 | "accordion-up": { 127 | from: { height: "var(--radix-accordion-content-height)" }, 128 | to: { height: "0" }, 129 | }, 130 | }, 131 | animation: { 132 | "accordion-down": "accordion-down 0.2s ease-out", 133 | "accordion-up": "accordion-up 0.2s ease-out", 134 | }, 135 | }, 136 | }, 137 | plugins: [AnimatePlugin], 138 | } 139 | ); 140 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { getToken } from "next-auth/jwt"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export default async function middleware(req: NextRequest) { 5 | // Get the pathname of the request (e.g. /, /protected) 6 | const path = req.nextUrl.pathname; 7 | 8 | // If it's the root path, just render it 9 | if (path === "/") { 10 | return NextResponse.next(); 11 | } 12 | 13 | const session = await getToken({ 14 | req, 15 | secret: process.env.NEXTAUTH_SECRET, 16 | }); 17 | 18 | if (!session && path === "/protected") { 19 | return NextResponse.redirect(new URL("/login", req.url)); 20 | } else if (session && (path === "/login" || path === "/register")) { 21 | return NextResponse.redirect(new URL("/protected", req.url)); 22 | } 23 | return NextResponse.next(); 24 | } 25 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | typescript: { 4 | ignoreBuildErrors: true, 5 | }, 6 | swcMinify: true, 7 | }; 8 | 9 | module.exports = nextConfig; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "prisma generate && next dev", 5 | "build": "prisma generate && prisma db push && next build", 6 | "start": "next start", 7 | "lint": "next lint" 8 | }, 9 | "dependencies": { 10 | "@heroicons/react": "^2.0.18", 11 | "@prisma/client": "^4.14.0", 12 | "@radix-ui/react-avatar": "^1.0.4", 13 | "@radix-ui/react-dialog": "^1.0.5", 14 | "@radix-ui/react-dropdown-menu": "^2.0.6", 15 | "@radix-ui/react-icons": "^1.3.0", 16 | "@radix-ui/react-label": "^2.0.2", 17 | "@radix-ui/react-popover": "^1.0.7", 18 | "@radix-ui/react-select": "^2.0.0", 19 | "@radix-ui/react-slot": "^1.0.2", 20 | "@types/node": "^18.11.9", 21 | "@types/react": "^18.0.25", 22 | "bcrypt": "^5.1.0", 23 | "class-variance-authority": "^0.7.0", 24 | "clsx": "^2.0.0", 25 | "cmdk": "^0.2.0", 26 | "lucide-react": "^0.293.0", 27 | "next": "^13.4.2", 28 | "next-auth": "^4.22.1", 29 | "react": "^18.2.0", 30 | "react-dom": "^18.2.0", 31 | "react-hot-toast": "^2.4.1", 32 | "tailwind-merge": "^2.0.0", 33 | "tailwindcss-animate": "^1.0.7" 34 | }, 35 | "devDependencies": { 36 | "@types/bcrypt": "^5.0.0", 37 | "autoprefixer": "^10.4.16", 38 | "eslint": "8.11.0", 39 | "eslint-config-next": "^13.0.5", 40 | "postcss": "^8.4.31", 41 | "prisma": "^4.14.0", 42 | "tailwindcss": "^3.3.5", 43 | "typescript": "^4.6.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "postgresql" 10 | url = env("POSTGRES_PRISMA_URL") // uses connection pooling 11 | directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection 12 | shadowDatabaseUrl = env("POSTGRES_URL_NON_POOLING") // used for migrations 13 | } 14 | 15 | model User { 16 | id Int @id @default(autoincrement()) 17 | email String @unique 18 | password String 19 | } 20 | 21 | // add more models here -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dinuda/next.js-14-postgres-prisma-shadcn-template/cd0a89811b541068421c27302567338c486df998/public/logo.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { shadcnPlugin } from "./lib/shadcn-plugin"; 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | const config = { 5 | darkMode: ["class"], 6 | content: [ 7 | './pages/**/*.{ts,tsx}', 8 | './components/**/*.{ts,tsx}', 9 | './app/**/*.{ts,tsx}', 10 | './src/**/*.{ts,tsx}', 11 | ], 12 | plugins: [shadcnPlugin], 13 | }; 14 | 15 | export default config; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/components/*": ["components/*"], 10 | "@/pages/*": ["pages/*"], 11 | "@/app/*": ["app/*"], 12 | "@/lib/*": ["lib/*"], 13 | "@/styles/*": ["styles/*"], 14 | "@theme/*": ["theme/*"], 15 | }, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true, 19 | "esModuleInterop": true, 20 | "module": "esnext", 21 | "moduleResolution": "node", 22 | "resolveJsonModule": true, 23 | "isolatedModules": true, 24 | "jsx": "preserve", 25 | "incremental": true, 26 | "plugins": [ 27 | { 28 | "name": "next" 29 | } 30 | ] 31 | }, 32 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 33 | "exclude": ["node_modules"] 34 | } 35 | --------------------------------------------------------------------------------