parent
0cdcc915f4
commit
4fdb17733d
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={props.post.title}
|
||||
secondary={`${props.post.createdBy?.firstName} ${props.post.createdBy?.lastName}`}
|
||||
/>
|
||||
<ListItem
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
mt: "1rem",
|
||||
justifyContent: "start",
|
||||
alignItems: "start",
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleUserClick} sx={{ textTransform: "none" }}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
sx={{ textAlign: "left", width: "100%" }}
|
||||
variant="body2"
|
||||
component="div"
|
||||
color="text.secondary"
|
||||
>
|
||||
{props.post.createdBy?.firstName} {props.post.createdBy?.lastName}
|
||||
</Typography>
|
||||
</Button>
|
||||
<Card sx={{ maxWidth: 400, width: "100%" }}>
|
||||
<CardActionArea sx={{ mb: "0.8rem" }} onClick={handlePostClick}>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
{props.post.title}
|
||||
</Typography>
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
{props.post.content}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
sx={{ display: "flex", justifyContent: "end" }}
|
||||
>
|
||||
<StyledGrid item>
|
||||
<Button onClick={handleLike}>
|
||||
<StyledTypography variant="body2" color="text.secondary">
|
||||
{numberOfLikes}
|
||||
</StyledTypography>
|
||||
|
||||
{liked ? <ThumbUpAltIcon /> : <ThumbUpOffAltIcon />}
|
||||
</Button>
|
||||
</StyledGrid>
|
||||
<StyledGrid item>
|
||||
<Button onClick={handlePostClick}>
|
||||
<StyledTypography variant="body2" color="text.secondary">
|
||||
{props.post.comments?.length || 0}
|
||||
</StyledTypography>
|
||||
<MessageIcon />
|
||||
</Button>
|
||||
</StyledGrid>
|
||||
</Grid>
|
||||
</Card>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
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",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<HTMLElement>) => {
|
||||
setAnchorElUser(event.currentTarget);
|
||||
|
|
@ -41,15 +44,14 @@ function TopAppBar() {
|
|||
};
|
||||
|
||||
return (
|
||||
<AppBar position="static">
|
||||
<Container maxWidth="xl" sx={{ zIndex: 2500 }}>
|
||||
<AppBar position="absolute" sx={{zIndex:1600, height: `${props.height}px` }}>
|
||||
<Toolbar disableGutters>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
component="a"
|
||||
href="#app-bar-with-responsive-menu"
|
||||
sx={{
|
||||
marginLeft: "1rem",
|
||||
mr: 2,
|
||||
display: "flex",
|
||||
fontFamily: "monospace",
|
||||
|
|
@ -72,7 +74,7 @@ function TopAppBar() {
|
|||
<NotificationBell />
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 0 }}>
|
||||
<Box sx={{ flexGrow: 0, mr:"2rem" }}>
|
||||
<Tooltip title="Open settings" >
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||
<Avatar
|
||||
|
|
@ -109,7 +111,6 @@ function TopAppBar() {
|
|||
</Menu>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default function AuthRoot() {
|
|||
|
||||
useEffect(() => {
|
||||
if (loggedIn) {
|
||||
return navigate("/");
|
||||
return navigate("/feed");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
alignContent: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
{props.type === "all" && (
|
||||
<List>
|
||||
{globalPosts.map((post: Post) => (
|
||||
<PostListItem post={post} key={post.id} />
|
||||
<PostListItem post={post} postType="all" key={post.id} />
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
{props.type === "user" && <List></List>}
|
||||
</>
|
||||
{props.type === "user" && (
|
||||
<List>
|
||||
{followedPosts.map((post: Post) => (
|
||||
<PostListItem post={post} postType="user" key={post.id} />
|
||||
))}
|
||||
{followedPosts.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h3"
|
||||
sx={{ color: "text.secondary", mb: "2rem" }}
|
||||
>
|
||||
Whops!
|
||||
</Typography>
|
||||
<Typography textAlign="center" sx={{ color: "text.secondary" }}>
|
||||
There is no content here! Try following some users, or check the
|
||||
global feed
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</List>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<Box>
|
||||
<TopAppBar />
|
||||
<TopAppBar height={topAppBarHeight} />
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -51,6 +52,7 @@ export default function Root() {
|
|||
flexGrow: 1,
|
||||
p: 3,
|
||||
width: { sm: `calc(100% - ${width > 600 ? drawerWidth : 0}px)` },
|
||||
marginTop: { xs: `${topAppBarHeight}px`},
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
|
|
|
|||
Loading…
Reference in New Issue