source: chapterx-frontend/src/components/story/CommentSection.tsx

main
Last change on this file was 73b69b2, checked in by kikisrbinoska <srbinoskakristina07@…>, 3 months ago

Fixed reading lists,comments and likes

  • Property mode set to 100644
File size: 6.0 KB
RevLine 
[73b69b2]1import React, { useEffect, useState } from 'react'
[b62cefc]2import { MessageCircle, Trash2, Send } from 'lucide-react'
[73b69b2]3import axios from 'axios'
[b62cefc]4import { useAuthStore } from '../../store/authStore'
5import { useNotificationStore } from '../../store/notificationStore'
6import { useUIStore } from '../../store/uiStore'
7import { Avatar } from '../ui/Avatar'
8import { Button } from '../ui/Button'
9
[73b69b2]10const API = 'https://localhost:7125/api'
11
12function getAuthHeaders() {
13 try {
14 const token = JSON.parse(localStorage.getItem('chapterx-auth') || '{}')?.state?.token
15 return token ? { Authorization: `Bearer ${token}` } : {}
16 } catch { return {} }
[b62cefc]17}
18
19function timeAgo(str: string): string {
20 const diff = Date.now() - new Date(str).getTime()
21 const m = Math.floor(diff / 60000)
22 if (m < 1) return 'just now'
23 if (m < 60) return `${m}m ago`
24 const h = Math.floor(m / 60)
25 if (h < 24) return `${h}h ago`
26 return `${Math.floor(h / 24)}d ago`
27}
28
[73b69b2]29interface BackendComment {
30 id: number
31 content: string
32 userId: number
33 storyId: number
34 username: string
35 createdAt: string
36}
37
38interface CommentSectionProps {
39 storyId: number
40 authorUserId: number
41 onCountChange?: (count: number) => void
42}
43
44export const CommentSection: React.FC<CommentSectionProps> = ({ storyId, authorUserId, onCountChange }) => {
[b62cefc]45 const { currentUser } = useAuthStore()
46 const { addNotification } = useNotificationStore()
47 const { addToast } = useUIStore()
[73b69b2]48 const [comments, setComments] = useState<BackendComment[]>([])
[b62cefc]49 const [text, setText] = useState('')
50 const [submitting, setSubmitting] = useState(false)
51
[73b69b2]52 useEffect(() => {
53 axios.get(`${API}/comments/story/${storyId}`)
54 .then(res => {
55 const data = res.data ?? []
56 setComments(data)
57 onCountChange?.(data.length)
58 })
59 .catch(() => {})
60 }, [storyId])
[b62cefc]61
62 const handleSubmit = async () => {
63 if (!text.trim() || !currentUser) return
64 setSubmitting(true)
[73b69b2]65 try {
66 const res = await axios.post(`${API}/comments`, {
67 content: text.trim(),
68 userId: currentUser.user_id,
69 storyId,
70 }, { headers: getAuthHeaders() })
71 const newComment: BackendComment = {
72 id: res.data?.id ?? Date.now(),
73 content: text.trim(),
74 userId: currentUser.user_id,
75 storyId,
76 username: currentUser.username,
77 createdAt: new Date().toISOString(),
78 }
79 setComments(prev => { const next = [newComment, ...prev]; onCountChange?.(next.length); return next })
80 if (currentUser.user_id !== authorUserId) {
81 await addNotification({
82 recipientUserId: authorUserId,
83 type: 'comment',
84 content: `${currentUser.username} commented: "${text.trim().slice(0, 60)}${text.length > 60 ? '...' : ''}"`,
85 link: `/story/${storyId}`,
86 })
87 }
88 setText('')
89 addToast('Comment posted!')
90 } catch {
91 addToast('Failed to post comment.', 'error')
[b62cefc]92 }
93 setSubmitting(false)
[73b69b2]94 }
95
96 const handleDelete = async (commentId: number) => {
97 try {
98 await axios.delete(`${API}/comments/${commentId}`, { headers: getAuthHeaders() })
99 setComments(prev => { const next = prev.filter(c => c.id !== commentId); onCountChange?.(next.length); return next })
100 addToast('Comment deleted', 'info')
101 } catch {
102 addToast('Failed to delete comment.', 'error')
103 }
[b62cefc]104 }
105
106 return (
107 <div className="space-y-4">
108 <div className="flex items-center gap-2 mb-4">
109 <MessageCircle size={18} className="text-indigo-400" />
[73b69b2]110 <h3 className="text-white font-semibold">Comments ({comments.length})</h3>
[b62cefc]111 </div>
112
113 {currentUser ? (
114 <div className="flex gap-3">
115 <Avatar name={currentUser.name} size="sm" />
116 <div className="flex-1">
117 <textarea
118 value={text}
119 onChange={e => setText(e.target.value)}
120 placeholder="Share your thoughts..."
121 rows={3}
122 className="w-full bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-none"
123 />
124 <div className="flex justify-end mt-2">
[73b69b2]125 <Button size="sm" onClick={handleSubmit} loading={submitting} disabled={!text.trim()}>
[b62cefc]126 <Send size={14} />
127 Post Comment
128 </Button>
129 </div>
130 </div>
131 </div>
132 ) : (
133 <div className="bg-slate-800 border border-slate-700 rounded-xl p-4 text-center">
134 <p className="text-slate-400 text-sm">
135 <a href="/login" className="text-indigo-400 hover:text-indigo-300">Sign in</a> to leave a comment.
136 </p>
137 </div>
138 )}
139
140 <div className="space-y-3">
[73b69b2]141 {comments.length === 0 ? (
[b62cefc]142 <div className="text-center py-8 text-slate-500 text-sm">
143 No comments yet. Be the first to share your thoughts!
144 </div>
145 ) : (
[73b69b2]146 comments.map(comment => (
147 <div key={comment.id} className="flex gap-3 p-4 bg-slate-800 rounded-xl border border-slate-700">
148 <Avatar name={comment.username} size="sm" />
149 <div className="flex-1 min-w-0">
150 <div className="flex items-center justify-between">
151 <span className="text-sm font-medium text-white">{comment.username}</span>
152 <div className="flex items-center gap-2">
153 <span className="text-xs text-slate-500">{timeAgo(comment.createdAt)}</span>
154 {(currentUser?.user_id === comment.userId || currentUser?.role === 'admin') && (
155 <button onClick={() => handleDelete(comment.id)} className="text-slate-500 hover:text-rose-400 transition-colors">
156 <Trash2 size={13} />
157 </button>
158 )}
159 </div>
160 </div>
161 <p className="text-slate-300 text-sm mt-1 leading-relaxed">{comment.content}</p>
162 </div>
163 </div>
[b62cefc]164 ))
165 )}
166 </div>
167 </div>
168 )
169}
Note: See TracBrowser for help on using the repository browser.