├── .eslintrc.json ├── nextjs.png ├── src ├── app │ ├── favicon.ico │ ├── profile │ │ ├── [id] │ │ │ └── page.tsx │ │ └── page.tsx │ ├── layout.tsx │ ├── api │ │ └── users │ │ │ ├── logout │ │ │ └── route.ts │ │ │ ├── me │ │ │ └── route.ts │ │ │ ├── verifyemail │ │ │ └── route.ts │ │ │ ├── signup │ │ │ └── route.ts │ │ │ └── login │ │ │ └── route.ts │ ├── globals.css │ ├── verifyemail │ │ └── page.tsx │ ├── login │ │ └── page.tsx │ ├── signup │ │ └── page.tsx │ └── page.tsx ├── helpers │ ├── getDataFromToken.ts │ └── mailer.ts ├── dbConfig │ └── dbConfig.ts ├── middleware.ts └── models │ └── userModel.js ├── explainer-diagram.png ├── next.config.js ├── postcss.config.js ├── sample.env ├── .gitignore ├── tailwind.config.js ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /nextjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiteshchoudhary/nextjs-fullstack-auth/HEAD/nextjs.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiteshchoudhary/nextjs-fullstack-auth/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /explainer-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiteshchoudhary/nextjs-fullstack-auth/HEAD/explainer-diagram.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | MONGO_URI=mongodb+srv://hitesh:subscribe@cluster0.nwxsjhi.mongodb.net/ 2 | TOKEN_SECRET=nextjsyoutube 3 | DOMAIN=http://localhost:3000 -------------------------------------------------------------------------------- /src/app/profile/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | export default function UserProfile({params}: any) { 2 | return ( 3 |
4 |

Profile

5 |
6 |

Profile page 7 | {params.id} 8 |

9 | 10 |
11 | ) 12 | } -------------------------------------------------------------------------------- /src/helpers/getDataFromToken.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import jwt from "jsonwebtoken"; 3 | 4 | export const getDataFromToken = (request: NextRequest) => { 5 | try { 6 | const token = request.cookies.get("token")?.value || ''; 7 | const decodedToken:any = jwt.verify(token, process.env.TOKEN_SECRET!); 8 | return decodedToken.id; 9 | } catch (error: any) { 10 | throw new Error(error.message); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | .env 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 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import { Inter } from 'next/font/google' 3 | 4 | const inter = Inter({ subsets: ['latin'] }) 5 | 6 | export const metadata = { 7 | title: 'Create Next App', 8 | description: 'Generated by create next app', 9 | } 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: { 14 | children: React.ReactNode 15 | }) { 16 | return ( 17 | 18 | 19 | {children} 20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 12 | 'gradient-conic': 13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /src/app/api/users/logout/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | 4 | export async function GET() { 5 | try { 6 | const response = NextResponse.json( 7 | { 8 | message: "Logout successful", 9 | success: true, 10 | } 11 | ) 12 | response.cookies.set("token", "", 13 | { httpOnly: true, expires: new Date(0) 14 | }); 15 | return response; 16 | } catch (error: any) { 17 | return NextResponse.json({ error: error.message }, { status: 500 }); 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /src/dbConfig/dbConfig.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | export async function connect() { 4 | try { 5 | mongoose.connect(process.env.MONGO_URI!); 6 | const connection = mongoose.connection; 7 | 8 | connection.on('connected', () => { 9 | console.log('MongoDB connected successfully'); 10 | }) 11 | 12 | connection.on('error', (err) => { 13 | console.log('MongoDB connection error. Please make sure MongoDB is running. ' + err); 14 | process.exit(); 15 | }) 16 | 17 | } catch (error) { 18 | console.log('Something goes wrong!'); 19 | console.log(error); 20 | 21 | } 22 | 23 | 24 | } -------------------------------------------------------------------------------- /src/app/api/users/me/route.ts: -------------------------------------------------------------------------------- 1 | import { getDataFromToken } from "@/helpers/getDataFromToken"; 2 | 3 | import { NextRequest, NextResponse } from "next/server"; 4 | import User from "@/models/userModel"; 5 | import { connect } from "@/dbConfig/dbConfig"; 6 | 7 | connect(); 8 | 9 | export async function GET(request:NextRequest){ 10 | 11 | try { 12 | const userId = await getDataFromToken(request); 13 | const user = await User.findOne({_id: userId}).select("-password"); 14 | return NextResponse.json({ 15 | mesaaage: "User found", 16 | data: user 17 | }) 18 | } catch (error:any) { 19 | return NextResponse.json({error: error.message}, {status: 400}); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server' 2 | import type { NextRequest } from 'next/server' 3 | 4 | 5 | export function middleware(request: NextRequest) { 6 | const path = request.nextUrl.pathname 7 | 8 | const isPublicPath = path === '/login' || path === '/signup' || path === '/verifyemail' 9 | 10 | const token = request.cookies.get('token')?.value || '' 11 | 12 | if(isPublicPath && token) { 13 | return NextResponse.redirect(new URL('/', request.nextUrl)) 14 | } 15 | 16 | if (!isPublicPath && !token) { 17 | return NextResponse.redirect(new URL('/login', request.nextUrl)) 18 | } 19 | 20 | } 21 | 22 | 23 | // See "Matching Paths" below to learn more 24 | export const config = { 25 | matcher: [ 26 | '/', 27 | '/profile', 28 | '/login', 29 | '/signup', 30 | '/verifyemail' 31 | ] 32 | } -------------------------------------------------------------------------------- /src/models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = new mongoose.Schema({ 4 | username: { 5 | type: String, 6 | required: [true, "Please provide a username"], 7 | unique: true, 8 | }, 9 | email: { 10 | type: String, 11 | required: [true, "Please provide a email"], 12 | unique: true, 13 | }, 14 | password: { 15 | type: String, 16 | required: [true, "Please provide a password"], 17 | }, 18 | isVerfied: { 19 | type: Boolean, 20 | default: false, 21 | }, 22 | isAdmin: { 23 | type: Boolean, 24 | default: false, 25 | }, 26 | forgotPasswordToken: String, 27 | forgotPasswordTokenExpiry: Date, 28 | verifyToken: String, 29 | verifyTokenExpiry: Date, 30 | }) 31 | 32 | const User = mongoose.models.users || mongoose.model("users", userSchema); 33 | 34 | export default User; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth-nextjs-youtube", 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 | "@types/node": "20.3.2", 13 | "@types/react": "18.2.14", 14 | "@types/react-dom": "18.2.6", 15 | "autoprefixer": "10.4.14", 16 | "axios": "^1.4.0", 17 | "bcryptjs": "^2.4.3", 18 | "eslint": "8.43.0", 19 | "eslint-config-next": "13.4.7", 20 | "jsonwebtoken": "^9.0.0", 21 | "mongoose": "^7.3.1", 22 | "next": "13.4.7", 23 | "nodemailer": "^6.9.3", 24 | "postcss": "8.4.24", 25 | "react": "18.2.0", 26 | "react-dom": "18.2.0", 27 | "react-hot-toast": "^2.4.1", 28 | "tailwindcss": "3.3.2", 29 | "typescript": "5.1.6" 30 | }, 31 | "devDependencies": { 32 | "@types/bcryptjs": "^2.4.2", 33 | "@types/jsonwebtoken": "^9.0.2", 34 | "@types/nodemailer": "^6.4.8" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/api/users/verifyemail/route.ts: -------------------------------------------------------------------------------- 1 | import {connect} from "@/dbConfig/dbConfig"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import User from "@/models/userModel"; 4 | 5 | 6 | 7 | connect() 8 | 9 | 10 | export async function POST(request: NextRequest){ 11 | 12 | try { 13 | const reqBody = await request.json() 14 | const {token} = reqBody 15 | console.log(token); 16 | 17 | const user = await User.findOne({verifyToken: token, verifyTokenExpiry: {$gt: Date.now()}}); 18 | 19 | if (!user) { 20 | return NextResponse.json({error: "Invalid token"}, {status: 400}) 21 | } 22 | console.log(user); 23 | 24 | user.isVerfied = true; 25 | user.verifyToken = undefined; 26 | user.verifyTokenExpiry = undefined; 27 | await user.save(); 28 | 29 | return NextResponse.json({ 30 | message: "Email verified successfully", 31 | success: true 32 | }) 33 | 34 | 35 | } catch (error:any) { 36 | return NextResponse.json({error: error.message}, {status: 500}) 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A detailed course to undestand nextjs 2 | 3 | This long video was designed to give you an indepth understanding about latest nextjs and how it works. 4 | 5 | ## Tech Stack 6 | - Nextjs 7 | - typescript 8 | - mongodb 9 | - mailtrap 10 | 11 | ![Course Image](./nextjs.png) 12 | 13 | --- 14 | Available on my youtube channel 15 | [Youtube channel link](https://www.youtube.com/@HiteshChoudharydotcom) 16 | 17 | ## Getting Started 18 | 19 | First, run the development server: 20 | 21 | ```bash 22 | npm run dev 23 | # or 24 | yarn dev 25 | # or 26 | pnpm dev 27 | ``` 28 | ## Assignment 29 | 1. Improve the UI of the application 30 | 2. Add feature of forgot password 31 | 32 | --- 33 | ### Hint: 34 | For forgot password feature. 35 | 1. User needs a page to enter his email and submit. 36 | 2. Validate if user exists, if yes, send him same token email that we discussed in this course 37 | 3. User clicks on email and get a page to enter new password with a submit button. 38 | 4. As soon as he click submit button, he is sending you a token and new password. 39 | 5. Verify the token and save the new password after encrypting it. 40 | 41 | --- 42 | ## your completed assignments 43 | 44 | - Add your repo link here 45 | - -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/users/signup/route.ts: -------------------------------------------------------------------------------- 1 | import {connect} from "@/dbConfig/dbConfig"; 2 | import User from "@/models/userModel"; 3 | import { NextRequest, NextResponse } from "next/server"; 4 | import bcryptjs from "bcryptjs"; 5 | import { sendEmail } from "@/helpers/mailer"; 6 | 7 | 8 | connect() 9 | 10 | 11 | export async function POST(request: NextRequest){ 12 | try { 13 | const reqBody = await request.json() 14 | const {username, email, password} = reqBody 15 | 16 | console.log(reqBody); 17 | 18 | //check if user already exists 19 | const user = await User.findOne({email}) 20 | 21 | if(user){ 22 | return NextResponse.json({error: "User already exists"}, {status: 400}) 23 | } 24 | 25 | //hash password 26 | const salt = await bcryptjs.genSalt(10) 27 | const hashedPassword = await bcryptjs.hash(password, salt) 28 | 29 | const newUser = new User({ 30 | username, 31 | email, 32 | password: hashedPassword 33 | }) 34 | 35 | const savedUser = await newUser.save() 36 | console.log(savedUser); 37 | 38 | //send verification email 39 | 40 | await sendEmail({email, emailType: "VERIFY", userId: savedUser._id}) 41 | 42 | return NextResponse.json({ 43 | message: "User created successfully", 44 | success: true, 45 | savedUser 46 | }) 47 | 48 | 49 | 50 | 51 | } catch (error: any) { 52 | return NextResponse.json({error: error.message}, {status: 500}) 53 | 54 | } 55 | } -------------------------------------------------------------------------------- /src/app/profile/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import axios from "axios"; 3 | import Link from "next/link"; 4 | import React, {useState} from "react"; 5 | import {toast} from "react-hot-toast"; 6 | import {useRouter} from "next/navigation"; 7 | 8 | 9 | export default function ProfilePage() { 10 | const router = useRouter() 11 | const [data, setData] = useState("nothing") 12 | const logout = async () => { 13 | try { 14 | await axios.get('/api/users/logout') 15 | toast.success('Logout successful') 16 | router.push('/login') 17 | } catch (error:any) { 18 | console.log(error.message); 19 | toast.error(error.message) 20 | } 21 | } 22 | 23 | const getUserDetails = async () => { 24 | const res = await axios.get('/api/users/me') 25 | console.log(res.data); 26 | setData(res.data.data._id) 27 | } 28 | 29 | return ( 30 |
31 |

Profile

32 |
33 |

Profile page

34 |

{data === 'nothing' ? "Nothing" : {data} 35 | }

36 |
37 | 41 | 42 | 46 | 47 | 48 |
49 | ) 50 | } -------------------------------------------------------------------------------- /src/app/api/users/login/route.ts: -------------------------------------------------------------------------------- 1 | import {connect} from "@/dbConfig/dbConfig"; 2 | import User from "@/models/userModel"; 3 | import { NextRequest, NextResponse } from "next/server"; 4 | import bcryptjs from "bcryptjs"; 5 | import jwt from "jsonwebtoken"; 6 | 7 | connect() 8 | 9 | export async function POST(request: NextRequest){ 10 | try { 11 | 12 | const reqBody = await request.json() 13 | const {email, password} = reqBody; 14 | console.log(reqBody); 15 | 16 | //check if user exists 17 | const user = await User.findOne({email}) 18 | if(!user){ 19 | return NextResponse.json({error: "User does not exist"}, {status: 400}) 20 | } 21 | console.log("user exists"); 22 | 23 | 24 | //check if password is correct 25 | const validPassword = await bcryptjs.compare(password, user.password) 26 | if(!validPassword){ 27 | return NextResponse.json({error: "Invalid password"}, {status: 400}) 28 | } 29 | console.log(user); 30 | 31 | //create token data 32 | const tokenData = { 33 | id: user._id, 34 | username: user.username, 35 | email: user.email 36 | } 37 | //create token 38 | const token = await jwt.sign(tokenData, process.env.TOKEN_SECRET!, {expiresIn: "1d"}) 39 | 40 | const response = NextResponse.json({ 41 | message: "Login successful", 42 | success: true, 43 | }) 44 | response.cookies.set("token", token, { 45 | httpOnly: true, 46 | 47 | }) 48 | return response; 49 | 50 | } catch (error: any) { 51 | return NextResponse.json({error: error.message}, {status: 500}) 52 | } 53 | } -------------------------------------------------------------------------------- /src/app/verifyemail/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import Link from "next/link"; 5 | import React, { useEffect, useState } from "react"; 6 | 7 | 8 | export default function VerifyEmailPage() { 9 | 10 | const [token, setToken] = useState(""); 11 | const [verified, setVerified] = useState(false); 12 | const [error, setError] = useState(false); 13 | 14 | const verifyUserEmail = async () => { 15 | try { 16 | await axios.post('/api/users/verifyemail', {token}) 17 | setVerified(true); 18 | } catch (error:any) { 19 | setError(true); 20 | console.log(error.reponse.data); 21 | 22 | } 23 | 24 | } 25 | 26 | useEffect(() => { 27 | const urlToken = window.location.search.split("=")[1]; 28 | setToken(urlToken || ""); 29 | }, []); 30 | 31 | 32 | useEffect(() => { 33 | if(token.length > 0) { 34 | verifyUserEmail(); 35 | } 36 | }, [token]); 37 | 38 | return( 39 |
40 | 41 |

Verify Email

42 |

{token ? `${token}` : "no token"}

43 | 44 | {verified && ( 45 |
46 |

Email Verified

47 | 48 | Login 49 | 50 |
51 | )} 52 | {error && ( 53 |
54 |

Error

55 | 56 |
57 | )} 58 |
59 | ) 60 | 61 | } -------------------------------------------------------------------------------- /src/helpers/mailer.ts: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer'; 2 | import User from "@/models/userModel"; 3 | import bcryptjs from 'bcryptjs'; 4 | 5 | 6 | export const sendEmail = async({email, emailType, userId}:any) => { 7 | try { 8 | // create a hased token 9 | const hashedToken = await bcryptjs.hash(userId.toString(), 10) 10 | 11 | if (emailType === "VERIFY") { 12 | await User.findByIdAndUpdate(userId, 13 | {verifyToken: hashedToken, verifyTokenExpiry: Date.now() + 3600000}) 14 | } else if (emailType === "RESET"){ 15 | await User.findByIdAndUpdate(userId, 16 | {forgotPasswordToken: hashedToken, forgotPasswordTokenExpiry: Date.now() + 3600000}) 17 | } 18 | 19 | var transport = nodemailer.createTransport({ 20 | host: "sandbox.smtp.mailtrap.io", 21 | port: 2525, 22 | auth: { 23 | user: "3fd364695517df", 24 | pass: "7383d58fd399cf" 25 | //TODO: add these credentials to .env file 26 | } 27 | }); 28 | 29 | 30 | const mailOptions = { 31 | from: 'hitesh@gmail.com', 32 | to: email, 33 | subject: emailType === "VERIFY" ? "Verify your email" : "Reset your password", 34 | html: `

Click here to ${emailType === "VERIFY" ? "verify your email" : "reset your password"} 35 | or copy and paste the link below in your browser.
${process.env.DOMAIN}/verifyemail?token=${hashedToken} 36 |

` 37 | } 38 | 39 | const mailresponse = await transport.sendMail 40 | (mailOptions); 41 | return mailresponse; 42 | 43 | } catch (error:any) { 44 | throw new Error(error.message); 45 | } 46 | } -------------------------------------------------------------------------------- /src/app/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Link from "next/link"; 3 | import React, {useEffect} from "react"; 4 | import {useRouter} from "next/navigation"; 5 | import axios from "axios"; 6 | import { toast } from "react-hot-toast"; 7 | 8 | 9 | 10 | 11 | 12 | export default function LoginPage() { 13 | const router = useRouter(); 14 | const [user, setUser] = React.useState({ 15 | email: "", 16 | password: "", 17 | 18 | }) 19 | const [buttonDisabled, setButtonDisabled] = React.useState(false); 20 | const [loading, setLoading] = React.useState(false); 21 | 22 | 23 | const onLogin = async () => { 24 | try { 25 | setLoading(true); 26 | const response = await axios.post("/api/users/login", user); 27 | console.log("Login success", response.data); 28 | toast.success("Login success"); 29 | router.push("/profile"); 30 | } catch (error:any) { 31 | console.log("Login failed", error.message); 32 | toast.error(error.message); 33 | } finally{ 34 | setLoading(false); 35 | } 36 | } 37 | 38 | useEffect(() => { 39 | if(user.email.length > 0 && user.password.length > 0) { 40 | setButtonDisabled(false); 41 | } else{ 42 | setButtonDisabled(true); 43 | } 44 | }, [user]); 45 | 46 | return ( 47 |
48 |

{loading ? "Processing" : "Login"}

49 |
50 | 51 | 52 | setUser({...user, email: e.target.value})} 58 | placeholder="email" 59 | /> 60 | 61 | setUser({...user, password: e.target.value})} 67 | placeholder="password" 68 | /> 69 | 72 | Visit Signup page 73 |
74 | ) 75 | 76 | } -------------------------------------------------------------------------------- /src/app/signup/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Link from "next/link"; 3 | import React, { useEffect } from "react"; 4 | import {useRouter} from "next/navigation"; 5 | import axios from "axios"; 6 | import { toast } from "react-hot-toast"; 7 | 8 | 9 | 10 | 11 | export default function SignupPage() { 12 | const router = useRouter(); 13 | const [user, setUser] = React.useState({ 14 | email: "", 15 | password: "", 16 | username: "", 17 | }) 18 | const [buttonDisabled, setButtonDisabled] = React.useState(false); 19 | const [loading, setLoading] = React.useState(false); 20 | 21 | const onSignup = async () => { 22 | try { 23 | setLoading(true); 24 | const response = await axios.post("/api/users/signup", user); 25 | console.log("Signup success", response.data); 26 | router.push("/login"); 27 | 28 | } catch (error:any) { 29 | console.log("Signup failed", error.message); 30 | 31 | toast.error(error.message); 32 | }finally { 33 | setLoading(false); 34 | } 35 | } 36 | 37 | useEffect(() => { 38 | if(user.email.length > 0 && user.password.length > 0 && user.username.length > 0) { 39 | setButtonDisabled(false); 40 | } else { 41 | setButtonDisabled(true); 42 | } 43 | }, [user]); 44 | 45 | 46 | return ( 47 |
48 |

{loading ? "Processing" : "Signup"}

49 |
50 | 51 | setUser({...user, username: e.target.value})} 57 | placeholder="username" 58 | /> 59 | 60 | setUser({...user, email: e.target.value})} 66 | placeholder="email" 67 | /> 68 | 69 | setUser({...user, password: e.target.value})} 75 | placeholder="password" 76 | /> 77 | 80 | Visit login page 81 |
82 | ) 83 | 84 | } -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 |

8 | Get started by editing  9 | src/app/page.tsx 10 |

11 |
12 | 18 | By{' '} 19 | Vercel Logo 27 | 28 |
29 |
30 | 31 |
32 | Next.js Logo 40 |
41 | 42 |
43 | 49 |

50 | Docs{' '} 51 | 52 | -> 53 | 54 |

55 |

56 | Find in-depth information about Next.js features and API. 57 |

58 |
59 | 60 | 66 |

67 | Learn{' '} 68 | 69 | -> 70 | 71 |

72 |

73 | Learn about Next.js in an interactive course with quizzes! 74 |

75 |
76 | 77 | 83 |

84 | Templates{' '} 85 | 86 | -> 87 | 88 |

89 |

90 | Explore the Next.js 13 playground. 91 |

92 |
93 | 94 | 100 |

101 | Deploy{' '} 102 | 103 | -> 104 | 105 |

106 |

107 | Instantly deploy your Next.js site to a shareable URL with Vercel. 108 |

109 |
110 |
111 |
112 | ) 113 | } 114 | --------------------------------------------------------------------------------