Skip to content

Commit

Permalink
Merge pull request #81 from Code-Hammers/CHE-2/story/Create-Profile-Page
Browse files Browse the repository at this point in the history
[CHE-2] Create Profile Page
  • Loading branch information
brok3turtl3 authored Apr 21, 2024
2 parents d7eb74c + d55720a commit 16bdb42
Show file tree
Hide file tree
Showing 26 changed files with 1,194 additions and 215 deletions.
File renamed without changes.
4 changes: 3 additions & 1 deletion client/src/AuthenticatedApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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";

Expand Down Expand Up @@ -41,7 +42,8 @@ const AuthenticatedApp = () => {
<Routes>
<Route path="main" element={<MainPage />} />
<Route path="/profiles" element={<Profiles />} />
<Route path="/profile" element={<Profile />} />
<Route path="/profile/:userId" element={<Profile />} />
<Route path="/editProfile" element={<EditProfilePage />} />
<Route path="/forums" element={<Forums />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/Banner/Banner.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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", () => {
Expand Down
8 changes: 4 additions & 4 deletions client/src/components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const Banner = (): JSX.Element => {
//TODO CLEAR ALL STATE
};

const goToProfile = () => {
navigate("profile");
const goToEditProfile = () => {
navigate("editProfile");
setShowDropdown(false);
};
return (
Expand All @@ -40,9 +40,9 @@ const Banner = (): JSX.Element => {
<a
href="#!"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
onClick={goToProfile}
onClick={goToEditProfile}
>
Go to Profile
Edit Profile
</a>
<a
href="#!"
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ const Header = (): JSX.Element => {
href="#!"
className="block px-4 py-2 text-sm text-white hover:bg-gray-800"
onClick={() => {
navigate("/app/profile");
navigate("/app/editProfile");
setShowDropdown(false);
}}
>
Go to Profile
Edit Profile
</a>
<a
href="#!"
Expand Down
16 changes: 8 additions & 8 deletions client/src/components/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,42 +35,42 @@ const Login: React.FC = () => {
};

return (
<div className="w-full max-w-xs">
<div className="w-full max-w-md">
<form
className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
className="bg-gradient-to-r from-gray-700 via-gray-800 to-gray-900 shadow-lg rounded px-8 pt-6 pb-8 mb-4"
onSubmit={handleSubmit}
>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
className="block text-gray-300 text-sm font-bold mb-2"
htmlFor="email"
>
Email
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
className="appearance-none border-2 border-gray-700 rounded w-full py-2 px-3 text-gray-300 leading-tight focus:outline-none focus:border-blue-500"
id="email"
name="email"
type="email"
value={email}
placeholder="Email"
placeholder="Enter your Email"
onChange={handleChange}
/>
</div>
<div className="mb-6">
<label
className="block text-gray-700 text-sm font-bold mb-2"
className="block text-gray-300 text-sm font-bold mb-2"
htmlFor="password"
>
Password
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
className="appearance-none border-2 border-gray-700 rounded w-full py-2 px-3 text-gray-300 mb-3 leading-tight focus:outline-none focus:border-blue-500"
id="password"
name="password"
type="password"
value={password}
placeholder="******************"
placeholder="Enter your password"
onChange={handleChange}
/>
</div>
Expand Down
15 changes: 10 additions & 5 deletions client/src/components/ProfileThumb/ProfileThumb.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import React from "react";
import { useAppDispatch } from "../../app/hooks";
import { IProfile } from "../../../types/profile";

interface ProfileThumbProps {
profile: IProfile;
}

const ProfileThumb = ({ profile }: ProfileThumbProps): JSX.Element => {
const dispatch = useAppDispatch();
const defaultImage = "https://picsum.photos/200";

return (
<div className="min-h-screen bg-gray-100 flex flex-col items-center justify-center">
<h1 className="text-4xl font-extrabold mb-4">{profile.firstName}</h1>
<h2 className="text-4xl font-extrabold mb-4">{profile.bio}</h2>
<div className="bg-gradient-to-r from-gray-700 via-gray-800 to-gray-900 text-white p-4 rounded-lg flex flex-col items-center justify-center h-64 w-64">
<img
src={profile.profilePhoto || defaultImage}
alt={profile.fullName || "Default Profile"}
className="rounded-full h-24 w-24 object-cover mb-4"
/>

<h1 className="text-xl font-bold mb-2">{profile.fullName}</h1>
<h2 className="text-md mb-2">{profile.personalBio}</h2>
</div>
);
};
Expand Down
70 changes: 69 additions & 1 deletion client/src/features/userProfile/userProfileSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -31,6 +31,52 @@ export const fetchUserProfile = createAsyncThunk(
}
);

export const updateUserProfile = createAsyncThunk(
"profile/updateUserProfile",
async (
{ userID, ...updateData }: Partial<IProfile> & { 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);
}
}
);

export const uploadProfilePicture = createAsyncThunk(
"profile/uploadProfilePicture",
async (
{ formData, userID }: { formData: FormData; userID: string },
thunkAPI
) => {
try {
const response = await axios.post(
`/api/images/profile-picture/${userID}`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
return response.data;
} catch (error) {
let errorMessage = "An error occurred during profile picture upload";
if (axios.isAxiosError(error)) {
errorMessage = error.response?.data || errorMessage;
}
return thunkAPI.rejectWithValue(errorMessage);
}
}
);

const userProfileSlice = createSlice({
name: "profile",
initialState,
Expand All @@ -53,6 +99,28 @@ 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;
state.status = "idle";
})
.addCase(updateUserProfile.rejected, (state, action) => {
state.status = "failed";
state.error = action.payload as string;
})
.addCase(uploadProfilePicture.pending, (state) => {
state.status = "updating";
})
.addCase(uploadProfilePicture.fulfilled, (state, action) => {
state.profile = action.payload;
state.status = "idle";
})
.addCase(uploadProfilePicture.rejected, (state, action) => {
state.status = "failed";
state.error = action.payload as string;
});
},
});
Expand Down
Loading

0 comments on commit 16bdb42

Please sign in to comment.