📄 Moderate.js
/home/palash/git/site/src/components/Moderate.js
Language: js • Lines: 126
import React, { useState, useEffect } from "react";
import {
  collection,
  query,
  orderBy,
  limit,
  getDocs,
  doc,
  updateDoc,
} from "firebase/firestore";
import { db } from "../firebase";
import { useAuth } from "../contexts/AuthContext";
import { Container, Spinner, Button, Form } from "react-bootstrap";
import { FaCheck, FaBan, FaExternalLinkAlt, FaUserCircle } from "react-icons/fa";
import { Link } from "react-router-dom";
import { col } from "../utils/firestorePath";

export default function Moderate() {
  const { profile } = useAuth();
  const [comments, setComments] = useState([]);
  const [loading, setLoading] = useState(true);
  const [filter, setFilter] = useState("pending");

  const loadComments = async () => {
    setLoading(true);
    try {
      const q = query(
        collection(db, col("comments")),
        orderBy("createdAt", "desc"),
        limit(100)
      );
      const snap = await getDocs(q);
      setComments(snap.docs.map((d) => ({ id: d.id, ...d.data() })));
    } catch (e) {
      console.error(e);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { loadComments(); }, []);

  const handleApprove = async (id) => {
    try { await updateDoc(doc(db, col("comments"), id), { status: "approved" }); await loadComments(); }
    catch (e) { console.error(e); }
  };

  const handleReject = async (id) => {
    try { await updateDoc(doc(db, col("comments"), id), { status: "rejected" }); await loadComments(); }
    catch (e) { console.error(e); }
  };

  if (profile?.role !== "moderator") return null;

  const filtered = filter === "all"
    ? comments
    : comments.filter((c) => c.status === filter);

  return (
    <Container className="py-4" style={{ maxWidth: 800 }}>
      <h4 className="mb-3">Comment Moderation</h4>

      <Form.Group className="mb-3">
        <Form.Select
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
          className="bg-dark text-light border-secondary"
          style={{ maxWidth: 200 }}
        >
          <option value="pending">Pending</option>
          <option value="rejected">Rejected</option>
          <option value="all">All</option>
        </Form.Select>
      </Form.Group>

      {loading ? (
        <div className="text-center py-5"><Spinner animation="border" variant="light" /></div>
      ) : filtered.length === 0 ? (
        <p className="text-secondary small">No comments found.</p>
      ) : (
        <div>
          {filtered.map((c) => (
            <div key={c.id} className="d-flex gap-2 mb-2 p-2 rounded" style={{ background: "#1e1e1e" }}>
              <div className="flex-shrink-0">
                {c.userPhoto ? (
                  <img src={c.userPhoto} alt="" width={28} height={28} className="rounded-circle" />
                ) : (
                  <FaUserCircle size={28} className="text-secondary" />
                )}
              </div>
              <div className="flex-grow-1">
                <div className="d-flex gap-2 align-items-baseline flex-wrap">
                  <strong className="small">{c.userName}</strong>
                  <span className="text-secondary" style={{ fontSize: "10px" }}>
                    {c.createdAt?.toDate?.()?.toLocaleDateString() || ""}
                  </span>
                  <span className={`badge ${c.status === "pending" ? "bg-warning text-dark" : c.status === "rejected" ? "bg-danger" : "bg-success"}`} style={{ fontSize: "9px" }}>
                    {c.status || "approved"}
                  </span>
                  {c.contentId && (
                    <Link to={`/content/${c.contentId}`} className="text-decoration-none" style={{ fontSize: "10px" }} target="_blank" rel="noopener">
                      <FaExternalLinkAlt className="me-1" />view
                    </Link>
                  )}
                </div>
                <p className="mb-1 small mt-1" style={{ whiteSpace: "pre-wrap" }}>{c.text}</p>
                <div className="d-flex gap-1">
                  {c.status !== "approved" && (
                    <Button size="sm" variant="outline-success" className="py-0 px-1" style={{ fontSize: "11px" }} onClick={() => handleApprove(c.id)}>
                      <FaCheck className="me-1" />Approve
                    </Button>
                  )}
                  {c.status !== "rejected" && (
                    <Button size="sm" variant="outline-warning" className="py-0 px-1" style={{ fontSize: "11px" }} onClick={() => handleReject(c.id)}>
                      <FaBan className="me-1" />Reject
                    </Button>
                  )}
                </div>
              </div>
            </div>
          ))}
        </div>
      )}
    </Container>
  );
}