| 1 | import React, { useEffect, useState } from 'react'
|
|---|
| 2 | import { Heart } from 'lucide-react'
|
|---|
| 3 | import { useNavigate } from 'react-router-dom'
|
|---|
| 4 | import axios from 'axios'
|
|---|
| 5 | import { useAuthStore } from '../../store/authStore'
|
|---|
| 6 | import { useNotificationStore } from '../../store/notificationStore'
|
|---|
| 7 | import { useUIStore } from '../../store/uiStore'
|
|---|
| 8 |
|
|---|
| 9 | const API = 'https://localhost:7125/api'
|
|---|
| 10 |
|
|---|
| 11 | function getAuthHeaders() {
|
|---|
| 12 | try {
|
|---|
| 13 | const token = JSON.parse(localStorage.getItem('chapterx-auth') || '{}')?.state?.token
|
|---|
| 14 | return token ? { Authorization: `Bearer ${token}` } : {}
|
|---|
| 15 | } catch { return {} }
|
|---|
| 16 | }
|
|---|
| 17 |
|
|---|
| 18 | interface LikeButtonProps {
|
|---|
| 19 | storyId: number
|
|---|
| 20 | authorUserId: number
|
|---|
| 21 | totalLikes: number
|
|---|
| 22 | onCountChange?: (count: number) => void
|
|---|
| 23 | }
|
|---|
| 24 |
|
|---|
| 25 | export const LikeButton: React.FC<LikeButtonProps> = ({ storyId, authorUserId, totalLikes, onCountChange }) => {
|
|---|
| 26 | const navigate = useNavigate()
|
|---|
| 27 | const { currentUser } = useAuthStore()
|
|---|
| 28 | const { addNotification } = useNotificationStore()
|
|---|
| 29 | const { addToast } = useUIStore()
|
|---|
| 30 | const [liked, setLiked] = useState(false)
|
|---|
| 31 | const [count, setCount] = useState(totalLikes)
|
|---|
| 32 | const [loading, setLoading] = useState(false)
|
|---|
| 33 |
|
|---|
| 34 | useEffect(() => {
|
|---|
| 35 | axios.get(`${API}/likes/story/${storyId}`)
|
|---|
| 36 | .then(res => {
|
|---|
| 37 | const c = res.data?.count ?? totalLikes
|
|---|
| 38 | setCount(c)
|
|---|
| 39 | onCountChange?.(c)
|
|---|
| 40 | if (currentUser) {
|
|---|
| 41 | setLiked((res.data?.userIds ?? []).includes(currentUser.user_id))
|
|---|
| 42 | }
|
|---|
| 43 | })
|
|---|
| 44 | .catch(() => {})
|
|---|
| 45 | }, [storyId, currentUser?.user_id])
|
|---|
| 46 |
|
|---|
| 47 | const handleClick = async () => {
|
|---|
| 48 | if (!currentUser) {
|
|---|
| 49 | addToast('Please sign in to like stories.', 'info')
|
|---|
| 50 | navigate('/login')
|
|---|
| 51 | return
|
|---|
| 52 | }
|
|---|
| 53 | if (loading) return
|
|---|
| 54 | setLoading(true)
|
|---|
| 55 | try {
|
|---|
| 56 | if (liked) {
|
|---|
| 57 | await axios.delete(`${API}/likes/user/${currentUser.user_id}/story/${storyId}`, { headers: getAuthHeaders() })
|
|---|
| 58 | setLiked(false)
|
|---|
| 59 | setCount(c => { const n = c - 1; onCountChange?.(n); return n })
|
|---|
| 60 | addToast('Removed from likes', 'info')
|
|---|
| 61 | } else {
|
|---|
| 62 | await axios.post(`${API}/likes`, { userId: currentUser.user_id, storyId }, { headers: getAuthHeaders() })
|
|---|
| 63 | setLiked(true)
|
|---|
| 64 | setCount(c => { const n = c + 1; onCountChange?.(n); return n })
|
|---|
| 65 | if (currentUser.user_id !== authorUserId) {
|
|---|
| 66 | await addNotification({
|
|---|
| 67 | recipientUserId: authorUserId,
|
|---|
| 68 | type: 'like',
|
|---|
| 69 | content: `${currentUser.username} liked your story.`,
|
|---|
| 70 | link: `/story/${storyId}`,
|
|---|
| 71 | })
|
|---|
| 72 | }
|
|---|
| 73 | addToast('Added to likes!', 'success')
|
|---|
| 74 | }
|
|---|
| 75 | } catch {
|
|---|
| 76 | addToast('Something went wrong.', 'error')
|
|---|
| 77 | }
|
|---|
| 78 | setLoading(false)
|
|---|
| 79 | }
|
|---|
| 80 |
|
|---|
| 81 | return (
|
|---|
| 82 | <button
|
|---|
| 83 | onClick={handleClick}
|
|---|
| 84 | disabled={loading}
|
|---|
| 85 | className={`flex items-center gap-2 px-4 py-2 rounded-xl border transition-all duration-200 ${
|
|---|
| 86 | liked
|
|---|
| 87 | ? 'bg-rose-500/20 border-rose-500/40 text-rose-400 hover:bg-rose-500/30'
|
|---|
| 88 | : 'bg-slate-800 border-slate-700 text-slate-400 hover:border-rose-500/40 hover:text-rose-400 hover:bg-rose-500/10'
|
|---|
| 89 | }`}
|
|---|
| 90 | >
|
|---|
| 91 | <Heart size={16} className={liked ? 'fill-rose-400' : ''} />
|
|---|
| 92 | <span className="text-sm font-medium">{count.toLocaleString()}</span>
|
|---|
| 93 | </button>
|
|---|
| 94 | )
|
|---|
| 95 | }
|
|---|