├── .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 | 
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 |
Logout
41 |
42 |
GetUser Details
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 | email
52 | setUser({...user, email: e.target.value})}
58 | placeholder="email"
59 | />
60 | password
61 | setUser({...user, password: e.target.value})}
67 | placeholder="password"
68 | />
69 | Login here
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 | username
51 | setUser({...user, username: e.target.value})}
57 | placeholder="username"
58 | />
59 | email
60 | setUser({...user, email: e.target.value})}
66 | placeholder="email"
67 | />
68 | password
69 | setUser({...user, password: e.target.value})}
75 | placeholder="password"
76 | />
77 | {buttonDisabled ? "No signup" : "Signup"}
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 |
29 |
30 |
31 |
32 |
40 |
41 |
42 |
111 |
112 | )
113 | }
114 |
--------------------------------------------------------------------------------