├── .eslintrc.json
├── .gitignore
├── README.md
├── components.json
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── next.svg
└── vercel.svg
├── sample-env
├── src
├── app
│ ├── api
│ │ └── users
│ │ │ ├── forgotpassword
│ │ │ └── route.ts
│ │ │ ├── login
│ │ │ └── route.ts
│ │ │ ├── logout
│ │ │ └── route.js
│ │ │ ├── me
│ │ │ └── route.ts
│ │ │ ├── passwordreset
│ │ │ └── route.ts
│ │ │ ├── signup
│ │ │ └── route.ts
│ │ │ └── verifyemail
│ │ │ └── route.ts
│ ├── favicon.ico
│ ├── forgotpassword
│ │ └── page.tsx
│ ├── globals.css
│ ├── layout.tsx
│ ├── login
│ │ └── page.tsx
│ ├── page.tsx
│ ├── passwordreset
│ │ └── page.tsx
│ ├── profile
│ │ ├── [id]
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── signup
│ │ └── page.tsx
│ └── verifyemail
│ │ └── page.tsx
├── components
│ ├── theme-provider.tsx
│ └── ui
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── hover-card.tsx
│ │ ├── input.tsx
│ │ └── label.tsx
├── dbConfig
│ └── dbConfig.ts
├── helpers
│ ├── getDataFromToken.ts
│ └── mailer.ts
├── lib
│ └── utils.ts
├── middleware.ts
└── models
│ └── userModel.js
├── tailwind.config.js
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # Secret file
28 | .env
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 | First, Replace the values from .env-example with your actual values
5 |
6 | Install the required packages
7 |
8 | run the development server:
9 |
10 | ```bash
11 | npm run dev
12 | # or
13 | yarn dev
14 | # or
15 | pnpm dev
16 | ```
17 |
18 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
19 |
20 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
21 |
22 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
23 |
24 | ## Learn More
25 |
26 | To learn more about Next.js, take a look at the following resources:
27 |
28 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
29 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
30 |
31 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
32 |
33 | ## Deploy on Vercel
34 |
35 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
36 |
37 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
38 |
--------------------------------------------------------------------------------
/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.js",
8 | "css": "src/app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "authnextjs",
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-hover-card": "^1.0.6",
13 | "@radix-ui/react-label": "^2.0.2",
14 | "@radix-ui/react-slot": "^1.0.2",
15 | "@types/node": "20.4.5",
16 | "@types/react": "18.2.16",
17 | "@types/react-dom": "18.2.7",
18 | "autoprefixer": "10.4.14",
19 | "axios": "^1.4.0",
20 | "bcryptjs": "^2.4.3",
21 | "class-variance-authority": "^0.7.0",
22 | "clsx": "^2.0.0",
23 | "eslint": "8.45.0",
24 | "eslint-config-next": "13.4.12",
25 | "jsonwebtoken": "^9.0.1",
26 | "lucide-react": "^0.265.0",
27 | "mongoose": "^7.4.1",
28 | "next": "13.4.12",
29 | "next-themes": "^0.2.1",
30 | "nodemailer": "^6.9.4",
31 | "postcss": "8.4.27",
32 | "react": "18.2.0",
33 | "react-dom": "18.2.0",
34 | "react-hot-toast": "^2.4.1",
35 | "tailwind-merge": "^1.14.0",
36 | "tailwindcss": "3.3.3",
37 | "tailwindcss-animate": "^1.0.6",
38 | "typescript": "5.1.6"
39 | },
40 | "devDependencies": {
41 | "@types/bcryptjs": "^2.4.2",
42 | "@types/jsonwebtoken": "^9.0.2",
43 | "@types/nodemailer": "^6.4.9"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sample-env:
--------------------------------------------------------------------------------
1 | MONGO_URI= "Your Mongo DB Connection String"
2 | TOKEN_SECRET= "Your Token Secret"
3 | DOMAIN= "Your Domain"
4 | MAILTRAP_USER=" Mailtrap user id "
5 | MAILTRAP_PASS=" Mailtrap password "
--------------------------------------------------------------------------------
/src/app/api/users/forgotpassword/route.ts:
--------------------------------------------------------------------------------
1 | import {connect} from '@/dbConfig/dbConfig'
2 | import User from '@/models/userModel'
3 | import { NextRequest, NextResponse } from 'next/server'
4 | import { sendEmail } from '@/helpers/mailer'
5 |
6 | connect()
7 |
8 | export async function POST(request:NextRequest){
9 |
10 | try {
11 | const reqBody= await request.json()
12 | const {email} = reqBody
13 | console.log(reqBody)
14 |
15 | const user = await User.findOne({email})
16 | if(!user){
17 | return NextResponse.json({error: "User not found"}, {status: 400})
18 | }
19 |
20 | //send reset password email
21 | await sendEmail({email, emailType: "RESET", userId: user._id})
22 |
23 | user.forgotPasswordToken = undefined;
24 | user.forgotPasswordTokenExpiry = undefined;
25 | await user.save()
26 |
27 | console.log(user)
28 | return NextResponse.json({
29 | message: "User Found",
30 | success: true
31 | })
32 |
33 |
34 |
35 | } catch (error: any) {
36 | return NextResponse.json({error: error.message}, {status: 500})
37 | }
38 | }
--------------------------------------------------------------------------------
/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 | const reqBody = await request.json()
12 | const {email, password} = reqBody
13 | console.log(reqBody)
14 |
15 | //Check if user is exists
16 | const user = await User.findOne({email})
17 | if(!user){
18 | return NextResponse.json({error: "User not found"}, {status: 400})
19 | }
20 |
21 | //Check if password is correct
22 | const validPassword = await bcryptjs.compare(password, user.password)
23 | if(!validPassword){
24 | return NextResponse.json({error: "Invalid Password"}, {status: 400})
25 | }
26 |
27 | //Create token data
28 | const tokenData = {
29 | id: user._id,
30 | username: user.username,
31 | email: user.email,
32 | }
33 |
34 | //Create token
35 | const token = await jwt.sign(tokenData, process.env.TOKEN_SECRET!, {expiresIn: "1d"})
36 |
37 | //Sent token to user cookies
38 | const response = NextResponse.json({
39 | message: "Login successful",
40 | success: true,
41 | })
42 | response.cookies.set("token", token, {
43 | httpOnly: true,
44 | })
45 | return response
46 |
47 | } catch (error: any) {
48 | return NextResponse.json({error: error.message}, {status: 500})
49 | }
50 | }
--------------------------------------------------------------------------------
/src/app/api/users/logout/route.js:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 |
3 | export async function GET() {
4 | try {
5 | const response = NextResponse.json({
6 | message: "Logout Successfull",
7 | success: true,
8 | })
9 | response.cookies.set("token", "", { httpOnly: true, expires: new Date(0)})
10 | return response
11 | } catch (error) {
12 | return NextResponse.json({ error: error.message}, {status: 500})
13 | }
14 | }
--------------------------------------------------------------------------------
/src/app/api/users/me/route.ts:
--------------------------------------------------------------------------------
1 | import { getDataFromToken } from '@/helpers/getDataFromToken'
2 | import { NextRequest, NextResponse } from 'next/server'
3 | import User from "@/models/userModel"
4 | import {connect} from "@/dbConfig/dbConfig"
5 |
6 | connect()
7 |
8 | export async function GET(request:NextRequest) {
9 | try {
10 | const userId = await getDataFromToken(request)
11 | const user = await User.findOne({_id: userId}).select("-password")
12 | return NextResponse.json({
13 | message: "User found",
14 | data: user,
15 | })
16 | } catch (error:any) {
17 | return NextResponse.json({ error: error.message}, {status: 400})
18 |
19 | }
20 | }
--------------------------------------------------------------------------------
/src/app/api/users/passwordreset/route.ts:
--------------------------------------------------------------------------------
1 | import {connect} from '@/dbConfig/dbConfig'
2 | import { NextRequest, NextResponse } from 'next/server'
3 | import User from '@/models/userModel'
4 | import bcryptjs from 'bcryptjs'
5 |
6 | connect()
7 |
8 | export async function POST(request: NextRequest){
9 | try {
10 |
11 | const reqBody = await request.json()
12 | const {token, password} = reqBody
13 | console.log(token, password)
14 |
15 | const user = await User.findOne({ forgotPasswordToken: token, forgotPasswordTokenExpiry: { $gt: Date.now() }})
16 |
17 | if(!user){
18 | return NextResponse.json({error: "Invalid Token"}, {status: 400})
19 | }
20 |
21 | //Hash the password
22 | const salt = await bcryptjs.genSalt(10)
23 | const hashedPassword = await bcryptjs.hash(password, salt)
24 |
25 | user.password = hashedPassword;
26 | user.forgorPasswordToken = undefined
27 | user.forgotPasswordTokenExpiry = undefined
28 | await user.save()
29 |
30 | return NextResponse.json({
31 | message: "Password changed successfully",
32 | success: true
33 | })
34 |
35 | } catch (error:any) {
36 | return NextResponse.json({error: error.message} , {status: 500})
37 | }
38 | }
--------------------------------------------------------------------------------
/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 | connect()
8 |
9 |
10 | export async function POST(request: NextRequest){
11 | try {
12 | const reqBody = await request.json()
13 | const {username, email, password} = reqBody
14 | console.log(reqBody)
15 |
16 | //check if user already exists
17 | const user = await User.findOne({email})
18 | if(user){
19 | return NextResponse.json({error :'User Already Exists'}, {status: 400})
20 | }
21 | //Hash the password
22 | const salt = await bcryptjs.genSalt(10)
23 | const hashedPassword = await bcryptjs.hash(password, salt)
24 | //Create a new user
25 | const newUser = new User({
26 | username,
27 | email,
28 | password: hashedPassword
29 | })
30 | //Save the new user to the database
31 | const savedUser = await newUser.save()
32 | console.log(savedUser)
33 |
34 | //send verification email
35 |
36 | await sendEmail({email, emailType: "VERIFY", userId: savedUser._id})
37 |
38 | //Returning the response
39 | return NextResponse.json({
40 | message: "User created successfully",
41 | success: true,
42 | savedUser
43 | })
44 |
45 | } catch(error: any) {
46 | return NextResponse.json({ error: error.message },
47 | {status: 500})
48 | }
49 | }
--------------------------------------------------------------------------------
/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 | export async function POST(request: NextRequest){
10 | try {
11 | const reqBody = await request.json()
12 | const {token} = reqBody
13 |
14 | const user = await User.findOne({verifyToken: token, verifyTokenExpiry: {$gt: Date.now()}})
15 |
16 | if(!user){
17 | return NextResponse.json({error: "Invalid Token"}, {status: 400})
18 | }
19 |
20 | user.isVerified = true
21 | user.verifyToken = undefined
22 | user.verifyTokenExpiry = undefined
23 | await user.save()
24 |
25 | return NextResponse.json({
26 | message: "Email Verified Successfully",
27 | success: true
28 | })
29 |
30 | } catch (error:any) {
31 | return NextResponse.json({error: error.message} , {status: 500})
32 | }
33 | }
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samabid/authnextjs/d640987a912c818be103e4652823f1b6a8370857/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/forgotpassword/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState } from "react"
4 | import axios from "axios"
5 | import { toast } from "react-hot-toast"
6 | import {
7 | Card,
8 | CardContent,
9 | CardDescription,
10 | CardFooter,
11 | CardHeader,
12 | CardTitle,
13 | } from "@/components/ui/card"
14 | import { Button } from "@/components/ui/button"
15 | import { Input } from "@/components/ui/input"
16 | import { Label } from "@/components/ui/label"
17 | import { Loader2 } from "lucide-react"
18 | import { set } from "mongoose"
19 |
20 | export default function ForgotPasswordPage() {
21 | const [email, setEmail] = useState("")
22 | const [loading, setLoading] = useState(false)
23 |
24 | const verifyEmail = async () => {
25 | try {
26 | setLoading(true)
27 | await axios.post('/api/users/forgotpassword', {email})
28 | toast.success("User Found!")
29 | toast("Please check your inbox and click on verification link.", {duration: 10000})
30 | } catch (error: any) {
31 | toast.error("Something went wrong try again", error.message)
32 | } finally {
33 | setLoading(false)
34 | setEmail("")
35 | }
36 | }
37 |
38 | return (
39 |
40 |
41 |
42 | Passwrod Reset
43 |
44 | Enter your email to reset the password
45 |
46 |
47 |
48 |
49 | Email
50 | setEmail(e.target.value)}
56 | />
57 |
58 |
59 |
60 | 0 ? false : true} onClick={verifyEmail}>
61 | {loading ? : "Submit"}
62 |
63 |
64 |
65 |
66 | )
67 | }
--------------------------------------------------------------------------------
/src/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: hsl(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 | }
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import './globals.css'
2 | import type { Metadata } from 'next'
3 | import { Inter } from 'next/font/google'
4 | import { Toaster } from 'react-hot-toast';
5 | import { ThemeProvider } from '@/components/theme-provider'
6 |
7 | const inter = Inter({ subsets: ['latin'] })
8 |
9 | export const metadata: Metadata = {
10 | title: 'Create Next App',
11 | description: 'Generated by create next app',
12 | }
13 |
14 | export default function RootLayout({
15 | children,
16 | }: {
17 | children: React.ReactNode
18 | }) {
19 | return (
20 |
21 |
22 |
23 | {children}
24 |
25 |
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/login/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import Link from "next/link"
3 | import React, {useEffect, useState} from "react"
4 | import { useRouter } from "next/navigation"
5 | import axios from "axios"
6 | import toast from "react-hot-toast"
7 | import {
8 | Card,
9 | CardContent,
10 | CardDescription,
11 | CardFooter,
12 | CardHeader,
13 | CardTitle,
14 | } from "@/components/ui/card"
15 | import { Button } from "@/components/ui/button"
16 | import { Input } from "@/components/ui/input"
17 | import { Label } from "@/components/ui/label"
18 | import { Loader2 } from "lucide-react"
19 |
20 |
21 | export default function LoginPage() {
22 | const router = useRouter()
23 | const [user, setUser] = useState({
24 | email: "",
25 | password: "",
26 | })
27 | const [loading, setLoading] = useState(false)
28 |
29 | const onLogin = async () => {
30 | try {
31 | setLoading(true)
32 | await axios.post("/api/users/login", user)
33 | toast.success("Login Successfull")
34 | router.push("/profile")
35 |
36 | } catch (error: any) {
37 | toast.error("Email or Password is incorrect", error.message)
38 | }finally{
39 | setLoading(false)
40 | }
41 | }
42 |
43 |
44 | return (
45 |
83 |
84 | )
85 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/app/passwordreset/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { useState, useEffect } from "react"
3 | import axios from "axios"
4 | import { toast } from "react-hot-toast"
5 | import { useRouter } from "next/navigation"
6 | import { Button } from "@/components/ui/button"
7 | import { Input } from "@/components/ui/input"
8 | import {
9 | Card,
10 | CardContent,
11 | CardDescription,
12 | CardHeader,
13 | CardTitle,
14 | } from "@/components/ui/card"
15 | import {
16 | HoverCard,
17 | HoverCardContent,
18 | HoverCardTrigger,
19 | } from "@/components/ui/hover-card"
20 | import { Loader2 } from "lucide-react"
21 |
22 | export default function PasswordReset() {
23 | const router = useRouter()
24 | const [token, setToken] = useState("")
25 | const [password, setPassword] = useState("")
26 | const [loading, setLoading] = useState(false)
27 |
28 | const changePassword = async () => {
29 | try {
30 | setLoading(true)
31 | const response = await axios.post('/api/users/passwordreset/', {token, password})
32 | toast.success("Password Changed Successfully", response.data.message)
33 | setPassword("")
34 | router.push("/login")
35 | } catch (error:any) {
36 | toast.error(error.message)
37 | }finally{
38 | setLoading(false)
39 | }
40 | }
41 |
42 | useEffect(() => {
43 | const urlToken = window.location.search.split("=")[1]
44 | setToken(urlToken || "")
45 | }, [])
46 |
47 | return (
48 |
49 |
50 |
51 | Token
52 |
53 |
54 | {token ? `${token}` : "no token"}
55 |
56 |
57 |
58 |
59 | Password Reset
60 |
61 | Please Enter Your New Password
62 |
63 |
64 |
65 |
66 | setPassword(e.target.value)} />
67 | 0 ? false : true} onClick={changePassword}>
68 | {loading ? : "Submit"}
69 |
70 |
71 |
72 |
73 |
74 | )
75 | }
--------------------------------------------------------------------------------
/src/app/profile/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | export default function UserProfile({params}:any) {
2 | return (
3 |
4 |
Profile
5 |
{params.id}
6 |
7 | )
8 | }
--------------------------------------------------------------------------------
/src/app/profile/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { useState } from "react"
3 | import axios from "axios"
4 | import Link from "next/link"
5 | import { useRouter } from "next/navigation"
6 | import toast from "react-hot-toast"
7 | import {
8 | Card,
9 | CardContent,
10 | CardDescription,
11 | CardFooter,
12 | CardHeader,
13 | CardTitle,
14 | } from "@/components/ui/card"
15 | import { Button } from "@/components/ui/button"
16 |
17 |
18 | export default function ProfilePage() {
19 |
20 | const router = useRouter()
21 | const [data, setData] = useState("nothing")
22 |
23 | const logout = async () => {
24 | try {
25 | await axios.get('/api/users/logout')
26 | toast.success('Logout Successfully')
27 | router.push('/login')
28 | } catch (error: any) {
29 | toast.error(error.message)
30 | }
31 | }
32 |
33 | const getUserDetails = async () => {
34 | const res = await axios.get('/api/users/me')
35 | setData(res.data.data._id)
36 | }
37 |
38 | return (
39 |
40 |
41 |
42 | User Details
43 |
44 | {data === "nothing" ? "Nothing" : {data}}
45 |
46 |
47 |
48 |
49 | Get User Detailes
50 | Logout
51 |
52 |
53 |
54 |
55 |
56 | )
57 | }
--------------------------------------------------------------------------------
/src/app/signup/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import Link from "next/link"
3 | import React, {useEffect, useState} from "react"
4 | import { useRouter } from "next/navigation"
5 | import axios from "axios"
6 | import toast from "react-hot-toast"
7 | import {
8 | Card,
9 | CardContent,
10 | CardDescription,
11 | CardFooter,
12 | CardHeader,
13 | CardTitle,
14 | } from "@/components/ui/card"
15 | import { Button } from "@/components/ui/button"
16 | import { Input } from "@/components/ui/input"
17 | import { Label } from "@/components/ui/label"
18 | import { Loader2 } from "lucide-react"
19 |
20 |
21 | export default function SignupPage() {
22 | const router = useRouter()
23 | const [user, setUser] = useState({
24 | email: "",
25 | password: "",
26 | username:"",
27 | })
28 |
29 | const [loading, setLoading] = useState(false)
30 |
31 | const onSignup = async () => {
32 | try {
33 | setLoading(true)
34 | await axios.post("/api/users/signup", user)
35 | toast.success("Signup successfull")
36 | toast("Please check your inbox and click on verification link.", {duration: 10000})
37 | router.push("/login")
38 | } catch (error: any) {
39 | toast.error(error.message)
40 | }finally{
41 | setLoading(false)
42 | }
43 | }
44 |
45 |
46 | return (
47 |
95 | )
96 | }
--------------------------------------------------------------------------------
/src/app/verifyemail/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import axios from "axios"
3 | import Link from "next/link"
4 | import { useState, useEffect } from "react"
5 | import { toast } from "react-hot-toast"
6 | import {Button} from '@/components/ui/button'
7 | import { useRouter } from "next/navigation"
8 |
9 |
10 | export default function VerifyEmailPage() {
11 |
12 | const router = useRouter()
13 |
14 | const [token, setToken] = useState("")
15 | const [verified, setVerified] = useState(false)
16 | const [error, setError] = useState(false)
17 |
18 | const verifyUserEmail = async () => {
19 | try {
20 | await axios.post('/api/users/verifyemail', {token})
21 | setVerified(true)
22 | toast.success("Your account has been verified")
23 | } catch (error: any) {
24 | setError(true)
25 | toast.error(error.message)
26 | }
27 | }
28 |
29 | useEffect(() => {
30 | const urlToken = window.location.search.split("=")[1]
31 | setToken(urlToken || "")
32 | }, [])
33 |
34 | useEffect(() => {
35 | if(token.length > 0){
36 | verifyUserEmail()
37 | }
38 | }, [token])
39 |
40 | useEffect(() => {
41 | const timer = setTimeout(() => {
42 | router.push("/login")
43 | }, 5000);
44 | return () => clearTimeout(timer);
45 | }, []);
46 |
47 | return (
48 |
49 |
Email Verification
50 |
{token ? `${token}` : "no token"}
51 | {verified && (
52 |
53 | Login
54 |
55 | )}
56 | {error && (
57 |
58 |
Error
59 |
60 | )}
61 |
62 | )
63 | }
--------------------------------------------------------------------------------
/src/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ThemeProvider as NextThemesProvider } from "next-themes"
5 | import { type ThemeProviderProps } from "next-themes/dist/types"
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children}
9 | }
10 |
--------------------------------------------------------------------------------
/src/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 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 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const HoverCard = HoverCardPrimitive.Root
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
26 | ))
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28 |
29 | export { HoverCard, HoverCardTrigger, HoverCardContent }
30 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/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('Connected Successfully')
10 | })
11 |
12 | connection.on('error', (err) =>{
13 | console.log('MongoDB Connection Error, Please try again. ' + err)
14 | process.exit()
15 | })
16 | } catch(err){
17 | console.log("Something went wrong")
18 | console.log(err)
19 | }
20 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 Hashed Token
9 | const hashedToken = await bcryptjs.hash(userId.toString(), 10)
10 |
11 | if(emailType === "VERIFY"){
12 | await User.findByIdAndUpdate(userId, { verifyToken: hashedToken, verifyTokenExpiry: Date.now() + 3600000})
13 | }else if(emailType === "RESET") {
14 | await User.findByIdAndUpdate(userId, { forgotPasswordToken: hashedToken, forgotPasswordTokenExpiry: Date.now() + 3600000})
15 | }
16 |
17 | //Create Transporter
18 | var transport = nodemailer.createTransport({
19 | host: "sandbox.smtp.mailtrap.io",
20 | port: 2525,
21 | auth: {
22 | user: process.env.MAILTRAP_USER,
23 | pass: process.env.MAILTRAP_PASS
24 | }
25 | })
26 |
27 | //Create Mail Options
28 | const mailOptions = {
29 | from: 'usamaabid.wp@gmail.com',
30 | to: email,
31 | subject: emailType === "VERIFY" ? "Verify Your Email" : "Reset Your Password",
32 | html: `Click here to ${emailType === "VERIFY" ? "verify your email" : "reset your password"}
33 | or copy and paste the link below in your browser
34 |
35 | ${emailType === "VERIFY" ? `${process.env.DOMAIN}/verifyemail?token=${hashedToken}` : `${process.env.DOMAIN}/passwordreset?token=${hashedToken}`}
36 |
`
37 | }
38 |
39 | const mailResponse = await transport.sendMail(mailOptions)
40 | return mailResponse
41 |
42 | } catch (error: any) {
43 | throw new Error(error.message)
44 | }
45 | }
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import { NextRequest } from 'next/server'
3 |
4 | // This function can be marked `async` if using `await` inside
5 | export function middleware(request: NextRequest) {
6 | const path = request.nextUrl.pathname
7 | const isPublicPath = path === '/login' || path === '/signup' || path === '/verifyemail' || path === '/forgotpassword'
8 | const token = request.cookies.get('token')?.value || ''
9 |
10 | if(isPublicPath && token) {
11 | return NextResponse.redirect( new URL('/', request.nextUrl))
12 | }
13 | if(!isPublicPath && !token) {
14 | return NextResponse.redirect( new URL('/login', request.nextUrl))
15 | }
16 | }
17 |
18 | // See "Matching Paths" below to learn more
19 | export const config = {
20 | matcher: [
21 | '/',
22 | '/profile/:path*',
23 | '/login',
24 | '/signup',
25 | '/verifyemail',
26 | '/forgotpassword',
27 | '/resetpassword'
28 | ]
29 | }
--------------------------------------------------------------------------------
/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 | isVerified: {
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
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: "2rem",
14 | screens: {
15 | "2xl": "1400px",
16 | },
17 | },
18 | extend: {
19 | colors: {
20 | border: "hsl(var(--border))",
21 | input: "hsl(var(--input))",
22 | ring: "hsl(var(--ring))",
23 | background: "hsl(var(--background))",
24 | foreground: "hsl(var(--foreground))",
25 | primary: {
26 | DEFAULT: "hsl(var(--primary))",
27 | foreground: "hsl(var(--primary-foreground))",
28 | },
29 | secondary: {
30 | DEFAULT: "hsl(var(--secondary))",
31 | foreground: "hsl(var(--secondary-foreground))",
32 | },
33 | destructive: {
34 | DEFAULT: "hsl(var(--destructive))",
35 | foreground: "hsl(var(--destructive-foreground))",
36 | },
37 | muted: {
38 | DEFAULT: "hsl(var(--muted))",
39 | foreground: "hsl(var(--muted-foreground))",
40 | },
41 | accent: {
42 | DEFAULT: "hsl(var(--accent))",
43 | foreground: "hsl(var(--accent-foreground))",
44 | },
45 | popover: {
46 | DEFAULT: "hsl(var(--popover))",
47 | foreground: "hsl(var(--popover-foreground))",
48 | },
49 | card: {
50 | DEFAULT: "hsl(var(--card))",
51 | foreground: "hsl(var(--card-foreground))",
52 | },
53 | },
54 | borderRadius: {
55 | lg: "var(--radius)",
56 | md: "calc(var(--radius) - 2px)",
57 | sm: "calc(var(--radius) - 4px)",
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: { height: 0 },
62 | to: { height: "var(--radix-accordion-content-height)" },
63 | },
64 | "accordion-up": {
65 | from: { height: "var(--radix-accordion-content-height)" },
66 | to: { height: 0 },
67 | },
68 | },
69 | animation: {
70 | "accordion-down": "accordion-down 0.2s ease-out",
71 | "accordion-up": "accordion-up 0.2s ease-out",
72 | },
73 | },
74 | },
75 | plugins: [require("tailwindcss-animate")],
76 | }
--------------------------------------------------------------------------------
/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": "bundler",
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 |
--------------------------------------------------------------------------------