From 4fdb17733dd1fbe8974f16e5b250fe988f3d99a6 Mon Sep 17 00:00:00 2001 From: Pau Costa Date: Sat, 10 Feb 2024 14:39:57 +0100 Subject: [PATCH] :sparkles: Implemented post list view Signed-off-by: Pau Costa --- client/src/app/postSlice.ts | 30 ++++- client/src/components/notificationBell.tsx | 2 +- client/src/components/postListItem.tsx | 131 +++++++++++++++++++-- client/src/components/topAppBar.tsx | 17 +-- client/src/routes/Auth/authRoot.tsx | 2 +- client/src/routes/postList.tsx | 44 ++++++- client/src/routes/root.tsx | 4 +- 7 files changed, 201 insertions(+), 29 deletions(-) diff --git a/client/src/app/postSlice.ts b/client/src/app/postSlice.ts index b3cb600..30ff9c4 100644 --- a/client/src/app/postSlice.ts +++ b/client/src/app/postSlice.ts @@ -1,15 +1,15 @@ import { Store, createSlice } from "@reduxjs/toolkit"; import { Configuration, Post, PostsApi } from "../api"; import { Status } from "../util/types"; -import { store } from "./store"; +import { AppThunk, store } from "./store"; -interface postSlice { +interface postSliceInterface { status: Status; followedPosts: Post[]; globalPosts: Post[]; } -const initialState: postSlice = { +const initialState: postSliceInterface = { status: Status.idle, followedPosts: [], globalPosts: [], @@ -31,7 +31,7 @@ export const postSlice = createSlice({ }, }); -export const fetchFollowedPosts = () => async (dispatch: any) => { +export const fetchFollowedPosts = (): AppThunk => async (dispatch: any) => { const postApi = createApi(store); dispatch(setStatus(Status.loading)); @@ -40,7 +40,7 @@ export const fetchFollowedPosts = () => async (dispatch: any) => { dispatch(setStatus(Status.idle)); }; -export const fetchGlobalPosts = () => async (dispatch: any) => { +export const fetchGlobalPosts = (): AppThunk => async (dispatch: any) => { const postApi = createApi(store); dispatch(setStatus(Status.loading)); @@ -49,6 +49,26 @@ export const fetchGlobalPosts = () => async (dispatch: any) => { dispatch(setStatus(Status.idle)); }; +export const likePost = + (postId: number): AppThunk => + async (dispatch: any) => { + const postApi = createApi(store); + + dispatch(setStatus(Status.loading)); + await postApi.likePost(postId); + dispatch(setStatus(Status.idle)); + }; + +export const unLikePost = + (postId: number): AppThunk => + async (dispatch: any) => { + const postApi = createApi(store); + + dispatch(setStatus(Status.loading)); + await postApi.unlikePost(postId); + dispatch(setStatus(Status.idle)); + }; + export const { setFollowedPosts, setGlobalPosts, setStatus } = postSlice.actions; diff --git a/client/src/components/notificationBell.tsx b/client/src/components/notificationBell.tsx index 97e17e9..064cfb6 100644 --- a/client/src/components/notificationBell.tsx +++ b/client/src/components/notificationBell.tsx @@ -4,7 +4,7 @@ import { Badge, Tooltip } from "@mui/material"; export default function NotificationBell() { return ( - + diff --git a/client/src/components/postListItem.tsx b/client/src/components/postListItem.tsx index 1e77f2c..dee8d3c 100644 --- a/client/src/components/postListItem.tsx +++ b/client/src/components/postListItem.tsx @@ -1,19 +1,136 @@ -import { ListItem, ListItemText } from "@mui/material"; +import { + Box, + Button, + Card, + CardActionArea, + CardContent, + Grid, + ListItem, + styled, + Typography, +} from "@mui/material"; import { Post } from "../api"; +import MessageIcon from "@mui/icons-material/Message"; +import ThumbUpOffAltIcon from "@mui/icons-material/ThumbUpOffAlt"; +import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt"; +import { useNavigate } from "react-router-dom"; +import { likePost, unLikePost } from "../app/postSlice"; +import { useAppDispatch } from "../app/store"; +import { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; +import { selectUserInfo } from "../app/loginSlice"; interface PostListItemProps { post: Post; + postType: "all" | "user"; } export default function PostListItem(props: PostListItemProps) { - console.log(props.post); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const userInfo = useSelector(selectUserInfo); + const [liked, setLiked] = useState(false); + const [numberOfLikes, setNumberOfLikes] = useState(0); + + // On component mount, set the number of likes + // and whether the user has liked the post + useEffect(() => { + setNumberOfLikes(props.post.likedBy?.length || 0); + setLiked(props.post.likedBy?.includes(userInfo) || false); + }, []); + + const handleLike = () => { + if (!liked) { + // Instant feedback to the user + setLiked(true); + setNumberOfLikes(numberOfLikes + 1); + // Dispatch the call to the API + dispatch(likePost(props.post.id as number)); + } + // If the user has already liked the post, unlike it + else { + setLiked(false); + setNumberOfLikes(numberOfLikes - 1); + + dispatch(unLikePost(props.post.id as number)); + } + }; + + const handlePostClick = () => { + navigate(`/post/${props.post.id}`); + }; + const handleUserClick = () => { + navigate(`/user/${props.post.createdBy?.id}`); + }; return ( - - + + + + + + + {props.post.title} + + + {props.post.content} + + + + + + + + + + + + ); } + +const StyledGrid = styled(Grid)({ + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: "auto", + marginBottom: "0.8rem", + marginRight: "0.8rem", +}); +const StyledTypography = styled(Typography)({ + padding: "0 0.5rem", +}); diff --git a/client/src/components/topAppBar.tsx b/client/src/components/topAppBar.tsx index 4d13a55..a1a65b0 100644 --- a/client/src/components/topAppBar.tsx +++ b/client/src/components/topAppBar.tsx @@ -17,7 +17,11 @@ import { useNavigate } from "react-router-dom"; const settings = ["Profile", "Account", "Dashboard", "Logout"]; -function TopAppBar() { +interface TopAppBarProps { + height: number; +} + +function TopAppBar(props: TopAppBarProps) { const dispatch = useAppDispatch(); const navigate = useNavigate(); const userInfo = useSelector(selectUserInfo); @@ -25,7 +29,6 @@ function TopAppBar() { null ); - console.log(userInfo); const handleOpenUserMenu = (event: React.MouseEvent) => { setAnchorElUser(event.currentTarget); @@ -41,15 +44,14 @@ function TopAppBar() { }; return ( - - + - - + + - ); } diff --git a/client/src/routes/Auth/authRoot.tsx b/client/src/routes/Auth/authRoot.tsx index 641cd64..7cd9f52 100644 --- a/client/src/routes/Auth/authRoot.tsx +++ b/client/src/routes/Auth/authRoot.tsx @@ -12,7 +12,7 @@ export default function AuthRoot() { useEffect(() => { if (loggedIn) { - return navigate("/"); + return navigate("/feed"); } }); diff --git a/client/src/routes/postList.tsx b/client/src/routes/postList.tsx index a6f98dc..860603e 100644 --- a/client/src/routes/postList.tsx +++ b/client/src/routes/postList.tsx @@ -7,7 +7,7 @@ import { } from "../app/postSlice"; import { store, useAppDispatch } from "../app/store"; import { useEffect } from "react"; -import { List, ListItem, ListItemText } from "@mui/material"; +import { Box, List, ListItem, ListItemText, Typography } from "@mui/material"; import { Post } from "../api"; import PostListItem from "../components/postListItem"; @@ -25,17 +25,49 @@ export default function PostList(props: PostListProps) { if (props.type === "user") dispatch(fetchFollowedPosts()); }, []); - console.log(globalPosts); return ( - <> + {props.type === "all" && ( {globalPosts.map((post: Post) => ( - + ))} )} - {props.type === "user" && } - + {props.type === "user" && ( + + {followedPosts.map((post: Post) => ( + + ))} + {followedPosts.length === 0 && ( + + + Whops! + + + There is no content here! Try following some users, or check the + global feed + + + )} + + )} + ); } diff --git a/client/src/routes/root.tsx b/client/src/routes/root.tsx index 97dd365..97d025f 100644 --- a/client/src/routes/root.tsx +++ b/client/src/routes/root.tsx @@ -10,6 +10,7 @@ import BottomAppBar from "../components/bottomAppBar"; import TopAppBar from "../components/topAppBar"; const drawerWidth = 240; +const topAppBarHeight = 64; export default function Root() { const navigate = useNavigate(); @@ -30,7 +31,7 @@ export default function Root() { return ( <> - + 600 ? drawerWidth : 0}px)` }, + marginTop: { xs: `${topAppBarHeight}px`}, }} >