Compare commits
4 Commits
75f98755b7
...
024b2b589d
Author | SHA1 | Date |
---|---|---|
Jason Zhu | 024b2b589d | |
Jason Zhu | 47e543f2b4 | |
Jason Zhu | 1d970d5879 | |
Jason Zhu | 3b6ee94253 |
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"printWidth": 80
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"prettier": "^2.8.4",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
@ -5656,6 +5657,18 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
@ -19905,6 +19918,11 @@
|
|||
"whatwg-url": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"prettier": "^2.8.4",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import { useSelector } from "react-redux";
|
||||
import { selectAllPosts } from "./postsSlice";
|
||||
import PostAuthor from "./PostAuthor";
|
||||
import { useSelector } from 'react-redux';
|
||||
import { selectAllPosts } from './postsSlice';
|
||||
import PostAuthor from './PostAuthor';
|
||||
import TimeAgo from './TimeAgo';
|
||||
import ReactionButtons from './ReactionButton';
|
||||
|
||||
const PostsList = () => {
|
||||
const posts = useSelector(selectAllPosts);
|
||||
|
||||
const renderedPosts = posts.map((post) => (
|
||||
const orderedPosts = posts
|
||||
.slice()
|
||||
.sort((a, b) => b.date.localeCompare(a.date));
|
||||
|
||||
const renderedPosts = orderedPosts.map((post) => (
|
||||
<article key={post.id}>
|
||||
<h3>{post.title}</h3>
|
||||
<p>{post.content.substring(0, 100)}</p>
|
||||
<p className="postCredit">
|
||||
<PostAuthor userId={post.userId} />
|
||||
<TimeAgo timestamp={post.date} />
|
||||
</p>
|
||||
<ReactionButtons post={post} />
|
||||
</article>
|
||||
));
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { reactionAdded } from './postsSlice';
|
||||
|
||||
const reactionEmoji = {
|
||||
thumbsUp: '👍',
|
||||
wow: '😮',
|
||||
heart: '❤️',
|
||||
rocket: '🚀',
|
||||
coffee: '☕',
|
||||
};
|
||||
|
||||
const ReactionButtons = ({ post }) => {
|
||||
const dispatch = useDispatch();
|
||||
const reactionButtons = Object.entries(reactionEmoji).map(([name, emoji]) => {
|
||||
return (
|
||||
<button
|
||||
key={name}
|
||||
type="button"
|
||||
className="reactionButton"
|
||||
onClick={() =>
|
||||
dispatch(reactionAdded({ postId: post.id, reaction: name }))
|
||||
}
|
||||
>
|
||||
{emoji} {post.reactions[name]}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
return <div>{reactionButtons}</div>;
|
||||
};
|
||||
|
||||
export default ReactionButtons;
|
|
@ -0,0 +1,18 @@
|
|||
import { parseISO, formatDistanceToNow } from "date-fns";
|
||||
|
||||
const TimeAgo = ({ timestamp }) => {
|
||||
let timeAgo = "";
|
||||
if (timestamp) {
|
||||
const date = parseISO(timestamp);
|
||||
const timePeriod = formatDistanceToNow(date);
|
||||
timeAgo = `${timePeriod} ago`;
|
||||
}
|
||||
|
||||
return (
|
||||
<span title={timestamp}>
|
||||
<i>{timeAgo}</i>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeAgo;
|
|
@ -1,15 +1,32 @@
|
|||
import { createSlice, nanoid } from "@reduxjs/toolkit";
|
||||
import { sub } from "date-fns";
|
||||
|
||||
const initialState = [
|
||||
{
|
||||
id: "1",
|
||||
title: "Learning Redux Toolkit",
|
||||
content: "I've heard good things.",
|
||||
date: sub(new Date(), { minutes: 10 }).toISOString(),
|
||||
reactions: {
|
||||
thumbsUp: 0,
|
||||
wow: 0,
|
||||
heart: 0,
|
||||
rocket: 0,
|
||||
coffee: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Slice...",
|
||||
content: "The more I say slice, the more I want pizza.",
|
||||
date: sub(new Date(), { minutes: 5 }).toISOString(),
|
||||
reactions: {
|
||||
thumbsUp: 0,
|
||||
wow: 0,
|
||||
heart: 0,
|
||||
rocket: 0,
|
||||
coffee: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -29,15 +46,30 @@ const postsSlice = createSlice({
|
|||
title,
|
||||
content,
|
||||
userId,
|
||||
date: new Date().toISOString(),
|
||||
reactions: {
|
||||
thumbsUp: 0,
|
||||
wow: 0,
|
||||
heart: 0,
|
||||
rocket: 0,
|
||||
coffee: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
reactionAdded(state, action) {
|
||||
const { postId, reaction } = action.payload;
|
||||
const existingPost = state.find((post) => post.id === postId);
|
||||
if (existingPost) {
|
||||
existingPost.reactions[reaction]++; // this kind of immer action can only happen in slice
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const selectAllPosts = (state) => state.posts;
|
||||
|
||||
export const { postAdded } = postsSlice.actions;
|
||||
export const { postAdded, reactionAdded } = postsSlice.actions;
|
||||
|
||||
export default postsSlice.reducer;
|
||||
|
|
Loading…
Reference in New Issue