From 3374eb1ec0054e75feae83670a30ae4f8fe0fa8d Mon Sep 17 00:00:00 2001 From: echo Date: Wed, 4 Feb 2026 23:51:37 +0100 Subject: [PATCH] reply to comment implemented --- .../features/comments/CommentItem.tsx | 154 ++++++++++++++++++ .../features/comments/CommentSection.tsx | 31 +--- frontend/src/hooks/useLiveBlogStream.ts | 4 +- frontend/src/lib/api.ts | 60 ++++--- 4 files changed, 204 insertions(+), 45 deletions(-) create mode 100644 frontend/src/components/features/comments/CommentItem.tsx diff --git a/frontend/src/components/features/comments/CommentItem.tsx b/frontend/src/components/features/comments/CommentItem.tsx new file mode 100644 index 0000000..94c9efc --- /dev/null +++ b/frontend/src/components/features/comments/CommentItem.tsx @@ -0,0 +1,154 @@ +import React, { useState } from 'react'; +import { useAuth } from '../../../contexts/AuthContext'; +import { useCreateComment } from '../../../queries/comments'; +import { Button } from '../../ui/button'; +import { Textarea } from '../../ui/textarea'; +import { Card, CardContent } from '../../ui/card'; +import { format } from 'date-fns'; +import { mk } from 'date-fns/locale'; +import type { Comment } from '../../../lib/api'; + +interface CommentItemProps { + comment: Comment; + articleId?: string; + liveBlogId?: string; + depth?: number; +} + +export function CommentItem({ comment, articleId, liveBlogId, depth = 0 }: CommentItemProps) { + const { isAuthenticated } = useAuth(); + const [showReplyForm, setShowReplyForm] = useState(false); + const [replyContent, setReplyContent] = useState(''); + const [isSubmittingReply, setIsSubmittingReply] = useState(false); + + const createCommentMutation = useCreateComment(); + + const handleSubmitReply = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!replyContent.trim() || !isAuthenticated) return; + + setIsSubmittingReply(true); + try { + await createCommentMutation.mutateAsync({ + content: replyContent, + articleId, + liveBlogId, + parentCommentId: comment.id, + }); + setReplyContent(''); + setShowReplyForm(false); + } catch (error) { + console.error('Failed to post reply:', error); + } finally { + setIsSubmittingReply(false); + } + }; + + // Maximum depth to prevent infinite nesting (optional) + const maxDepth = 5; + const canReply = depth < maxDepth; + + return ( +
0 ? 'ml-8 mt-4 border-l-2 border-border pl-4' : ''}> + + +
+
+
+ {comment.user?.username || 'Анонимен корисник'} +
+
+ {format(new Date(comment.createdAt), 'dd MMMM yyyy, HH:mm', { locale: mk })} +
+
+ {comment.user?.role === 'admin' && ( + + Администратор + + )} +
+ +

{comment.content}

+ + {/* Reply button and form */} + {isAuthenticated && canReply && ( +
+ {!showReplyForm ? ( + + ) : ( +
+
+