From 629a472f5e48d3fa7a358070135797b5fc9a43d0 Mon Sep 17 00:00:00 2001 From: "luisa.bellitto" Date: Sun, 29 Jun 2025 11:12:00 +0200 Subject: [PATCH] get image and profile data from database --- .../src/components/ChangeAvatarDialog.tsx | 103 +++++++++--------- .../src/components/QuiltedImageList.tsx | 63 +++++++---- .../src/components/changeAvatarDialog.css | 4 + code/frontend/src/pages/Profile.tsx | 61 +++++++---- code/frontend/src/types/UserProfile.ts | 2 +- 5 files changed, 139 insertions(+), 94 deletions(-) diff --git a/code/frontend/src/components/ChangeAvatarDialog.tsx b/code/frontend/src/components/ChangeAvatarDialog.tsx index cce78e9..e7d17ba 100644 --- a/code/frontend/src/components/ChangeAvatarDialog.tsx +++ b/code/frontend/src/components/ChangeAvatarDialog.tsx @@ -15,10 +15,11 @@ import CloseIcon from "@mui/icons-material/Close"; import EditSquareIcon from "@mui/icons-material/EditSquare"; import "./changeAvatarDialog.css"; import ButtonRotkehlchen from "./ButtonRotkehlchen"; -import { useFilePicker } from "use-file-picker"; import Username from "./Username"; import "./username.css"; import api from "../api/axios"; +import { ChangeEvent, useState } from "react"; +import { UserProfile } from "../types/UserProfile"; const BootstrapDialog = styled(Dialog)(({ theme }) => ({ "& .MuiDialogContent-root": { @@ -32,52 +33,50 @@ const BootstrapDialog = styled(Dialog)(({ theme }) => ({ export default function AvatarDialog({ ownAccount, username, + setUserData, + imageUrl, }: { ownAccount: boolean; username: string; + setUserData: React.Dispatch>; + imageUrl?: string | null; }) { - const [profilePicture, setProfilePicture] = React.useState( - null - ); + const [file, setFile] = useState(); + + const handleFileChange = (e: ChangeEvent) => { + if (e.target.files) { + setFile(e.target.files[0]); + } + }; const saveProfilePicture = async () => { try { - const response = await api.post("/profile/updateProfilePicture", { - profilePicture: profilePicture, - }); - setProfilePicture(response.data.data.profilePicture); - console.log( - "Profile picture saved successfully:", - response.data.data.profilePicture - ); + const formData = new FormData(); + console.log("Saving profile picture:", file); + if (file) { + formData.append("image", file); + const response = await api.post( + "/profile/uploadProfilePicture", + formData + ); + console.log("Profile picture saved:", response.data); + setUserData((prevData) => (prevData ?{ + ...prevData, + profilePictureUrl: response.data.url + } : null)); + } setOpen(false); // Close the dialog after saving } catch (error) { console.error("Error saving profile picture:", error); } }; - const { openFilePicker, filesContent, loading, clear } = useFilePicker({ - accept: ".png, .jpg, .jpeg", - multiple: false, - readAs: "DataURL", - limitFilesConfig: { max: 1 }, - }); - - const setImageURL = ({ newImage = false }: { newImage: boolean }) => { - if (newImage) { - return filesContent[0].content; - } - // TODO: If no image is selected, return the image already in the database or undefined - return undefined; - }; - const [open, setOpen] = React.useState(false); const handleClickOpen = () => { setOpen(true); }; const handleClose = () => { - clear(); // Reset the selected image when closing setOpen(false); }; @@ -90,11 +89,7 @@ export default function AvatarDialog({ alt="Username" // current code does not work yet // TODO: If no image is selected, return the image already in the database or undefined - src={ - filesContent.length > 0 - ? setImageURL({ newImage: true }) - : undefined - } + src={imageUrl ? imageUrl : undefined} > U @@ -135,30 +130,34 @@ export default function AvatarDialog({ objectFit: "cover", maxWidth: "30rem", maxHeight: "30rem", + marginBottom: "1rem", }} > - {filesContent.map((file) => ( - {file.name} - ))} + {file {ownAccount && (
- openFilePicker()} - > - - +
)} diff --git a/code/frontend/src/components/QuiltedImageList.tsx b/code/frontend/src/components/QuiltedImageList.tsx index 5a976ca..2ad7748 100644 --- a/code/frontend/src/components/QuiltedImageList.tsx +++ b/code/frontend/src/components/QuiltedImageList.tsx @@ -3,44 +3,66 @@ import { StyledEngineProvider } from "@mui/material/styles"; import "./quiltedImageList.css"; import { Box, Grid } from "@mui/material"; import api from "../api/axios"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { UserProfile } from "../types/UserProfile"; import { useNavigate } from "react-router-dom"; -export default function StandardImageList({user} : {user: UserProfile}) { - +export default function StandardImageList({ user }: { user: UserProfile }) { const navigate = useNavigate(); - const [itemData, setData] = useState<{ img: string; title: string }[]>([]); + const [images, setImages] = useState< + { imageUrl: string; id: string; description: string }[] + >([]); + + useEffect(() => { + fetchUserPosts().then(console.log); + },[user]) const fetchUserPosts = async () => { try { - const response = await api.get(`/profile/posts/${user.username}`); - const posts = response.data.data; - const images = posts.map((post: { imageUrl: string; title: string }) => ({ - img: post.imageUrl, - title: post.title, - })); - setData(images); + if (images.length <= 0) { + console.log("Fetching user posts for:", user.username); + const response = await api.get(`/posts/getUserPosts/${user.username}`); + const posts = response.data.posts; + posts.map(async (post: { id: string; description: string }) => { + try { + await api + .get(`/posts/getPost/{postId}?postId=${post.id}`) + .then((response) => { + if (response.data) { + setImages((prevImages) => [ + ...prevImages, + { + imageUrl: response.data.images[0].url, + id: post.id, + description: post.description || "", + }, + ]); + } + }) + .catch((error) => { + console.error("Error fetching post image:", error); + }); + } catch (error) { + console.error("Error processing post:", error); + } + }); + } } catch (error) { console.error("Error fetching user posts:", error); } }; - fetchUserPosts().then(console.log); - - return ( - {itemData.map((item, index) => ( + {images.map((item, index) => ( {item.title} - navigate("/feed", { replace: true }) + src={item.imageUrl} + alt={item.description} + onClick={ + () => navigate("/feed", { replace: true }) // anchor to post that was clicked } loading="lazy" @@ -52,4 +74,3 @@ export default function StandardImageList({user} : {user: UserProfile}) { ); } - diff --git a/code/frontend/src/components/changeAvatarDialog.css b/code/frontend/src/components/changeAvatarDialog.css index 99fe5cc..6f75c41 100644 --- a/code/frontend/src/components/changeAvatarDialog.css +++ b/code/frontend/src/components/changeAvatarDialog.css @@ -40,6 +40,10 @@ border-radius: 1rem; } +.edit-icon { + cursor: pointer; +} + @media screen and (min-width: 768px) { .profile-avatar { width: 5rem; diff --git a/code/frontend/src/pages/Profile.tsx b/code/frontend/src/pages/Profile.tsx index 3ab45f9..b14df9a 100644 --- a/code/frontend/src/pages/Profile.tsx +++ b/code/frontend/src/pages/Profile.tsx @@ -22,18 +22,18 @@ function Profile() { const [userData, setUserData] = useState({ id: "", username: "", - bio: "default", + bio: "", profilePictureUrl: null, followers: 0, following: 0, - // posts: 0, + posts: 0, }); const userProfile = async () => { try { const response = await api.get(`/profile/get/${username}`); setUserData(response.data.data); - return + return; } catch (error) { navigate("/", { replace: true }); /* replace to 404 page */ console.error("Error fetching user profile:", error); @@ -42,42 +42,63 @@ function Profile() { }; const ownAccount = username === user?.username; -useEffect(() => { + useEffect(() => { + userProfile(); + }, []); - userProfile(); -}, []); - -const setBio = (bio: string) => { - setUserData((prevData) => { - if (prevData) { - return { ...prevData, bio: bio }; + const setBio = (bio: string) => { + setUserData((prevData) => { + if (prevData) { + return { ...prevData, bio: bio }; + } + return prevData; + }); + }; + function handleFollowUser() { + // TODO: implement follow user functionality + if (user) { + api.post(`follower/follow/${username}`) } - return prevData; - }); -} + } + return (
- - + + {/* TODO: Change data to data from Database */}
- {userData?.following} + {userData?.posts} Posts
- {userData?.followers} + + {userData?.followers} + Followers
- {userData?.following} + + {userData?.following} + Following
- + {!ownAccount && ( + + )}
{userData && }
diff --git a/code/frontend/src/types/UserProfile.ts b/code/frontend/src/types/UserProfile.ts index 93d5f79..2e4e824 100644 --- a/code/frontend/src/types/UserProfile.ts +++ b/code/frontend/src/types/UserProfile.ts @@ -5,5 +5,5 @@ export type UserProfile = { profilePictureUrl: string | null; followers: number; following: number; - // posts: number; + posts: number; } \ No newline at end of file