Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added Forgot Password Features #456

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ MONGO_URL=mongodb://localhost:27017
PORT=8080
REFRESH_TOKEN_COOKIE_EXPIRE=30
REFRESH_TOKEN_SECRET=XYZ

GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
JWT_SECRET=yourSecretKey
Expand Down
2 changes: 2 additions & 0 deletions client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import CustomerProfile from './Pages/CustomerProfile.jsx';
import GiftCards from './Pages/GiftCards.jsx';
import Careers from './Pages/Careers.jsx';
import NotFound from './Pages/NotFound.jsx';
import ResetPassword from "./Pages/ResetPassword.jsx";

function App() {
const [darkMode, setDarkMode] = useState(false);
Expand Down Expand Up @@ -79,6 +80,7 @@ function App() {
<Route path="/contributors" element={<Contributors />} />
<Route path="/careers" element={<Careers />} />
<Route path="/giftcards" element={<GiftCards />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="*" element={<NotFound />} /> {/* Fallback route */}
</Routes>
<Toast position="bottom-right" />
Expand Down
11 changes: 0 additions & 11 deletions client/src/Components/index.js

This file was deleted.

2 changes: 1 addition & 1 deletion client/src/Pages/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Newarrivals from "./Newarrivals";
import Review from "./Review";
import Trending from "../Components/Trending";
import Book from "../Components/Card/Book";
import Review from './Review';
//import Review from './Review';

const Home = () => {
const [isLoading, setIsLoading] = useState(true);
Expand Down
20 changes: 15 additions & 5 deletions client/src/Pages/LoginPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,21 @@ const LoginPage = () => {
</button>
{/* Google Login Button */}
<GoogleLogin />
<div className='w-full flex items-center justify-center mt-3 mb-3'>
<p className='text-sm text-[#060606] dark:text-white'>
Don't have an account?{' '} <Link to="/signup">Sign up</Link>
</p>
</div>
<div className='w-full flex flex-col items-start justify-start mt-3 mb-3'>
{/* Sign up link */}
<p className='text-sm text-[#060606] dark:text-white'>
Don't have an account?{' '} <Link to="/signup">Sign up</Link>
</p>

{/* Add spacing between the two sections */}
<div className="my-2"></div> {/* This adds vertical spacing */}

{/* Forgot password link */}
<p className='text-sm text-[#060606] dark:text-white'>
Forgot Password?{' '} <Link to="/reset-password">Reset here</Link> {/* Update link if necessary */}
</p>
</div>

</form>
</Grid>
</Grid>
Expand Down
179 changes: 179 additions & 0 deletions client/src/Pages/ResetPassword.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import React, { useState } from "react";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import VisibilityIcon from "@mui/icons-material/Visibility";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import { Box, Container, Grid, Typography } from "@mui/material";
import Lottie from "lottie-react";
import loginAnimation from "../Lottie-animation/loginAnimation.json"; // Ensure the animation is correct for this page
import axios from "axios";
import toast, { Toaster } from "react-hot-toast";
import { Link, useNavigate } from "react-router-dom";

const ResetPassword = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(""); // Added state for confirm password
const [showPassword, setShowPassword] = useState(false); // State for toggling password visibility
const [error, setError] = useState("");
let navigate = useNavigate();

const handleSubmit = async (e) => {
e.preventDefault();

// Reset error state
setError("");

// Validate that password and confirmPassword match
if (password !== confirmPassword) {
setError("Re-enter password does not match with new password!");
return;
}

try {
const response = await axios.post("http://localhost:8080/customer/resetpassword", {
email,
newPassword: password, // Send new password
});
toast.success("Password reset successfully!");
navigate("/login", { replace: true });
} catch (err) {
// Handle different error responses
if (err.response && err.response.data && err.response.data.error) {
setError(err.response.data.error);
} else {
setError("An unexpected error occurred.");
}
}
};

// Function to toggle password visibility
const togglePasswordVisibility = () => {
setShowPassword(!showPassword);
};

return (
<>
<Toaster />
<Container maxWidth="xl">
<div style={{ marginTop: "100px", marginBottom: "180px" }}>
<Grid container spacing={2} sx={{ justifyContent: "center" }}>
<Grid item xs={12} md={6}>
<Box sx={{ display: { xs: "none", md: "block" } }}>
<Lottie animationData={loginAnimation} style={{ height: "500px" }} />
</Box>
</Grid>
<Grid item xs={12} md={4} sx={{ maxWidth: "500px" }}>
{/* Add a Box with hover effect */}
<Box
sx={{
backgroundColor: "white",
p: 4,
borderRadius: "8px",
boxShadow: 3,
transition: "transform 0.3s",
"&:hover": {
transform: "scale(1.05)", // Zoom effect on hover
},
display: "flex",
flexDirection: "column",
}}
>
<form onSubmit={handleSubmit}>
<b>
<Typography variant="h5" align="center" gutterBottom className="dark:text-white">
Book4U
</Typography>
</b>
<Typography variant="h5" align="center" gutterBottom className="dark:text-white">
Reset Password
</Typography>
<TextField
variant="outlined"
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
margin="normal"
fullWidth // Use fullWidth for consistent sizing
required
/>
<Box sx={{ position: "relative", display: "flex", alignItems: "center", width: '100%' }}>
<TextField
variant="outlined"
label="New Password"
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
margin="normal"
fullWidth // Use fullWidth for consistent sizing
required
/>
<IconButton
onClick={togglePasswordVisibility}
sx={{
position: "absolute",
right: "10px",
top: "50%",
transform: "translateY(-50%)",
height: '100%', // Make button height equal to input height
width: '40px', // Adjust width as needed
padding: '0', // Remove default padding
borderRadius: '0', // Remove border radius
}}
aria-label={showPassword ? "Hide password" : "Show password"}
>
{showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
</Box>
<Box sx={{ position: "relative", display: "flex", alignItems: "center", width: '100%' }}>
<TextField
variant="outlined"
label="Re-enter New Password"
type={showPassword ? "text" : "password"}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
margin="normal"
fullWidth // Use fullWidth for consistent sizing
required
/>
<IconButton
onClick={togglePasswordVisibility}
sx={{
position: "absolute",
right: "10px",
top: "50%",
transform: "translateY(-50%)",
height: '100%', // Make button height equal to input height
width: '40px', // Adjust width as needed
padding: '0', // Remove default padding
borderRadius: '0', // Remove border radius
}}
aria-label={showPassword ? "Hide password" : "Show password"}
>
{showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
</Box>
{error && (
<Typography color="error" align="center">
{error}
</Typography>
)}
<Button variant="contained" type="submit" fullWidth sx={{ mt: 1 }}>
Reset Password
</Button>
<Typography align="left" sx={{ mt: 2, mr: 2 }}>
<u><Link to="/login">Go to Login</Link></u>
</Typography>
</form>
</Box>
</Grid>
</Grid>
</div>
</Container>
</>
);
};

export default ResetPassword;
45 changes: 37 additions & 8 deletions controllers/customerController.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const errorHandler = require("../utils/errorHandler");
const responseHandler = require("../utils/responseHandler");
const jwt = require("jsonwebtoken");
require("dotenv").config();
const bcrypt = require("bcryptjs");
const saltRounds = 10;
const validator = require("validator");
const disposableEmailDomains = require("disposable-email-domains");

Expand Down Expand Up @@ -204,6 +206,8 @@ exports.getCustomerDetails = catchAsyncErrors(async (req, res, next) => {
});
});



// UPDATE CUSTOMER PASSWORD
exports.updatePassword = catchAsyncErrors(async (req, res, next) => {
const customer = await Customer.findById(req.user.id).select("+password");
Expand All @@ -213,17 +217,11 @@ exports.updatePassword = catchAsyncErrors(async (req, res, next) => {
);

if (!isPasswordMatched) {
return res
.status(404)
.send(
errorHandler(404, "Bad Request", "Please enter the correct password")
);
return next(new ErrorHandler("Old password is incorrect", 400));
}

if (req.body.newPassword !== req.body.confirmPassword) {
return res
.status(404)
.send(errorHandler(404, "Bad Request", "Password do not match"));
return next(new ErrorHandler("Password does not match", 400));
}

customer.password = req.body.newPassword;
Expand All @@ -233,6 +231,8 @@ exports.updatePassword = catchAsyncErrors(async (req, res, next) => {
sendToken(customer, 200, res);
});



// UPDATE CUSTOMER PROFILE
exports.updateProfile = catchAsyncErrors(async (req, res, next) => {
const newCustomerData = {
Expand Down Expand Up @@ -261,6 +261,35 @@ exports.updateProfile = catchAsyncErrors(async (req, res, next) => {
});
});

//RESET Password
exports.resetPassword = async (req, res) => {
const { email, newPassword } = req.body; // expecting newPassword

if (!newPassword) {
return res.status(400).json({ error: 'New password is required.' });
}

try {
const hashedPassword = await bcrypt.hash(newPassword, saltRounds); // Hash new password

const updatedUser = await Customer.findOneAndUpdate(
{ email }, // Find user by email
{ $set: { password: hashedPassword } }, // Set new hashed password
{ new: true }
);

if (updatedUser) {
return res.json({ message: 'Password updated successfully.' });
} else {
return res.status(404).json({ error: 'User not found.' });
}
} catch (error) {
console.error('Error updating password:', error.message);
res.status(500).json({ error: 'Internal Server Error' });
}
};


exports.addFeedback = catchAsyncErrors(async (req, res, next) => {
const { feedback, topic } = req.body;
const newFeedback = await Feedback.create({
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,4 @@ function closeModal() {
function purchaseBook() {
alert('Book purchased!');
closeModal();
}
}
34 changes: 34 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading