From 7df60dc84ab6e8908a05b2238eef8617425d6e48 Mon Sep 17 00:00:00 2001 From: haseebzaki-07 Date: Tue, 22 Oct 2024 22:46:14 +0530 Subject: [PATCH 01/12] Add trains --- backend/controllers/trainController.js | 62 ++++++++ backend/index.js | 2 + backend/models/Trains.js | 82 ++++++++++ backend/routes/trainRoutes.js | 13 ++ frontend/src/Pages/schedule.jsx | 211 ++++++++++++++----------- 5 files changed, 274 insertions(+), 96 deletions(-) create mode 100644 backend/controllers/trainController.js create mode 100644 backend/models/Trains.js create mode 100644 backend/routes/trainRoutes.js diff --git a/backend/controllers/trainController.js b/backend/controllers/trainController.js new file mode 100644 index 0000000..caee9d9 --- /dev/null +++ b/backend/controllers/trainController.js @@ -0,0 +1,62 @@ +import Train from "../models/Trains.js"; + + +// Create a new train +export const createTrain = async (req, res) => { + try { + const { trainNumber, trainName, nextStation, services, platformDetails, coachDetails } = req.body; + + + const existingTrain = await Train.findOne({ trainNumber }); + if (existingTrain) { + return res.status(400).json({ message: "Train with this number already exists" }); + } + + + const newTrain = new Train({ + trainNumber, + trainName, + nextStation, + services, + platformDetails, + coachDetails, + }); + + const savedTrain = await newTrain.save(); + + return res.status(201).json({ + message: "Train created successfully", + train: savedTrain, + }); + } catch (error) { + return res.status(500).json({ message: "Error creating train", error: error.message }); + } +}; + +// Fetch all trains +export const getAllTrains = async (req, res) => { + try { + const trains = await Train.find(); + return res.status(200).json(trains); + } catch (error) { + return res.status(500).json({ message: "Error fetching trains", error: error.message }); + } +}; + +// Fetch a single train by train number +export const getTrainByNumber = async (req, res) => { + try { + const { trainNumber } = req.params; + + const train = await Train.findOne({ trainNumber }); + + if (!train) { + return res.status(404).json({ message: "Train not found" }); + } + + return res.status(200).json(train); + } catch (error) { + return res.status(500).json({ message: "Error fetching train", error: error.message }); + } +}; + diff --git a/backend/index.js b/backend/index.js index 3ecc689..6c33177 100644 --- a/backend/index.js +++ b/backend/index.js @@ -29,10 +29,12 @@ connectDB(); import authRoutes from "./routes/authRoutes.js"; import stationRoutes from "./routes/stationRoutes.js"; +import trainRoutes from "./routes/trainRoutes.js"; app.use("/auth", authRoutes); app.use("/api", authRoutes); app.use("/station", stationRoutes); +app.use("/train", trainRoutes); app.get("/", (req, res) => { res.send("Working..."); diff --git a/backend/models/Trains.js b/backend/models/Trains.js new file mode 100644 index 0000000..9d29e6e --- /dev/null +++ b/backend/models/Trains.js @@ -0,0 +1,82 @@ +// train.js +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +const trainSchema = new Schema( + { + trainNumber: { + type: Number, + required: true, + unique: true, + trim: true, + }, + trainName: { + type: String, + required: true, + trim: true, + }, + nextStation: { + type: { + name: { + type: String, + required: true, + trim: true, + }, + stationCode: { + type: String, + required: true, + trim: true, + }, + arrivalTime: { + type: Date, + required: true, + }, + }, + required: true, + }, + services: { + type: [String], // An array of available services like ["WiFi", "Food", "Lounge"] + required: true, + }, + platformDetails: { + platformNumber: { + type: Number, + required: true, + }, + boardingTime: { + type: Date, + required: true, + }, + }, + coachDetails: [ + { + coachNumber: { + type: String, + required: true, + trim: true, + }, + coachType: { + type: String, + enum: ['Sleeper', 'AC', 'General', 'ChairCar', 'FirstClass'], + required: true, + }, + capacity: { + type: Number, + required: true, + }, + reservedSeats: { + type: Number, + required: true, + default: 0, + }, + }, + ], + }, + { + timestamps: true, + } +); + +const Train = mongoose.model("Train", trainSchema); +export default Train; diff --git a/backend/routes/trainRoutes.js b/backend/routes/trainRoutes.js new file mode 100644 index 0000000..ec16855 --- /dev/null +++ b/backend/routes/trainRoutes.js @@ -0,0 +1,13 @@ +// routes.js +import express from 'express'; +import { createTrain, getAllTrains, getTrainByNumber } from '../controllers/trainController.js'; + + +const router = express.Router(); + +router.get('/:trainNumber', getTrainByNumber); +router.post('/', createTrain); +router.get('/', getAllTrains); + + +export default router; diff --git a/frontend/src/Pages/schedule.jsx b/frontend/src/Pages/schedule.jsx index 1ef0b6a..049310d 100644 --- a/frontend/src/Pages/schedule.jsx +++ b/frontend/src/Pages/schedule.jsx @@ -1,24 +1,47 @@ import React, { useState, useEffect } from 'react'; import { IoArrowBack, IoSearchOutline } from 'react-icons/io5'; import { useNavigate } from 'react-router-dom'; -//import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; + +// Placeholder API URL (replace with the actual API endpoint) +const API_URL = 'http://localhost:3000/train'; const SchedulePage = () => { useEffect(() => { document.title = 'Station Saarthi | Schedule'; }, []); - const [trainNumber, setTrainNumber] = useState('22436'); - const [trainName, setTrainName] = useState('Vande Bharat'); - const [nextStation, setNextStation] = useState('Indore Jn.'); - const [services, setServices] = useState('-SELECT-'); - const [platformDetails, setPlatformDetails] = useState('Platform 3'); - const [coachDetails, setCoachDetails] = useState('A4'); - //const [date, setDate] = useState(null); - + const [searchQuery, setSearchQuery] = useState(''); + const [trainDetails, setTrainDetails] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const navigate = useNavigate(); + // Function to search train by either number or name + const searchTrain = async () => { + if (!searchQuery) { + setError('Please enter a train number or name to search'); + return; + } + + setLoading(true); + setError(null); + + try { + const response = await fetch(`${API_URL}/${searchQuery}`); + if (!response.ok) { + throw new Error('Train not found'); + } + + const data = await response.json(); + setTrainDetails(data); // Assuming the API returns a train object + } catch (error) { + setError(error.message || 'An error occurred while fetching the train details'); + } finally { + setLoading(false); + } + }; + return (
{
+ {/* Search input */}
- +
setSearchQuery(e.target.value)} className="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" /> - -
-
- -
- -
- setTrainNumber(e.target.value)} - className="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" + -
+ {error &&

{error}

}
-
- -
- setTrainName(e.target.value)} - className="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - /> - + {/* Display train details */} + {loading &&

Loading...

} + {trainDetails && ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + `${coach.coachNumber} (${coach.coachType})`).join(', ')} + readOnly + className="w-full px-4 py-2 border border-gray-300 rounded-lg" + /> +
-
- -
- - setNextStation(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - /> -
- -
- - -
- -
- - setPlatformDetails(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - /> -
- -
- - setCoachDetails(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - /> -
- - {/*
- -
- {/* setDate(date)} - dateFormat="dd/MM/yyyy" - placeholderText="DD/MM/YY" - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - popperClassName="z-50" - /> } -
-
*/} + )} + + {/* Button to search */} +
- -
From acc5dbed6e6e2d5e0a061b4217bcd2aadc437b6f Mon Sep 17 00:00:00 2001 From: SPC Date: Thu, 24 Oct 2024 01:42:46 +0530 Subject: [PATCH 02/12] reduced scroll bar width --- frontend/src/App.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 8358b80..92558d7 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -10,7 +10,7 @@ body{ ::-webkit-scrollbar { - width: 12px; + width: 5px; height: 12px; } @@ -20,9 +20,9 @@ body{ } ::-webkit-scrollbar-thumb:hover { - background: #0056b3; + background: #3591f3; } ::-webkit-scrollbar-thumb { - background: #007BFF; - border-radius: 10px; + background: #3591f3; + border-radius: 12px; } From dfd99165462557403df2ef1a3bc29fb8c675be03 Mon Sep 17 00:00:00 2001 From: Anubhav Sharma Date: Thu, 24 Oct 2024 15:50:24 +0530 Subject: [PATCH 03/12] Text space fixed --- frontend/src/Pages/AboutUs.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/Pages/AboutUs.jsx b/frontend/src/Pages/AboutUs.jsx index 15e2ae8..accbc4d 100644 --- a/frontend/src/Pages/AboutUs.jsx +++ b/frontend/src/Pages/AboutUs.jsx @@ -39,7 +39,7 @@ const About = () => { {/* Introduction Section */}

- Welcome to + Welcome to{' '} StationSaarthi , @@ -59,7 +59,7 @@ const About = () => {

- At + At{' '} StationSaarthi , @@ -97,7 +97,7 @@ const About = () => {

- With + With{' '} StationSaarthi , @@ -125,7 +125,7 @@ const About = () => {

- StationSaarthi + StationSaarthi{' '} is more than a service; it's a commitment to revolutionizing your railway station experience. We believe in technology's power to enhance every aspect of your journey, ensuring safety, From 64fbad00e993b6ab22dee54c99bf297abdab7433 Mon Sep 17 00:00:00 2001 From: haseebzaki-07 Date: Thu, 24 Oct 2024 17:02:50 +0530 Subject: [PATCH 04/12] fix merge conflicts --- frontend/src/Pages/schedule.jsx | 102 ++++++-------------------------- 1 file changed, 17 insertions(+), 85 deletions(-) diff --git a/frontend/src/Pages/schedule.jsx b/frontend/src/Pages/schedule.jsx index 54482b6..6bc1081 100644 --- a/frontend/src/Pages/schedule.jsx +++ b/frontend/src/Pages/schedule.jsx @@ -14,7 +14,6 @@ const SchedulePage = () => { const [trainDetails, setTrainDetails] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const navigate = useNavigate(); // Function to search train by either number or name @@ -32,9 +31,9 @@ const SchedulePage = () => { if (!response.ok) { throw new Error('Train not found'); } - + const data = await response.json(); - setTrainDetails(data); // Assuming the API returns a train object + setTrainDetails(data); // Assuming the API returns a train object } catch (error) { setError(error.message || 'An error occurred while fetching the train details'); } finally { @@ -44,7 +43,7 @@ const SchedulePage = () => { return (

-
{
{/* Search input */}
- +
{ onChange={(e) => setSearchQuery(e.target.value)} className="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" /> - @@ -116,7 +117,7 @@ const SchedulePage = () => { @@ -126,7 +127,7 @@ const SchedulePage = () => { @@ -136,7 +137,7 @@ const SchedulePage = () => { @@ -146,94 +147,25 @@ const SchedulePage = () => { `${coach.coachNumber} (${coach.coachType})`).join(', ')} + value={ + trainDetails.coachDetails + ?.map(coach => `${coach.coachNumber} (${coach.coachType})`) + .join(', ') || 'N/A' + } readOnly className="w-full px-4 py-2 border border-gray-300 rounded-lg" />
- )} {/* Button to search */} - - -
- -
- - setNextStation(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - /> -
- -
- - -
- -
- - setPlatformDetails(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - /> -
- -
- - -
- - - {/*
- -
- {/* setDate(date)} - dateFormat="dd/MM/yyyy" - placeholderText="DD/MM/YY" - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - popperClassName="z-50" - /> } -
-
*/} -
From 876f96e7a80c6f168f98807f8ea9da9296a7d881 Mon Sep 17 00:00:00 2001 From: Vinay Anand Lodhi Date: Thu, 24 Oct 2024 17:05:42 +0530 Subject: [PATCH 05/12] forgot password feature added backend and frontend --- backend/.env.sample | 9 + backend/controllers/submitFeedback.js | 106 +++++++++++ backend/index.js | 2 + backend/models/User.js | 8 + backend/package-lock.json | 10 ++ backend/package.json | 1 + backend/routes/stationRoutes.js | 6 +- backend/utils/emailUtils.js | 28 +++ backend/utils/otputils.js | 16 ++ frontend/src/Pages/PasswordRecovery.jsx | 222 +++++++++++++++++++++--- 10 files changed, 384 insertions(+), 24 deletions(-) create mode 100644 backend/.env.sample create mode 100644 backend/utils/emailUtils.js create mode 100644 backend/utils/otputils.js diff --git a/backend/.env.sample b/backend/.env.sample new file mode 100644 index 0000000..3d377ae --- /dev/null +++ b/backend/.env.sample @@ -0,0 +1,9 @@ +EMAIL_USER=your_gmail +#your email +EMAIL_USER=your_gmail + +# To create a passkey on the phone or computer you’re on: + +# 1. Go to https://myaccount.google.com/signinoptions/passkeys. +# 2. Tap Create a passkey and then Continue.(You'll be required to unlock your device.) +# 3. A 16 character passkey is generated which you can use in below. \ No newline at end of file diff --git a/backend/controllers/submitFeedback.js b/backend/controllers/submitFeedback.js index 5062e6d..43654f4 100644 --- a/backend/controllers/submitFeedback.js +++ b/backend/controllers/submitFeedback.js @@ -1,4 +1,9 @@ +import nodemailer from 'nodemailer'; +import User from '../models/User.js'; +import { generateOTP , verifyOTP} from '../utils/otputils.js'; // Import the OTP generation function +import { sendOTPEmail } from '../utils/emailUtils.js'; // Import the email sender utility if separated into a different file +import { hashPassword } from '../utils/authFunctions.js'; // Controller to handle user feedback submission export const submitFeedback = async (req, res) => { @@ -21,3 +26,104 @@ export const submitFeedback = async (req, res) => { return res.status(error.statusCode || 500).json({ message: error.message || 'An error occurred while submitting feedback' }); } }; + +export const sendOTPToEmail = async (req, res) => { + try { + const { email } = req.body; + + // Check if email is provided + if (!email) { + return res.status(400).json({ error: 'Email is required' }); + } + + // Find user by email + const user = await User.findOne({ email }); + + if (!user) { + return res.status(404).json({ error: 'User does not exist' }); + } + + // Generate OTP and set expiry (10 minutes from now) + const otp = generateOTP(); + const otpExpiry = new Date(Date.now() + 10 * 60 * 1000); // OTP expires in 10 minutes + + // Store the OTP and expiry in the user's document + user.otp = otp; + user.otpExpiry = otpExpiry; + await user.save(); + + // Send OTP to user's email using the utility + await sendOTPEmail(email, otp); + + res.status(200).json({ message: 'OTP sent to email' }); + } catch (error) { + console.error('Error sending OTP:', error); + return res.status(500).json({ error: error.message || 'Internal Server Error' }); + } +}; + +export const verifyOTPController = async (req, res) => { + try { + const { email, otp } = req.body; + + // Check if email and OTP are provided + if (!email || !otp) { + return res.status(400).json({ error: 'Email and OTP are required' }); + } + + // Find user by email + const user = await User.findOne({ email }); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + // Verify the OTP and check expiration + const isValid = verifyOTP(user, otp); + + if (!isValid) { + return res.status(400).json({ error: 'Invalid or expired OTP' }); + } + + // Clear OTP after successful verification (optional, but recommended) + user.otp = null; + user.otpExpiry = null; + await user.save(); + + // OTP is valid + res.status(200).json({ message: 'OTP verified successfully' }); + } catch (error) { + console.error('Error verifying OTP:', error); + res.status(500).json({ error: 'Internal Server Error' }); + } +}; + +export const resetPassword = async (req, res) => { + try { + const { email, newPassword } = req.body; + + // Check if both email and new password are provided + if (!email || !newPassword) { + return res.status(400).json({ error: 'Email and new password are required' }); + } + + // Find the user by email + const user = await User.findOne({ email }); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + // Hash the new password + const hashedPassword = await hashPassword(newPassword); + + // Update the user's password in the database + user.password = hashedPassword; + await user.save(); + + res.status(200).json({ message: 'Password reset successfully' }); + } catch (error) { + console.error('Error resetting password:', error); + res.status(500).json({ error: 'Internal Server Error' }); + } +}; diff --git a/backend/index.js b/backend/index.js index 3ecc689..a871fca 100644 --- a/backend/index.js +++ b/backend/index.js @@ -34,6 +34,8 @@ app.use("/auth", authRoutes); app.use("/api", authRoutes); app.use("/station", stationRoutes); + + app.get("/", (req, res) => { res.send("Working..."); }); diff --git a/backend/models/User.js b/backend/models/User.js index 05ac800..ca95f98 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -32,6 +32,14 @@ const userSchema = new Schema({ type: String, trim: true, default: '', + }, + otp: { + type: String, // The OTP code + required: false, + }, + otpExpiry: { + type: Date, // The OTP expiry timestamp + required: false, } }, { timestamps: true diff --git a/backend/package-lock.json b/backend/package-lock.json index 67ee812..54dbbc5 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,6 +19,7 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^8.7.0", "node": "^22.8.0", + "nodemailer": "^6.9.15", "nodemon": "^3.1.7", "socket.io": "^4.8.0" } @@ -2305,6 +2306,15 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/nodemailer": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", + "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", diff --git a/backend/package.json b/backend/package.json index ff8eb97..7004140 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,6 +14,7 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^8.7.0", "node": "^22.8.0", + "nodemailer": "^6.9.15", "nodemon": "^3.1.7", "socket.io": "^4.8.0" }, diff --git a/backend/routes/stationRoutes.js b/backend/routes/stationRoutes.js index 1629b20..3ea5ecc 100644 --- a/backend/routes/stationRoutes.js +++ b/backend/routes/stationRoutes.js @@ -2,7 +2,7 @@ import express from 'express'; import { getCloakroomBookingsByStation, getCoolieBookingsByStation, getStationBookings, getWheelchairBookingsByStation } from '../controllers/stationBookingsController.js'; import { createStation, getAllStations } from '../controllers/StationController.js'; - +import { sendOTPToEmail ,verifyOTPController, resetPassword} from '../controllers/submitFeedback.js'; const router = express.Router(); // Route to fetch all bookings for a station @@ -15,5 +15,7 @@ router.post('/', createStation); router.get('/:id/coolies', getCoolieBookingsByStation); router.get('/:id/wheelchairs', getWheelchairBookingsByStation); router.get('/:id/cloakrooms', getCloakroomBookingsByStation); - +router.post('/send-otp', sendOTPToEmail); +router.post('/verify-otp', verifyOTPController); +router.post('/reset-password', resetPassword); export default router; diff --git a/backend/utils/emailUtils.js b/backend/utils/emailUtils.js new file mode 100644 index 0000000..906e3b8 --- /dev/null +++ b/backend/utils/emailUtils.js @@ -0,0 +1,28 @@ +import nodemailer from 'nodemailer'; + +// Function to send OTP via email +export const sendOTPEmail = async (email, otp) => { + try { + // Create a transporter using SMTP settings (e.g., Gmail) + const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.EMAIL_USER, // Your email address + pass: process.env.EMAIL_PASS // Your email password or app-specific password + } + }); + + const mailOptions = { + from: process.env.EMAIL_USER, + to: email, + subject: 'Your Password Reset OTP', + text: `Your OTP for password reset is ${otp}. It will expire in 10 minutes.` + }; + + // Send the email + await transporter.sendMail(mailOptions); + } catch (error) { + console.error('Error sending email:', error); + throw new Error('Failed to send OTP email'); + } +}; diff --git a/backend/utils/otputils.js b/backend/utils/otputils.js new file mode 100644 index 0000000..f9e6348 --- /dev/null +++ b/backend/utils/otputils.js @@ -0,0 +1,16 @@ +// Generate a 6-digit OTP +export const generateOTP = () => { + return Math.floor(100000 + Math.random() * 900000).toString(); + }; + + // Check if OTP is valid (both matching and not expired) + export const verifyOTP = (user, submittedOtp) => { + return user.otp === submittedOtp && user.otpExpiry > Date.now(); + }; + + // Clear OTP after it's used + export const clearOTP = async (user) => { + user.otp = null; + user.otpExpiry = null; + await user.save(); + }; diff --git a/frontend/src/Pages/PasswordRecovery.jsx b/frontend/src/Pages/PasswordRecovery.jsx index 8a7e1fc..beb1dae 100644 --- a/frontend/src/Pages/PasswordRecovery.jsx +++ b/frontend/src/Pages/PasswordRecovery.jsx @@ -3,15 +3,114 @@ import { useNavigate } from "react-router-dom"; const PasswordRecovery = () => { const [email, setEmail] = useState(""); + const [otp, setOtp] = useState(""); // For OTP input + const [newPassword, setNewPassword] = useState(""); // For new password input + const [confirmPassword, setConfirmPassword] = useState(""); // For confirm password input + const [step, setStep] = useState(1); // For handling the steps (1: Email, 2: OTP, 3: Password Reset) + const [otpDisabled, setOtpDisabled] = useState(false); // For disabling OTP input after submission const [message, setMessage] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); const navigate = useNavigate(); + // Step 1: Handle sending OTP to email const handlePasswordRecovery = async (e) => { e.preventDefault(); - // Implement password recovery logic here - setMessage( - "If an account with that email exists, a password recovery link has been sent." - ); + setMessage(""); + setError(""); + setLoading(true); + + try { + const response = await fetch("http://localhost:5000/station/send-otp", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email }), + }); + + const data = await response.json(); + + if (response.ok) { + setMessage("OTP has been sent to your email."); + setStep(2); // Move to OTP input step + } else { + setError(data.error || "Failed to send OTP. Please try again."); + } + } catch (error) { + setError("An error occurred. Please try again later."); + } finally { + setLoading(false); + } + }; + + // Step 2: Handle OTP verification + const handleVerifyOTP = async (e) => { + e.preventDefault(); + setMessage(""); + setError(""); + setLoading(true); + + try { + const response = await fetch("http://localhost:5000/station/verify-otp", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, otp }), + }); + + const data = await response.json(); + + if (response.ok) { + setMessage("OTP verified successfully. You can now reset your password."); + setStep(3); // Move to password reset step + setOtpDisabled(true); // Disable OTP input after successful verification + } else { + setError(data.error || "Invalid OTP. Please try again."); + } + } catch (error) { + setError("An error occurred. Please try again later."); + } finally { + setLoading(false); + } + }; + + // Step 3: Handle new password submission + const handlePasswordReset = async (e) => { + e.preventDefault(); + setMessage(""); + setError(""); + setLoading(true); + + if (newPassword !== confirmPassword) { + setError("Passwords do not match."); + setLoading(false); + return; + } + + try { + const response = await fetch("http://localhost:5000/station/reset-password", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, newPassword }), + }); + + const data = await response.json(); + + if (response.ok) { + setMessage("Password reset successfully. You can now log in."); + navigate("/login"); + } else { + setError(data.error || "Failed to reset password. Please try again."); + } + } catch (error) { + setError("An error occurred. Please try again later."); + } finally { + setLoading(false); + } }; const handleBackToLogin = () => { @@ -19,44 +118,123 @@ const PasswordRecovery = () => { }; return ( -
-
-

+
+
+

Password Recovery

+ + {/* Success Message */} {message && ( -

+

{message}

)} -
-
-