From 43f62ade90c15f6910374c4b847618ab96304c18 Mon Sep 17 00:00:00 2001 From: Nahlam4 Date: Tue, 11 Jun 2024 23:21:20 +0200 Subject: [PATCH 1/5] Implement User-Posts (let me have this one, i am done fo today) Multiple fetches = headache and sleepless nights --- .../Search/components/SearchService.tsx | 11 +- src/main/web/src/scenes/User/UserPost.tsx | 263 ++++++++++++++++++ src/main/web/src/scenes/User/index.css | 27 +- src/main/web/src/scenes/User/index.tsx | 161 ++++++++++- src/main/web/src/services/UserPageService.tsx | 13 +- 5 files changed, 454 insertions(+), 21 deletions(-) create mode 100644 src/main/web/src/scenes/User/UserPost.tsx diff --git a/src/main/web/src/scenes/Search/components/SearchService.tsx b/src/main/web/src/scenes/Search/components/SearchService.tsx index a4c28208..01715de8 100644 --- a/src/main/web/src/scenes/Search/components/SearchService.tsx +++ b/src/main/web/src/scenes/Search/components/SearchService.tsx @@ -12,8 +12,6 @@ import {Post} from "../../Home/components/post/Post"; import {UserModel} from "./models/UserModel"; - - interface SearchedPostsProps { sortOption: string; query: string | null; @@ -45,8 +43,13 @@ export const SearchService: React.FC = ({sortOption, query, }); if (response.ok) { const data = await response.json(); - setSearchResults(data); - setNotFound(false); + if (data.length === 0) { + setNotFound(true); + } else { + setSearchResults(data); + setNotFound(false); + } + } else { setNotFound(true); } diff --git a/src/main/web/src/scenes/User/UserPost.tsx b/src/main/web/src/scenes/User/UserPost.tsx new file mode 100644 index 00000000..7f629433 --- /dev/null +++ b/src/main/web/src/scenes/User/UserPost.tsx @@ -0,0 +1,263 @@ +import React, {useCallback, useEffect, useRef, useState} from 'react'; +import '../../scenes/Home/components/post/Post.css'; +import {Share} from "../../organisms/share/Share"; +import {Link, useLocation} from "react-router-dom"; +import {Report} from "../../organisms/report/Report"; +import {PostMenu} from "../../organisms/post-menu/PostMenu"; +import {Tag} from "../../atoms/Tag"; +import {handleLike} from '../../services/LikeService'; +import {timeDifference} from "../../services/TimeService"; +import {PostModel} from "../Home/components/post/models/PostModel"; +import {Interaction} from "../../organisms/interaction/Interaction"; +import {shortenDescription} from "../../services/DescriptionService"; +import {useMediaQuery} from "@mui/system"; +import config from "../../config/config"; +import {getJWT, getUserId} from "../../services/AuthService"; +import {sendReportToBackend} from "../../services/ReportService"; + + +export const UserPost: React.FC = (props: PostModel) => { + const { + id, + title, + description, + tags, + likeAmount, + commentAmount, + timestamp, + postImage, + accountId, + username + } = props; + + const matches: boolean = useMediaQuery('(max-width: 412px)') + const formattedTime: string = timeDifference(new Date(timestamp)); + const [comments] = useState(commentAmount); + const [menuOpen, setMenuOpen] = useState(false); + const [shareWindowOpen, setShareWindowOpen] = useState(false); + const currentPageURL: string = window.location.href; + const location = useLocation(); + const [shortDescription, setShortDescription] = useState(''); + //const [userId, setUserId] = useState(null); + const userSelfId: number | null = getUserId(); + const jwt: string | null = getJWT(); + const headersWithJwt = { + ...config.headers, + 'Authorization': jwt ? `Bearer ${jwt}` : '' + }; + + const [reportOpen, setReportOpen] = useState(false); + const [reportReason, setReportReason] = useState(''); + const [reportDescription, setReportDescription] = useState(''); + + const handleReportClick = (): void => { + setReportOpen(!reportOpen); + }; + + const handleReportSubmit = (): void => { + sendReportToBackend(reportReason, reportDescription, id, accountId, "post"); + setReportOpen(!reportOpen); + setReportReason(''); + setReportDescription(''); + }; + + const [likes, setLikes] = useState(likeAmount); + const [userLiked, setUserLiked] = useState(false); + const [heartClass, setHeartClass] = useState('heart-empty'); + + useEffect((): void => { + const userLikedPost: string | null = localStorage.getItem(`post_liked_${id}`); + if (userLikedPost) { + setUserLiked(true); + setHeartClass('heart-filled'); + } + }, [location, id]); + + useEffect((): void => { + if (description){ + setShortDescription(shortenDescription(description, postImage ? 190 : matches ? 190 : 280)); + } + else { + setShortDescription(description); + } + }, [description, postImage, matches]); + + const handleMenuClick = (): void => { + setMenuOpen(!menuOpen); + setShareWindowOpen(false); + }; + + const handleShareClick = (): void => { + setShareWindowOpen(!shareWindowOpen); + }; + + const handleSaveClick = async (): Promise => { + try { + const response: Response = await fetch(config.apiUrl + "saved-post", { + method: 'POST', + credentials: 'include', + body: JSON.stringify({ + postId: id, + userId: userSelfId, + }), + headers: headersWithJwt + }); + if (response.ok) { + console.log('Post has been saved!'); + alert('Post has been saved!'); + } else { + console.error('Error saving the post: ', response.statusText); + } + } catch (err) { + console.error('Error saving the post: ', err); + alert('Error saving the post'); + } + }; + + const handleUnsaveClick = async (): Promise => { + try { + const response: Response = await fetch(config.apiUrl + `saved-post`, { + method: 'DELETE', + credentials: 'include', + body: JSON.stringify({ + postId: id, + userId: userSelfId, + }), + headers: headersWithJwt + }); + if (response.ok) { + alert('Post has been unsaved!'); + } else { + console.error('Error unsaving the post: ', response.statusText); + alert('Error unsaving the post'); + } + } catch (err) { + console.error('Error unsaving the post: ', err); + } + }; + + const imageRef = useRef(null); + const [imageWidth, setImageWidth] = useState(); + const [marginLeft, setMarginLeft] = useState(); + const [width, setWidth] = useState(); + const [marginTop, setMarginTop] = useState(); + + const handleClose = useCallback((): void => { + setReportOpen(false); + }, [setReportOpen]); + + useEffect((): () => void => { + const updateMargins = (): void => { + if (matches) { + setWidth(postImage ? '240px' : '260px'); + setMarginLeft(tags ? '110px' : '0'); + setMarginTop(postImage ? '140px' : '5px'); + } else { + setWidth(postImage ? '360px' : '600px'); + setMarginLeft(postImage ? (imageWidth ? `${imageWidth + 20}px` : '280px') : '10px'); + setMarginTop('0'); + } + }; + updateMargins(); + const imageElement = imageRef.current; + const handleResize = (): void => { + if (imageElement) { + const computedStyle: CSSStyleDeclaration = window.getComputedStyle(imageElement); + const width: number = parseInt(computedStyle.getPropertyValue('width')); + setImageWidth(width); + updateMargins(); + } + }; + if (imageElement) { + handleResize(); + window.addEventListener('resize', handleResize); + } + return (): void => { + window.removeEventListener('resize', handleResize); + }; + }, [matches, postImage, imageWidth, tags]); + + useEffect(() => { + const handleEsc = (event: KeyboardEvent): void => { + if (event.key === 'Escape' && reportOpen) { + handleClose(); + } + }; + document.addEventListener('keydown', handleEsc); + return (): void => { + document.removeEventListener('keydown', handleEsc); + }; + }, [reportOpen, handleClose]); + + return ( +
+
+ + {postImage && Post} + + Menu dots +
+ +

+ {title ? shortenDescription(title, 50): title }

+ +
+ {tags && tags.slice(0, 3).map((tag: string, index: number) => ( + + ))} +
+ +

+ {postImage && shortDescription ? shortenDescription(shortDescription, 150) : shortDescription} +

+ +
+
+ {username} +
+  · {formattedTime} +
+
+
+ handleLike(id, "post", likes, setLikes, setUserLiked, setHeartClass)} + id={id} + isHomepage={true} + /> +
+
+ + {menuOpen && ( +
+ +
+ )} + {shareWindowOpen && ( +
+ +
+ )} + {reportOpen && ( + + )} +
+ ); +}; \ No newline at end of file diff --git a/src/main/web/src/scenes/User/index.css b/src/main/web/src/scenes/User/index.css index 48e32699..59fb3ac9 100644 --- a/src/main/web/src/scenes/User/index.css +++ b/src/main/web/src/scenes/User/index.css @@ -59,7 +59,6 @@ .follow-button { font-size: var(--headline-font-size); - padding: 10px; color: white; align-items: center; @@ -68,3 +67,29 @@ width: auto; } +.user-not-found { + font-size: var(--headline-font-size); + padding: 10px; + color: white; + border-radius: 5px; +} + +.user-posts { + width: 780px; + display: grid; + grid-template-columns: 1fr; + grid-gap: 10px; + position: relative; +} + +@media (max-width: 412px) { + .user-posts { + margin-left: 20px; + margin-top: -5px; + } +} + +.user-posts-header { + width: 780px; + padding: 20px; +} \ No newline at end of file diff --git a/src/main/web/src/scenes/User/index.tsx b/src/main/web/src/scenes/User/index.tsx index 9af27c96..84cf2762 100644 --- a/src/main/web/src/scenes/User/index.tsx +++ b/src/main/web/src/scenes/User/index.tsx @@ -1,10 +1,14 @@ -import React, {useEffect, useState} from "react"; +import React, { useEffect, useState} from "react"; import "./index.css"; import {Header} from "../../organisms/header/Header"; import {Footer} from "../../organisms/footer/Footer"; import config from "../../config/config"; import {getJWT, getUserId} from "../../services/AuthService"; import {getDefaultOrRandomPicture} from "../../atoms/Pictures/PicturesComponent"; +import Lottie from "lottie-react"; +import animationData from "../../assets/loading.json"; + +import {UserPost} from "./UserPost"; export const UserPage = () => { const searchParams: URLSearchParams = new URLSearchParams(window.location.search); @@ -13,50 +17,100 @@ export const UserPage = () => { const [userId, setUserId] = useState(null); const [username, setUserName] = useState(null); - const [picture, setPicture] = useState<{id: number | null; name: string | null; imageData: string | null}>({id: null, name: null, imageData: null}); + const [picture, setPicture] = useState<{ + id: number | null; + name: string | null; + imageData: string | null + }>({id: null, name: null, imageData: null}); + const [userPosts, setUserPosts] = useState([]); const [amountFollower, setAmountFollower] = useState(null); const [age, setAge] = useState(null); const [description, setDescription] = useState(null); const [course, setCourse] = useState(null); + const [loading, setLoading] = useState(true); + const [notFound, setNotFound] = useState(true); const jwt: string | null = getJWT(); const headersWithJwt = { ...config.headers, 'Authorization': jwt ? `Bearer ${jwt}` : '' }; - useEffect((): void => { - const fetchUserData = async (): Promise => { + useEffect(() => { + + const fetchUserData = async () => { + setUserPosts([]); + setLoading(true); + setNotFound(false); + try { - const response: Response = await fetch(config.apiUrl +`user/user-information/${id}`, { + const response = await fetch(config.apiUrl + `user/user-information/${id}`, { headers: config.headers }); if (response.ok) { const data = await response.json(); setUserId(data.userId); setUserName(data.username); - if(data.picture !== null){ + if (data.picture !== null) { setPicture(data.picture); } setAmountFollower(data.amountFollower); setAge(data.age); setDescription(data.description); setCourse(data.course); - console.log("successful fetching of userdata") + console.log("successful fetching of userdata"); + setLoading(false); + setNotFound(false); } else { console.log(new Error("Failed to fetch Userdata")); + setLoading(false); + setNotFound(true); } } catch (error) { console.error("Error fetching Data:", error); + setLoading(false); + setNotFound(true); } }; + fetchUserData(); - }, [id]); + + const fetchUserPosts = async () => { + try { + const response = await fetch(config.apiUrl + `post/user-posts/${userId}`, { + headers: headersWithJwt + }); + if (response.ok) { + const data = await response.json(); + if (data.length === 0) { + // handle empty posts + } else { + setUserPosts(data); + } + } else { + console.log(new Error("Failed to fetch User Posts")); + // setLoading(false); + // setNotFound(true); + // handle error + } + } catch (error) { + console.error("Error when retrieving the search data:", error) + // setLoading(false); + // setNotFound(true); + // handle error + } + }; + + fetchUserPosts(); + + + }, [id, userId, config.headers, config.apiUrl]); + const handleFollow = async () => { if (userId === null) return; try { - const response = await fetch(config.apiUrl +`friendship/follow-user`, { + const response = await fetch(config.apiUrl + `friendship/follow-user`, { method: 'POST', headers: headersWithJwt, body: JSON.stringify({ @@ -77,15 +131,85 @@ export const UserPage = () => { } } + + if (notFound) { + return ( +
+
+
+

This User doesn´t exist

+
+
+
+ ); + } + if (loading) { + return ( +
+
+
+
+
+ +
+
+
+
+
+ ); + } + + class FollowButton extends React.Component<{ userId: any, senderId: any, handleFollow: any }> { + render() { + let {userId, senderId, handleFollow} = this.props; + if (userId === senderId) { + return ( +
+ ); + } else { + return ( + + ); + } + } + } + + class DisplayPosts extends React.Component { + + render() { + return ( +
+ {userPosts.map(post => ( + + ))} +
+ ); + } + } + return (
-
+
{picture.id !== null && picture.imageData ? ( {"user"} ) : ( - {"random-image"} + {"random-image"} )}
@@ -104,20 +228,29 @@ export const UserPage = () => {
{age !== null ? ( Age: {age} - ): ( + ) : ( Age: Not set )}
{description !== null ? ( Description: {description} - ): ( + ) : ( Description: Not set )}
- + +
+ {userPosts.length >0 ? +
+
Posts by {username}:
+
+ +
+
:
}
); + }; diff --git a/src/main/web/src/services/UserPageService.tsx b/src/main/web/src/services/UserPageService.tsx index 96288afb..3aaef0fe 100644 --- a/src/main/web/src/services/UserPageService.tsx +++ b/src/main/web/src/services/UserPageService.tsx @@ -2,8 +2,17 @@ import config from "../config/config"; export const getUserIdByAccountId = async (accountId:number) => { + try { + const response = await fetch(config.apiUrl +`account/get-user-id/${accountId}`) - const response = await fetch(config.apiUrl +`account/get-user-id/${accountId}`); - return (response.json()); + if (response.ok) { + return (response.json()); + } else { + console.log(new Error("Failed to fetch User Id")); + } + } catch (error) { + console.error("Error when retrieving the user Id,"+ error) + + } }; From 622a62a103c841b4ccd9e6407547e7dd6fe21be0 Mon Sep 17 00:00:00 2001 From: Nahlam4 Date: Wed, 12 Jun 2024 09:37:31 +0200 Subject: [PATCH 2/5] Finish reloading problem for user page --- src/main/web/src/scenes/User/UserPost.tsx | 9 +- src/main/web/src/scenes/User/index.css | 7 ++ src/main/web/src/scenes/User/index.tsx | 121 ++++++++++++++-------- 3 files changed, 91 insertions(+), 46 deletions(-) diff --git a/src/main/web/src/scenes/User/UserPost.tsx b/src/main/web/src/scenes/User/UserPost.tsx index 7f629433..c4d64e50 100644 --- a/src/main/web/src/scenes/User/UserPost.tsx +++ b/src/main/web/src/scenes/User/UserPost.tsx @@ -38,7 +38,8 @@ export const UserPost: React.FC = (props: PostModel) => { const currentPageURL: string = window.location.href; const location = useLocation(); const [shortDescription, setShortDescription] = useState(''); - //const [userId, setUserId] = useState(null); + const searchParams: URLSearchParams = new URLSearchParams(window.location.search); + const userSelfid: string | null = searchParams.get('id'); const userSelfId: number | null = getUserId(); const jwt: string | null = getJWT(); const headersWithJwt = { @@ -193,7 +194,7 @@ export const UserPost: React.FC = (props: PostModel) => {
- {postImage && Post} + {postImage && Post} Menu dots @@ -213,9 +214,9 @@ export const UserPost: React.FC = (props: PostModel) => {

-
+ {username} -
+  · {formattedTime}
diff --git a/src/main/web/src/scenes/User/index.css b/src/main/web/src/scenes/User/index.css index 59fb3ac9..76af9ed4 100644 --- a/src/main/web/src/scenes/User/index.css +++ b/src/main/web/src/scenes/User/index.css @@ -74,6 +74,13 @@ border-radius: 5px; } +.user-post-component { + font-size: var(--headline-font-size); + padding: 10px; + color: white; + border-radius: 5px; +} + .user-posts { width: 780px; display: grid; diff --git a/src/main/web/src/scenes/User/index.tsx b/src/main/web/src/scenes/User/index.tsx index 84cf2762..39987bcf 100644 --- a/src/main/web/src/scenes/User/index.tsx +++ b/src/main/web/src/scenes/User/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState} from "react"; +import React, {useEffect, useState} from "react"; import "./index.css"; import {Header} from "../../organisms/header/Header"; import {Footer} from "../../organisms/footer/Footer"; @@ -7,7 +7,6 @@ import {getJWT, getUserId} from "../../services/AuthService"; import {getDefaultOrRandomPicture} from "../../atoms/Pictures/PicturesComponent"; import Lottie from "lottie-react"; import animationData from "../../assets/loading.json"; - import {UserPost} from "./UserPost"; export const UserPage = () => { @@ -29,6 +28,8 @@ export const UserPage = () => { const [course, setCourse] = useState(null); const [loading, setLoading] = useState(true); const [notFound, setNotFound] = useState(true); + const [postLoading, setPostsLoading] = useState(true); + const [postsNotFound, setPostsNotFound] = useState(false); const jwt: string | null = getJWT(); const headersWithJwt = { ...config.headers, @@ -36,11 +37,8 @@ export const UserPage = () => { }; useEffect(() => { - + setNotFound(false); const fetchUserData = async () => { - setUserPosts([]); - setLoading(true); - setNotFound(false); try { const response = await fetch(config.apiUrl + `user/user-information/${id}`, { @@ -58,23 +56,29 @@ export const UserPage = () => { setDescription(data.description); setCourse(data.course); console.log("successful fetching of userdata"); - setLoading(false); setNotFound(false); + setLoading(false); + } else { console.log(new Error("Failed to fetch Userdata")); - setLoading(false); setNotFound(true); + setLoading(false); + } } catch (error) { console.error("Error fetching Data:", error); - setLoading(false); setNotFound(true); + setLoading(false); + } }; fetchUserData(); const fetchUserPosts = async () => { + setPostsLoading(true); + setPostsNotFound(false); + try { const response = await fetch(config.apiUrl + `post/user-posts/${userId}`, { headers: headersWithJwt @@ -82,25 +86,29 @@ export const UserPage = () => { if (response.ok) { const data = await response.json(); if (data.length === 0) { - // handle empty posts + setPostsNotFound(true); } else { + console.log(("Successful fetching of user posts")); setUserPosts(data); + setPostsLoading(false); + setPostsNotFound(false); } + } else { console.log(new Error("Failed to fetch User Posts")); - // setLoading(false); - // setNotFound(true); - // handle error + setPostsLoading(false); + setPostsNotFound(true); + } } catch (error) { console.error("Error when retrieving the search data:", error) - // setLoading(false); - // setNotFound(true); + setPostsLoading(false); + setPostsNotFound(true); // handle error } }; - fetchUserPosts(); + fetchUserPosts(); }, [id, userId, config.headers, config.apiUrl]); @@ -159,6 +167,7 @@ export const UserPage = () => { ); } + class FollowButton extends React.Component<{ userId: any, senderId: any, handleFollow: any }> { render() { let {userId, senderId, handleFollow} = this.props; @@ -179,25 +188,54 @@ export const UserPage = () => { class DisplayPosts extends React.Component { render() { - return ( -
- {userPosts.map(post => ( - - ))} -
- ); + if (postsNotFound) { + return ( + +
+

This User doesn´t have Posts

+
+ ); + } + if (postLoading) { + return ( + +
+
+
+ +
+
+
+ + ); + } + else { + return ( +
+
Posts by {username}:
+
+
+ {userPosts.map(post => ( + + ))} +
+
+
+ ); + } + } } @@ -242,13 +280,12 @@ export const UserPage = () => {
- {userPosts.length >0 ? -
-
Posts by {username}:
-
- -
-
:
} + +
+
+ +
+
); From aaec52ec28ec1609a4b539be47c0d6771ef61302 Mon Sep 17 00:00:00 2001 From: Nahlam4 Date: Wed, 12 Jun 2024 10:43:15 +0200 Subject: [PATCH 3/5] further implementation of Follow-Button --- src/main/web/src/scenes/User/index.css | 11 ++ src/main/web/src/scenes/User/index.tsx | 145 +++++++++++++++++++++---- 2 files changed, 133 insertions(+), 23 deletions(-) diff --git a/src/main/web/src/scenes/User/index.css b/src/main/web/src/scenes/User/index.css index 76af9ed4..8229f1f3 100644 --- a/src/main/web/src/scenes/User/index.css +++ b/src/main/web/src/scenes/User/index.css @@ -58,6 +58,7 @@ } .follow-button { + cursor: pointer; font-size: var(--headline-font-size); padding: 10px; color: white; @@ -66,6 +67,16 @@ background-color: var(--red); width: auto; } +.unfollow-button { + cursor: pointer; + font-size: var(--headline-font-size); + padding: 10px; + color: white; + align-items: center; + border-radius: 5px; + background-color: var(--lighter-grey); + width: auto; +} .user-not-found { font-size: var(--headline-font-size); diff --git a/src/main/web/src/scenes/User/index.tsx b/src/main/web/src/scenes/User/index.tsx index 39987bcf..64bebeb5 100644 --- a/src/main/web/src/scenes/User/index.tsx +++ b/src/main/web/src/scenes/User/index.tsx @@ -28,6 +28,7 @@ export const UserPage = () => { const [course, setCourse] = useState(null); const [loading, setLoading] = useState(true); const [notFound, setNotFound] = useState(true); + const [follow, setFollow] = useState(true); const [postLoading, setPostsLoading] = useState(true); const [postsNotFound, setPostsNotFound] = useState(false); const jwt: string | null = getJWT(); @@ -114,28 +115,65 @@ export const UserPage = () => { }, [id, userId, config.headers, config.apiUrl]); - const handleFollow = async () => { + const handleFollow = async (following:boolean) => { if (userId === null) return; + isFollowing(); + if (following) { + try { - try { - const response = await fetch(config.apiUrl + `friendship/follow-user`, { - method: 'POST', - headers: headersWithJwt, - body: JSON.stringify({ - "requesterId": senderId, - "receiverId": id - }) - }); + const response = await fetch(config.apiUrl + `friendship/follow-user`, { + method: 'POST', + headers: headersWithJwt, + body: JSON.stringify({ + "requesterId": senderId, + "receiverId": id + }) + }); - if (response.ok) { - console.log("Successfully followed the user"); + if (response.ok) { + if (following) { + console.log("Successfully followed the user"); + setAmountFollower(prev => (prev !== null ? prev + 1 : 1)); + } else { + console.log("Successfully unfollowed the user"); + } - setAmountFollower(prev => (prev !== null ? prev + 1 : 1)); - } else { - console.error("Failed to follow the user"); + } else { + if (following) { + console.error("Failed to follow the user"); + } else { + console.error("Failed to unfollow the user"); + } + + } + } catch (error) { + if (following) { + console.error("Failed to follow the user " + error); + } else { + console.error("Failed to unfollow the user " + error); + } + } + } + else { + try { + + const response = await fetch(config.apiUrl + `friendship/unfollow-user`, { + method: 'POST', + headers: headersWithJwt, + body: JSON.stringify({ + "requesterId": senderId, + "receiverId": id + }) + }); + + if (response.ok) { + console.log("Successfully unfollowed the user"); + } else { + console.error("Failed to unfollow the user"); + } + } catch (error) { + console.error("Failed to unfollow the user " + error); } - } catch (error) { - console.error("Error following the user:", error); } } @@ -168,23 +206,84 @@ export const UserPage = () => { } - class FollowButton extends React.Component<{ userId: any, senderId: any, handleFollow: any }> { + class FollowButton extends React.Component<{ userId: any, senderId: any }> { render() { - let {userId, senderId, handleFollow} = this.props; + let {userId, senderId} = this.props; if (userId === senderId) { return (
); - } else { + } else if (follow) { return ( - ); + + } + return ( + + ); + } + } + const isFollowing = async () => { + + try { + const response = await fetch(config.apiUrl + `friendship/is-following-user`, { + method: 'POST', + headers: headersWithJwt, + body: JSON.stringify({ + "requesterId": senderId, + "receiverId": id + }) + }); + + if (response.ok) { + setFollow(true); + console.log("Successfully unfollowed the user"); + + } else { + setFollow(false); + console.error("Failed to follow the user"); + } + } catch (error) { + console.error("Failed to follow the user " + error); + } } + + if (notFound) { + return ( +
+
+
+

This User doesn´t exist

+
+
+
+ ); + } + if (loading) { + return ( +
+
+
+
+
+ +
+
+
+
+
+ ); + } + + class DisplayPosts extends React.Component { render() { @@ -277,7 +376,7 @@ export const UserPage = () => { Description: Not set )} - + From 6ccc3ac6c177302866bb0a82df53d0b5e80e91b8 Mon Sep 17 00:00:00 2001 From: Nahlam4 Date: Wed, 12 Jun 2024 20:48:06 +0200 Subject: [PATCH 4/5] final implementation of Follow-Button and reworking of User-Page --- src/main/web/src/scenes/User/index.css | 10 + src/main/web/src/scenes/User/index.tsx | 334 ++++++++++--------------- 2 files changed, 146 insertions(+), 198 deletions(-) diff --git a/src/main/web/src/scenes/User/index.css b/src/main/web/src/scenes/User/index.css index 8229f1f3..0197df7e 100644 --- a/src/main/web/src/scenes/User/index.css +++ b/src/main/web/src/scenes/User/index.css @@ -110,4 +110,14 @@ .user-posts-header { width: 780px; padding: 20px; +} + +.follow-loading-animation { + cursor: pointer; + font-size: var(--headline-font-size); + padding: 10px; + align-items: center; + border-radius: 5px; + + width: auto; } \ No newline at end of file diff --git a/src/main/web/src/scenes/User/index.tsx b/src/main/web/src/scenes/User/index.tsx index 64bebeb5..3a0fc8d0 100644 --- a/src/main/web/src/scenes/User/index.tsx +++ b/src/main/web/src/scenes/User/index.tsx @@ -13,13 +13,12 @@ export const UserPage = () => { const searchParams: URLSearchParams = new URLSearchParams(window.location.search); const id: string | null = searchParams.get('id'); const senderId: number | null = getUserId(); - const [userId, setUserId] = useState(null); const [username, setUserName] = useState(null); const [picture, setPicture] = useState<{ id: number | null; name: string | null; - imageData: string | null + imageData: string | null; }>({id: null, name: null, imageData: null}); const [userPosts, setUserPosts] = useState([]); const [amountFollower, setAmountFollower] = useState(null); @@ -27,8 +26,9 @@ export const UserPage = () => { const [description, setDescription] = useState(null); const [course, setCourse] = useState(null); const [loading, setLoading] = useState(true); - const [notFound, setNotFound] = useState(true); - const [follow, setFollow] = useState(true); + const [followLoading, setFollowLoading] = useState(true); + const [notFound, setNotFound] = useState(false); + const [follow, setFollow] = useState(false); const [postLoading, setPostsLoading] = useState(true); const [postsNotFound, setPostsNotFound] = useState(false); const jwt: string | null = getJWT(); @@ -38,9 +38,9 @@ export const UserPage = () => { }; useEffect(() => { + setFollowLoading(true); setNotFound(false); const fetchUserData = async () => { - try { const response = await fetch(config.apiUrl + `user/user-information/${id}`, { headers: config.headers @@ -56,21 +56,18 @@ export const UserPage = () => { setAge(data.age); setDescription(data.description); setCourse(data.course); - console.log("successful fetching of userdata"); + console.log("Successful fetching of userdata"); setNotFound(false); setLoading(false); - } else { - console.log(new Error("Failed to fetch Userdata")); + console.log(new Error("Failed to fetch userdata")); setNotFound(true); setLoading(false); - } } catch (error) { - console.error("Error fetching Data:", error); + console.error("Error fetching data:", error); setNotFound(true); setLoading(false); - } }; @@ -81,7 +78,7 @@ export const UserPage = () => { setPostsNotFound(false); try { - const response = await fetch(config.apiUrl + `post/user-posts/${userId}`, { + const response = await fetch(config.apiUrl + `post/user-posts/${id}`, { headers: headersWithJwt }); if (response.ok) { @@ -89,172 +86,173 @@ export const UserPage = () => { if (data.length === 0) { setPostsNotFound(true); } else { - console.log(("Successful fetching of user posts")); + console.log("Successful fetching of user posts"); setUserPosts(data); setPostsLoading(false); setPostsNotFound(false); } - } else { - console.log(new Error("Failed to fetch User Posts")); + console.log(new Error("Failed to fetch user posts")); setPostsLoading(false); setPostsNotFound(true); - } } catch (error) { - console.error("Error when retrieving the search data:", error) + console.error("Error when retrieving the search data:", error); setPostsLoading(false); setPostsNotFound(true); - // handle error } }; fetchUserPosts(); + const checkIfFollowing = async () => { + try { + const response = await fetch(config.apiUrl + `friendship/is-following-user`, { + method: 'POST', + headers: headersWithJwt, + body: JSON.stringify({ + requesterId: senderId, + receiverId: id + }) + }); - }, [id, userId, config.headers, config.apiUrl]); - + if (response.ok) { + const isFollowing = await response.json(); + setFollow(isFollowing); + setFollowLoading(false); + console.log("Successfully checked following status"); + } else { + setFollow(false); + setFollowLoading(false); + console.error("Failed to check following status"); + } + } catch (error) { + setFollow(false); + setFollowLoading(false); + console.error("Failed to check following status: ", error); + } + }; + checkIfFollowing(); + }, [id, senderId]); - const handleFollow = async (following:boolean) => { + const handleFollow = async (following: boolean) => { if (userId === null) return; - isFollowing(); - if (following) { + if (!following) { try { - const response = await fetch(config.apiUrl + `friendship/follow-user`, { method: 'POST', headers: headersWithJwt, body: JSON.stringify({ - "requesterId": senderId, - "receiverId": id + requesterId: senderId, + receiverId: id }) }); if (response.ok) { - if (following) { - console.log("Successfully followed the user"); - setAmountFollower(prev => (prev !== null ? prev + 1 : 1)); - } else { - console.log("Successfully unfollowed the user"); - } - + console.log("Successfully followed the user"); + setAmountFollower(prev => (prev !== null ? prev + 1 : 1)); + setFollow(true); } else { - if (following) { - console.error("Failed to follow the user"); - } else { - console.error("Failed to unfollow the user"); - } - + console.error("Failed to follow the user"); } } catch (error) { - if (following) { - console.error("Failed to follow the user " + error); - } else { - console.error("Failed to unfollow the user " + error); - } + console.error("Failed to follow the user: ", error); } - } - else { + } else { try { - const response = await fetch(config.apiUrl + `friendship/unfollow-user`, { method: 'POST', headers: headersWithJwt, body: JSON.stringify({ - "requesterId": senderId, - "receiverId": id + requesterId: senderId, + receiverId: id }) }); if (response.ok) { - console.log("Successfully unfollowed the user"); + console.log("Successfully unfollowed the user"); + setAmountFollower(prev => (prev !== null ? prev - 1 : 0)); + setFollow(false); } else { - console.error("Failed to unfollow the user"); + console.error("Failed to unfollow the user"); } } catch (error) { - console.error("Failed to unfollow the user " + error); + console.error("Failed to unfollow the user: ", error); } } - } + }; + const FollowButton = () => { + if (userId === senderId) { + return
; + } else if (followLoading) { + return ( +
+
+ +
+
+ ); + } else if (follow) { + return ( + + ); + } else { + return ( + + ); + } + }; - if (notFound) { - return ( -
-
+ const DisplayPosts = () => { + if (postsNotFound) { + return (
-

This User doesn´t exist

+

This User doesn´t have Posts

-
-
- ); - } - if (loading) { - return ( -
-
-
+ ); + } + if (postLoading) { + return ( +
-
-
- ); - } - - - class FollowButton extends React.Component<{ userId: any, senderId: any }> { - render() { - let {userId, senderId} = this.props; - if (userId === senderId) { - return ( -
- ); - } else if (follow) { - return ( - - ); - - } + ); + } else { return ( - +
+
Posts by {username}:
+
+
+ {userPosts.map(post => ( + + ))} +
+
+
); } - } - const isFollowing = async () => { - - try { - const response = await fetch(config.apiUrl + `friendship/is-following-user`, { - method: 'POST', - headers: headersWithJwt, - body: JSON.stringify({ - "requesterId": senderId, - "receiverId": id - }) - }); - - if (response.ok) { - setFollow(true); - console.log("Successfully unfollowed the user"); - - } else { - setFollow(false); - console.error("Failed to follow the user"); - - } - } catch (error) { - console.error("Failed to follow the user " + error); - - } - } - + }; if (notFound) { return ( @@ -267,6 +265,7 @@ export const UserPage = () => {
); } + if (loading) { return (
@@ -283,70 +282,15 @@ export const UserPage = () => { ); } - - class DisplayPosts extends React.Component { - - render() { - if (postsNotFound) { - return ( - -
-

This User doesn´t have Posts

-
- ); - } - if (postLoading) { - return ( - -
-
-
- -
-
-
- - ); - } - else { - return ( -
-
Posts by {username}:
-
-
- {userPosts.map(post => ( - - ))} -
-
-
- ); - } - - } - } - return (
{picture.id !== null && picture.imageData ? ( - {"user"} + user ) : ( - {"random-image"} + random-image )}
@@ -355,31 +299,26 @@ export const UserPage = () => {
Followers: {amountFollower}
-
- {course !== null ? ( + {course !== null && ( +
Course: {course} - ) : ( - Course: Not set - )} -
-
- {age !== null ? ( +
) + } + + {age !== null && ( +
Age: {age} - ) : ( - Age: Not set - )} -
-
- {description !== null ? ( - Description: {description} - ) : ( - Description: Not set - )} -
- +
+ )} + {description !== null && ( +
+ {description} +
+ ) + } +
-
@@ -388,5 +327,4 @@ export const UserPage = () => {
); - }; From 5ab706a870239318770a59e2d1a0d94f98caa223 Mon Sep 17 00:00:00 2001 From: Nahlam4 Date: Wed, 12 Jun 2024 21:42:43 +0200 Subject: [PATCH 5/5] Fixed Friend page --- src/main/web/src/scenes/Friends/UserFriend.css | 10 +++++----- src/main/web/src/scenes/Friends/UserFriend.tsx | 8 ++++---- src/main/web/src/scenes/User/UserPost.tsx | 8 +------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/main/web/src/scenes/Friends/UserFriend.css b/src/main/web/src/scenes/Friends/UserFriend.css index 253f10e6..f7365318 100644 --- a/src/main/web/src/scenes/Friends/UserFriend.css +++ b/src/main/web/src/scenes/Friends/UserFriend.css @@ -1,4 +1,4 @@ -.user-container { +.friend-user-container { font-size: var(--headline-font-size); align-items: center; text-align: center; @@ -10,24 +10,24 @@ color: white; } -.user-image { +.friend-user-image { width: 100px; height: 100px; border-radius: 50px; object-fit: cover; } -.custom-image { +.friend-custom-image { width: 100px; height: 100px; border-radius: 50px; object-fit: cover; } -.user-button { +.friend-user-button { text-decoration: none; color: inherit; } -.user-name{ +.friend-user-name{ padding-top: 10px; } diff --git a/src/main/web/src/scenes/Friends/UserFriend.tsx b/src/main/web/src/scenes/Friends/UserFriend.tsx index 40c00246..9ad67e63 100644 --- a/src/main/web/src/scenes/Friends/UserFriend.tsx +++ b/src/main/web/src/scenes/Friends/UserFriend.tsx @@ -14,14 +14,14 @@ export const UserFriend: React.FC = (props: UserFriendModel) => const truncatedUsername = username.length > 17 ? username.slice(0, 15) + "..." : username; return ( -
+
{image ? ( - {username} + {username} ) : ( - {"random"} + {"random"} )} -
{truncatedUsername}
+
{truncatedUsername}
); diff --git a/src/main/web/src/scenes/User/UserPost.tsx b/src/main/web/src/scenes/User/UserPost.tsx index c4d64e50..8683e926 100644 --- a/src/main/web/src/scenes/User/UserPost.tsx +++ b/src/main/web/src/scenes/User/UserPost.tsx @@ -251,13 +251,7 @@ export const UserPost: React.FC = (props: PostModel) => {
)} {reportOpen && ( - + )}
);