added like button logic

This commit is contained in:
srgooglo 2022-03-02 17:01:22 +01:00
parent f495d802d2
commit 1259735498
2 changed files with 252 additions and 4 deletions

View File

@ -1,10 +1,54 @@
import React from "react" import React from "react"
import * as antd from "antd" import * as antd from "antd"
import { Icons } from "components/Icons" import { Icons } from "components/Icons"
import classnames from "classnames"
import moment from "moment" import moment from "moment"
import { User } from "models"
import "./index.less" import "./index.less"
function LikeButton(props) {
const [liked, setLiked] = React.useState(props.defaultLiked ?? false)
const handleClick = async () => {
let to = !liked
if (typeof props.onClick === "function") {
const result = await props.onClick(to)
if (typeof result === "boolean") {
to = result
}
}
setLiked(to)
}
return <button
className={classnames("likeButton", { ["clicked"]: liked })}
onClick={handleClick}
>
<div
className={classnames(
"ripple",
{ ["clicked"]: liked }
)}
></div>
<svg
className={classnames(
"heart",
{ ["empty"]: !liked },
{ ["clicked"]: liked },
)}
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"></path>
</svg>
</button>
}
function PostHeader({ postData }) { function PostHeader({ postData }) {
const [timeAgo, setTimeAgo] = React.useState(0) const [timeAgo, setTimeAgo] = React.useState(0)
@ -50,9 +94,9 @@ function PostContent({ message }) {
function PostActions(props) { function PostActions(props) {
return <div className="actions"> return <div className="actions">
<div className="action" id="likes" onClick={props.onClickLike}> <div className="action" id="likes">
<div className="icon"> <div className="icon">
<Icons.Heart /> <LikeButton defaultLiked={props.defaultLiked} onClick={props.onClickLike} />
</div> </div>
<div className="value"> <div className="value">
{String(props.likes)} {String(props.likes)}
@ -80,7 +124,56 @@ function PostActions(props) {
} }
export default class PostCard extends React.Component { export default class PostCard extends React.Component {
state = {
loading: true,
selfId: null,
data: this.props.data,
}
api = window.app.request
componentDidMount = async () => {
const selfId = await User.selfUserId()
window.app.ws.listen(`like.post.${this.props.data._id}`, async (data) => {
await this.setState({ data })
})
window.app.ws.listen(`unlike.post.${this.props.data._id}`, async (data) => {
await this.setState({ data })
})
await this.setState({
selfId,
likes: this.props.data.likes,
loading: false
})
}
onClickLike = async (to) => {
let result = false
if (to) {
const apiResult = await await this.api.put.like({ post_id: this.props.data._id })
result = apiResult.success
} else {
const apiResult = await await this.api.put.unlike({ post_id: this.props.data._id })
result = apiResult.success
}
return result
}
hasLiked = () => {
return this.props.data.likes.some(user_id => user_id === this.state.selfId)
}
render() { render() {
const defaultLiked = this.hasLiked()
if (this.state.loading) {
return <antd.Skeleton active />
}
return <div className="postCard"> return <div className="postCard">
<div className="wrapper"> <div className="wrapper">
<PostHeader <PostHeader
@ -92,8 +185,10 @@ export default class PostCard extends React.Component {
</div> </div>
<div className="actionsWrapper"> <div className="actionsWrapper">
<PostActions <PostActions
likes={this.props.data.likes.length} onClickLike={this.onClickLike}
comments={this.props.data.comments.length} defaultLiked={defaultLiked}
likes={this.state.data.likes.length}
comments={this.state.data.comments.length}
/> />
</div> </div>
</div> </div>

View File

@ -190,4 +190,157 @@
to { to {
opacity: 1; opacity: 1;
} }
}
@color-heart : #EA442B;
@likeAnimationDuration : .5s;
@likeAnimationEasing : cubic-bezier(.7, 0, .3, 1);
.likeButton {
display: flex;
align-items: center;
justify-content: center;
.ripple,
.ripple:before,
.ripple:after {
position : relative;
box-sizing: border-box;
}
font-size : 40px;
border : none;
border-radius: 50%;
width : 1em;
height : 1em;
padding : 0;
margin : 0;
outline : none;
z-index : 2;
transition : transform @likeAnimationDuration @likeAnimationEasing;
cursor : pointer;
background-color: transparent;
&:before {
z-index : -1;
content : '';
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
border-radius: inherit;
transition : inherit;
}
&:after {
content : '';
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
border-radius : inherit;
z-index : -1;
}
.heart {
position: relative;
>path {
stroke : var(--primaryColor);
stroke-width: 2;
transition : fill @likeAnimationDuration @likeAnimationEasing;
fill : var(--primaryColor);
}
&.empty {
>path {
stroke : var(--primaryColor);
stroke-width: 2;
transition : fill @likeAnimationDuration @likeAnimationEasing;
fill : transparent;
}
}
&.clicked {
animation: heart-bounce @likeAnimationDuration @likeAnimationEasing;
@keyframes heart-bounce {
40% {
transform: scale(0.7);
}
0%,
80%,
100% {
transform: scale(1);
}
}
}
animation: none;
}
.ripple {
position: absolute;
height : 1em;
width : 1em;
border-radius: 50%;
overflow : hidden;
z-index : 1;
&:before {
content : '';
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
border : .4em solid var(--primaryColor);
border-radius: inherit;
transform : scale(0);
}
&.clicked {
&:before {
animation: ripple-out @likeAnimationDuration @likeAnimationEasing;
}
}
}
}
@keyframes ripple-out {
from {
transform: scale(0);
}
to {
transform: scale(5);
}
}
@keyframes depress {
from,
to {
transform: none;
}
50% {
transform: translateY(5%) scale(0.9);
}
}
@keyframes depress-shadow {
from,
to {
transform: none;
}
50% {
transform: scale(0.5);
}
} }