diff --git a/frontend/src/components/organisms/ChangePasswordForm.tsx b/frontend/src/components/organisms/ChangePasswordForm.tsx new file mode 100644 index 00000000..7c9d807c --- /dev/null +++ b/frontend/src/components/organisms/ChangePasswordForm.tsx @@ -0,0 +1,207 @@ +import React, { useState } from "react"; +import Button from "../atoms/Button"; +import TextField from "../atoms/TextField"; +import { auth } from "@/utils/firebase"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { reauthenticateWithCredential, EmailAuthProvider } from "firebase/auth"; +import Snackbar from "../atoms/Snackbar"; +import { useMutation } from "@tanstack/react-query"; +import { updatePassword } from "firebase/auth"; +import { User } from "firebase/auth"; + +type FormValues = { + email: string; + oldPassword: string; + newPassword: string; + confirmNewPassword: string; +}; + +type formData = { + id: string; + email: string; + role?: string; + status?: string; + createdAt?: string; + verified?: boolean; + disciplinaryNotices?: number; + imageUrl?: string; +}; + +interface ChangePasswordFormProps { + userDetails: formData; +} + +const ChangePasswordForm = ({ userDetails }: ChangePasswordFormProps) => { + /** State variables for the notification popups for profile update */ + const [successNotificationOpen, setSuccessNotificationOpen] = useState(false); + const [errorNotificationOpen, setErrorNotificationOpen] = useState(false); + + /** Handles form errors */ + const [errorMessage, setErrorMessage] = React.useState(""); + const handleErrors = (errors: any) => { + const errorParsed = errors?.split("/")[1]?.slice(0, -2); + switch (errorParsed) { + case "invalid-email": + return "Invalid email address format."; + case "user-disabled": + return "User with this email has been disabled."; + case "user-not-found": + return "There is no user with this email address."; + case "wrong-password": + return "Old password is incorrect."; + case "weak-password": + return "Password must be at least 6 characters."; + case "invalid-password": + return "Invalid password."; + case "requires-recent-login": + return "Please reauthenticate to change your password."; + case "too-many-requests": + return "You have made too many requests to change your password. Please try again later."; + default: + return "Something went wrong. Please try again."; + } + }; + + /** React hook form */ + const { + register, + handleSubmit, + watch, + reset, + formState: { errors, isDirty }, + } = useForm({ + defaultValues: { + email: userDetails.email, + oldPassword: "", + newPassword: "", + confirmNewPassword: "", + }, + }); + + /** Tanstack query mutation to reauthenticate the user session */ + const ReAuthenticateUserSession = useMutation({ + mutationFn: async (data: any) => { + const currentUser = auth.currentUser; + if (currentUser != null) { + const credentials = EmailAuthProvider.credential( + data.email, + data.oldPassword + ); + return reauthenticateWithCredential(currentUser, credentials); + } + }, + retry: false, + }); + + /** Tanstack query mutation to update user password in Firebase */ + const updateUserPasswordInFirebase = useMutation({ + mutationFn: async (data: any) => { + const user = auth.currentUser as User; + return updatePassword(user, data.newPassword); + }, + retry: false, + }); + + /** Handles form submit for profile changes */ + const handleChanges: SubmitHandler = async (data) => { + try { + await ReAuthenticateUserSession.mutateAsync(data); + await updateUserPasswordInFirebase.mutateAsync(data); + setSuccessNotificationOpen(true); + } catch (error: any) { + setErrorNotificationOpen(true); + setErrorMessage(error.message); + } + }; + + return ( + <> + {/* Profile update error snackbar */} + setErrorNotificationOpen(false)} + > + Error: {handleErrors(errorMessage)} + + + {/* Profile update success snackbar */} + setSuccessNotificationOpen(false)} + > + Success: Password update was successful! + + + {/* Profile form */} +
+ + + /.*[A-Z].*/.test(value) || + "Password must contain at least one uppercase letter", + hasLower: (value) => + /.*[a-z].*/.test(value) || + "Password must contain at least one lowercase letter", + hasNumber: (value) => + /.*[0-9].*/.test(value) || + "Password must contain at least one number", + hasSpecialChar: (value) => + /.*[\W_].*/.test(value) || + "Password must contain at least one special character", + }, + })} + /> + + value === watch("newPassword") || "Passwords do not match", + }, + })} + /> +
+
+ +
+
+ +
+
+ + + ); +}; + +export default ChangePasswordForm; diff --git a/frontend/src/components/organisms/ProfileForm.tsx b/frontend/src/components/organisms/ProfileForm.tsx index d4b7ef9a..eb4f3df2 100644 --- a/frontend/src/components/organisms/ProfileForm.tsx +++ b/frontend/src/components/organisms/ProfileForm.tsx @@ -2,9 +2,7 @@ import React, { useState } from "react"; import Button from "../atoms/Button"; import TextField from "../atoms/TextField"; import Checkbox from "../atoms/Checkbox"; -import { auth } from "@/utils/firebase"; import { useForm, SubmitHandler } from "react-hook-form"; -import { reauthenticateWithCredential, EmailAuthProvider } from "firebase/auth"; import Snackbar from "../atoms/Snackbar"; import { api } from "@/utils/api"; import { useMutation, useQueryClient } from "@tanstack/react-query"; @@ -16,10 +14,8 @@ type FormValues = { email: string; firstName: string; lastName: string; + phoneNumber: string; // preferredName: string; - oldPassword: string; - newPassword: string; - confirmNewPassword: string; emailNotifications: boolean; }; @@ -28,6 +24,7 @@ type formData = { email: string; firstName: string; lastName: string; + phoneNumber: string; nickname: string; role?: string; status?: string; @@ -45,7 +42,7 @@ interface ProfileFormProps { const ProfileForm = ({ userDetails }: ProfileFormProps) => { const queryClient = useQueryClient(); - /** State variables for the notification popups */ + /** State variables for the notification popups for profile update */ const [successNotificationOpen, setSuccessNotificationOpen] = useState(false); const [errorNotificationOpen, setErrorNotificationOpen] = useState(false); @@ -54,24 +51,8 @@ const ProfileForm = ({ userDetails }: ProfileFormProps) => { const handleErrors = (errors: any) => { const errorParsed = errors?.split("/")[1]?.slice(0, -2); switch (errorParsed) { - case "invalid-email": - return "Invalid email address format."; - case "user-disabled": - return "User with this email has been disabled."; - case "user-not-found": - return "There is no user with this email address."; - case "wrong-password": - return "Old password is incorrect."; - case "weak-password": - return "Password must be at least 6 characters."; - case "invalid-password": - return "Invalid password."; - case "requires-recent-login": - return "Please reauthenticate to change your password."; - case "too-many-requests": - return "You have made too many requests to change your password. Please try again later."; default: - return "Something went wrong. Please try again"; + return "Something went wrong. Please try again."; } }; @@ -88,46 +69,19 @@ const ProfileForm = ({ userDetails }: ProfileFormProps) => { email: userDetails.email, firstName: userDetails.firstName, lastName: userDetails.lastName, + phoneNumber: userDetails.phoneNumber, // preferredName: userDetails.nickname, - oldPassword: "", - newPassword: "", - confirmNewPassword: "", emailNotifications: userDetails.sendEmailNotification, }, }); - /** Handles checkbox */ - - /** Tanstack query mutation to reauthenticate the user session */ - const ReAuthenticateUserSession = useMutation({ - mutationFn: async (data: any) => { - const currentUser = auth.currentUser; - if (currentUser != null) { - const credentials = EmailAuthProvider.credential( - data.email, - data.oldPassword - ); - return reauthenticateWithCredential(currentUser, credentials); - } - }, - retry: false, - }); - - /** Tanstack query mutation to update user password in Firebase */ - const updateUserPasswordInFirebase = useMutation({ - mutationFn: async (data: any) => { - const user = auth.currentUser as User; - return updatePassword(user, data.newPassword); - }, - retry: false, - }); - /** Tanstack query mutation to update the user profile */ const updateProfileInDB = useMutation({ mutationFn: async (data: any) => { return api.put(`/users/${userDetails.id}/profile`, { firstName: data.firstName, lastName: data.lastName, + phoneNumber: data.phoneNumber, // nickname: data.preferredName, }); }, @@ -153,8 +107,6 @@ const ProfileForm = ({ userDetails }: ProfileFormProps) => { /** Handles form submit */ const handleChanges: SubmitHandler = async (data) => { try { - await ReAuthenticateUserSession.mutateAsync(data); - await updateUserPasswordInFirebase.mutateAsync(data); await updateProfileInDB.mutateAsync(data); await updatePreferencesInDB.mutateAsync(data.emailNotifications); setSuccessNotificationOpen(true); @@ -186,18 +138,6 @@ const ProfileForm = ({ userDetails }: ProfileFormProps) => { {/* Profile form */}
- { required: { value: true, message: "Required" }, })} /> - {/* */} - /.*[A-Z].*/.test(value) || - "Password must contain at least one uppercase letter", - hasLower: (value) => - /.*[a-z].*/.test(value) || - "Password must contain at least one lowercase letter", - hasNumber: (value) => - /.*[0-9].*/.test(value) || - "Password must contain at least one number", - hasSpecialChar: (value) => - /.*[\W_].*/.test(value) || - "Password must contain at least one special character", + pattern: { + value: + /^\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}$/, + message: "Invalid phone number", }, })} /> - - value === watch("newPassword") || "Passwords do not match", - }, })} - /> + /> */} { )} -

My Profile

+

My Profile

{ }} /> +

My Account

+ + + ); };