parent
d73bd1dd04
commit
ca4fb32413
|
|
@ -7,12 +7,14 @@ interface postSliceInterface {
|
||||||
status: Status;
|
status: Status;
|
||||||
followedPosts: Post[];
|
followedPosts: Post[];
|
||||||
globalPosts: Post[];
|
globalPosts: Post[];
|
||||||
|
aPost: Post | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: postSliceInterface = {
|
const initialState: postSliceInterface = {
|
||||||
status: Status.idle,
|
status: Status.idle,
|
||||||
followedPosts: [],
|
followedPosts: [],
|
||||||
globalPosts: [],
|
globalPosts: [],
|
||||||
|
aPost: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postSlice = createSlice({
|
export const postSlice = createSlice({
|
||||||
|
|
@ -28,6 +30,9 @@ export const postSlice = createSlice({
|
||||||
setGlobalPosts: (state, action) => {
|
setGlobalPosts: (state, action) => {
|
||||||
state.globalPosts = action.payload;
|
state.globalPosts = action.payload;
|
||||||
},
|
},
|
||||||
|
setAPost: (state, action) => {
|
||||||
|
state.aPost = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -69,7 +74,29 @@ export const unLikePost =
|
||||||
dispatch(setStatus(Status.idle));
|
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;
|
postSlice.actions;
|
||||||
|
|
||||||
export default postSlice.reducer;
|
export default postSlice.reducer;
|
||||||
|
|
@ -77,6 +104,7 @@ export default postSlice.reducer;
|
||||||
export const selectFollowedPosts = (state: any) => state.post.followedPosts;
|
export const selectFollowedPosts = (state: any) => state.post.followedPosts;
|
||||||
export const selectAllPosts = (state: any) => state.post.globalPosts;
|
export const selectAllPosts = (state: any) => state.post.globalPosts;
|
||||||
export const selectStatus = (state: any) => state.post.status;
|
export const selectStatus = (state: any) => state.post.status;
|
||||||
|
export const selectAPost = (state: any) => state.post.aPost;
|
||||||
|
|
||||||
function createApi(store: Store) {
|
function createApi(store: Store) {
|
||||||
return new PostsApi(
|
return new PostsApi(
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { User, UserWithRelations } from "../api";
|
||||||
|
|
||||||
interface AppAvatarProps {
|
interface AppAvatarProps {
|
||||||
user: User | UserWithRelations;
|
user: User | UserWithRelations;
|
||||||
|
small?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppAvatar(props: AppAvatarProps) {
|
export function AppAvatar(props: AppAvatarProps) {
|
||||||
|
|
@ -10,6 +11,10 @@ export function AppAvatar(props: AppAvatarProps) {
|
||||||
<Avatar
|
<Avatar
|
||||||
alt={`${props.user.firstName} ${props.user.lastName}`}
|
alt={`${props.user.firstName} ${props.user.lastName}`}
|
||||||
src={`/images/${props.user.profilePictureId}`}
|
src={`/images/${props.user.profilePictureId}`}
|
||||||
|
sx={{
|
||||||
|
width: props.small ? "24px" : "42px",
|
||||||
|
height: props.small ? "24px" : "42px",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { Provider } from "react-redux";
|
||||||
import { store } from "./app/store";
|
import { store } from "./app/store";
|
||||||
import PostList from "./routes/postList";
|
import PostList from "./routes/postList";
|
||||||
import Profile from "./routes/profile";
|
import Profile from "./routes/profile";
|
||||||
import Post from "./routes/post";
|
import PostView from "./routes/post";
|
||||||
import NewPost from "./routes/newPost";
|
import NewPost from "./routes/newPost";
|
||||||
import Search from "./routes/search";
|
import Search from "./routes/search";
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ const router = createBrowserRouter([
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "post/:id",
|
path: "post/:id",
|
||||||
element: <Post />,
|
element: <PostView />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "search",
|
path: "search",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,134 @@
|
||||||
export default function Post() {
|
import { useSelector } from "react-redux";
|
||||||
return <>Post</>;
|
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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue