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";