A Post and comments view

Signed-off-by: Pau Costa <mico@micodev.es>
main
Pau Costa Ferrer 2024-02-11 16:15:43 +01:00
parent d73bd1dd04
commit ca4fb32413
4 changed files with 169 additions and 5 deletions

View File

@ -7,12 +7,14 @@ interface postSliceInterface {
status: Status;
followedPosts: Post[];
globalPosts: Post[];
aPost: Post | null;
}
const initialState: postSliceInterface = {
status: Status.idle,
followedPosts: [],
globalPosts: [],
aPost: null,
};
export const postSlice = createSlice({
@ -28,6 +30,9 @@ export const postSlice = createSlice({
setGlobalPosts: (state, action) => {
state.globalPosts = action.payload;
},
setAPost: (state, action) => {
state.aPost = action.payload;
},
},
});
@ -69,7 +74,29 @@ export const unLikePost =
dispatch(setStatus(Status.idle));
};
export const { setFollowedPosts, setGlobalPosts, setStatus } =
export const fetchAPost =
(postId: number): AppThunk =>
async (dispatch: any) => {
const postApi = createApi(store);
dispatch(setStatus(Status.loading));
const response = await postApi.getPost(postId);
dispatch(setAPost(response.data));
dispatch(setStatus(Status.idle));
};
export const commentAPost =
(postId: number, comment: string): AppThunk =>
async (dispatch: any) => {
const postApi = createApi(store);
dispatch(setStatus(Status.loading));
await postApi.commentPost(postId, { content: comment });
dispatch(fetchAPost(postId));
dispatch(setStatus(Status.idle));
};
export const { setFollowedPosts, setGlobalPosts, setStatus, setAPost } =
postSlice.actions;
export default postSlice.reducer;
@ -77,6 +104,7 @@ export default postSlice.reducer;
export const selectFollowedPosts = (state: any) => state.post.followedPosts;
export const selectAllPosts = (state: any) => state.post.globalPosts;
export const selectStatus = (state: any) => state.post.status;
export const selectAPost = (state: any) => state.post.aPost;
function createApi(store: Store) {
return new PostsApi(

View File

@ -3,6 +3,7 @@ import { User, UserWithRelations } from "../api";
interface AppAvatarProps {
user: User | UserWithRelations;
small?: boolean;
}
export function AppAvatar(props: AppAvatarProps) {
@ -10,6 +11,10 @@ export function AppAvatar(props: AppAvatarProps) {
<Avatar
alt={`${props.user.firstName} ${props.user.lastName}`}
src={`/images/${props.user.profilePictureId}`}
sx={{
width: props.small ? "24px" : "42px",
height: props.small ? "24px" : "42px",
}}
/>
);
}

View File

@ -14,7 +14,7 @@ import { Provider } from "react-redux";
import { store } from "./app/store";
import PostList from "./routes/postList";
import Profile from "./routes/profile";
import Post from "./routes/post";
import PostView from "./routes/post";
import NewPost from "./routes/newPost";
import Search from "./routes/search";
@ -52,7 +52,7 @@ const router = createBrowserRouter([
},
{
path: "post/:id",
element: <Post />,
element: <PostView />,
},
{
path: "search",

View File

@ -1,3 +1,134 @@
export default function Post() {
return <>Post</>;
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";
import { commentAPost, fetchAPost, selectAPost } from "../app/postSlice";
import { useAppDispatch } from "../app/store";
import { useEffect } from "react";
import React from "react";
import { Post } from "../api";
import {
Box,
Button,
Divider,
Paper,
TextField,
Typography,
} from "@mui/material";
import { AppAvatar } from "../components/appAvatar";
export default function PostView() {
const postId = useParams<{ id: string }>().id;
const storePost = useSelector(selectAPost);
const [postToDisplay, setPostToDisplay] = React.useState<Post | null>(null);
const [newComentText, setNewCommentText] = React.useState("");
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchAPost(parseInt(postId!, 10)));
}, []);
useEffect(() => {
setPostToDisplay(storePost);
}, [storePost]);
const handleCommentPost = () => {
if (newComentText.length > 0) {
dispatch(commentAPost(postToDisplay!.id!, newComentText));
setNewCommentText("");
}
};
const handleCancelComment = () => {
setNewCommentText("");
};
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
width: "60%",
margin: "auto",
minWidth: "330px",
}}
>
<Box sx={{ width: "100%" }}>
<Paper>
<Box sx={{ margin: "1rem" }}>
<Box
sx={{ display: "flex", alignItems: "center", paddingTop: "1rem" }}
>
{postToDisplay?.createdBy && (
<AppAvatar user={postToDisplay?.createdBy!} />
)}
<Typography
variant="subtitle2"
sx={{ mb: "0.2rem", ml: "0.3rem" }}
>
{postToDisplay?.createdBy?.firstName}{" "}
{postToDisplay?.createdBy?.lastName}
</Typography>
</Box>
<Divider sx={{ mb: "0.3rem" }} />
<Typography variant="h4">{postToDisplay?.title}</Typography>
<Typography variant="body1">{postToDisplay?.content}</Typography>
</Box>
</Paper>
</Box>
<Box sx={{ width: "100%" }}>
<TextField
label="Add a comment..."
fullWidth
multiline
value={newComentText}
onChange={(e) => setNewCommentText(e.target.value)}
InputProps={{
endAdornment: (
<>
<Button
variant="contained"
onClick={handleCommentPost}
sx={{
display: `${newComentText.length > 0 ? "inherit" : "none"}`,
}}
>
Accept
</Button>
<Button
variant="outlined"
onClick={handleCancelComment}
sx={{
display: `${newComentText.length > 0 ? "inherit" : "none"}`,
ml: "0.5rem",
}}
>
Cancel
</Button>
</>
),
}}
sx={{
mb: "1rem",
}}
/>
<Box>
{postToDisplay?.comments?.map((comment) => (
<Paper>
<Box sx={{ margin: "1rem" }}>
<Box sx={{ display: "flex", mb: "0.3rem" }}>
<AppAvatar user={comment.createdBy!} small />
<Typography variant="subtitle2" sx={{ ml: "0.5rem" }}>
{comment.createdBy?.firstName} {comment.createdBy?.lastName}
</Typography>
</Box>
<Divider sx={{ mb: "0.3rem" }} />
<Typography variant="body1">{comment.content}</Typography>
</Box>
</Paper>
))}
</Box>
</Box>
</Box>
);
}