229 lines
9.9 KiB
TypeScript
229 lines
9.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { FaReply, FaTrash, FaEye, FaEyeSlash, FaEdit, FaSave, FaTimes } from "react-icons/fa";
|
|
|
|
type Reply = {
|
|
id: string;
|
|
text: string;
|
|
timestamp: string;
|
|
username: string;
|
|
hidden?: boolean;
|
|
like_count?: number;
|
|
};
|
|
|
|
type Comment = {
|
|
id: string;
|
|
text: string;
|
|
username: string;
|
|
timestamp: string;
|
|
like_count?: number;
|
|
hidden?: boolean;
|
|
replies?: { data: Reply[] };
|
|
};
|
|
|
|
interface SocialCommentItemProps {
|
|
comment: Comment;
|
|
onReply: (commentId: string, text: string) => void;
|
|
onDelete: (commentId: string, isReply?: boolean, parentId?: string) => void;
|
|
onHide: (commentId: string, currentHiddenStatus: boolean, isReply?: boolean) => void;
|
|
onEdit: (commentId: string, newText: string, isReply: boolean, parentId?: string) => void;
|
|
}
|
|
|
|
const SocialCommentItem = ({ comment, onReply, onDelete, onHide, onEdit }: SocialCommentItemProps) => {
|
|
const [replyText, setReplyText] = useState("");
|
|
const [isReplying, setIsReplying] = useState(false);
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [editText, setEditText] = useState(comment.text);
|
|
|
|
const handlePostReply = () => {
|
|
if (!replyText.trim()) return;
|
|
onReply(comment.id, replyText);
|
|
setReplyText("");
|
|
setIsReplying(false);
|
|
};
|
|
|
|
const handleSaveEdit = () => {
|
|
if (!editText.trim()) return;
|
|
onEdit(comment.id, editText, false, undefined);
|
|
setIsEditing(false);
|
|
};
|
|
|
|
return (
|
|
<div className="bg-[#242424] backdrop-blur-lg border border-white/10 p-4 rounded-xl mb-4 shadow text-sm">
|
|
{/* TOP LEVEL COMMENT */}
|
|
<div className="flex justify-between items-start">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className="font-bold text-white text-base">{comment.username}</span>
|
|
<span className="text-xs text-gray-500">{new Date(comment.timestamp).toLocaleString()}</span>
|
|
{comment.hidden && (
|
|
<span className="text-xs bg-red-500/20 text-red-400 px-2 py-0.5 rounded-full border border-red-500/20">
|
|
Hidden
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{isEditing ? (
|
|
<div className="mt-2">
|
|
<textarea
|
|
className="w-full bg-[#111111] border border-white/20 rounded p-2 text-gray-200 focus:outline-none focus:border-purple-500"
|
|
value={editText}
|
|
onChange={(e) => setEditText(e.target.value)}
|
|
/>
|
|
<div className="flex gap-2 mt-2">
|
|
<button onClick={handleSaveEdit} className="flex items-center gap-1 px-3 py-1 bg-green-600 rounded text-white text-xs hover:bg-green-700">
|
|
<FaSave /> Save
|
|
</button>
|
|
<button onClick={() => setIsEditing(false)} className="flex items-center gap-1 px-3 py-1 bg-gray-600 rounded text-white text-xs hover:bg-gray-700">
|
|
<FaTimes /> Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<p className="text-gray-300 leading-relaxed text-base">{comment.text}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ACTIONS BAR */}
|
|
<div className="flex items-center gap-4 mt-3 pt-2 border-t border-white/5">
|
|
<button
|
|
onClick={() => setIsReplying(!isReplying)}
|
|
className="flex items-center gap-1.5 text-indigo-400 hover:text-indigo-300 transition-colors text-xs font-medium uppercase tracking-wide"
|
|
>
|
|
<FaReply /> Reply
|
|
</button>
|
|
<button
|
|
onClick={() => setIsEditing(!isEditing)}
|
|
className="flex items-center gap-1.5 text-blue-400 hover:text-blue-300 transition-colors text-xs font-medium uppercase tracking-wide"
|
|
>
|
|
<FaEdit /> Edit
|
|
</button>
|
|
<button
|
|
onClick={() => onHide(comment.id, !!comment.hidden, false)}
|
|
className="flex items-center gap-1.5 text-yellow-500 hover:text-yellow-400 transition-colors text-xs font-medium uppercase tracking-wide"
|
|
>
|
|
{comment.hidden ? <FaEye /> : <FaEyeSlash />} {comment.hidden ? "Unhide" : "Hide"}
|
|
</button>
|
|
<button
|
|
onClick={() => onDelete(comment.id, false)}
|
|
className="flex items-center gap-1.5 text-red-500 hover:text-red-400 transition-colors text-xs font-medium uppercase tracking-wide"
|
|
>
|
|
<FaTrash /> Delete
|
|
</button>
|
|
</div>
|
|
|
|
{/* REPLY INPUT AREA */}
|
|
{isReplying && (
|
|
<div className="mt-3 flex gap-2 animate-fadeIn">
|
|
<input
|
|
type="text"
|
|
placeholder={`Reply to ${comment.username}...`}
|
|
value={replyText}
|
|
onChange={(e) => setReplyText(e.target.value)}
|
|
className="flex-1 bg-[#111111] border border-white/10 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-indigo-500"
|
|
/>
|
|
<button
|
|
onClick={handlePostReply}
|
|
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-medium transition-colors"
|
|
>
|
|
Send
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* REPLIES LIST */}
|
|
{comment.replies?.data && comment.replies.data.length > 0 && (
|
|
<div className="mt-4 pl-4 border-l-2 border-white/10 space-y-4">
|
|
{comment.replies.data.map((reply) => (
|
|
<ReplyItem
|
|
key={reply.id}
|
|
reply={reply}
|
|
parentId={comment.id}
|
|
onDelete={onDelete}
|
|
onHide={onHide}
|
|
onEdit={onEdit}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const ReplyItem = ({
|
|
reply,
|
|
parentId,
|
|
onDelete,
|
|
onHide,
|
|
onEdit
|
|
}: {
|
|
reply: Reply;
|
|
parentId: string;
|
|
onDelete: (id: string, isReply: boolean, parentId?: string) => void; // parentId needed to update state optimistically? Maybe not for DB but for state.
|
|
onHide: (id: string, currentStatus: boolean, isReply: boolean) => void;
|
|
onEdit: (id: string, text: string, isReply: boolean, parentId: string) => void;
|
|
}) => {
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [editText, setEditText] = useState(reply.text);
|
|
|
|
const handleSaveEdit = () => {
|
|
if (!editText.trim()) return;
|
|
onEdit(reply.id, editText, true, parentId);
|
|
setIsEditing(false);
|
|
};
|
|
|
|
return (
|
|
<div className="bg-[#1a1a1a] p-3 rounded-lg border border-white/5 relative group">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className="font-bold text-white text-sm">{reply.username}</span>
|
|
<span className="text-xs text-gray-500">{new Date(reply.timestamp).toLocaleString()}</span>
|
|
{reply.hidden && (
|
|
<span className="text-xs bg-red-500/20 text-red-400 px-1.5 rounded border border-red-500/20">Hidden</span>
|
|
)}
|
|
</div>
|
|
|
|
{isEditing ? (
|
|
<div className="mt-1">
|
|
<textarea
|
|
className="w-full bg-[#0a0a0a] border border-white/20 rounded p-2 text-gray-200 text-sm focus:outline-none focus:border-purple-500"
|
|
value={editText}
|
|
onChange={(e) => setEditText(e.target.value)}
|
|
/>
|
|
<div className="flex gap-2 mt-2">
|
|
<button onClick={handleSaveEdit} className="text-xs px-2 py-1 bg-green-600 rounded text-white cursor-pointer">Save</button>
|
|
<button onClick={() => setIsEditing(false)} className="text-xs px-2 py-1 bg-gray-600 rounded text-white cursor-pointer">Cancel</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<p className="text-gray-400 text-sm">{reply.text}</p>
|
|
)}
|
|
|
|
{/* ACTION BUTTONS FOR REPLY */}
|
|
<div className="flex gap-3 mt-2 opacity-60 group-hover:opacity-100 transition-opacity">
|
|
<button
|
|
onClick={() => setIsEditing(!isEditing)}
|
|
className="flex items-center gap-1 text-[10px] uppercase font-bold text-blue-400 hover:text-blue-300"
|
|
>
|
|
<FaEdit /> Edit
|
|
</button>
|
|
<button
|
|
onClick={() => onHide(reply.id, !!reply.hidden, true)}
|
|
className="flex items-center gap-1 text-[10px] uppercase font-bold text-yellow-500 hover:text-yellow-400"
|
|
>
|
|
{reply.hidden ? <FaEye /> : <FaEyeSlash />} {reply.hidden ? "Unhide" : "Hide"}
|
|
</button>
|
|
<button
|
|
onClick={() => onDelete(reply.id, true, parentId)}
|
|
className="flex items-center gap-1 text-[10px] uppercase font-bold text-red-500 hover:text-red-400"
|
|
>
|
|
<FaTrash /> Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SocialCommentItem;
|