From 01223cc4928c93910cf9ca5c1d23cf8791444a4d Mon Sep 17 00:00:00 2001 From: Brok3Turtl3 Date: Thu, 28 Mar 2024 09:44:32 -0400 Subject: [PATCH 1/6] CHE-7 Seting up basic component --- client/src/pages/EditProfilePage/EditProfilePage.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 client/src/pages/EditProfilePage/EditProfilePage.tsx diff --git a/client/src/pages/EditProfilePage/EditProfilePage.tsx b/client/src/pages/EditProfilePage/EditProfilePage.tsx new file mode 100644 index 0000000..19fdf8d --- /dev/null +++ b/client/src/pages/EditProfilePage/EditProfilePage.tsx @@ -0,0 +1,12 @@ +import React, { useEffect, useState } from "react"; +import { useAppSelector, useAppDispatch } from "../../app/hooks"; +import { + fetchUserProfile, + updateUserProfile, +} from "../../features/userProfile/userProfileSlice"; + +const EditProfilePage = () => { + const dispatch = useAppDispatch(); +}; + +export default EditProfilePage; From a1f966b796ef89bb056b44cac441ac6abb43c687 Mon Sep 17 00:00:00 2001 From: Brok3Turtl3 Date: Thu, 28 Mar 2024 10:12:26 -0400 Subject: [PATCH 2/6] CHE-7 Basic edit page set-up --- client/src/AuthenticatedApp.tsx | 2 + client/src/components/Banner/Banner.tsx | 8 +- .../pages/EditProfilePage/EditProfilePage.tsx | 82 ++++++++++++++++++- server/models/profileModel.ts | 1 - 4 files changed, 87 insertions(+), 6 deletions(-) diff --git a/client/src/AuthenticatedApp.tsx b/client/src/AuthenticatedApp.tsx index fd71270..559989a 100644 --- a/client/src/AuthenticatedApp.tsx +++ b/client/src/AuthenticatedApp.tsx @@ -7,6 +7,7 @@ import MainPage from "./pages/MainPage/MainPage"; import Forums from "./pages/Forums/Forums"; import Profiles from "./pages/Profiles/Profiles"; import Profile from "./pages/Profile/Profile"; +import EditProfilePage from "./pages/EditProfilePage/EditProfilePage"; import NotFoundPage from "./pages/NotFoundPage/NotFoundPage"; import { useNavigate } from "react-router-dom"; @@ -27,6 +28,7 @@ const AuthenticatedApp = () => { } /> } /> } /> + } /> } /> } /> diff --git a/client/src/components/Banner/Banner.tsx b/client/src/components/Banner/Banner.tsx index 26da12c..3729191 100644 --- a/client/src/components/Banner/Banner.tsx +++ b/client/src/components/Banner/Banner.tsx @@ -16,8 +16,8 @@ const Banner = (): JSX.Element => { //TODO CLEAR ALL STATE }; - const goToProfile = () => { - navigate("profile"); + const goToEditProfile = () => { + navigate("editProfile"); setShowDropdown(false); }; return ( @@ -40,9 +40,9 @@ const Banner = (): JSX.Element => { - Go to Profile + Edit Profile { const dispatch = useAppDispatch(); + const { profile, status } = useAppSelector((state) => state.userProfile); + const userID = useAppSelector((state) => state.user.userData._id); + + const [formData, setFormData] = useState({ + fullName: "", + email: "", + personalBio: "", + }); + + useEffect(() => { + dispatch(fetchUserProfile(userID)); + }, [dispatch]); + + useEffect(() => { + if (profile) { + setFormData({ + fullName: profile.fullName || "", + email: profile.email || "", + }); + } + }, [profile]); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prevFormData) => ({ + ...prevFormData, + [name]: value, + })); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + // Again, assuming you have a way to include the user ID + const userID = "currentUser_ID"; + dispatch(updateUserProfile({ ...formData, userID })); + }; + + if (status === "loading") { + return
Loading...
; + } + + return ( +
+

Edit Profile

+
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+ ); }; export default EditProfilePage; diff --git a/server/models/profileModel.ts b/server/models/profileModel.ts index 4b770d0..bfdd800 100644 --- a/server/models/profileModel.ts +++ b/server/models/profileModel.ts @@ -1,4 +1,3 @@ -// profileModel.ts import mongoose, { Schema } from "mongoose"; import { IProfile } from "../types/profile"; From a6d7d5d11418a8a34cacafbcb434610d772a909a Mon Sep 17 00:00:00 2001 From: Brok3Turtl3 Date: Thu, 28 Mar 2024 11:09:26 -0400 Subject: [PATCH 3/6] CHE-7 Updated userProfileSlice to include new action creator and state functionality for updating users profile --- .../features/userProfile/userProfileSlice.ts | 32 ++++++++++++++++++- .../pages/EditProfilePage/EditProfilePage.tsx | 2 +- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/client/src/features/userProfile/userProfileSlice.ts b/client/src/features/userProfile/userProfileSlice.ts index 0d67961..9c4f4b7 100644 --- a/client/src/features/userProfile/userProfileSlice.ts +++ b/client/src/features/userProfile/userProfileSlice.ts @@ -4,7 +4,7 @@ import { IProfile } from "../../../types/profile"; export interface ProfileState { profile: IProfile | null; - status: "idle" | "loading" | "failed"; + status: "idle" | "loading" | "failed" | "updating"; error: string | null; } @@ -31,6 +31,25 @@ export const fetchUserProfile = createAsyncThunk( } ); +export const updateUserProfile = createAsyncThunk( + "profile/updateUserProfile", + async ( + { userID, ...updateData }: Partial & { userID: string }, + thunkAPI + ) => { + try { + const response = await axios.put(`/api/profiles/${userID}`, updateData); + return response.data; + } catch (error) { + let errorMessage = "An error occurred during profile update"; + if (axios.isAxiosError(error)) { + errorMessage = error.response?.data || errorMessage; + } + return thunkAPI.rejectWithValue(errorMessage); + } + } +); + const userProfileSlice = createSlice({ name: "profile", initialState, @@ -53,6 +72,17 @@ const userProfileSlice = createSlice({ .addCase(fetchUserProfile.rejected, (state, action) => { state.status = "failed"; state.error = action.payload as string; + }) + .addCase(updateUserProfile.pending, (state) => { + state.status = "updating"; + }) + .addCase(updateUserProfile.fulfilled, (state, action) => { + state.profile = action.payload; // Assuming the backend returns the updated profile + state.status = "idle"; + }) + .addCase(updateUserProfile.rejected, (state, action) => { + state.status = "failed"; + state.error = action.payload as string; }); }, }); diff --git a/client/src/pages/EditProfilePage/EditProfilePage.tsx b/client/src/pages/EditProfilePage/EditProfilePage.tsx index e5e4399..b5b59d0 100644 --- a/client/src/pages/EditProfilePage/EditProfilePage.tsx +++ b/client/src/pages/EditProfilePage/EditProfilePage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import { useAppSelector, useAppDispatch } from "../../app/hooks"; import { fetchUserProfile, - // updateUserProfile, + updateUserProfile, } from "../../features/userProfile/userProfileSlice"; const EditProfilePage = () => { From bbf064db4b9add53789f49a570311da50a9b1100 Mon Sep 17 00:00:00 2001 From: Brok3Turtl3 Date: Thu, 28 Mar 2024 14:42:07 -0400 Subject: [PATCH 4/6] CHE-7 Basic functionality in place for edits and state updates --- .../src/pages/EditProfilePage/EditProfilePage.tsx | 13 ++++++++----- server/controllers/profileController.ts | 12 +++++------- server/routes/profileRoutes.ts | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/client/src/pages/EditProfilePage/EditProfilePage.tsx b/client/src/pages/EditProfilePage/EditProfilePage.tsx index b5b59d0..2ff77fa 100644 --- a/client/src/pages/EditProfilePage/EditProfilePage.tsx +++ b/client/src/pages/EditProfilePage/EditProfilePage.tsx @@ -8,7 +8,7 @@ import { const EditProfilePage = () => { const dispatch = useAppDispatch(); const { profile, status } = useAppSelector((state) => state.userProfile); - const userID = useAppSelector((state) => state.user.userData._id); + const userID = useAppSelector((state) => state.user.userData?._id); const [formData, setFormData] = useState({ fullName: "", @@ -17,7 +17,7 @@ const EditProfilePage = () => { }); useEffect(() => { - dispatch(fetchUserProfile(userID)); + if (userID) dispatch(fetchUserProfile(userID as string)); }, [dispatch]); useEffect(() => { @@ -25,6 +25,7 @@ const EditProfilePage = () => { setFormData({ fullName: profile.fullName || "", email: profile.email || "", + personalBio: profile.personalBio || "", }); } }, [profile]); @@ -39,12 +40,14 @@ const EditProfilePage = () => { const handleSubmit = (e) => { e.preventDefault(); - // Again, assuming you have a way to include the user ID - const userID = "currentUser_ID"; + if (!userID) { + console.error("UserID is undefined."); + return; + } dispatch(updateUserProfile({ ...formData, userID })); }; - if (status === "loading") { + if (status === "loading" || !userID) { return
Loading...
; } diff --git a/server/controllers/profileController.ts b/server/controllers/profileController.ts index 81ace0c..af31c69 100644 --- a/server/controllers/profileController.ts +++ b/server/controllers/profileController.ts @@ -78,7 +78,7 @@ const createProfile = async ( } }; -// ENDPOINT PATCH api/profiles/:UserID +// ENDPOINT PUT api/profiles/:UserID // PURPOSE Update an existing profile // ACCESS Private const updateProfile = async ( @@ -87,13 +87,11 @@ const updateProfile = async ( next: NextFunction ) => { const { userID } = req.params; - const { firstName, lastName, bio, job, socials } = req.body; + const { fullName, email, personalBio } = req.body; const newProfile = { - firstName, - lastName, - bio, - job, - socials, + fullName, + email, + personalBio, }; try { diff --git a/server/routes/profileRoutes.ts b/server/routes/profileRoutes.ts index 5b843d2..802c050 100644 --- a/server/routes/profileRoutes.ts +++ b/server/routes/profileRoutes.ts @@ -10,7 +10,7 @@ import { const router = express.Router(); router.post("/", createProfile); -router.patch("/:userID", updateProfile); +router.put("/:userID", updateProfile); router.get("/:userID", getProfileById); router.get("/", getAllProfiles); From 09877fd359403b6c82e20d0611468def77fc2cdc Mon Sep 17 00:00:00 2001 From: Brok3Turtl3 Date: Fri, 29 Mar 2024 08:00:18 -0400 Subject: [PATCH 5/6] CHE-7 Updated test to reflect new button text and added event typing for handlers --- client/src/components/Banner/Banner.test.tsx | 2 +- client/src/pages/EditProfilePage/EditProfilePage.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/Banner/Banner.test.tsx b/client/src/components/Banner/Banner.test.tsx index f2ed51a..da3da40 100644 --- a/client/src/components/Banner/Banner.test.tsx +++ b/client/src/components/Banner/Banner.test.tsx @@ -62,7 +62,7 @@ describe("Banner Component", () => { const optionsButton = screen.getByRole("button", { name: "Options" }); fireEvent.click(optionsButton); - const profileOption = screen.getByText("Go to Profile"); + const profileOption = screen.getByText("Edit Profile"); const logoutOption = screen.getByText("Logout"); expect(profileOption).toBeInTheDocument(); diff --git a/client/src/pages/EditProfilePage/EditProfilePage.tsx b/client/src/pages/EditProfilePage/EditProfilePage.tsx index 2ff77fa..aee251a 100644 --- a/client/src/pages/EditProfilePage/EditProfilePage.tsx +++ b/client/src/pages/EditProfilePage/EditProfilePage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, ChangeEvent, FormEvent } from "react"; import { useAppSelector, useAppDispatch } from "../../app/hooks"; import { fetchUserProfile, @@ -30,7 +30,7 @@ const EditProfilePage = () => { } }, [profile]); - const handleChange = (e) => { + const handleChange = (e: ChangeEvent) => { const { name, value } = e.target; setFormData((prevFormData) => ({ ...prevFormData, @@ -38,7 +38,7 @@ const EditProfilePage = () => { })); }; - const handleSubmit = (e) => { + const handleSubmit = (e: FormEvent) => { e.preventDefault(); if (!userID) { console.error("UserID is undefined."); From 3d4a5e1468f4480ed035528e0da3f7f9c8efb7bf Mon Sep 17 00:00:00 2001 From: Brok3Turtl3 Date: Fri, 29 Mar 2024 08:07:35 -0400 Subject: [PATCH 6/6] CHE-7 Updated Banner button test for editProfile routing --- client/src/components/Banner/Banner.test.tsx | 4 ++-- client/src/pages/Profiles/Profiles.tsx | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/components/Banner/Banner.test.tsx b/client/src/components/Banner/Banner.test.tsx index da3da40..7d7e4dd 100644 --- a/client/src/components/Banner/Banner.test.tsx +++ b/client/src/components/Banner/Banner.test.tsx @@ -74,10 +74,10 @@ describe("Banner Component", () => { const optionsButton = screen.getByRole("button", { name: "Options" }); fireEvent.click(optionsButton); - const profileOption = screen.getByText("Go to Profile"); + const profileOption = screen.getByText("Edit Profile"); fireEvent.click(profileOption); - expect(mockNavigate).toHaveBeenCalledWith("profile"); + expect(mockNavigate).toHaveBeenCalledWith("editProfile"); }); it("handles logout on clicking Logout", () => { diff --git a/client/src/pages/Profiles/Profiles.tsx b/client/src/pages/Profiles/Profiles.tsx index 916dae4..9795e37 100644 --- a/client/src/pages/Profiles/Profiles.tsx +++ b/client/src/pages/Profiles/Profiles.tsx @@ -17,8 +17,9 @@ const Profiles = (): JSX.Element => {

PROFILES

- {profiles.map((profile) => ( - + {/* TODO Look at better key for this */} + {profiles.map((profile, index) => ( + ))}