154 lines
5.3 KiB
TypeScript
154 lines
5.3 KiB
TypeScript
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 (
|
||
<div className={depth > 0 ? 'ml-8 mt-4 border-l-2 border-border pl-4' : ''}>
|
||
<Card>
|
||
<CardContent className="pt-6">
|
||
<div className="flex items-start justify-between mb-4">
|
||
<div>
|
||
<div className="font-medium">
|
||
{comment.user?.username || 'Анонимен корисник'}
|
||
</div>
|
||
<div className="text-sm text-muted-foreground">
|
||
{format(new Date(comment.createdAt), 'dd MMMM yyyy, HH:mm', { locale: mk })}
|
||
</div>
|
||
</div>
|
||
{comment.user?.role === 'admin' && (
|
||
<span className="px-2 py-1 text-xs rounded-full bg-primary/10 text-primary">
|
||
Администратор
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
<p className="whitespace-pre-wrap mb-4">{comment.content}</p>
|
||
|
||
{/* Reply button and form */}
|
||
{isAuthenticated && canReply && (
|
||
<div className="mt-4">
|
||
{!showReplyForm ? (
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => setShowReplyForm(true)}
|
||
className="text-sm"
|
||
>
|
||
Одговори
|
||
</Button>
|
||
) : (
|
||
<div className="mt-4 p-4 border rounded-lg bg-muted/20">
|
||
<form onSubmit={handleSubmitReply}>
|
||
<Textarea
|
||
placeholder="Вашиот одговор..."
|
||
value={replyContent}
|
||
onChange={(e) => setReplyContent(e.target.value)}
|
||
className="min-h-[80px] mb-3"
|
||
disabled={isSubmittingReply}
|
||
autoFocus
|
||
/>
|
||
<div className="flex justify-end gap-2">
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={() => {
|
||
setShowReplyForm(false);
|
||
setReplyContent('');
|
||
}}
|
||
disabled={isSubmittingReply}
|
||
>
|
||
Откажи
|
||
</Button>
|
||
<Button
|
||
type="submit"
|
||
size="sm"
|
||
disabled={!replyContent.trim() || isSubmittingReply}
|
||
>
|
||
{isSubmittingReply ? 'Поставување...' : 'Постави одговор'}
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Reactions (if implemented) */}
|
||
{comment.reactions && (
|
||
<div className="flex items-center gap-4 mt-4 pt-4 border-t">
|
||
<div className="flex items-center gap-1">
|
||
<span className="text-sm text-muted-foreground">👍 {comment.reactions.likes}</span>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
<span className="text-sm text-muted-foreground">👎 {comment.reactions.dislikes}</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Render replies recursively */}
|
||
{comment.replies && comment.replies.length > 0 && (
|
||
<div className="mt-4">
|
||
{comment.replies.map((reply) => (
|
||
<CommentItem
|
||
key={reply.id}
|
||
comment={reply}
|
||
articleId={articleId}
|
||
liveBlogId={liveBlogId}
|
||
depth={depth + 1}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
} |