Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

LikeComment Doc

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 7

Với api sau:

@PutMapping("/api/comments/like/{commentId}")
public Comment likeComment(@RequestHeader("Authorization") String jwt,
@PathVariable Integer commentId) throws Exception {

User user = userService.findUserByJwt(jwt);

Comment likedComment = commentService.likeComment(commentId, user.getId());

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';

const PostCard = ({ item }) => {


const [showComments, setShowComments] = useState(false);
const [numLikes, setNumLikes] = useState(0);
const [anchorEl, setAnchorEl] = useState(null);
const { auth } = useSelector(store => store);
const dispatch = useDispatch();
const [isSaved, setIsSaved] = useState(false);
const { post } = useSelector(store => store);
const [commentContent, setCommentContent] = useState('');
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [showFullCaption, setShowFullCaption] = useState(false); // State để kiểm
tra xem nội dung caption có đang được hiển thị đầy đủ hay không

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));
}
}, []);

const handleShowComment = () => setShowComments(!showComments);

const handleCreateComment = (content) => {


const reqData = {
postId: item.id,
data: { content }
};
dispatch(createCommentAction(reqData));
};

useEffect(() => {
dispatch(getAllPostAction())
}, [post.newComment])

const handleLikePost = () => {


dispatch(likePostAction(item.id));
};

const handleSavePost = async () => {


try {
const jwt = localStorage.getItem('jwt');
if (!jwt) {
console.error('JWT token not found in localStorage');
return;
}
const response = await
axios.put(`${API_BASE_URL}/posts/${item.id}/save`, {}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwt}`
}
});
if (response.status === 200) {
setIsSaved(prevIsSaved => !prevIsSaved);
} else {
console.error('Failed to save post');
}
} catch (error) {
console.error('Error saving post:', error);
}
};

const handleDeletePost = async () => {


try {
dispatch(deletePostAction(item.id));
} catch (error) {
console.error('Error deleting post:', error);
}
};

const formatCreatedAt = (createdAt) => {


const now = new Date();
const differenceInMinutesValue = differenceInMinutes(now, new
Date(createdAt));
const differenceInHoursValue = differenceInHours(now, new Date(createdAt));

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");
}
};

const formattedCreatedAt = formatCreatedAt(item.createAt);

const handleClickMenu = (event) => {


setAnchorEl(event.currentTarget);
};

const handleCloseMenu = () => {


setAnchorEl(null);
};

const handleEmojiClick = (emoji) => {


setCommentContent((prevContent) => prevContent + emoji);
};

const toggleEmojiPicker = () => {


setShowEmojiPicker(!showEmojiPicker);
};

const toggleShowFullCaption = () => {


setShowFullCaption(!showFullCaption);
};

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>
);
}

export default PostCard;

You might also like