diff --git a/client/src/App.jsx b/client/src/App.jsx index 81312d6..85460fc 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -9,6 +9,7 @@ import Login from "./pages/Login/login"; import ResetPassword from "./components/Reset-Password"; import ForgotPassword from "./components/Forgot-Password"; import Signup from "./pages/Signup/signup"; +import VerifyEmail from "./pages/Verify-email"; import Layout_retailer from "./components/layout-retailer"; import Designer_home from "./pages/Designers"; import About from "./pages/About"; @@ -38,6 +39,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/client/src/main.jsx b/client/src/main.jsx index 836d5fc..d4447af 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -10,11 +10,11 @@ import {persistStore} from 'redux-persist'; const persistor = persistStore(store) ReactDOM.createRoot(document.getElementById("root")).render( - + - + ); diff --git a/client/src/pages/Signup/signup.jsx b/client/src/pages/Signup/signup.jsx index cc72470..b5e0bca 100644 --- a/client/src/pages/Signup/signup.jsx +++ b/client/src/pages/Signup/signup.jsx @@ -13,7 +13,8 @@ const Signup = () => { const [city, setCity] = useState(""); const [state, setState] = useState(""); const [pincode, setPincode] = useState(0); - const [registered, setRegistered] = useState(false); + const [showVerificationMessage, setShowVerificationMessage] = useState(false); + const registerf = async (e) => { e.preventDefault(); if ( @@ -51,15 +52,13 @@ const Signup = () => { } ); toast(data.message); - setRegistered(true); + setShowVerificationMessage(true); } catch (error) { toast.error(error.response.data.message); console.error(error); } }; - if (registered) { - return ; - } + return ( <>
@@ -69,222 +68,216 @@ const Signup = () => { REGISTER HERE!!! -
-
- { - setName(e.target.value); - }} - /> -
- - - - -
+ {showVerificationMessage ? ( +
+

+ Please check your email ({email}) to verify your account. +

-
- { - setEmail(e.target.value); - }} - /> -
- - - - + ) : ( + +
+ { + setName(e.target.value); + }} + /> +
+ + + + +
-
-
- { - setPasswd(e.target.value); - }} - required - /> -
- - - +
+ { + setEmail(e.target.value); + }} + /> +
+ + + + +
-
-
- { - setPpic(e.target.value); - }} - /> -
- - - +
+ { + setPasswd(e.target.value); + }} + required + /> +
+ + + +
-
-
- { - setStreet(e.target.value); - }} - required - /> -
- - - +
+ { + setPpic(e.target.value); + }} + /> +
+ + + +
-
-
- { - setCity(e.target.value); - }} - /> -
- - - +
+ { + setStreet(e.target.value); + }} + required + /> +
+ + + +
-
-
- { - setState(e.target.value); - }} - /> -
- - - +
+ { + setCity(e.target.value); + }} + required + /> +
+ + + +
-
-
- { - setPincode(e.target.value); - }} - /> -
- +
+ + + +
+
+
+ { + setPincode(parseInt(e.target.value)); + }} + required + /> +
+ + + +
+
+
+
-
+ + )} -
- - -
-
- -
-
-
-

- HAVE A ACCOUNT ?! -

- - Login - -
- + +

+ Already Registered? +

+
diff --git a/client/src/pages/Verify-email/index.jsx b/client/src/pages/Verify-email/index.jsx new file mode 100644 index 0000000..397fc01 --- /dev/null +++ b/client/src/pages/Verify-email/index.jsx @@ -0,0 +1,68 @@ +import React, { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import axios from 'axios'; +import toast from 'react-hot-toast'; + +const VerifyEmail = () => { + const location = useLocation(); + const navigate = useNavigate(); + const query = new URLSearchParams(location.search); + const token = query.get('token'); + const email = query.get('email'); + const status = query.get('status'); + const [isVerified, setIsVerified] = useState(false); + + useEffect(() => { + const verifyAccount = async () => { + if (status === 'success') { + toast.success('Account verified successfully!'); + setIsVerified(true); + return; + } + + if (token && email && !isVerified) { + try { + const response = await axios.get(`http://localhost:3000/api/users/verify-email?token=${token}&email=${email}`); + if (response.data.success) { + toast.success('Account verified successfully!'); + setIsVerified(true); + + } else { + toast.error('Verification failed. Please try again.'); + } + } catch (error) { + toast.error('Verification failed. Please try again.'); + } + } + }; + + verifyAccount(); + }, [token, email, status, isVerified]); + + const handleLoginRedirect = () => { + navigate('/login'); + }; + + return ( +
+
+ {isVerified ? ( +
+

Verification Successful

+

Your account has been verified successfully!

+ + +
+ ) : ( +
+

Verification failed

+

Try after some time.

+
+ )} +
+
+ ); +}; + +export default VerifyEmail; diff --git a/server/controllers/user.js b/server/controllers/user.js index c66cd42..8d2d74b 100644 --- a/server/controllers/user.js +++ b/server/controllers/user.js @@ -1,6 +1,8 @@ import bcrypt from "bcryptjs"; import { sendCookie } from "../utils/features.js"; import { PrismaClient } from "@prisma/client"; +import { sendVerifyEmail } from "../utils/verify-email.js"; +import { v4 as uuidv4 } from "uuid"; const prisma = new PrismaClient(); @@ -18,22 +20,42 @@ const signup = async (req, res) => { message: "User already exists." }); } - - const hpasswd = await bcrypt.hash(passwd, 10); - const newUser = await prisma.mainuser.create({ - data: { + + const token = uuidv4(); + const hashedToken = await bcrypt.hash(token, 10); + const hashedPassword = await bcrypt.hash(passwd, 10); + + + await prisma.tempUser.upsert({ + where: { email }, + update: { + name, + profilepic:ppic, + passwd: hashedPassword, + street, + city, + state, + pincode: parseInt(pincode), + token: hashedToken, + }, + create: { name, email, - profilepic: ppic, - passwd: hpasswd, + profilepic:ppic, + passwd: hashedPassword, street, city, state, - pincode: parseInt(pincode), // making pincode as an integer because it's get a string from 'req' + pincode: parseInt(pincode), + token: hashedToken, }, }); - sendCookie(newUser.email, res, "Registered Successfully", 201); + + const verificationLink = `http://localhost:5173/verify-email?token=${token}&email=${email}`; + await sendVerifyEmail(email, verificationLink); + res.status(200).json({ message: 'Verification email sent' }); + } catch (err) { console.error(err); res.status(500).json({ diff --git a/server/controllers/verifyEmail.js b/server/controllers/verifyEmail.js new file mode 100644 index 0000000..bd160e2 --- /dev/null +++ b/server/controllers/verifyEmail.js @@ -0,0 +1,51 @@ + +import { PrismaClient } from '@prisma/client'; +import bcrypt from 'bcryptjs'; + + +const prisma = new PrismaClient(); + +export const verifyEmail = async (req, res) => { + const { token, email } = req.query; + + try { + + const tempUser = await prisma.tempUser.findUnique({ + where: { email }, + }); + + if (!tempUser) { + return res.status(400).json({ message: 'Invalid or expired token' }); + } + + const isValid = await bcrypt.compare(token, tempUser.token); + + if (!isValid) { + return res.status(400).json({ message: 'Invalid or expired token' }); + } + + await prisma.mainuser.create({ + data: { + name: tempUser.name, + email: tempUser.email, + profilepic: tempUser.profilepic, + passwd: tempUser.passwd, + street: tempUser.street, + city: tempUser.city, + state: tempUser.state, + pincode: tempUser.pincode, + }, + }); + + await prisma.tempUser.delete({ + where: { email }, + }); + + res.status(200).json({ success: true, message: 'Account verified successfully!' }); + + } catch (error) { + console.error('Error verifying email:', error); + res.status(500).json({ message: 'Internal Server Error' }); + } +}; + diff --git a/server/prisma/migrations/20240805211932_add_temp_user_model/migration.sql b/server/prisma/migrations/20240805211932_add_temp_user_model/migration.sql new file mode 100644 index 0000000..46dd3f8 --- /dev/null +++ b/server/prisma/migrations/20240805211932_add_temp_user_model/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE `TempUser` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `name` VARCHAR(191) NOT NULL, + `email` VARCHAR(191) NOT NULL, + `profilepic` VARCHAR(191) NULL, + `passwd` VARCHAR(191) NOT NULL, + `street` VARCHAR(191) NOT NULL, + `city` VARCHAR(191) NOT NULL, + `state` VARCHAR(191) NOT NULL, + `pincode` INTEGER NOT NULL, + `token` VARCHAR(191) NOT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + UNIQUE INDEX `TempUser_email_key`(`email`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 61a8c2b..a6c1a7f 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -41,6 +41,20 @@ model Designer { mainuser Mainuser @relation(fields: [email], references: [email]) } +model TempUser { + id Int @id @default(autoincrement()) + name String + email String @unique + profilepic String? + passwd String + street String + city String + state String + pincode Int + token String + createdAt DateTime @default(now()) +} + model Mainuser { name String email String @id diff --git a/server/routes/user.js b/server/routes/user.js index 1585f47..16635c6 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -2,7 +2,7 @@ import express from "express"; import { login, signup, getMyProfile,updateUserProfile, registerdesigner, registerretailer } from "../controllers/user.js"; import { requestPasswordReset, resetPassword } from "../controllers/forgotPassword.js"; import { isAuthenticated } from "../middlewares/auth.js"; - +import { verifyEmail } from "../controllers/verifyEmail.js"; const router = express.Router(); @@ -10,6 +10,8 @@ router.post("/login", login); router.post("/signup", signup); +router.get('/verify-email', verifyEmail); + router.post("/forgot-password", requestPasswordReset); router.post("/reset-password/:token/:id", resetPassword); diff --git a/server/utils/verify-email.js b/server/utils/verify-email.js new file mode 100644 index 0000000..e40e18b --- /dev/null +++ b/server/utils/verify-email.js @@ -0,0 +1,28 @@ +import nodemailer from "nodemailer"; + +export const sendVerifyEmail = async (email, verificationLink) => { + const transporter = nodemailer.createTransport({ + service: "Gmail", + port: 465, + + secureConnection:false, + + auth: { + user: process.env.EMAIL, + pass: process.env.PASSWORD, + }, + + tls: { + rejectunAuthorized:true + } + }); + + const mailOptions = { + from: process.env.EMAIL, + to: email, + subject: 'Email Verification', + text:`Click the following link to verify your email: ${verificationLink}`, + }; + + await transporter.sendMail(mailOptions); +};