From 85bf774eb983de01a7e28c92122f592e5b12d617 Mon Sep 17 00:00:00 2001 From: adityapat24 Date: Tue, 24 Feb 2026 18:32:56 -0500 Subject: [PATCH 1/3] new designs --- .../settings/ChangePasswordModal.tsx | 152 ++++++++++++++++++ frontend/src/main-page/settings/Settings.tsx | 129 ++++++++++++--- frontend/src/sign-up/PasswordField.tsx | 4 +- frontend/src/sign-up/PasswordRequirements.tsx | 5 + frontend/src/sign-up/index.ts | 2 +- 5 files changed, 267 insertions(+), 25 deletions(-) create mode 100644 frontend/src/main-page/settings/ChangePasswordModal.tsx diff --git a/frontend/src/main-page/settings/ChangePasswordModal.tsx b/frontend/src/main-page/settings/ChangePasswordModal.tsx new file mode 100644 index 0000000..2d2c9ef --- /dev/null +++ b/frontend/src/main-page/settings/ChangePasswordModal.tsx @@ -0,0 +1,152 @@ +import { useState } from "react"; +import { + PasswordField, + PasswordRequirements, + isPasswordValid, +} from "../../sign-up"; + + +export type ChangePasswordFormValues = { + currentPassword: string; + newPassword: string; +}; + +type ChangePasswordModalProps = { + isOpen: boolean; + onClose: () => void; + + onSubmit?: (values: ChangePasswordFormValues) => void; + error?: string | null; +}; + +function CloseIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +export default function ChangePasswordModal({ + isOpen, + onClose, + onSubmit, + error = null, +}: ChangePasswordModalProps) { + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [reEnterPassword, setReEnterPassword] = useState(""); + + if (!isOpen) return null; + + const newPasswordValid = isPasswordValid(newPassword); + const passwordsMatch = newPassword !== "" && newPassword === reEnterPassword; + const allFilled = + currentPassword.trim() !== "" && + newPassword !== "" && + reEnterPassword !== ""; + const canSave = + allFilled && newPasswordValid && passwordsMatch; + + const handleClose = () => { + setCurrentPassword(""); + setNewPassword(""); + setReEnterPassword(""); + onClose(); + }; + + const handleSave = () => { + if (!canSave) return; + onSubmit?.({ + currentPassword: currentPassword.trim(), + newPassword, + }); + handleClose(); + }; + + return ( +
+
+
+

+ Change Password +

+ +
+ +
+ setCurrentPassword(e.target.value)} + error={!!error} + /> + + setNewPassword(e.target.value)} + /> + + setReEnterPassword(e.target.value)} + /> + + + + {error && ( +
+ {error} +
+ )} + + +
+
+
+ ); +} diff --git a/frontend/src/main-page/settings/Settings.tsx b/frontend/src/main-page/settings/Settings.tsx index 1c76c69..3b81592 100644 --- a/frontend/src/main-page/settings/Settings.tsx +++ b/frontend/src/main-page/settings/Settings.tsx @@ -1,31 +1,56 @@ +import { useState } from "react"; import Button from "../../components/Button"; import InfoCard from "./components/InfoCard"; import logo from "../../images/logo.svg"; import { faPenToSquare } from "@fortawesome/free-solid-svg-icons"; +import ChangePasswordModal from "./ChangePasswordModal"; + +const initialPersonalInfo = { + firstName: "John", + lastName: "Doe", + email: "john.doe@gmail.com", +}; export default function Settings() { + const [personalInfo, setPersonalInfo] = useState(initialPersonalInfo); + const [isEditingPersonalInfo, setIsEditingPersonalInfo] = useState(false); + const [editForm, setEditForm] = useState(initialPersonalInfo); + const [isChangePasswordModalOpen, setIsChangePasswordModalOpen] = useState(false); + const [changePasswordError, setChangePasswordError] = useState(null); + + const handleStartEdit = () => { + setEditForm(personalInfo); + setIsEditingPersonalInfo(true); + }; + + const handleCancelEdit = () => { + setEditForm(personalInfo); + setIsEditingPersonalInfo(false); + }; + + const handleSaveEdit = () => { + setPersonalInfo(editForm); + setIsEditingPersonalInfo(false); + }; + return (

Settings

- {/* Avatar */} Profile - {/* Buttons + helper text */}

Profile Picture

-
- alert("edit personal info")} - className="bg-white text-black border-2 border-grey-500" - logo={faPenToSquare} - logoPosition="right" - /> - } - fields={[ - { label: "First Name", value: "John" }, - { label: "Last Name", value: "Doe" }, - { label: "Email Address", value: "john.doe@gmail.com" }, - ]} - /> + {isEditingPersonalInfo ? ( +
+

Personal Information

+
+
+ + setEditForm((f) => ({ ...f, firstName: e.target.value }))} + className="w-full px-3 py-2 rounded-md border border-gray-300 bg-white text-gray-900" + /> +
+
+ + setEditForm((f) => ({ ...f, lastName: e.target.value }))} + className="w-full px-3 py-2 rounded-md border border-gray-300 bg-white text-gray-900" + /> +
+
+ + setEditForm((f) => ({ ...f, email: e.target.value }))} + className="w-full px-3 py-2 rounded-md border border-gray-300 bg-white text-gray-900" + /> +
+
+
+
+
+ ) : ( + + } + fields={[ + { label: "First Name", value: personalInfo.firstName }, + { label: "Last Name", value: personalInfo.lastName }, + { label: "Email Address", value: personalInfo.email }, + ]} + /> + )}
@@ -70,10 +142,23 @@ export default function Settings() {
+ + setIsChangePasswordModalOpen(false)} + error={changePasswordError} + onSubmit={(values) => { + // Backend: call API with values.currentPassword and values.newPassword + void values; + }} + />
); } diff --git a/frontend/src/sign-up/PasswordField.tsx b/frontend/src/sign-up/PasswordField.tsx index 671389a..081c57a 100644 --- a/frontend/src/sign-up/PasswordField.tsx +++ b/frontend/src/sign-up/PasswordField.tsx @@ -90,9 +90,9 @@ export default function PasswordField({ tabIndex={-1} > {visible ? ( - - ) : ( + ) : ( + )}
diff --git a/frontend/src/sign-up/PasswordRequirements.tsx b/frontend/src/sign-up/PasswordRequirements.tsx index 69c32d2..69fa543 100644 --- a/frontend/src/sign-up/PasswordRequirements.tsx +++ b/frontend/src/sign-up/PasswordRequirements.tsx @@ -21,6 +21,11 @@ export const PASSWORD_REQUIREMENTS: PasswordRequirement[] = [ { id: "lower", label: "1 Lowercase", check: (p) => /[a-z]/.test(p) }, ]; +/** Returns true if the password meets all requirements (same logic as sign-up). */ +export function isPasswordValid(password: string): boolean { + return PASSWORD_REQUIREMENTS.every((r) => r.check(password)); +} + type PasswordRequirementsProps = { password: string; }; diff --git a/frontend/src/sign-up/index.ts b/frontend/src/sign-up/index.ts index 39dca3d..752f1a3 100644 --- a/frontend/src/sign-up/index.ts +++ b/frontend/src/sign-up/index.ts @@ -2,7 +2,7 @@ export { default as BrandingPanel } from "./BrandingPanel"; export { default as InputField } from "../components/InputField"; export { default as LoginPrompt } from "./LoginPrompt"; export { default as PasswordField } from "./PasswordField"; -export { default as PasswordRequirements } from "./PasswordRequirements"; +export { default as PasswordRequirements, isPasswordValid } from "./PasswordRequirements"; export { default as SignUpButton } from "./SignUpButton"; export { default as SignUpForm } from "./SignUpForm"; export type { SignUpFormProps, SignUpFormValues } from "./SignUpForm"; From 5c57ea3cb36f9f6554dd899c9d1cbffeeaf019d6 Mon Sep 17 00:00:00 2001 From: adityapat24 Date: Thu, 26 Feb 2026 19:02:12 -0500 Subject: [PATCH 2/3] fixes based on comments --- .../settings/ChangePasswordModal.tsx | 34 +++++-------------- frontend/src/main-page/settings/Settings.tsx | 2 +- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/frontend/src/main-page/settings/ChangePasswordModal.tsx b/frontend/src/main-page/settings/ChangePasswordModal.tsx index 2d2c9ef..744aad4 100644 --- a/frontend/src/main-page/settings/ChangePasswordModal.tsx +++ b/frontend/src/main-page/settings/ChangePasswordModal.tsx @@ -1,11 +1,12 @@ import { useState } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { PasswordField, PasswordRequirements, isPasswordValid, } from "../../sign-up"; - export type ChangePasswordFormValues = { currentPassword: string; newPassword: string; @@ -14,30 +15,10 @@ export type ChangePasswordFormValues = { type ChangePasswordModalProps = { isOpen: boolean; onClose: () => void; - onSubmit?: (values: ChangePasswordFormValues) => void; error?: string | null; }; -function CloseIcon({ className }: { className?: string }) { - return ( - - - - ); -} - export default function ChangePasswordModal({ isOpen, onClose, @@ -52,6 +33,8 @@ export default function ChangePasswordModal({ const newPasswordValid = isPasswordValid(newPassword); const passwordsMatch = newPassword !== "" && newPassword === reEnterPassword; + const passwordsDontMatch = + reEnterPassword !== "" && newPassword !== reEnterPassword; const allFilled = currentPassword.trim() !== "" && newPassword !== "" && @@ -96,7 +79,7 @@ export default function ChangePasswordModal({ className="rounded p-1 text-grey-600 hover:bg-grey-200 hover:text-grey-800" aria-label="Close" > - +
@@ -127,13 +110,14 @@ export default function ChangePasswordModal({ placeholder="Re-enter your password" value={reEnterPassword} onChange={(e) => setReEnterPassword(e.target.value)} + error={!!error || passwordsDontMatch} /> - {error && ( -
- {error} + {(error || passwordsDontMatch) && ( +
+ {error ?? "Your passwords do not match."}
)} diff --git a/frontend/src/main-page/settings/Settings.tsx b/frontend/src/main-page/settings/Settings.tsx index 3b81592..891b222 100644 --- a/frontend/src/main-page/settings/Settings.tsx +++ b/frontend/src/main-page/settings/Settings.tsx @@ -68,7 +68,7 @@ export default function Settings() {
{isEditingPersonalInfo ? ( -
+

Personal Information

From debad0e71763be6696846a7f3e7d75848f6003c0 Mon Sep 17 00:00:00 2001 From: adityapat24 Date: Mon, 9 Mar 2026 17:09:59 -0400 Subject: [PATCH 3/3] email verification and moved edit info --- frontend/src/main-page/settings/Settings.tsx | 139 +++++++++++------- .../settings/components/InfoCard.tsx | 34 +++-- 2 files changed, 105 insertions(+), 68 deletions(-) diff --git a/frontend/src/main-page/settings/Settings.tsx b/frontend/src/main-page/settings/Settings.tsx index 891b222..e296464 100644 --- a/frontend/src/main-page/settings/Settings.tsx +++ b/frontend/src/main-page/settings/Settings.tsx @@ -5,6 +5,8 @@ import logo from "../../images/logo.svg"; import { faPenToSquare } from "@fortawesome/free-solid-svg-icons"; import ChangePasswordModal from "./ChangePasswordModal"; +const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + const initialPersonalInfo = { firstName: "John", lastName: "Doe", @@ -15,22 +17,31 @@ export default function Settings() { const [personalInfo, setPersonalInfo] = useState(initialPersonalInfo); const [isEditingPersonalInfo, setIsEditingPersonalInfo] = useState(false); const [editForm, setEditForm] = useState(initialPersonalInfo); + const [personalInfoError, setPersonalInfoError] = useState(null); const [isChangePasswordModalOpen, setIsChangePasswordModalOpen] = useState(false); const [changePasswordError, setChangePasswordError] = useState(null); const handleStartEdit = () => { setEditForm(personalInfo); + setPersonalInfoError(null); setIsEditingPersonalInfo(true); }; const handleCancelEdit = () => { setEditForm(personalInfo); + setPersonalInfoError(null); setIsEditingPersonalInfo(false); }; const handleSaveEdit = () => { + if (!EMAIL_REGEX.test(editForm.email)) { + setPersonalInfoError("Email is not valid."); + return; + } + setPersonalInfo(editForm); setIsEditingPersonalInfo(false); + setPersonalInfoError(null); }; return ( @@ -67,55 +78,10 @@ export default function Settings() {
- {isEditingPersonalInfo ? ( -
-

Personal Information

-
-
- - setEditForm((f) => ({ ...f, firstName: e.target.value }))} - className="w-full px-3 py-2 rounded-md border border-gray-300 bg-white text-gray-900" - /> -
-
- - setEditForm((f) => ({ ...f, lastName: e.target.value }))} - className="w-full px-3 py-2 rounded-md border border-gray-300 bg-white text-gray-900" - /> -
-
- - setEditForm((f) => ({ ...f, email: e.target.value }))} - className="w-full px-3 py-2 rounded-md border border-gray-300 bg-white text-gray-900" - /> -
-
-
-
-
- ) : ( - - } - fields={[ - { label: "First Name", value: personalInfo.firstName }, - { label: "Last Name", value: personalInfo.lastName }, - { label: "Email Address", value: personalInfo.email }, - ]} - /> - )} + ) + } + fields={[ + { label: "First Name", value: personalInfo.firstName }, + { label: "Last Name", value: personalInfo.lastName }, + { label: "Email Address", value: personalInfo.email }, + ]} + isEditing={isEditingPersonalInfo} + editContent={ + <> +
+
+ + + setEditForm((f) => ({ ...f, firstName: e.target.value })) + } + className="w-full px-3 py-2 rounded-md border border-gray-300 bg-white text-gray-900" + /> +
+
+ + + setEditForm((f) => ({ ...f, lastName: e.target.value })) + } + className="w-full px-3 py-2 rounded-md border border-gray-300 bg-white text-gray-900" + /> +
+
+ + + setEditForm((f) => ({ ...f, email: e.target.value })) + } + className={`w-full px-3 py-2 rounded-md border bg-white text-gray-900 ${ + personalInfoError ? "border-[#CC0000]" : "border-gray-300" + }`} + /> +
+
+ {personalInfoError && ( +
+ {personalInfoError} +
+ )} +
+
+ + } + />
diff --git a/frontend/src/main-page/settings/components/InfoCard.tsx b/frontend/src/main-page/settings/components/InfoCard.tsx index a8ffa24..04bcdc1 100644 --- a/frontend/src/main-page/settings/components/InfoCard.tsx +++ b/frontend/src/main-page/settings/components/InfoCard.tsx @@ -9,9 +9,17 @@ type InfoCardProps = { title?: string; fields: InfoField[]; action?: ReactNode; + isEditing?: boolean; + editContent?: ReactNode; }; -export default function InfoCard({ title, fields, action }: InfoCardProps) { +export default function InfoCard({ + title, + fields, + action, + isEditing, + editContent, +}: InfoCardProps) { return (
{(title || action) && ( @@ -25,16 +33,20 @@ export default function InfoCard({ title, fields, action }: InfoCardProps) {
)} -
- {fields.map((field) => ( -
-

{field.label}

-

- {field.value} -

-
- ))} -
+ {isEditing && editContent ? ( + editContent + ) : ( +
+ {fields.map((field) => ( +
+

{field.label}

+

+ {field.value} +

+
+ ))} +
+ )}
); }