Fix the missing data in current user's profile and bad online status
This commit is contained in:
parent
5a742bf32e
commit
db1e65b3fa
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue