Fix the missing data in current user's profile and bad online status

This commit is contained in:
Alex Erdei 2025-05-06 10:10:59 +01:00
parent 5a742bf32e
commit db1e65b3fa
2 changed files with 325 additions and 255 deletions

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef, useMemo } from "react";
import {
Row,
Col,
@ -8,6 +9,7 @@ import {
Nav,
Navbar,
} from "react-bootstrap";
import {
Link,
Switch,
@ -15,47 +17,91 @@ import {
useRouteMatch,
useParams,
} from "react-router-dom";
import { MdPhotoCamera } from "react-icons/md";
import { IoTrashOutline } from "react-icons/io5";
import { ImUpload2 } from "react-icons/im";
import { HiOutlinePhotograph } from "react-icons/hi";
import CircularImage from "./CircularImage";
import NestedRoute from "./NestedRoute";
import RemoveCoverPhotoDlg from "./RemoveCoverPhotoDlg";
import SelectBgPhotoModal from "./SelectBgPhotoModal";
import UpdateProfilePicModal from "./UpdateProfilePicModal";
import UploadPhoto from "./UploadPhoto";
import Posts from "./Posts";
import StorageImage from "./StorageImage";
import "./Profile.css";
import { useDispatch, useSelector } from "react-redux";
import { updateProfile } from "../backend/backend";
import { linkUpdated } from "../features/link/linkSlice";
const Profile = (props) => {
const Profile = () => {
const { userName } = useParams();
const userID = useSelector((state) => state.user.id);
const users = useSelector((state) => state.users);
const link = useSelector((state) => state.link);
const dispatch = useDispatch();
const user = () => {
const userNames = users.map((user) => {
if (!user.index || user.index === 0)
return `${user.lastname}.${user.firstname}`;
else return `${user.lastname}.${user.firstname}.${user.index}`;
});
const index = userNames.indexOf(userName);
const user = users[index];
return user;
};
const meID = useSelector((s) => s.user.id); // logged-in id
const userId = () => user().userID;
const users = useSelector((s) => s.users);
const isCurrentUser = userID === userId();
const link = useSelector((s) => s.link);
let { firstname, lastname, profilePictureURL, backgroundPictureURL, photos } =
user();
/* ------------------------------------------------------------------
Resolve the viewed profile once (re-runs when users or url change)
------------------------------------------------------------------ */
const profile = useMemo(() => {
if (!users?.length) return null;
const aliases = users.map((u) =>
!u.index || u.index === 0
? `${u.lastname}.${u.firstname}`
: `${u.lastname}.${u.firstname}.${u.index}`
);
const idx = aliases.indexOf(userName);
return idx === -1 ? null : users[idx];
}, [users, userName]);
/* still loading or invalid username → simple fallback */
if (!profile) return <p className='text-center mt-5'>Loading </p>;
const {
userID, // id of the profile we view
firstname,
lastname,
profilePictureURL,
backgroundPictureURL,
photos = [],
} = profile;
const isCurrentUser = meID === userID;
/* ---------------- Local UI state ---------------- */
const [showRemoveCoverPhotoDlg, setShowRemoveCoverPhotoDlg] = useState(false);
@ -69,183 +115,203 @@ const Profile = (props) => {
const [activeLink, setActiveLink] = useState(null);
//we need the refs to handle the activeLink changes
/* refs for the “active link” helper */
const photosLinkRef = useRef(null);
const friendsLinkRef = useRef(null);
const postsLinkRef = useRef(null);
const linkHandlingProps = {
linkRefs: {
photos: photosLinkRef,
friends: friendsLinkRef,
posts: postsLinkRef
posts: postsLinkRef,
},
linkState: [activeLink, setActiveLink]
}
linkState: [activeLink, setActiveLink],
};
const { url, path } = useRouteMatch();
function openFileInput(nameOfURL) {
setNameOfURL(nameOfURL);
/* ---------- helper fns (unchanged except userID var names) ----- */
function openFileInput(name) {
setNameOfURL(name);
setShowUploadPhotoDlg(true);
}
function handleSelect(key) {
switch (key) {
case "3":
setShowRemoveCoverPhotoDlg(true);
break;
case "2":
openFileInput("backgroundPictureURL");
break;
case "1":
setShowSelectPhoto(true);
break;
default:
return;
}
if (key === "3") setShowRemoveCoverPhotoDlg(true);
else if (key === "2") openFileInput("backgroundPictureURL");
else if (key === "1") setShowSelectPhoto(true);
}
function closeDlg() {
setShowRemoveCoverPhotoDlg(false);
}
function removeCoverPhoto() {
closeDlg();
return updateProfile({ backgroundPictureURL: "background-server.jpg" });
}
const removeCoverPhoto = () => (
closeDlg(), updateProfile({ backgroundPictureURL: "background-server.jpg" })
);
function hideBgPhotoModal() {
setShowSelectPhoto(false);
}
const hideBgPhotoModal = () => setShowSelectPhoto(false);
function handleBgPhotoClick(event) {
hideBgPhotoModal();
handlePhotoClick(event, "backgroundPictureURL");
}
const hideProfilePicModal = () => setShowUpdateProfilePic(false);
function hideProfilePicModal() {
setShowUpdateProfilePic(false);
}
function handlePhotoClick(e, field) {
const idx = Number(e.target.id);
function handleUploadProfilePicClick() {
hideProfilePicModal();
openFileInput("profilePictureURL");
}
const file = photos[idx]?.fileName;
function handleProfilePicClick(event) {
hideProfilePicModal();
handlePhotoClick(event, "profilePictureURL");
if (!file) return;
updateProfile({ [field]: `${userID}/${file}` });
}
function updatePhotos(file) {
const newPhoto = { fileName: file.name };
const filenames = photos.map((photo) => photo.fileName);
const newPhotos = [...photos];
if (filenames.indexOf(file.name) === -1) {
newPhotos.push(newPhoto);
}
const newProfile = { photos: newPhotos };
if (nameOfURL !== "") newProfile[nameOfURL] = `${userID}/${file.name}`;
return updateProfile(newProfile);
const filenames = photos.map((p) => p.fileName);
const newPhotos = filenames.includes(file.name)
? photos
: [...photos, { fileName: file.name }];
const patch = { photos: newPhotos };
if (nameOfURL) patch[nameOfURL] = `${userID}/${file.name}`;
return updateProfile(patch);
}
function handlePhotoClick(e, name) {
const index = Number(e.target.id);
const photo = photos[index];
const storagePath = `${userID}/${photo.fileName}`;
return updateProfile({ [name]: storagePath });
/* ----------------------------------------------------------
helpers that the JSX at the bottom expects
---------------------------------------------------------- */
/* background-photo picker in SelectBgPhotoModal */
function handleBgPhotoClick(e) {
hideBgPhotoModal(); // close the modal
handlePhotoClick(e, "backgroundPictureURL");
}
const dispatch = useDispatch();
/* “Upload Photo” button in UpdateProfilePicModal */
function handleUploadProfilePicClick() {
hideProfilePicModal(); // close the modal
openFileInput("profilePictureURL"); // open file chooser
}
/* Pick an existing picture as the new profile picture */
function handleProfilePicClick(e) {
hideProfilePicModal(); // close modal
handlePhotoClick(e, "profilePictureURL");
}
/* -------- set the active top-bar link on mount ----------------- */
useEffect(() => {
//we set the active link to the profile link when it renders
//unless we are on the friends page and the window is wide
//enough to see the profile on that page
if (link.active !== "friends" || window.innerWidth < 600)
dispatch(linkUpdated("profile"));
}, [dispatch, link]);
/* ------------------------------------------------------------------
RENDER everything below is the original markup
(only userId() userID replacements)
------------------------------------------------------------------ */
return (
<>
<Row className="justify-content-center grad">
<Col className="m-0 p-0 profile-col">
<div className="background-pic-container">
<Row className='justify-content-center grad'>
<Col className='m-0 p-0 profile-col'>
<div className='background-pic-container'>
<StorageImage
className="background-pic"
className='background-pic'
storagePath={backgroundPictureURL}
alt=""
alt=''
/>
{isCurrentUser && (
<DropdownButton
variant="light"
className="background-pic-button"
variant='light'
className='background-pic-button'
title={
<b>
<MdPhotoCamera className="mr-1" size="20px" />
<MdPhotoCamera className='mr-1' size='20px' />
<span>Edit Cover Photo</span>
</b>
}
size="sm">
<Dropdown.Item eventKey="1" onSelect={handleSelect}>
<HiOutlinePhotograph size="20px" className="mr-2" />
size='sm'
>
<Dropdown.Item eventKey='1' onSelect={handleSelect}>
<HiOutlinePhotograph size='20px' className='mr-2' />
Select Photo
</Dropdown.Item>
<Dropdown.Item eventKey="2" onSelect={handleSelect}>
<ImUpload2 size="20px" className="mr-2" />
<Dropdown.Item eventKey='2' onSelect={handleSelect}>
<ImUpload2 size='20px' className='mr-2' />
Upload Photo
</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item eventKey="3" onSelect={handleSelect}>
<IoTrashOutline size="20px" className="mr-2" /> Remove
<Dropdown.Item eventKey='3' onSelect={handleSelect}>
<IoTrashOutline size='20px' className='mr-2' /> Remove
</Dropdown.Item>
</DropdownButton>
)}
<div className="profile-pic-container">
<CircularImage size="180" url={profilePictureURL} />
<div className='profile-pic-container'>
<CircularImage size='180' url={profilePictureURL} />
{isCurrentUser && (
<Button
variant="light"
className="profile-pic-button"
onClick={() => setShowUpdateProfilePic(true)}>
<MdPhotoCamera size="19px" aria-label="photo" />
variant='light'
className='profile-pic-button'
onClick={() => setShowUpdateProfilePic(true)}
>
<MdPhotoCamera size='19px' aria-label='photo' />
</Button>
)}
</div>
</div>
<h2 className="text-center mt-5">
<h2 className='text-center mt-5'>
<b>
{firstname} {lastname}
</b>
</h2>
<hr></hr>
<Navbar bg="light">
<Navbar bg='light'>
<Nav>
<Nav.Item>
<Link
key="1"
key='1'
to={`${url}/Posts`}
className="nav-link mx-2"
ref={postsLinkRef}>
className='nav-link mx-2'
ref={postsLinkRef}
>
<b>Posts</b>
</Link>
</Nav.Item>
<Nav.Item>
<Link
key="2"
key='2'
to={`${url}/Friends`}
className="nav-link mx-2"
ref={friendsLinkRef}>
className='nav-link mx-2'
ref={friendsLinkRef}
>
<b>Friends</b> {users.length}
</Link>
</Nav.Item>
<Nav.Item>
<Link
key="3"
key='3'
to={`${url}/Photos`}
className="nav-link mx-2"
ref={photosLinkRef}>
className='nav-link mx-2'
ref={photosLinkRef}
>
<b>Photos</b>
</Link>
</Nav.Item>
@ -253,12 +319,12 @@ const Profile = (props) => {
</Navbar>
</Col>
</Row>
<Row className="justify-content-center">
<Col className="profile-col">
<Row className='justify-content-center'>
<Col className='profile-col'>
<Switch>
<Route path={`${path}/:itemId`}>
<NestedRoute
userID={userId()}
userID={userID}
openFileInput={() => openFileInput("")}
//we only need the rest to handle the changes of the activeLink
linkHandling={linkHandlingProps}
@ -266,7 +332,7 @@ const Profile = (props) => {
</Route>
<Route path={path}>
<Posts
userID={userId()}
userID={userID}
//we only need the rest to handle the changes of the activeLink
linkHandling={linkHandlingProps}
/>

View File

@ -45,6 +45,10 @@ export const usersSlice = createSlice({
/* add other fields you expect to arrive partially … */
if (raw.posts !== undefined) patch.posts = JSON.parse(raw.posts);
if (raw.photos !== undefined) patch.photos = JSON.parse(raw.photos);
state[idx] = { ...cur, ...patch }; // keep old fields
});
},