├── 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 | 
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 | | First Name |
22 | Last Name |
23 | Action |
24 |
25 |
26 |
27 |
28 | {allUsers?.map((user) => (
29 |
30 | | {user.firstName} |
31 | {user.lastName} |
32 |
33 |
43 | |
44 |
45 | ))}
46 |
47 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------