Skip to content

Commit 16ffbb1

Browse files
authored
Merge pull request #71 from Presson-coder/presson_view_profile
Presson view profile Page and Side Bar
2 parents 491f3a1 + 141f84c commit 16ffbb1

23 files changed

+1424
-16
lines changed

frontend/src/App.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function App() {
1919
<Route path="/signup" element={<SignUpView />} />
2020
<Route path="/notifications" element={<NotificationsPage />} />
2121
<Route path="/messages" element={<MessagesPage />} />
22-
<Route path="/profile/:username" element={<ProfilesPage />} />
22+
<Route path="/profile/:id" element={<ProfilesPage />} />
2323
</Routes>
2424
</div>
2525
</>

frontend/src/components/Home/SideBar.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
11
import React from "react";
22
import { images } from "../../constants";
33
import { Link } from "react-router-dom";
4+
import useFetchProfileDetails from "../../hooks/profile/useFetchProfileDetails";
5+
import SkeletonLoader from "../Profile/Loaders/HomeProfileSkeleton";
46

57
const SideBar = () => {
6-
const username = "John Doe";
7-
const bio = "Python developer based in Bulawayo";
8-
const numberOfFollowers = 34;
9-
const numberOfFollowing = 20;
8+
const id = 6;
9+
const { isLoading, userProfileData, error, fetchProfileDetails } =useFetchProfileDetails(id)
10+
11+
const userFirstName = userProfileData?.first_name;
12+
const userLastName = userProfileData?.last_name;
13+
const realname = `${userFirstName} ${userLastName}`;
14+
const userProfilePicture = userProfileData?.profile_picture;
15+
const numberOfFollowers = userProfileData?.followers.length;
16+
const numberOfFollowing = userProfileData?.follows.length;
17+
const username = userProfileData?.user;
18+
19+
if(isLoading){
20+
return <SkeletonLoader/>
21+
}
22+
1023
return (
1124
<div className="w-full border-2 border-gray-100 h-fit">
1225
<div className="w-full relative h-[16rem] border-gray-100 border-b-2">
1326
<div className="bg-primary-500 w-full h-[8rem]"></div>
1427
<div className="w-full absolute top-[11rem] flex flex-col justify-center">
1528
<h1 className="text-xl font-bold text-center">{username}</h1>
16-
<p className="text-base font-normal text-center">{bio}</p>
1729
</div>
1830
<div className="md:w-[6rem] md:h-[6rem] h-[3rem] w-[3rem] rounded-full absolute top-20 left-1/2 transform -translate-x-1/2 lg:left-[2rem] xl:left-[6rem] md:translate-x-0 border-4 border-white flex items-center justify-center">
1931
<img
20-
src={images.profilePicture}
32+
src={userProfilePicture || images.profilePicture}
2133
alt="profile"
2234
className="object-cover w-full h-full rounded-full"
2335
/>
@@ -37,7 +49,7 @@ const SideBar = () => {
3749
</div>
3850
<div className="w-full p-4 flex justify-center">
3951
<a
40-
href={`/profile/:username${username}`}
52+
href={`/profile/${id}`}
4153
className="text-base font-normal text-center text-primary-400"
4254
>
4355
View Profile
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from "react";
2+
import useFetchProfileDetails from "../../hooks/profile/useFetchProfileDetails";
3+
import FollowerSkeletonLoader from "./Loaders/FollowerSkeletonLoader";
4+
import { useNavigate } from "react-router-dom";
5+
import Button from "../common/Button";
6+
import { FaUser } from "react-icons/fa";
7+
8+
interface FollowersProps {
9+
id: number;
10+
}
11+
12+
const Followers: React.FC<FollowersProps> = ({ id }) => {
13+
const navigate = useNavigate();
14+
15+
const handleViewProfile = (url: string) => {
16+
const match = url.match(/\/profiles\/(\d+)\//);
17+
if (match) {
18+
const id = match[1];
19+
navigate(`/profile/${id}`);
20+
}
21+
};
22+
const { isLoading, userProfileData, error } = useFetchProfileDetails(id);
23+
24+
if (isLoading) {
25+
return <FollowerSkeletonLoader />;
26+
}
27+
28+
if (error) {
29+
return <div className="text-red-500 p-4 rounded-xl">Error: {error}</div>;
30+
}
31+
32+
33+
return (
34+
<div>
35+
<h2 className="text-2xl font-bold mb-4">Followers</h2>
36+
<ul className="list-none p-0 w-full flex flex-wrap gap-2">
37+
{userProfileData?.followers?.map((follower) => (
38+
<li
39+
key={follower.url}
40+
className="bg-gray-100 p-2 mb-2 border rounded flex w-3/4 md:w-1/3 items-center justify-between"
41+
>
42+
<span className="flex gap-2 items-center">
43+
<FaUser size={17} color="red"/>
44+
<p className="text-black">{follower.username} </p>
45+
</span>
46+
<Button
47+
onClick={() => handleViewProfile(follower.url)}
48+
>View</Button>
49+
</li>
50+
)) || <p>No followers found.</p>}
51+
</ul>
52+
</div>
53+
);
54+
};
55+
56+
export default Followers;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from "react";
2+
import useFetchProfileDetails from "../../hooks/profile/useFetchProfileDetails";
3+
import FollowerSkeletonLoader from "./Loaders/FollowerSkeletonLoader";
4+
import { useNavigate } from "react-router-dom";
5+
import Button from "../common/Button";
6+
import { FaUserCircle } from "react-icons/fa";
7+
8+
interface FollowingProps {
9+
id: number;
10+
}
11+
12+
const Following: React.FC<FollowingProps> = ({ id }) => {
13+
const navigate = useNavigate();
14+
const handleViewProfile = (url: string) => {
15+
const match = url.match(/\/profiles\/(\d+)\//);
16+
if (match) {
17+
const id = match[1];
18+
navigate(`/profile/${id}`);
19+
}
20+
};
21+
const { isLoading, userProfileData, error } = useFetchProfileDetails(id);
22+
23+
if (isLoading) {
24+
return <FollowerSkeletonLoader />;
25+
}
26+
27+
if (error) {
28+
return <div className="text-red-500">Error: {error}</div>;
29+
}
30+
31+
return (
32+
<div>
33+
<h2 className="text-2xl font-bold mb-4">Following</h2>
34+
<ul className="list-none p-0 w-full flex flex-wrap gap-2">
35+
{userProfileData?.follows?.map((following) => (
36+
<li
37+
key={following.url}
38+
className="bg-gray-100 p-2 mb-2 border rounded flex w-3/4 md:w-1/3 items-center justify-between"
39+
>
40+
<span className="flex gap-2 items-center">
41+
<FaUserCircle size={20} color="red" />
42+
<p className="text-black align-middle leading-none my-0 py-0">{following.username}</p>
43+
</span>
44+
<Button
45+
onClick={() => handleViewProfile(following.url)}
46+
>
47+
View
48+
</Button>
49+
</li>
50+
)) || <p>No following found.</p>}
51+
</ul>
52+
</div>
53+
);
54+
};
55+
56+
export default Following;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from "react";
2+
3+
const FollowerSkeletonLoader = () => {
4+
return (
5+
<li className="bg-gray-100 p-2 mb-2 border rounded animate-pulse">
6+
<div className="h-6 bg-gray-300 rounded w-3/4 mb-2"></div>
7+
<div className="h-4 bg-gray-300 rounded w-1/2"></div>
8+
</li>
9+
);
10+
};
11+
12+
export default FollowerSkeletonLoader;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from "react";
2+
3+
const SkeletonLoader = () => {
4+
return (
5+
<div className="w-full border-2 border-gray-100 h-fit animate-pulse">
6+
<div className="w-full relative h-[16rem] border-gray-100 border-b-2">
7+
<div className="bg-gray-300 w-full h-[8rem]"></div>
8+
<div className="w-full absolute top-[11rem] flex flex-col justify-center">
9+
<div className="bg-gray-300 h-6 w-1/2 mx-auto mb-2"></div>
10+
</div>
11+
<div className="md:w-[6rem] md:h-[6rem] h-[3rem] w-[3rem] rounded-full absolute top-20 left-1/2 transform -translate-x-1/2 lg:left-[2rem] xl:left-[6rem] md:translate-x-0 border-4 border-white flex items-center justify-center">
12+
<div className="bg-gray-300 w-full h-full rounded-full"></div>
13+
</div>
14+
</div>
15+
<div className="w-full p-4 border-gray-100 border-b-2">
16+
<div className="bg-gray-300 h-6 w-1/3 mx-auto mb-2"></div>
17+
<div className="bg-gray-300 h-4 w-1/4 mx-auto"></div>
18+
</div>
19+
<div className="w-full p-4 border-gray-100 border-b-2">
20+
<div className="bg-gray-300 h-6 w-1/3 mx-auto mb-2"></div>
21+
<div className="bg-gray-300 h-4 w-1/4 mx-auto"></div>
22+
</div>
23+
<div className="w-full p-4 flex justify-center">
24+
<div className="bg-gray-300 h-4 w-1/4"></div>
25+
</div>
26+
</div>
27+
);
28+
};
29+
30+
export default SkeletonLoader;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from "react";
2+
3+
const PostsSkeletonLoader = () => {
4+
return (
5+
<div className="mb-4 animate-pulse">
6+
<h3 className="text-xl font-semibold mb-2 bg-gray-300 h-6 w-1/3"></h3>
7+
<ul className="list-none p-0">
8+
{Array.from({ length: 3 }).map((_, index) => (
9+
<li
10+
className="flex bg-white shadow-lg rounded-lg mx-4 md:mx-auto my-4 max-w-md md:max-w-2xl"
11+
key={index}
12+
>
13+
<div className="flex items-start px-4 py-6">
14+
<div className="w-12 h-12 rounded-full bg-gray-300 mr-4"></div>
15+
<div className="flex-1">
16+
<div className="flex items-center justify-between mb-2">
17+
<div className="bg-gray-300 h-4 w-1/3"></div>
18+
<div className="bg-gray-300 h-4 w-1/4"></div>
19+
</div>
20+
<div className="bg-gray-300 h-4 w-full mb-2"></div>
21+
<div className="bg-gray-300 h-4 w-5/6"></div>
22+
<div className="mt-4 flex items-center">
23+
<div className="bg-gray-300 h-4 w-6 mr-2"></div>
24+
<div className="bg-gray-300 h-4 w-6 mr-2"></div>
25+
<div className="bg-gray-300 h-4 w-6"></div>
26+
</div>
27+
</div>
28+
</div>
29+
</li>
30+
))}
31+
</ul>
32+
</div>
33+
);
34+
};
35+
36+
export default PostsSkeletonLoader;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react'
2+
3+
const ProfileSkeletonLoader = () => {
4+
return (
5+
<div>
6+
<div className="animate-pulse">
7+
<div className="flex items-center border-b pb-4 mb-4">
8+
<div className="flex">
9+
<div className="w-20 h-20 flex justify-center items-center rounded-full bg-gray-300 mr-4"></div>
10+
</div>
11+
<div className="h-8 bg-gray-300 rounded w-1/2"></div>
12+
</div>
13+
</div>
14+
</div>
15+
);
16+
}
17+
18+
export default ProfileSkeletonLoader
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React from "react";
2+
import useFetchProfileDetails from "../../hooks/profile/useFetchProfileDetails";
3+
import useFetchProfilePosts from "../../hooks/profile/useFetchPosts";
4+
import PostsSkeletonLoader from "./Loaders/PostsSkeleton";
5+
6+
interface PostsProps {
7+
id: number;
8+
}
9+
10+
const Posts: React.FC<PostsProps> = ({ id }) => {
11+
const {
12+
userProfileData,
13+
isLoading: profileLoading,
14+
error: profileError,
15+
} = useFetchProfileDetails(id);
16+
const {
17+
posts,
18+
isLoading: postsLoading,
19+
error: postsError,
20+
} = useFetchProfilePosts(userProfileData?.url || "");
21+
22+
const profilePicture = userProfileData?.profile_picture;
23+
24+
if (profileLoading || postsLoading) {
25+
return <PostsSkeletonLoader />;
26+
}
27+
28+
if (profileError || postsError) {
29+
return <div>Error: {profileError || postsError}</div>;
30+
}
31+
32+
return (
33+
<div className="mb-4">
34+
<h2 className="text-2xl font-bold mb-4">Posts</h2>
35+
<ul className="list-none p-0 ">
36+
{posts?.map((post) => (
37+
<li
38+
className="flex bg-white hover:shadow-xl hover:scale-105 duration-500 shadow-lg rounded-lg mx-4 md:mx-auto my-4 max-w-md md:max-w-2xl"
39+
key={post.id}
40+
>
41+
<div className="flex flex-wrap items-start px-4 py-6">
42+
<img
43+
className="w-12 h-12 rounded-full object-cover mr-4 shadow"
44+
src={
45+
profilePicture ||
46+
"https://images.unsplash.com/photo-1542156822-6924d1a71ace?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60"
47+
}
48+
alt="avatar"
49+
/>
50+
<div>
51+
<div className="flex w-full items-center gap-12 md:gap-24">
52+
<h2 className="text-lg text-start font-semibold text-gray-900 -mt-1">
53+
{userProfileData?.user}
54+
</h2>
55+
<p className="text-xs text-end md:text-sm text-gray-700">
56+
{new Date(post.created_at).toLocaleString()}
57+
</p>
58+
</div>
59+
<p className="mt-3 text-gray-700 text-sm">{post.body}</p>
60+
<div className="mt-4 flex items-center">
61+
<div className="flex text-gray-700 text-sm mr-3">
62+
<svg
63+
fill="none"
64+
viewBox="0 0 24 24"
65+
className="w-4 h-4 mr-1"
66+
stroke="currentColor"
67+
>
68+
<path
69+
strokeLinecap="round"
70+
strokeLinejoin="round"
71+
strokeWidth="2"
72+
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
73+
/>
74+
</svg>
75+
<span>{post.likes_count}</span>
76+
</div>
77+
<div className="flex text-gray-700 text-sm mr-8">
78+
<svg
79+
fill="none"
80+
viewBox="0 0 24 24"
81+
className="w-4 h-4 mr-1"
82+
stroke="currentColor"
83+
>
84+
<path
85+
strokeLinecap="round"
86+
strokeLinejoin="round"
87+
strokeWidth="2"
88+
d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"
89+
/>
90+
</svg>
91+
<span>Comments</span>
92+
</div>
93+
<div className="flex text-gray-700 text-sm mr-4">
94+
<svg
95+
fill="none"
96+
viewBox="0 0 24 24"
97+
className="w-4 h-4 mr-1"
98+
stroke="currentColor"
99+
>
100+
<path
101+
strokeLinecap="round"
102+
strokeLinejoin="round"
103+
strokeWidth="2"
104+
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
105+
/>
106+
</svg>
107+
<span>Share</span>
108+
</div>
109+
</div>
110+
</div>
111+
</div>
112+
</li>
113+
)) || <p>No posts available.</p>}
114+
</ul>
115+
</div>
116+
);
117+
};
118+
119+
export default Posts;

0 commit comments

Comments
 (0)