LikeComment Doc
LikeComment Doc
LikeComment Doc
@PutMapping("/api/comments/like/{commentId}")
public Comment likeComment(@RequestHeader("Authorization") String jwt,
@PathVariable Integer commentId) throws Exception {
return likedComment;
}
Giúp tôi xây dựng chức năng like comment , mỗi khi tôi bấm vào like comment thì
thay đổi trạng thái icon, kiểm tra trong cơ sở dữ liệu nếu đã llike thì hiển thị
FavouriteIcon còn chưa like comment thì hiển thị FavouriteBorderIcon.Xây dựng theo
hướng comment.actionType.js , comment.action.js, comment.reducer.js và component
PostCard:
import React, { useState, useEffect } from 'react';
import { Avatar, Card, CardActions, CardContent, CardHeader, Divider, IconButton,
Menu, MenuItem, Typography } from '@mui/material';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import FavoriteIcon from '@mui/icons-material/Favorite';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
import ShareIcon from '@mui/icons-material/Share';
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
import BookmarkIcon from '@mui/icons-material/Bookmark';
import DeleteIcon from '@mui/icons-material/Delete';
import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon';
import { useDispatch, useSelector } from 'react-redux';
import { createCommentAction, likePostAction, savePostAction, deletePostAction,
getAllPostAction } from '../../Redux/Post/post.action.js';
import { isLikedByRegUser } from '../../Utils/isLikedByReqUser';
import { format, differenceInMinutes, differenceInHours, isToday } from 'date-fns';
import { API_BASE_URL } from '../../config/api';
import axios from 'axios';
import emojiList from './emojiList';
useEffect(() => {
setNumLikes(item.liked.length);
setIsSaved(item.saved);
}, [item]);
useEffect(() => {
const savedStatus = localStorage.getItem(`saved_post_${item.id}`);
if (savedStatus !== null) {
setIsSaved(JSON.parse(savedStatus));
}
}, []);
useEffect(() => {
dispatch(getAllPostAction())
}, [post.newComment])
if (isToday(new Date(createdAt))) {
if (differenceInMinutesValue < 60) {
return `${differenceInMinutesValue} phút trước`;
} else {
return `${differenceInHoursValue} giờ trước`;
}
} else {
return format(new Date(createdAt), "dd/MM/yyyy 'lúc' HH:mm");
}
};
return (
<Card className='w-full'>
<CardHeader
avatar={
<Avatar
sx={{
bgcolor: '#191c29',
color: 'rgb(88,199,250)',
fontSize: '.8rem'
}}
aria-label="recipe">
{item.user?.lastName}
</Avatar>
}
action={
<div>
<IconButton className="hover:text-blue-400" aria-
label="settings" onClick={handleClickMenu}>
<MoreVertIcon />
</IconButton>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleCloseMenu}
>
<MenuItem onClick={handleDeletePost}>
<DeleteIcon />
<Typography variant="inherit"> Delete post
</Typography>
</MenuItem>
</Menu>
</div>
}
title={
<div style={{ display: 'flex', justifyContent: 'space-between',
alignItems: 'center', width: '100%' }}>
<Typography variant="subtitle1" component="div">
{item.user?.firstName + " " + item.user?.lastName}
</Typography>
<Typography variant="body2" color="textSecondary">
{formattedCreatedAt}
</Typography>
</div>
}
subheader={"@" + item.user?.firstName.toLowerCase() + "_" +
item.user?.lastName.toLowerCase()}
/>
<CardContent>
<Typography variant="body2" color="text.secondary" className="max-
w-4xl break-words whitespace-pre-line">
<p className='text-lg '>
{showFullCaption ? item.caption : (item.caption.length > 50
? item.caption.slice(0, 100) + '...' : item.caption)}
{item.caption.length > 100 && !showFullCaption && (
<button className="text-gray-500 hover:underline"
onClick={toggleShowFullCaption}> More</button>
)}
{showFullCaption && (
<button className="text-gray-500 hover:underline"
onClick={toggleShowFullCaption}> Hide</button>
)}
</p>
</Typography>
</CardContent>
<div className='flex flex-col gap-4'>
{!item.video || !item.image ? (
<div className='w-full'>
{item.image && <img className='w-full h-auto'
src={item.image} alt="" />}
{item.video && (
<video className='w-full h-auto' controls>
<source src={item.video} type="video/mp4" />
Your browser does not support the video tag.
</video>
)}
</div>
) : (
<div className='h-10rem w-10rem'>
{item.video && item.image && (
<div className='flex flex-row gap-4'>
<img className='w-1/2 h-auto' src={item.image}
alt="" />
<video className='w-1/2 h-auto' controls>
<source src={item.video} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
)}
</div>
)}
</div>
<CardActions disableSpacing className='flex justify-between'>
<div>
<Typography variant="body2" color="textSecondary">
{numLikes + " likes "}
{item.comments.length + " comments"}
</Typography>
<IconButton className="hover:text-red-500"
onClick={handleLikePost}>
{isLikedByRegUser(auth.user.id, item) ? <FavoriteIcon
style={{ color: 'red' }} /> : <FavoriteBorderIcon />}
</IconButton>
<IconButton className="hover:text-blue-400"
onClick={handleShowComment}>
<ChatBubbleOutlineIcon />
</IconButton>
<IconButton>
<ShareIcon style={{ color: 'lightblue' }} />
</IconButton>
</div>
<div>
<IconButton className="hover:text-yellow-400"
onClick={handleSavePost}>
{auth.isAuthenticated && isSaved ? <BookmarkIcon
style={{ color: 'yellow' }} /> : <BookmarkBorderIcon />}
</IconButton>
</div>
</CardActions>
{showComments && (
<section>
<div className='flex items-center space-x-5 mx-3 my-5'>
<Avatar
sx={{
bgcolor: '#191c29',
color: 'rgb(88,199,250)',
fontSize: '.8rem'
}}
aria-label="recipe">
{auth.user.lastName}
</Avatar>
<input
value={commentContent}
onChange={(e) => setCommentContent(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') {
handleCreateComment(commentContent);
setCommentContent('');
}
}}
className='w-full outline-none bg-transparent border
border-[#3b4054] rounded-full px-5 py-2'
type='text'
placeholder='Write your comment.'
/>
<IconButton onClick={toggleEmojiPicker}>
<InsertEmoticonIcon className='text-yellow-400' />
</IconButton>
{showEmojiPicker && (
<div className="emoji-picker ">
<div className=" overflow-auto max-h-36 max-w-7xl
cursor-pointer ">
{emojiList.map((emoji, index) => (
<span className="text-lg" key={index}
onClick={() => handleEmojiClick(emoji)}>{emoji}</span>
))}
</div>
</div>
)}
</div>
<Divider />
<div className='mx-3 space-y-2 my-5 text-xs'>
{item.comments?.map((comment) => (
<div className='flex flex-row'>
<div className='flex items-center space-x-5'
key={comment.id}>
<Avatar
sx={{
bgcolor: '#191c29',
color: 'rgb(88,199,250)',
fontSize: '.8rem'
}}
aria-label="recipe">
{comment.user.lastName}
</Avatar>
<div className='flex flex-col'>
<Typography variant="body2"
color="textSecondary">
<span className='text-white text-lg'
dangerouslySetInnerHTML={{ __html: comment.content }} />
</Typography>
<Typography variant="body2"
color="textSecondary">
{formatCreatedAt(comment.createAt)}
</Typography>
</div>
</div>
<div className="">
<IconButton >
<FavoriteBorderIcon />
</IconButton>
</div>
</div>
))}
</div>
</section>
)}
</Card>
);
}