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

View File

@ -45,6 +45,10 @@ export const usersSlice = createSlice({
/* add other fields you expect to arrive partially … */ /* 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 state[idx] = { ...cur, ...patch }; // keep old fields
}); });
}, },