├── middleware.ts ├── .eslintrc.json ├── thumb.png ├── app ├── page.tsx ├── api │ └── auth │ │ └── [...nextauth] │ │ └── route.ts ├── layout.tsx ├── private │ ├── settings │ │ └── page.tsx │ └── dashboard │ │ └── page.tsx ├── globals.css ├── register │ └── page.tsx └── login │ └── page.tsx ├── next.config.mjs ├── README.md ├── postcss.config.mjs ├── .env ├── lib ├── utils.ts ├── getSession.ts └── db.ts ├── components.json ├── models └── User.ts ├── .gitignore ├── tsconfig.json ├── components ├── ui │ ├── label.tsx │ ├── input.tsx │ ├── button.tsx │ ├── card.tsx │ ├── table.tsx │ └── dropdown-menu.tsx └── auth │ └── Navbar.tsx ├── package.json ├── action └── user.ts ├── tailwind.config.ts └── auth.ts /middleware.ts: -------------------------------------------------------------------------------- 1 | export { auth as middleware } from "@/auth"; 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuXn-WebDev/Auth.js-v5-Complete-Course/HEAD/thumb.png -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | const Home = () => { 2 | return
Home
; 3 | }; 4 | 5 | export default Home; 6 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { handlers } from "@/auth"; 2 | export const { GET, POST } = handlers; 3 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auth.js v5 Complete Course 👇 2 | 3 | # [Watch Full Course On My Channel](https://www.youtube.com/@huxnwebdev) 🤘🥂. 4 | 5 | ![Course Thumbnail](/thumb.png) 6 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MONGO_URI='mongodb://127.0.0.1:27017/nextAuth' 2 | AUTH_SECRET=klsgjcsr6ku987123kjdvlksadfadf0243 3 | GITHUB_CLIENT_ID= 4 | GITHUB_CLIENT_SECRET= 5 | GOOGLE_CLIENT_ID= 6 | GOOGLE_CLIENT_SECRET= -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/getSession.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@/auth"; 2 | import { cache } from "react"; 3 | 4 | export const getSession = cache(async () => { 5 | const session = await auth(); 6 | return session; 7 | }); 8 | -------------------------------------------------------------------------------- /lib/db.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const connectDB = async () => { 4 | try { 5 | await mongoose.connect(process.env.MONGO_URI!); 6 | console.log(`Successfully connected to mongoDB 🥂`); 7 | } catch (error: any) { 8 | console.error(`Error: ${error.message}`); 9 | process.exit(1); 10 | } 11 | }; 12 | 13 | export default connectDB; 14 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /models/User.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = new mongoose.Schema({ 4 | firstName: { type: String, required: true }, 5 | lastName: { type: String, required: true }, 6 | email: { type: String, required: true }, 7 | password: { type: String, select: false }, 8 | role: { type: String, default: "user" }, 9 | image: { type: String }, 10 | authProviderId: { type: String }, 11 | }); 12 | 13 | export const User = mongoose.models?.User || mongoose.model("User", userSchema); 14 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import Navbar from "@/components/auth/Navbar"; 4 | import "./globals.css"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export const metadata: Metadata = { 9 | title: "Create Next App", 10 | description: "Generated by create next app", 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: Readonly<{ 16 | children: React.ReactNode; 17 | }>) { 18 | return ( 19 | 20 | 21 | 22 | {children} 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-auth-pj", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-dropdown-menu": "^2.0.6", 13 | "@radix-ui/react-label": "^2.0.2", 14 | "@radix-ui/react-slot": "^1.0.2", 15 | "@tabler/icons-react": "^3.5.0", 16 | "bcryptjs": "^2.4.3", 17 | "class-variance-authority": "^0.7.0", 18 | "clsx": "^2.1.1", 19 | "lucide-react": "^0.381.0", 20 | "mongoose": "^8.4.1", 21 | "next": "14.2.3", 22 | "next-auth": "^5.0.0-beta.19", 23 | "react": "^18", 24 | "react-dom": "^18", 25 | "tailwind-merge": "^2.3.0", 26 | "tailwindcss-animate": "^1.0.7" 27 | }, 28 | "devDependencies": { 29 | "@types/bcryptjs": "^2.4.6", 30 | "@types/node": "^20", 31 | "@types/react": "^18", 32 | "@types/react-dom": "^18", 33 | "eslint": "^8", 34 | "eslint-config-next": "14.2.3", 35 | "postcss": "^8", 36 | "tailwindcss": "^3.4.1", 37 | "typescript": "^5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /components/auth/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Button } from "../ui/button"; 3 | import { getSession } from "@/lib/getSession"; 4 | import { signOut } from "@/auth"; 5 | 6 | const Navbar = async () => { 7 | const session = await getSession(); 8 | const user = session?.user; 9 | 10 | return ( 11 | 52 | ); 53 | }; 54 | 55 | export default Navbar; 56 | -------------------------------------------------------------------------------- /action/user.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import connectDB from "@/lib/db"; 4 | import { User } from "@/models/User"; 5 | import { redirect } from "next/navigation"; 6 | import { hash } from "bcryptjs"; 7 | import { CredentialsSignin } from "next-auth"; 8 | import { signIn } from "@/auth"; 9 | 10 | const login = async (formData: FormData) => { 11 | const email = formData.get("email") as string; 12 | const password = formData.get("password") as string; 13 | 14 | try { 15 | await signIn("credentials", { 16 | redirect: false, 17 | callbackUrl: "/", 18 | email, 19 | password, 20 | }); 21 | } catch (error) { 22 | const someError = error as CredentialsSignin; 23 | return someError.cause; 24 | } 25 | redirect("/"); 26 | }; 27 | 28 | const register = async (formData: FormData) => { 29 | const firstName = formData.get("firstname") as string; 30 | const lastName = formData.get("lastname") as string; 31 | const email = formData.get("email") as string; 32 | const password = formData.get("password") as string; 33 | 34 | if (!firstName || !lastName || !email || !password) { 35 | throw new Error("Please fill all fields"); 36 | } 37 | 38 | await connectDB(); 39 | 40 | // existing user 41 | const existingUser = await User.findOne({ email }); 42 | if (existingUser) throw new Error("User already exists"); 43 | 44 | const hashedPassword = await hash(password, 12); 45 | 46 | await User.create({ firstName, lastName, email, password: hashedPassword }); 47 | console.log(`User created successfully 🥂`); 48 | redirect("/login"); 49 | }; 50 | 51 | const fetchAllUsers = async () => { 52 | await connectDB(); 53 | const users = await User.find({}); 54 | return users; 55 | }; 56 | 57 | export { register, login, fetchAllUsers }; 58 | -------------------------------------------------------------------------------- /app/private/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { fetchAllUsers } from "@/action/user"; 2 | import { getSession } from "@/lib/getSession"; 3 | import { User } from "@/models/User"; 4 | import { redirect } from "next/navigation"; 5 | 6 | const Settings = async () => { 7 | const session = await getSession(); 8 | const user = session?.user; 9 | if (!user) return redirect("/login"); 10 | 11 | if (user?.role !== "admin") return redirect("/private/dashboard"); 12 | 13 | const allUsers = await fetchAllUsers(); 14 | 15 | return ( 16 |
17 |

users

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {allUsers?.map((user) => ( 29 | 30 | 31 | 32 | 44 | 45 | ))} 46 | 47 |
First NameLast NameAction
{user.firstName}{user.lastName} 33 |
{ 35 | "use server"; 36 | await User.findByIdAndDelete(user._id); 37 | }} 38 | > 39 | 42 |
43 |
48 |
49 | ); 50 | }; 51 | 52 | export default Settings; 53 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /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 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss" 2 | 3 | const config = { 4 | darkMode: ["class"], 5 | content: [ 6 | './pages/**/*.{ts,tsx}', 7 | './components/**/*.{ts,tsx}', 8 | './app/**/*.{ts,tsx}', 9 | './src/**/*.{ts,tsx}', 10 | ], 11 | prefix: "", 12 | theme: { 13 | container: { 14 | center: true, 15 | padding: "2rem", 16 | screens: { 17 | "2xl": "1400px", 18 | }, 19 | }, 20 | extend: { 21 | colors: { 22 | border: "hsl(var(--border))", 23 | input: "hsl(var(--input))", 24 | ring: "hsl(var(--ring))", 25 | background: "hsl(var(--background))", 26 | foreground: "hsl(var(--foreground))", 27 | primary: { 28 | DEFAULT: "hsl(var(--primary))", 29 | foreground: "hsl(var(--primary-foreground))", 30 | }, 31 | secondary: { 32 | DEFAULT: "hsl(var(--secondary))", 33 | foreground: "hsl(var(--secondary-foreground))", 34 | }, 35 | destructive: { 36 | DEFAULT: "hsl(var(--destructive))", 37 | foreground: "hsl(var(--destructive-foreground))", 38 | }, 39 | muted: { 40 | DEFAULT: "hsl(var(--muted))", 41 | foreground: "hsl(var(--muted-foreground))", 42 | }, 43 | accent: { 44 | DEFAULT: "hsl(var(--accent))", 45 | foreground: "hsl(var(--accent-foreground))", 46 | }, 47 | popover: { 48 | DEFAULT: "hsl(var(--popover))", 49 | foreground: "hsl(var(--popover-foreground))", 50 | }, 51 | card: { 52 | DEFAULT: "hsl(var(--card))", 53 | foreground: "hsl(var(--card-foreground))", 54 | }, 55 | }, 56 | borderRadius: { 57 | lg: "var(--radius)", 58 | md: "calc(var(--radius) - 2px)", 59 | sm: "calc(var(--radius) - 4px)", 60 | }, 61 | keyframes: { 62 | "accordion-down": { 63 | from: { height: "0" }, 64 | to: { height: "var(--radix-accordion-content-height)" }, 65 | }, 66 | "accordion-up": { 67 | from: { height: "var(--radix-accordion-content-height)" }, 68 | to: { height: "0" }, 69 | }, 70 | }, 71 | animation: { 72 | "accordion-down": "accordion-down 0.2s ease-out", 73 | "accordion-up": "accordion-up 0.2s ease-out", 74 | }, 75 | }, 76 | }, 77 | plugins: [require("tailwindcss-animate")], 78 | } satisfies Config 79 | 80 | export default config -------------------------------------------------------------------------------- /app/register/page.tsx: -------------------------------------------------------------------------------- 1 | import { register } from "@/action/user"; 2 | import { Input } from "@/components/ui/input"; 3 | import { Label } from "@/components/ui/label"; 4 | import { getSession } from "@/lib/getSession"; 5 | import Link from "next/link"; 6 | import { redirect } from "next/navigation"; 7 | 8 | const Register = async () => { 9 | const session = await getSession(); 10 | const user = session?.user; 11 | if (user) redirect("/"); 12 | 13 | return ( 14 |
15 |

16 | Welcome to MyShop 17 |

18 |

19 | Please provide all the necessary information 20 |

21 | 22 |
23 |
24 |
25 | 28 | 34 |
35 |
36 | 39 | 45 |
46 |
47 | 48 | 49 | 55 | 56 | 57 | 64 | 65 | 68 | 69 |

70 | Already have an account? Login 71 |

72 |
73 |
74 | ); 75 | }; 76 | export default Register; 77 | -------------------------------------------------------------------------------- /components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
10 | 15 | 16 | )) 17 | Table.displayName = "Table" 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )) 25 | TableHeader.displayName = "TableHeader" 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )) 37 | TableBody.displayName = "TableBody" 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className 48 | )} 49 | {...props} 50 | /> 51 | )) 52 | TableFooter.displayName = "TableFooter" 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )) 67 | TableRow.displayName = "TableRow" 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
81 | )) 82 | TableHead.displayName = "TableHead" 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | 93 | )) 94 | TableCell.displayName = "TableCell" 95 | 96 | const TableCaption = React.forwardRef< 97 | HTMLTableCaptionElement, 98 | React.HTMLAttributes 99 | >(({ className, ...props }, ref) => ( 100 |
105 | )) 106 | TableCaption.displayName = "TableCaption" 107 | 108 | export { 109 | Table, 110 | TableHeader, 111 | TableBody, 112 | TableFooter, 113 | TableHead, 114 | TableRow, 115 | TableCell, 116 | TableCaption, 117 | } 118 | -------------------------------------------------------------------------------- /auth.ts: -------------------------------------------------------------------------------- 1 | import NextAuth, { CredentialsSignin } from "next-auth"; 2 | import Credentials from "next-auth/providers/credentials"; 3 | import Github from "next-auth/providers/github"; 4 | import Google from "next-auth/providers/google"; 5 | import connectDB from "./lib/db"; 6 | import { User } from "./models/User"; 7 | import { compare } from "bcryptjs"; 8 | 9 | export const { handlers, signIn, signOut, auth } = NextAuth({ 10 | providers: [ 11 | Github({ 12 | clientId: process.env.GITHUB_CLIENT_ID, 13 | clientSecret: process.env.GITHUB_CLIENT_SECRET, 14 | }), 15 | 16 | Google({ 17 | clientId: process.env.GOOGLE_CLIENT_ID, 18 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 19 | }), 20 | 21 | Credentials({ 22 | name: "Credentials", 23 | 24 | credentials: { 25 | email: { label: "Email", type: "email" }, 26 | password: { label: "Password", type: "password" }, 27 | }, 28 | 29 | authorize: async (credentials) => { 30 | const email = credentials.email as string | undefined; 31 | const password = credentials.password as string | undefined; 32 | 33 | if (!email || !password) { 34 | throw new CredentialsSignin("Please provide both email & password"); 35 | } 36 | 37 | await connectDB(); 38 | 39 | const user = await User.findOne({ email }).select("+password +role"); 40 | 41 | if (!user) { 42 | throw new Error("Invalid email or password"); 43 | } 44 | 45 | if (!user.password) { 46 | throw new Error("Invalid email or password"); 47 | } 48 | 49 | const isMatched = await compare(password, user.password); 50 | 51 | if (!isMatched) { 52 | throw new Error("Password did not matched"); 53 | } 54 | 55 | const userData = { 56 | firstName: user.firstName, 57 | lastName: user.lastName, 58 | email: user.email, 59 | role: user.role, 60 | id: user._id, 61 | }; 62 | 63 | return userData; 64 | }, 65 | }), 66 | ], 67 | 68 | pages: { 69 | signIn: "/login", 70 | }, 71 | 72 | callbacks: { 73 | async session({ session, token }) { 74 | if (token?.sub && token?.role) { 75 | session.user.id = token.sub; 76 | session.user.role = token.role; 77 | } 78 | return session; 79 | }, 80 | 81 | async jwt({ token, user }) { 82 | if (user) { 83 | token.role = user.role; 84 | } 85 | return token; 86 | }, 87 | 88 | signIn: async ({ user, account }) => { 89 | if (account?.provider === "google") { 90 | try { 91 | const { email, name, image, id } = user; 92 | await connectDB(); 93 | const alreadyUser = await User.findOne({ email }); 94 | 95 | if (!alreadyUser) { 96 | await User.create({ email, name, image, authProviderId: id }); 97 | } else { 98 | return true; 99 | } 100 | } catch (error) { 101 | throw new Error("Error while creating user"); 102 | } 103 | } 104 | 105 | if (account?.provider === "credentials") { 106 | return true; 107 | } else { 108 | return false; 109 | } 110 | }, 111 | }, 112 | }); 113 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import { login } from "@/action/user"; 2 | import { Input } from "@/components/ui/input"; 3 | import { Label } from "@/components/ui/label"; 4 | import { IconBrandGithub, IconBrandGoogle } from "@tabler/icons-react"; 5 | import { signIn } from "@/auth"; 6 | import Link from "next/link"; 7 | import { redirect } from "next/navigation"; 8 | import { getSession } from "@/lib/getSession"; 9 | 10 | const Login = async () => { 11 | const session = await getSession(); 12 | const user = session?.user; 13 | if (user) redirect("/"); 14 | 15 | return ( 16 |
17 |
18 | 19 | 25 | 26 | 27 | 34 | 35 | 38 | 39 |

40 | Don't have account? Register 41 |

42 | 43 |
44 | 45 |
{ 47 | "use server"; 48 | await signIn("github"); 49 | }} 50 | > 51 | 60 |
61 |
{ 63 | "use server"; 64 | await signIn("google"); 65 | }} 66 | > 67 | 76 |
77 |
78 | ); 79 | }; 80 | 81 | export default Login; 82 | -------------------------------------------------------------------------------- /app/private/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 2 | import { 3 | Table, 4 | TableBody, 5 | TableCell, 6 | TableHead, 7 | TableHeader, 8 | TableRow, 9 | } from "@/components/ui/table"; 10 | import { getSession } from "@/lib/getSession"; 11 | import { redirect } from "next/navigation"; 12 | 13 | const Dashboard = async () => { 14 | const session = await getSession(); 15 | const user = session?.user; 16 | if (!user) return redirect("/"); 17 | 18 | return ( 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | Total Revenue 27 | 28 | 29 | 30 |
$45,231.87
31 |

32 | +20.1% from last month 33 |

34 |
35 |
36 | 37 | 38 | 39 | Subscriptions 40 | 41 | 42 | 43 |
+2350
44 |

45 | +180.1% from last month 46 |

47 |
48 |
49 | 50 | 51 | Sales 52 | 53 | 54 |
+12,234
55 |

56 | +19% from last month 57 |

58 |
59 |
60 | 61 | 62 | 63 | Active Now 64 | 65 | 66 | 67 |
+573
68 |

69 | +201 since last hour 70 |

71 |
72 |
73 |
74 | 75 |
76 | 77 | 78 | 79 | Recent Signups 80 | 81 | 82 | 83 | 84 | 85 | 86 | Name 87 | Email 88 | Plan 89 | Date 90 | 91 | 92 | 93 | 94 | 95 | John Doe 96 | john@example.com 97 | Pro 98 | 2024-04-16 99 | 100 | 101 | John Doe 102 | john@example.com 103 | Pro 104 | 2024-04-16 105 | 106 | 107 | John Doe 108 | john@example.com 109 | Pro 110 | 2024-04-16 111 | 112 | 113 | John Doe 114 | john@example.com 115 | Pro 116 | 2024-04-16 117 | 118 | 119 | John Doe 120 | john@example.com 121 | Pro 122 | 2024-04-16 123 | 124 | 125 | John Doe 126 | john@example.com 127 | Pro 128 | 2024-04-16 129 | 130 | 131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | ); 139 | }; 140 | 141 | export default Dashboard; 142 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------