From 4bea8fab378eda12f841d83a00817e593ca2ce24 Mon Sep 17 00:00:00 2001 From: thomastepi Date: Mon, 13 Jan 2025 03:44:54 -0500 Subject: [PATCH 1/6] fix invite and reset passwd email service --- .env | 4 ++-- backend/.env | 9 ++++++++- backend/package-lock.json | 1 - backend/src/controllers/auth.controller.js | 6 +++--- backend/src/service/email.service.js | 20 ++++++++++++++++---- backend/src/service/invite.service.js | 3 +++ backend/src/templates/invite.hbs | 7 +++++++ backend/src/templates/resetPassword.hbs | 11 +++++++---- backend/src/utils/constants.helper.js | 3 ++- frontend/src/scenes/login/SetNewPassword.jsx | 15 ++++++++++----- frontend/src/services/settingServices.js | 2 +- 11 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 backend/src/templates/invite.hbs diff --git a/.env b/.env index ffe8cd21..c75c0d90 100644 --- a/.env +++ b/.env @@ -11,9 +11,9 @@ DEV_DB_PORT=5432 # JWT Secret Key JWT_SECRET="NKrbO2lpCsOpVAlqAPsjZ0tZXzIoKru7gAmYZ7XlHn0=qqwqeq" -EMAIL=autobluewave@gmail.com +EMAIL=bluewaveguidefox@gmail.com EMAIL_PASSWORD=passwor EMAIL_HOST=smtp.gmail.com EMAIL_PORT=465 -APP_PASSWORD=password +APP_PASSWORD=ukzwakckupguegiw EMAIL_ENABLE=false \ No newline at end of file diff --git a/backend/.env b/backend/.env index b1057599..c106b2c2 100644 --- a/backend/.env +++ b/backend/.env @@ -8,7 +8,7 @@ DEV_DB_NAME=onboarding_db DEV_DB_HOST=db DEV_DB_PORT=5432 -EMAIL_ENABLE=false +EMAIL_ENABLE=true # JWT Secret Key JWT_SECRET="NKrbO2lpCsOpVAlqAPsjZ0tZXzIoKru7gAmYZ7XlHn0=qqwqeq" @@ -19,6 +19,13 @@ TEST_DB_NAME=onboarding_db_test TEST_DB_HOST=localhost TEST_DB_PORT=5432 +EMAIL=bluewaveguidefox@gmail.com +EMAIL_PASSWORD=passwor +EMAIL_HOST=smtp.gmail.com +EMAIL_PORT=465 +APP_PASSWORD=ukzwakckupguegiw +EMAIL_ENABLE=true + ENABLE_IP_CHECK=false # Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255) separated by comma ALLOWED_IP_RANGE=11.22.33/10-200, 192.168.65/1-255 diff --git a/backend/package-lock.json b/backend/package-lock.json index ca61f884..60552ed8 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -3830,7 +3830,6 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "license": "MIT", "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", diff --git a/backend/src/controllers/auth.controller.js b/backend/src/controllers/auth.controller.js index 28c59934..a0b882fc 100644 --- a/backend/src/controllers/auth.controller.js +++ b/backend/src/controllers/auth.controller.js @@ -7,7 +7,7 @@ const sequelize = db.sequelize; const { generateToken, verifyToken } = require('../utils/jwt.helper'); const crypto = require('crypto'); const { TOKEN_LIFESPAN } = require('../utils/constants.helper'); -const { sendSignupEmail, sendPasswordResetEmail, findUserByEmail } = require('../service/email.service'); +const { sendPasswordResetEmail, findUserByEmail } = require('../service/email.service'); const settings = require('../../config/settings'); const { decode } = require('../utils/auth.helper'); @@ -81,8 +81,6 @@ const register = async (req, res) => { await Token.create({ token, userId: newUser.id, type: 'auth' }); - await sendSignupEmail(newUser.email, newUser.name); - res.status(201).json({ user: { id: newUser.id, @@ -160,6 +158,8 @@ const forgetPassword = async (req, res) => { const user = await findUserByEmail(email); if (!user) return res.status(400).json({ error: 'User not found' }); + await Token.destroy({ where: { userId: user.id, type: 'reset' } }); + const resetToken = crypto.randomBytes(32).toString('hex'); const hash = await bcrypt.hash(resetToken, 10); const expiresAt = new Date(Date.now() + TOKEN_LIFESPAN); diff --git a/backend/src/service/email.service.js b/backend/src/service/email.service.js index bdeaf1c5..35be586c 100644 --- a/backend/src/service/email.service.js +++ b/backend/src/service/email.service.js @@ -2,18 +2,21 @@ const nodemailer = require("nodemailer"); const handlebars = require("handlebars"); const fs = require("fs"); const path = require("path"); -const { API_BASE_URL } = require("../utils/constants.helper"); +const { API_BASE_URL, FRONTEND_URL } = require('../utils/constants.helper'); const db = require("../models"); const User = db.User; const transporter = nodemailer.createTransport({ - host: process.env.EMAIL_HOST || "localhost", + host: process.env.EMAIL_HOST || 'localhost', port: process.env.EMAIL_PORT || 465, secure: true, auth: { user: process.env.EMAIL, pass: process.env.APP_PASSWORD, }, + tls: { + rejectUnauthorized: process.env.NODE_ENV === 'production', + }, }); const readHTMLFile = (filePath) => { @@ -59,16 +62,25 @@ const sendSignupEmail = async (email, name) => { }; const sendPasswordResetEmail = async (email, name, resetToken) => { - const resetLink = `${API_BASE_URL}reset-password?token=${resetToken}`; - await sendEmail(email, "Password Reset", "resetPassword", { + const resetLink = `${FRONTEND_URL}set-new-password?token=${resetToken}`; + await sendEmail(email, 'Reset your password for Guidefox', 'resetPassword', { name, resetLink, }); }; +const sendInviteEmail = async (email) => { + const inviteLink = FRONTEND_URL; + await sendEmail(email, 'You’re invited to join Guidefox!', 'invite', { + inviteLink, + }); +}; + + module.exports = { sendSignupEmail, sendPasswordResetEmail, + sendInviteEmail, findUserByEmail, transporter, }; diff --git a/backend/src/service/invite.service.js b/backend/src/service/invite.service.js index a1e5ad3d..1a5d1712 100644 --- a/backend/src/service/invite.service.js +++ b/backend/src/service/invite.service.js @@ -1,4 +1,5 @@ const settings = require("../../config/settings"); +const { sendInviteEmail } = require("./email.service"); const db = require("../models"); const Invite = db.Invite; const User = db.User; @@ -22,6 +23,7 @@ class InviteService { invitedBy: userId, role: settings.user.role[role], }) + sendInviteEmail(invitedEmail); } else { await Invite.create({ @@ -29,6 +31,7 @@ class InviteService { invitedEmail: invitedEmail, role: settings.user.role[role], }); + sendInviteEmail(invitedEmail); } } catch (err) { diff --git a/backend/src/templates/invite.hbs b/backend/src/templates/invite.hbs new file mode 100644 index 00000000..576cce7f --- /dev/null +++ b/backend/src/templates/invite.hbs @@ -0,0 +1,7 @@ +

Hello,

+

You’ve been invited to join Guidefox - a platform designed to help you build better user experiences.

+

To get started, simply click the link below to create your account:

+

Join Guidefox

+

Once you’ve created your account, you’ll be able to log in and start exploring all the features we’ve prepared for you.

+

We’re excited to have you with us and can’t wait for you to experience Guidefox!

+

--
The Guidefox Team

diff --git a/backend/src/templates/resetPassword.hbs b/backend/src/templates/resetPassword.hbs index 5d5d71ba..7c422d2d 100644 --- a/backend/src/templates/resetPassword.hbs +++ b/backend/src/templates/resetPassword.hbs @@ -1,4 +1,7 @@ -

Password Reset

-

Hi {{name}},

-

Please use the following link to reset your password:

-Reset Password +

Hello {{name}},

+

We received a request to reset your password for your Guidefox account. If you didn’t request a password reset, you can safely ignore this email.

+

To reset your password, click the link below:

+Reset My Password +

Thank you for using Guidefox!

+

--
The Guidefox Team

+ diff --git a/backend/src/utils/constants.helper.js b/backend/src/utils/constants.helper.js index b3d04bbc..75bc3d36 100644 --- a/backend/src/utils/constants.helper.js +++ b/backend/src/utils/constants.helper.js @@ -4,6 +4,7 @@ module.exports = Object.freeze({ TOKEN_LIFESPAN: 3600 * 1000, // API_BASE_URL: 'https://onboarding-demo.bluewavelabs.ca/api/', API_BASE_URL: 'localhost:3000/api/', + FRONTEND_URL: 'https://onboarding-demo.bluewavelabs.ca/', MAX_FILE_SIZE: 3 * 1024 * 1024, ROLE: { ADMIN: '1', @@ -13,5 +14,5 @@ module.exports = Object.freeze({ ORG_NAME_REGEX: /^[a-zA-Z0-9\s\-_&.]+$/, URL_PROTOCOL_REGEX: /^(https?:\/\/)/, URL_DOMAIN_REGEX: /^https?:\/\/([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/, - }); + }); \ No newline at end of file diff --git a/frontend/src/scenes/login/SetNewPassword.jsx b/frontend/src/scenes/login/SetNewPassword.jsx index bd26be5a..779ef084 100644 --- a/frontend/src/scenes/login/SetNewPassword.jsx +++ b/frontend/src/scenes/login/SetNewPassword.jsx @@ -5,13 +5,17 @@ import CustomTextField from "@components/TextFieldComponents/CustomTextField/Cus import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import CircularProgress from "@mui/material/CircularProgress"; import { resetPassword } from "../../services/loginServices"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import * as Yup from "yup"; import { Formik, Form } from "formik"; function SetNewPasswordPage({ email = "asdf@asdf.com" }) { const [serverErrors, setServerErrors] = useState([]); const navigate = useNavigate(); + const location = useLocation(); + + const queryParams = new URLSearchParams(location.search); + const token = queryParams.get("token"); const validationSchema = Yup.object({ password: Yup.string() @@ -35,17 +39,18 @@ function SetNewPasswordPage({ email = "asdf@asdf.com" }) { validationSchema={validationSchema} validateOnChange={false} validateOnBlur={true} - onSubmit={async (values, { setSubmitting }) => { + onSubmit={async (values, { setSubmitting, resetForm }) => { setServerErrors([]); try { const response = await resetPassword({ - email: email, - password: values.password, + token: token, + newPassword: values.password, }); resetForm(); navigate("/reset-password"); } catch (error) { console.error("Password Reset failed:", error); + console.log("error", error); if (error.response?.data?.errors) { setServerErrors(error.response?.data?.errors); } else if (error.response?.data?.error) { @@ -70,7 +75,7 @@ function SetNewPasswordPage({ email = "asdf@asdf.com" }) {

Set new Password

- Your new password must be different to previously used passwords. + Your new password must be different from previously used passwords.

{ export const inviteMember = async (inputs) => { try { - const response = await apiClient.post('/team/invite', { invitedEmail: inputs.email, role: inputs.role }); + const response = await apiClient.post('team/invite', { invitedEmail: inputs.email, role: inputs.role }); return response; } catch (error) { console.error('Error inviting member: ', error.message); From e17a4bfcdbc9f744caa16cbdd2924b74a3734d01 Mon Sep 17 00:00:00 2001 From: erenfn Date: Mon, 13 Jan 2025 14:53:45 +0300 Subject: [PATCH 2/6] some fixes to email and set new password --- backend/.env | 3 + backend/src/server.js | 4 +- backend/src/utils/constants.helper.js | 34 +-- frontend/src/scenes/login/SetNewPassword.jsx | 203 +++++++++--------- .../scenes/login/SetNewPassword.test.jsx | 63 ++---- 5 files changed, 144 insertions(+), 163 deletions(-) diff --git a/backend/.env b/backend/.env index c106b2c2..471d61ed 100644 --- a/backend/.env +++ b/backend/.env @@ -31,3 +31,6 @@ ENABLE_IP_CHECK=false ALLOWED_IP_RANGE=11.22.33/10-200, 192.168.65/1-255 # Allowed IP addresses for the API separated by comma ALLOWED_IPS=127.0.0.1, 11.22.33.44, 11.22.33.45, 11.22.33.46, 192.168.65.1 + +# FRONTEND_URL=https://onboarding-demo.bluewavelabs.ca/ +FRONTEND_URL=http://localhost:4173/ \ No newline at end of file diff --git a/backend/src/server.js b/backend/src/server.js index e6b42090..3aea931b 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -3,7 +3,7 @@ const cors = require("cors"); const helmet = require("helmet"); const dotenv = require("dotenv"); const bodyParser = require("body-parser"); -const compression = require("compression"); +// const compression = require("compression"); const jsonErrorMiddleware = require("./middleware/jsonError.middleware"); const fileSizeValidator = require("./middleware/fileSizeValidator.middleware"); const { MAX_FILE_SIZE } = require("./utils/constants.helper"); @@ -32,7 +32,7 @@ app.use(cors()); app.options('*', cors()); // this is for preflight requests app.use(helmet()); app.use(bodyParser.json({ limit: MAX_FILE_SIZE })); -app.use(compression()); +// app.use(compression()); app.use(jsonErrorMiddleware); if (process.env.ENABLE_IP_CHECK === 'true') { app.use(ipFilter); diff --git a/backend/src/utils/constants.helper.js b/backend/src/utils/constants.helper.js index 75bc3d36..dfa8a485 100644 --- a/backend/src/utils/constants.helper.js +++ b/backend/src/utils/constants.helper.js @@ -1,18 +1,18 @@ +require('dotenv').config(); + module.exports = Object.freeze({ - JWT_EXPIRES_IN_1H: '1h', - JWT_EXPIRES_IN_20M: '20m', - TOKEN_LIFESPAN: 3600 * 1000, - // API_BASE_URL: 'https://onboarding-demo.bluewavelabs.ca/api/', - API_BASE_URL: 'localhost:3000/api/', - FRONTEND_URL: 'https://onboarding-demo.bluewavelabs.ca/', - MAX_FILE_SIZE: 3 * 1024 * 1024, - ROLE: { - ADMIN: '1', - MEMBER: '2' - }, - MAX_ORG_NAME_LENGTH: 100, - ORG_NAME_REGEX: /^[a-zA-Z0-9\s\-_&.]+$/, - URL_PROTOCOL_REGEX: /^(https?:\/\/)/, - URL_DOMAIN_REGEX: /^https?:\/\/([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/, - }); - \ No newline at end of file + JWT_EXPIRES_IN_1H: '1h', + JWT_EXPIRES_IN_20M: '20m', + TOKEN_LIFESPAN: 3600 * 1000, + API_BASE_URL: process.env.API_BASE_URL || 'localhost:3000/api/', + FRONTEND_URL: process.env.FRONTEND_URL, + MAX_FILE_SIZE: 3 * 1024 * 1024, + ROLE: { + ADMIN: '1', + MEMBER: '2' + }, + MAX_ORG_NAME_LENGTH: 100, + ORG_NAME_REGEX: /^[a-zA-Z0-9\s\-_&.]+$/, + URL_PROTOCOL_REGEX: /^(https?:\/\/)/, + URL_DOMAIN_REGEX: /^https?:\/\/([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/, +}); diff --git a/frontend/src/scenes/login/SetNewPassword.jsx b/frontend/src/scenes/login/SetNewPassword.jsx index 779ef084..ce92974e 100644 --- a/frontend/src/scenes/login/SetNewPassword.jsx +++ b/frontend/src/scenes/login/SetNewPassword.jsx @@ -50,7 +50,6 @@ function SetNewPasswordPage({ email = "asdf@asdf.com" }) { navigate("/reset-password"); } catch (error) { console.error("Password Reset failed:", error); - console.log("error", error); if (error.response?.data?.errors) { setServerErrors(error.response?.data?.errors); } else if (error.response?.data?.error) { @@ -71,112 +70,110 @@ function SetNewPasswordPage({ email = "asdf@asdf.com" }) { handleBlur, values, }) => ( -
- -

Set new Password

-

- Your new password must be different from previously used passwords. -

-
- -
-
- - {serverErrors.length > 0 && ( -
- {serverErrors.map((error, index) => ( -
{error}
- ))} -
+ +

Set new Password

+

+ Your new password must be different from previously used passwords. +

+
+ +
+
+ + helperText={touched.confirmPassword && errors.confirmPassword} + /> + {serverErrors.length > 0 && ( +
+ {serverErrors.map((error, index) => ( +
{error}
+ ))} +
+ )} +
-
- = 8 - ? "green" - : "var(--light-border-color)", - fontSize: "20px", - marginRight: "5px", - }} - /> - Must be at least 8 characters -
-
- _\-=]/.test(values.password) +
+ = 8 ? "green" : "var(--light-border-color)", - fontSize: "20px", - marginRight: "5px", - }} - /> - Must contain one special character -
- - - -
+ fontSize: "20px", + marginRight: "5px", + }} + /> + Must be at least 8 characters +
+
+ _\-=]/.test(values.password) + ? "green" + : "var(--light-border-color)", + fontSize: "20px", + marginRight: "5px", + }} + /> + Must contain one special character +
+ + + )} ); diff --git a/frontend/src/tests/scenes/login/SetNewPassword.test.jsx b/frontend/src/tests/scenes/login/SetNewPassword.test.jsx index b4b04fee..3117854e 100644 --- a/frontend/src/tests/scenes/login/SetNewPassword.test.jsx +++ b/frontend/src/tests/scenes/login/SetNewPassword.test.jsx @@ -1,15 +1,9 @@ -import { - render, - screen, - fireEvent, - act, - waitFor, -} from "@testing-library/react"; +import { render, screen, fireEvent, act, waitFor } from "@testing-library/react"; import { describe, it, expect, vi } from "vitest"; -import { BrowserRouter as Router } from "react-router-dom"; -import { AuthProvider } from "../../../services/authProvider"; // Import your AuthProvider -import SetNewPassword from "../../../scenes/login/SetNewPassword"; -import { resetPassword } from "../../../services/loginServices"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; +import { AuthProvider } from "../../../services/authProvider"; +import SetNewPasswordPage from "../../../scenes/login/SetNewPassword"; +import { resetPassword } from "../../../services/loginServices"; vi.mock("../../../services/loginServices", () => ({ resetPassword: vi.fn(), @@ -18,41 +12,32 @@ vi.mock("../../../services/loginServices", () => ({ describe("SetNewPasswordPage", () => { it("renders the set new password reset page", () => { render( - + {/* Mock the route */} - + - + ); expect(screen.getByText("Set new Password")).toBeTruthy(); - expect( - screen.getByText( - "Your new password must be different to previously used passwords." - ) - ).toBeTruthy(); }); it("handles reset password success", async () => { resetPassword.mockResolvedValueOnce({ data: { success: true } }); render( - + - + - + ); await act(async () => { const validPassword = "Test123!@"; - fireEvent.change(screen.getByPlaceholderText("Create your password"), { - target: { value: validPassword }, - }); - fireEvent.change(screen.getByPlaceholderText("Confirm your password"), { - target: { value: validPassword }, - }); + fireEvent.change(screen.getByPlaceholderText("Create your password"), { target: { value: validPassword } }); + fireEvent.change(screen.getByPlaceholderText("Confirm your password"), { target: { value: validPassword } }); fireEvent.blur(screen.getByPlaceholderText("Create your password")); fireEvent.blur(screen.getByPlaceholderText("Confirm your password")); @@ -62,8 +47,8 @@ describe("SetNewPasswordPage", () => { }); expect(resetPassword).toHaveBeenCalledWith({ - email: "asdf@asdf.com", - password: "Test123!@", + token: "mock-token", // Ensure token is passed + newPassword: "Test123!@", // Ensure the new password is passed }); }); @@ -75,22 +60,18 @@ describe("SetNewPasswordPage", () => { }); render( - + - + - + ); await act(async () => { const validPassword = "Test123!@"; - fireEvent.change(screen.getByPlaceholderText("Create your password"), { - target: { value: validPassword }, - }); - fireEvent.change(screen.getByPlaceholderText("Confirm your password"), { - target: { value: validPassword }, - }); + fireEvent.change(screen.getByPlaceholderText("Create your password"), { target: { value: validPassword } }); + fireEvent.change(screen.getByPlaceholderText("Confirm your password"), { target: { value: validPassword } }); fireEvent.blur(screen.getByPlaceholderText("Create your password")); fireEvent.blur(screen.getByPlaceholderText("Confirm your password")); @@ -103,8 +84,8 @@ describe("SetNewPasswordPage", () => { }); expect(resetPassword).toHaveBeenCalledWith({ - email: "asdf@asdf.com", - password: "Test123!@", + token: "mock-token", + newPassword: "Test123!@", }); }); }); From 521de7794812c071d64b0e1ab508645754d747f8 Mon Sep 17 00:00:00 2001 From: erenfn Date: Mon, 13 Jan 2025 15:25:21 +0300 Subject: [PATCH 3/6] check email fixes --- backend/src/test/unit/services/email.test.js | 2 +- docker-compose.yml | 46 +++++++++---------- frontend/src/assets/theme.jsx | 6 +++ .../src/components/CustomLink/CustomLink.jsx | 13 +++++- .../src/scenes/login/CheckYourEmailPage.jsx | 36 ++++++++++++--- .../src/scenes/login/ForgotPasswordPage.jsx | 4 +- frontend/src/scenes/login/SetNewPassword.jsx | 4 +- 7 files changed, 76 insertions(+), 35 deletions(-) diff --git a/backend/src/test/unit/services/email.test.js b/backend/src/test/unit/services/email.test.js index 45689d25..2be839c6 100644 --- a/backend/src/test/unit/services/email.test.js +++ b/backend/src/test/unit/services/email.test.js @@ -92,7 +92,7 @@ describe("Test email service", () => { expect(params).to.deep.equal({ from: process.env.EMAIL, to: user().build().email, - subject: "Password Reset", + subject: "Reset your password for Guidefox", html: "html", }); }); diff --git a/docker-compose.yml b/docker-compose.yml index 7e6e9689..c8dae902 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,29 +59,29 @@ services: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data - frontend: - build: ./frontend - volumes: - - ./frontend:/app - - /app/node_modules - ports: - - "4173:4173" - develop: - watch: - - action: sync - path: ./frontend - target: /app - ignore: - - node_modules/ - environment: - - NODE_ENV=${NODE_ENV:-development} - command: > - bash -c " - if [ \"$NODE_ENV\" = \"development\" ]; then - npm run dev; - elif [ \"$NODE_ENV\" != \"development\" ]; then - npm run build && npm run preview; - fi" + # frontend: + # build: ./frontend + # volumes: + # - ./frontend:/app + # - /app/node_modules + # ports: + # - "4173:4173" + # develop: + # watch: + # - action: sync + # path: ./frontend + # target: /app + # ignore: + # - node_modules/ + # environment: + # - NODE_ENV=${NODE_ENV:-development} + # command: > + # bash -c " + # if [ \"$NODE_ENV\" = \"development\" ]; then + # npm run dev; + # elif [ \"$NODE_ENV\" != \"development\" ]; then + # npm run build && npm run preview; + # fi" mailhog: image: mailhog/mailhog ports: diff --git a/frontend/src/assets/theme.jsx b/frontend/src/assets/theme.jsx index e28505a0..f3f461cf 100644 --- a/frontend/src/assets/theme.jsx +++ b/frontend/src/assets/theme.jsx @@ -58,6 +58,9 @@ export const lightTheme = createTheme({ }, MuiOutlinedInput: { styleOverrides: { + input: { + boxSizing: 'border-box' + }, root: { borderRadius: "8px", "&:hover .MuiOutlinedInput-notchedOutline": { @@ -138,6 +141,9 @@ export const darkTheme = createTheme({ }, MuiOutlinedInput: { styleOverrides: { + input: { + boxSizing: 'border-box' + }, root: { borderRadius: "8px", "&:hover .MuiOutlinedInput-notchedOutline": { diff --git a/frontend/src/components/CustomLink/CustomLink.jsx b/frontend/src/components/CustomLink/CustomLink.jsx index 9922e368..9cf15d1d 100644 --- a/frontend/src/components/CustomLink/CustomLink.jsx +++ b/frontend/src/components/CustomLink/CustomLink.jsx @@ -5,15 +5,24 @@ import './CustomLinkStyles.css'; const CustomLink = ({ text = 'Default Text', - url = '#', + url = '', className = '', underline = 'none', + onClick, }) => { + const handleClick = (event) => { + if (onClick) { + event.preventDefault(); + onClick(event); + } + }; + return ( {text} @@ -25,5 +34,7 @@ CustomLink.propTypes = { url: PropTypes.string, className: PropTypes.string, underline: PropTypes.oneOf(['none', 'hover', 'always']), + onClick: PropTypes.func, }; + export default CustomLink; diff --git a/frontend/src/scenes/login/CheckYourEmailPage.jsx b/frontend/src/scenes/login/CheckYourEmailPage.jsx index 8dbecfe2..983bb623 100644 --- a/frontend/src/scenes/login/CheckYourEmailPage.jsx +++ b/frontend/src/scenes/login/CheckYourEmailPage.jsx @@ -3,22 +3,46 @@ import styles from './Login.module.css'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { useLocation, useNavigate } from 'react-router-dom'; import CustomLink from '@components/CustomLink/CustomLink'; +import { forgotPassword } from "../../services/loginServices"; const CheckYourEmailPage = () => { const navigate = useNavigate(); const location = useLocation(); - const { email: emailFromState } = location.state || {}; + const values = location.state.values + const { email: emailFromState } = values || {}; + + const handleResendClick = () => { + if (emailFromState) { + forgotPassword(values) + .then(response => { + console.log("Password reset link resent successfully:", response); + }) + .catch(error => { + console.error("Error resending password reset link:", error); + }); + } else { + console.warn("No email provided to resend the password reset link."); + } + }; return (

Check Your Email

-

We sent a password reset link to

-

{emailFromState}

- +

We sent a password reset link to

+

+ {emailFromState || "Email not provided"} +

- Didn't receive the email? + Didn't receive the email?
- +
); }; diff --git a/frontend/src/scenes/login/ForgotPasswordPage.jsx b/frontend/src/scenes/login/ForgotPasswordPage.jsx index c41d36fc..46d354f1 100644 --- a/frontend/src/scenes/login/ForgotPasswordPage.jsx +++ b/frontend/src/scenes/login/ForgotPasswordPage.jsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import styles from "./Login.module.css"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; -import { forgotPassword } from "../../services/loginServices"; // Make sure this function is properly implemented +import { forgotPassword } from "../../services/loginServices"; import { useNavigate } from "react-router-dom"; import CustomTextField from "../../components/TextFieldComponents/CustomTextField/CustomTextField"; import CircularProgress from "@mui/material/CircularProgress"; @@ -33,7 +33,7 @@ const ForgotPasswordPage = () => { onSubmit={async (values, { setSubmitting }) => { setServerErrors([]); try { - const response = await forgotPassword(values); + await forgotPassword(values); navigate("/check-email", { state: { values } }); } catch (error) { setServerErrors(error.response?.data?.error); diff --git a/frontend/src/scenes/login/SetNewPassword.jsx b/frontend/src/scenes/login/SetNewPassword.jsx index ce92974e..ebaae8c5 100644 --- a/frontend/src/scenes/login/SetNewPassword.jsx +++ b/frontend/src/scenes/login/SetNewPassword.jsx @@ -9,7 +9,7 @@ import { useNavigate, useLocation } from "react-router-dom"; import * as Yup from "yup"; import { Formik, Form } from "formik"; -function SetNewPasswordPage({ email = "asdf@asdf.com" }) { +function SetNewPasswordPage() { const [serverErrors, setServerErrors] = useState([]); const navigate = useNavigate(); const location = useLocation(); @@ -42,7 +42,7 @@ function SetNewPasswordPage({ email = "asdf@asdf.com" }) { onSubmit={async (values, { setSubmitting, resetForm }) => { setServerErrors([]); try { - const response = await resetPassword({ + await resetPassword({ token: token, newPassword: values.password, }); From caea6dc4a62775912373d421bacf4ab888f8c5c7 Mon Sep 17 00:00:00 2001 From: erenfn Date: Mon, 13 Jan 2025 15:30:18 +0300 Subject: [PATCH 4/6] custom link test adjusted --- .../tests/components/customLinkComponent/CustomLink.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/tests/components/customLinkComponent/CustomLink.test.jsx b/frontend/src/tests/components/customLinkComponent/CustomLink.test.jsx index 37aa7fab..4d1da4c8 100644 --- a/frontend/src/tests/components/customLinkComponent/CustomLink.test.jsx +++ b/frontend/src/tests/components/customLinkComponent/CustomLink.test.jsx @@ -14,7 +14,7 @@ describe('CustomLink', () => { render(); const linkElement = screen.getByText(/Default Text/i); expect(linkElement).not.toBeNull(); - expect(linkElement.getAttribute('href')).toBe('#'); + expect(linkElement.getAttribute('href')).toBe(''); }); it('applies custom class names correctly', () => { From 717a4a3613a27dd1b3f602cc6839a340a435b3b9 Mon Sep 17 00:00:00 2001 From: erenfn <81182796+erenfn@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:41:11 +0300 Subject: [PATCH 5/6] Update frontend/src/scenes/login/CheckYourEmailPage.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- frontend/src/scenes/login/CheckYourEmailPage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/scenes/login/CheckYourEmailPage.jsx b/frontend/src/scenes/login/CheckYourEmailPage.jsx index 983bb623..a319b2bd 100644 --- a/frontend/src/scenes/login/CheckYourEmailPage.jsx +++ b/frontend/src/scenes/login/CheckYourEmailPage.jsx @@ -8,8 +8,8 @@ import { forgotPassword } from "../../services/loginServices"; const CheckYourEmailPage = () => { const navigate = useNavigate(); const location = useLocation(); - const values = location.state.values - const { email: emailFromState } = values || {}; + const values = location.state?.values || {}; + const { email: emailFromState } = values; const handleResendClick = () => { if (emailFromState) { From 5e4470e0f3c9448c8cc2b4bff1c0d99519ca29b2 Mon Sep 17 00:00:00 2001 From: erenfn Date: Mon, 13 Jan 2025 15:56:25 +0300 Subject: [PATCH 6/6] handles tokenless case in set new password --- frontend/src/scenes/login/SetNewPassword.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/scenes/login/SetNewPassword.jsx b/frontend/src/scenes/login/SetNewPassword.jsx index ebaae8c5..9d912a52 100644 --- a/frontend/src/scenes/login/SetNewPassword.jsx +++ b/frontend/src/scenes/login/SetNewPassword.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import styles from "./Login.module.css"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import CustomTextField from "@components/TextFieldComponents/CustomTextField/CustomTextField"; @@ -17,6 +17,12 @@ function SetNewPasswordPage() { const queryParams = new URLSearchParams(location.search); const token = queryParams.get("token"); + useEffect(() => { + if (!token) { + navigate("/login"); + } + }, [token]); + const validationSchema = Yup.object({ password: Yup.string() .required("Password is required")