import { createSignal, For, Show, onMount } from "solid-js"; import CommentLock from "./CommentLock"; import CommentForm from "./CommentForm"; import type { CommentWithReplies, CommentRecord } from "../../../types/comments"; interface ToastMessage { type: "success" | "error"; message: string; } interface CommentsProps { postSlug: string; } export default function Comments(props: CommentsProps) { const [isAuthenticated, setIsAuthenticated] = createSignal(false); const [currentUser, setCurrentUser] = createSignal<{ id: string; name: string; email: string; avatar?: string; } | undefined>(undefined); const [isLoading, setIsLoading] = createSignal(true); const [comments, setComments] = createSignal([]); const [replyTo, setReplyTo] = createSignal(null); const [editingComment, setEditingComment] = createSignal(null); const [editContent, setEditContent] = createSignal(""); const [toast, setToast] = createSignal(null); const showToast = (message: ToastMessage): void => { setToast(message); setTimeout(() => setToast(null), 3000); }; onMount(async () => { try { const response = await fetch("/api/auth/me", { method: "GET", credentials: "include", }); const data = await response.json(); if (data.authenticated && data.user) { setIsAuthenticated(true); setCurrentUser({ id: data.user.id, name: data.user.name || "Пользователь", email: data.user.email, avatar: data.user.avatar, }); } } catch (error) { console.error("[Comments] Ошибка проверки авторизации:", error); } finally { setIsLoading(false); } }); const loadComments = async () => { try { const response = await fetch( `/api/comments?post_slug=${encodeURIComponent(props.postSlug)}&parent=null`, { method: "GET", credentials: "include", } ); if (!response.ok) { throw new Error("Failed to fetch comments"); } const data = await response.json(); const commentsWithReplies: CommentWithReplies[] = await Promise.all( data.items.map(async (comment: CommentRecord) => { const repliesResponse = await fetch( `/api/comments?post_slug=${encodeURIComponent(props.postSlug)}&parent=${comment.id}`, { method: "GET", credentials: "include", } ); const repliesData = await repliesResponse.json(); return { ...comment, replies: repliesData.items || [], }; }) ); setComments(commentsWithReplies); } catch (error) { console.error("[Comments] Ошибка загрузки комментариев:", error); } }; onMount(() => { if (props.postSlug) { loadComments(); } }); const handleNewComment = async (data: { content: string }) => { try { const response = await fetch("/api/comments", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ content: data.content, post_slug: props.postSlug, parent: null, }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Failed to create comment"); } await loadComments(); } catch (error) { console.error("[Comments] Ошибка создания комментария:", error); showToast({ type: "error", message: "Не удалось отправить комментарий" }); } }; const handleReply = async (commentId: string, data: { content: string }) => { try { const response = await fetch("/api/comments", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ content: data.content, post_slug: props.postSlug, parent: commentId, }), }); if (!response.ok) { const errorData = await response.json(); if (errorData.error) { showToast({ type: "error", message: errorData.error }); return; } throw new Error(errorData.error || "Failed to create reply"); } showToast({ type: "success", message: "Ответ успешно отправлен!" }); await loadComments(); setReplyTo(null); } catch (error) { console.error("[Comments] Ошибка создания ответа:", error); showToast({ type: "error", message: "Не удалось создать ответ. Попробуйте позже." }); } }; const handleEdit = async (commentId: string, data: { content: string }) => { try { const response = await fetch(`/api/comments/${commentId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ content: data.content, }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Failed to update comment"); } showToast({ type: "success", message: "Комментарий обновлен!" }); await loadComments(); setEditingComment(null); setEditContent(""); } catch (error) { console.error("[Comments] Ошибка обновления комментария:", error); showToast({ type: "error", message: "Не удалось обновить комментарий" }); } }; const handleDelete = async (commentId: string) => { try { const response = await fetch(`/api/comments/${commentId}`, { method: "DELETE", credentials: "include", }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Failed to delete comment"); } showToast({ type: "success", message: "Комментарий удален!" }); await loadComments(); setEditingComment(null); } catch (error) { console.error("[Comments] Ошибка удаления комментария:", error); showToast({ type: "error", message: "Не удалось удалить комментарий" }); } }; const handleReplyClick = (commentId: string) => { const currentUserId = currentUser()?.id; const comment = comments().find(c => c.id === commentId); const commentUserId = comment?.user; if (commentUserId && currentUserId === commentUserId) { showToast({ type: "error", message: "Нельзя ответить на свой комментарий" }); return; } setReplyTo(replyTo() === commentId ? null : commentId); }; const handleEditClick = (commentId: string, content: string) => { setEditingComment(commentId); setEditContent(content); }; const Avatar = (props: { author: string; avatar?: string; size?: "sm" | "md"; }) => { const size = props.size || "md"; const sizeClasses = { sm: "w-8 h-8 text-sm", md: "w-12 h-12 text-lg" }; return ( {props.author.charAt(0)} } > {props.author} ); }; const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString("ru-RU", { day: "numeric", month: "long", year: "numeric", hour: "2-digit", minute: "2-digit", }); }; const isCommentOwner = (commentUserId?: string) => { return currentUser()?.id === commentUserId; }; return ( <> {(t) => (
{t().message}
)}
{isLoading() ? (

Комментарии

0

Загрузка...

) : ( }>

Комментарии

{comments().length}
0} fallback={

Пока нет комментариев. Будьте первым!

} >
{(comment) => (
{comment.expand?.user?.firstName || comment.expand?.user?.name || "Аноним"} {formatDate(comment.created)}
{comment.content}

} > handleEdit(comment.id, data)} onCancel={() => { setEditingComment(null); setEditContent(""); }} onDelete={() => handleDelete(comment.id)} user={currentUser()} />
handleReply(comment.id, data)} onCancel={() => setReplyTo(null)} user={currentUser()} />
0}>
{(reply) => (
{reply.expand?.user?.firstName || reply.expand?.user?.name || "Аноним"} {formatDate(reply.created)}

{reply.content}

)}
)}
)} ); }