├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------