parent
0cdcc915f4
commit
4fdb17733d
|
|
@ -1,15 +1,15 @@
|
||||||
import { Store, createSlice } from "@reduxjs/toolkit";
|
import { Store, createSlice } from "@reduxjs/toolkit";
|
||||||
import { Configuration, Post, PostsApi } from "../api";
|
import { Configuration, Post, PostsApi } from "../api";
|
||||||
import { Status } from "../util/types";
|
import { Status } from "../util/types";
|
||||||
import { store } from "./store";
|
import { AppThunk, store } from "./store";
|
||||||
|
|
||||||
interface postSlice {
|
interface postSliceInterface {
|
||||||
status: Status;
|
status: Status;
|
||||||
followedPosts: Post[];
|
followedPosts: Post[];
|
||||||
globalPosts: Post[];
|
globalPosts: Post[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: postSlice = {
|
const initialState: postSliceInterface = {
|
||||||
status: Status.idle,
|
status: Status.idle,
|
||||||
followedPosts: [],
|
followedPosts: [],
|
||||||
globalPosts: [],
|
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);
|
const postApi = createApi(store);
|
||||||
|
|
||||||
dispatch(setStatus(Status.loading));
|
dispatch(setStatus(Status.loading));
|
||||||
|
|
@ -40,7 +40,7 @@ export const fetchFollowedPosts = () => async (dispatch: any) => {
|
||||||
dispatch(setStatus(Status.idle));
|
dispatch(setStatus(Status.idle));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchGlobalPosts = () => async (dispatch: any) => {
|
export const fetchGlobalPosts = (): AppThunk => async (dispatch: any) => {
|
||||||
const postApi = createApi(store);
|
const postApi = createApi(store);
|
||||||
|
|
||||||
dispatch(setStatus(Status.loading));
|
dispatch(setStatus(Status.loading));
|
||||||
|
|
@ -49,6 +49,26 @@ export const fetchGlobalPosts = () => async (dispatch: any) => {
|
||||||
dispatch(setStatus(Status.idle));
|
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 } =
|
export const { setFollowedPosts, setGlobalPosts, setStatus } =
|
||||||
postSlice.actions;
|
postSlice.actions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Badge, Tooltip } from "@mui/material";
|
||||||
export default function NotificationBell() {
|
export default function NotificationBell() {
|
||||||
return (
|
return (
|
||||||
<Badge badgeContent={4} color="secondary">
|
<Badge badgeContent={4} color="secondary">
|
||||||
<Tooltip title="Notifications">
|
<Tooltip title="Notifications" >
|
||||||
<NotificationsIcon />
|
<NotificationsIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
|
||||||
|
|
@ -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 { 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 {
|
interface PostListItemProps {
|
||||||
post: Post;
|
post: Post;
|
||||||
|
postType: "all" | "user";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PostListItem(props: PostListItemProps) {
|
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 (
|
return (
|
||||||
<ListItem>
|
<ListItem
|
||||||
<ListItemText
|
sx={{
|
||||||
primary={props.post.title}
|
display: "flex",
|
||||||
secondary={`${props.post.createdBy?.firstName} ${props.post.createdBy?.lastName}`}
|
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>
|
</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"];
|
const settings = ["Profile", "Account", "Dashboard", "Logout"];
|
||||||
|
|
||||||
function TopAppBar() {
|
interface TopAppBarProps {
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TopAppBar(props: TopAppBarProps) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const userInfo = useSelector(selectUserInfo);
|
const userInfo = useSelector(selectUserInfo);
|
||||||
|
|
@ -25,7 +29,6 @@ function TopAppBar() {
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(userInfo);
|
|
||||||
|
|
||||||
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
setAnchorElUser(event.currentTarget);
|
setAnchorElUser(event.currentTarget);
|
||||||
|
|
@ -41,15 +44,14 @@ function TopAppBar() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar position="static">
|
<AppBar position="absolute" sx={{zIndex:1600, height: `${props.height}px` }}>
|
||||||
<Container maxWidth="xl" sx={{ zIndex: 2500 }}>
|
|
||||||
<Toolbar disableGutters>
|
<Toolbar disableGutters>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
noWrap
|
noWrap
|
||||||
component="a"
|
component="a"
|
||||||
href="#app-bar-with-responsive-menu"
|
|
||||||
sx={{
|
sx={{
|
||||||
|
marginLeft: "1rem",
|
||||||
mr: 2,
|
mr: 2,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
fontFamily: "monospace",
|
fontFamily: "monospace",
|
||||||
|
|
@ -72,8 +74,8 @@ function TopAppBar() {
|
||||||
<NotificationBell />
|
<NotificationBell />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 0 }}>
|
<Box sx={{ flexGrow: 0, mr:"2rem" }}>
|
||||||
<Tooltip title="Open settings">
|
<Tooltip title="Open settings" >
|
||||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
alt={`${userInfo.firstName} ${userInfo.lastName}`}
|
alt={`${userInfo.firstName} ${userInfo.lastName}`}
|
||||||
|
|
@ -109,7 +111,6 @@ function TopAppBar() {
|
||||||
</Menu>
|
</Menu>
|
||||||
</Box>
|
</Box>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</Container>
|
|
||||||
</AppBar>
|
</AppBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default function AuthRoot() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
return navigate("/");
|
return navigate("/feed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from "../app/postSlice";
|
} from "../app/postSlice";
|
||||||
import { store, useAppDispatch } from "../app/store";
|
import { store, useAppDispatch } from "../app/store";
|
||||||
import { useEffect } from "react";
|
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 { Post } from "../api";
|
||||||
import PostListItem from "../components/postListItem";
|
import PostListItem from "../components/postListItem";
|
||||||
|
|
||||||
|
|
@ -25,17 +25,49 @@ export default function PostList(props: PostListProps) {
|
||||||
if (props.type === "user") dispatch(fetchFollowedPosts());
|
if (props.type === "user") dispatch(fetchFollowedPosts());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
console.log(globalPosts);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{props.type === "all" && (
|
{props.type === "all" && (
|
||||||
<List>
|
<List>
|
||||||
{globalPosts.map((post: Post) => (
|
{globalPosts.map((post: Post) => (
|
||||||
<PostListItem post={post} key={post.id} />
|
<PostListItem post={post} postType="all" key={post.id} />
|
||||||
))}
|
))}
|
||||||
</List>
|
</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";
|
import TopAppBar from "../components/topAppBar";
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
|
const topAppBarHeight = 64;
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -30,7 +31,7 @@ export default function Root() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box>
|
<Box>
|
||||||
<TopAppBar />
|
<TopAppBar height={topAppBarHeight} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -51,6 +52,7 @@ export default function Root() {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
p: 3,
|
p: 3,
|
||||||
width: { sm: `calc(100% - ${width > 600 ? drawerWidth : 0}px)` },
|
width: { sm: `calc(100% - ${width > 600 ? drawerWidth : 0}px)` },
|
||||||
|
marginTop: { xs: `${topAppBarHeight}px`},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue