import * as React from "react";
import { styled, createTheme, ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import MuiDrawer from "@mui/material/Drawer";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import List from "@mui/material/List";
import Typography from "@mui/material/Typography";
import Divider from "@mui/material/Divider";
import IconButton from "@mui/material/IconButton";
import Container from "@mui/material/Container";
import Grid from "@mui/material/Grid";
import Paper from "@mui/material/Paper";
import Link from "@mui/material/Link";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import PeopleIcon from "@mui/icons-material/People";
import PeopleOutlineIcon from "@mui/icons-material/PeopleOutline";
import PersonAddAltIcon from "@mui/icons-material/PersonAddAlt";
import { useState, useEffect } from "react";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Button from "@mui/material/Button";
import Title from "./FriendsUI/Title";
import { userRepository } from "../../firebase";
import useUser from "./useUser";
import { useNavigate, useParams } from "react-router-dom";
import GroupsIcon from "@mui/icons-material/Groups";
import TextField from "@mui/material/TextField";
import SingleUserComponent from "./SingleUserComponent";
import SearchRoundedIcon from "@mui/icons-material/SearchRounded";
import EmojiPeopleRoundedIcon from "@mui/icons-material/EmojiPeopleRounded";
import PeopleOutlineRoundedIcon from "@mui/icons-material/PeopleOutlineRounded";
import ErrorPage from "./ErrorPage";
const drawerWidth = 240;
const Drawer = styled(MuiDrawer, {
shouldForwardProp: (prop) => prop !== "open",
})(({ theme, open }) => ({
"& .MuiDrawer-paper": {
position: "relative",
whiteSpace: "nowrap",
width: drawerWidth,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
boxSizing: "border-box",
...(!open && {
overflowX: "hidden",
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing(7),
[theme.breakpoints.up("sm")]: {
width: theme.spacing(9),
},
}),
},
}));
const defaultTheme = createTheme();
/**
* @class FriendsPage
* @classdesc FriendsPage - A React functional component for displaying and managing friends, followers, and following.
* It offers features for viewing profiles, adding or removing friends, following or unfollowing users, and searching for new friends.
*/
export default function FriendsPage() {
const { UserId } = useParams();
const [open, setOpen] = React.useState(true);
const [type, setType] = useState("Friends");
const [showList, setShowList] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [currUser, setCurrUser] = useState(null);
const [users, setUsers] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const navigate = useNavigate();
const currUserId = UserId;
const { user } = useUser();
useEffect(() => {
setIsLoading(true);
userRepository
.getFriends(UserId)
.then((friends) => {
console.log(`friends: ${friends}`);
setShowList(friends);
setIsLoading(false);
})
.catch((e) => {
setError(e);
setIsLoading(false);
console.log(`Error: ${e}`);
});
}, []);
useEffect(() => {
setIsLoading(true);
userRepository
.getUserById(currUserId)
.then((res) => {
setCurrUser(res);
})
.catch((error) => {
console.log("error finding user with id: ", currUserId);
setError(error);
});
if (type == "Following") {
userRepository
.getFollowing(UserId)
.then((_following) => {
setShowList(_following);
setIsLoading(false);
})
.catch((e) => {
setError(e);
setIsLoading(false);
console.log(`Error: ${e}`);
});
} else if (type == "Followers") {
userRepository
.getFollowers(UserId)
.then((followers) => {
console.log(`followers: ${followers}`);
setShowList(followers);
setIsLoading(false);
})
.catch((e) => {
setError(e);
setIsLoading(false);
console.log(`Error: ${e}`);
});
} else if (type == "Find Friends") {
userRepository
.getAllUsers()
.then((res) => {
setIsLoading(false);
setUsers(res);
setFilteredData(res.filter((item) => item.id != UserId));
})
.catch(() => console.log("error fetching all users"));
} else {
userRepository
.getFriends(UserId)
.then((friends) => {
console.log(`friends: ${friends}`);
setShowList(friends);
setIsLoading(false);
})
.catch((e) => {
setError(e);
setIsLoading(false);
console.log(`Error: ${e}`);
});
}
}, [type]);
/**
* @memberof FriendsPage
* @function toggleDrawer
* @description Toggles the side drawer open and close state.
*/
const toggleDrawer = () => {
setOpen(!open);
};
const styles = {
marginBottom: "10%",
};
const mainListItems = (
<React.Fragment>
<ListItemButton style={styles}>
<ListItemIcon>
<GroupsIcon
onClick={() => {
setType("Friends");
}}
/>
</ListItemIcon>
<ListItemText
primary="Friends"
onClick={() => {
setType("Friends");
}}
/>
</ListItemButton>
<ListItemButton style={styles}>
<ListItemIcon>
<PeopleOutlineRoundedIcon
onClick={() => {
setType("Following");
}}
/>
</ListItemIcon>
<ListItemText
primary="Following"
onClick={() => {
setType("Following");
}}
/>
</ListItemButton>
<ListItemButton style={styles}>
<ListItemIcon>
<EmojiPeopleRoundedIcon
onClick={() => {
setType("Followers");
}}
/>
</ListItemIcon>
<ListItemText
primary="Followers"
onClick={() => {
setType("Followers");
}}
/>
</ListItemButton>
<ListItemButton style={styles}>
<ListItemIcon>
<SearchRoundedIcon
onClick={() => {
setType("Find Friends");
}}
/>
</ListItemIcon>
<ListItemText
primary="Find Friends"
onClick={() => {
setType("Find Friends");
}}
/>
</ListItemButton>
</React.Fragment>
);
/**
* @memberof FriendsPage
* @function MainList
* @description Renders the main list of friends, followers, or following based on the selected type.
* Allows users to manage their social connections.
* @returns {React.Component} A component representing the main list in the FriendsPage.
*/
function MainList() {
function removeFriend(uid) {
removeFollower(uid);
unfollowUser(uid);
}
/**
* @memberof FriendsPage
* @function removeFollower
* @description Removes a follower from the current user's followers list.
* @param {string} uid - The unique ID of the user to be removed from followers.
*/
function removeFollower(uid) {
setIsLoading(true);
userRepository
.removeFollower(UserId, uid)
.then(() => {
console.log(`Remove ${uid}.`);
setIsLoading(false);
})
.catch((e) => {
setError(e);
console.log(`Error: ${e}`);
setIsLoading(false);
});
}
/**
* @memberof FriendsPage
* @function viewUser
* @description Navigates to the profile page of a specified user.
* @param {string} uid - The unique ID of the user whose profile is to be viewed.
*/
function viewUser(uid) {
navigate(`/profile/${uid}`);
}
// function followBack(uid) {
// userRepository
// .addFollowing(UserId, uid)
// .then(() => {
// console.log(`Followed ${uid} back!`);
// })
// .catch((e) => {
// setError(e);
// console.log(`Error: ${e}`);
// });
// }
// function stopFollowing(uid) {
// setIsLoading(true);
// userRepository
// .removeFollowing(UserId, uid)
// .then(() => {
// console.log(`Stopped Following ${uid}`);
// setIsLoading(false);
// })
// .catch((e) => {
// setError(e);
// console.log(`Error: ${e}`);
// setIsLoading(false);
// });
// const index = showList.findIndex((x) => x.id === uid);
// showList.splice(index, 1);
// console.log(`Stopped following: ${showList}`);
// }
/**
* @memberof FriendsPage
* @function handlebtn1
* @description Handles actions for the first button in the table. This could be either following a user or viewing a user's profile.
* @param {string} uid - The unique ID of the user for which the action is to be taken.
*/
function handlebtn1(uid) {
if (type == "Followers") {
followUser(uid);
} else {
viewUser(uid);
}
}
/**
* @memberof FriendsPage
* @function handlebtn2
* @description Handles actions for the second button in the table. This could be removing a friend, unfollowing a user, or removing a follower.
* @param {string} uid - The unique ID of the user for which the action is to be taken.
*/
function handlebtn2(uid) {
if (type == "Followers") {
removeFollower(uid);
} else if (type == "Following") {
//stopFollowing(uid);
unfollowUser(uid);
} else {
removeFriend(uid);
}
}
/**
* @memberof FriendsPage
* @function handleFollowOrUnfollow
* @description Decides whether to follow or unfollow a user based on the current state.
* @param {string} userId - The unique ID of the user to follow or unfollow.
* @param {boolean} isFollowing - Indicates whether the current user is already following the target user.
*/
const handleFollowOrUnfollow = (userId, isFollowing) => {
const shouldFollow = isFollowing;
if (shouldFollow) {
unfollowUser(userId);
} else {
followUser(userId);
}
};
async function unfollowUser(userToUnfollowId) {
await userRepository.stopFollowing(currUser.id, userToUnfollowId);
}
async function followUser(userToFollowId) {
await userRepository.startFollowing(currUser.id, userToFollowId);
}
const handleSearch = (searchTerm) => {
const filtered = users.filter((item) => {
if (item.name != null) {
return item.name.toLowerCase().includes(searchTerm.toLowerCase());
}
if (item.firstName != null) {
return item.firstName
.toLowerCase()
.includes(searchTerm.toLowerCase());
}
if (item.email != null) {
return item.email.toLowerCase().includes(searchTerm.toLowerCase());
}
});
setFilteredData(filtered);
};
/**
* @memberof FriendsPage
* @function tableContent
* @description Generates the table content based on the current type (Friends, Following, or Followers).
* @returns {React.Component} Table rows with user data and action buttons.
*/
function tableContent() {
let firstbtn = "";
let secondbtn = "";
if (type == "Friends") {
firstbtn = "View Profile";
secondbtn = "Remove friend";
} else if (type == "Following") {
firstbtn = "View Profile";
secondbtn = "Stop Following";
} else {
firstbtn = "Follow Back";
secondbtn = "Remove Follower";
}
return (
<>
{showList.map((user) => (
<TableRow key={user.id}>
<TableCell>
{user.name || user.firstName || user.email || user.id}{" "}
</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell>
<Button
onClick={() => {
handlebtn1(user.id);
}}
variant="contained"
>
{firstbtn}
</Button>
</TableCell>
<TableCell align="right">
<Button
onClick={() => {
handlebtn2(user.id);
}}
sx={{ backgroundColor: "red" }}
variant="contained"
>
{secondbtn}
</Button>
</TableCell>
</TableRow>
))}
</>
);
}
if (isLoading) {
return (
<>
<head>
<link
href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
rel="stylesheet"
id="bootstrap-css"
/>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<div class="container emp-profile">
<h2>LOADING {type}...</h2>
</div>
</>
);
}
if (error) {
return (
<>
<head>
<link
href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
rel="stylesheet"
id="bootstrap-css"
/>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<div class="container emp-profile">
<h2>ERROR LOADING {type}...</h2>
</div>
</>
);
}
return (
<React.Fragment>
<Title> {type}</Title>
{type == "Find Friends" && currUser != null && (
<div>
<SearchBar onSearch={handleSearch} />
{filteredData.map((user, index) => {
return (
<SingleUserComponent
handleFollowOrUnfollow={handleFollowOrUnfollow}
key={user.id ?? index}
user={user}
shouldFollow={!currUser.following.includes(user.id)}
/>
);
})}
</div>
)}
{type != "Find Friends" && (
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</TableHead>
<TableBody>{tableContent()}</TableBody>
</Table>
)}
</React.Fragment>
);
}
if (!user || (user && user.uid != UserId)) {
return <ErrorPage code="401" />;
}
return (
<ThemeProvider theme={defaultTheme}>
<Box sx={{ display: "flex" }}>
<CssBaseline />
<Drawer variant="permanent" open={open}>
<Toolbar
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
px: [1],
}}
>
<IconButton onClick={toggleDrawer}>
<ChevronLeftIcon />
</IconButton>
</Toolbar>
<Divider />
<List component="nav">{mainListItems}</List>
</Drawer>
<Box
component="main"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[900],
flexGrow: 1,
height: "100vh",
overflow: "auto",
}}
>
<Toolbar />
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper
sx={{
p: 2,
display: "flex",
flexDirection: "column",
}}
>
{MainList()}
</Paper>
</Grid>
</Grid>
</Container>
</Box>
</Box>
</ThemeProvider>
);
}
const SearchBar = ({ onSearch }) => {
const handleSearch = (event) => {
const searchTerm = event.target.value;
onSearch(searchTerm);
};
return (
<TextField label="Search" variant="outlined" onChange={handleSearch} />
);
};