Catalyst / admin/Bid-Sentinel 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Bid-Sentinel

public

Bid Scrape and Tracking Application with AI Capability

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Bid-Sentinel / bid-sentinel-v2 / frontend / src / components / CommentsSection.jsx 3003 B · main
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { useEffect, useState } from "react";
import { addComment, deleteComment, fetchComments } from "../api/client";
import { useAuth } from "../context/AuthContext.jsx";

export default function CommentsSection({ tenderId }) {
  const { user } = useAuth();
  const [comments, setComments] = useState([]);
  const [body, setBody] = useState("");
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);

  useEffect(() => {
    fetchComments(tenderId)
      .then(setComments)
      .catch(() => {})
      .finally(() => setLoading(false));
  }, [tenderId]);

  async function submit(e) {
    e.preventDefault();
    const text = body.trim();
    if (!text) return;
    setSaving(true);
    try {
      const created = await addComment(tenderId, text);
      setComments((prev) => [...prev, created]);
      setBody("");
    } finally {
      setSaving(false);
    }
  }

  async function remove(c) {
    await deleteComment(tenderId, c.id);
    setComments((prev) => prev.filter((x) => x.id !== c.id));
  }

  const fmt = (d) => new Date(d).toLocaleString("en-GB");
  const canDelete = (c) => c.author_email === user?.email || user?.role === "admin";

  return (
    <div>
      <p className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">
        Comments
      </p>

      <div className="mb-3 space-y-2">
        {loading && <p className="text-xs text-slate-400">Loading</p>}
        {!loading && comments.length === 0 && (
          <p className="text-xs text-slate-400">No comments yet.</p>
        )}
        {comments.map((c) => (
          <div key={c.id} className="rounded-md border border-slate-200 bg-white px-3 py-2">
            <div className="mb-0.5 flex items-center justify-between">
              <span className="text-xs font-medium text-slate-700">{c.author_email}</span>
              <span className="flex items-center gap-2 text-xs text-slate-400">
                {fmt(c.created_at)}
                {canDelete(c) && (
                  <button onClick={() => remove(c)} className="text-slate-400 hover:text-red-500" title="Delete">
                    
                  </button>
                )}
              </span>
            </div>
            <p className="whitespace-pre-wrap text-sm text-slate-700">{c.body}</p>
          </div>
        ))}
      </div>

      <form onSubmit={submit} className="flex items-start gap-2">
        <textarea
          value={body}
          onChange={(e) => setBody(e.target.value)}
          rows={2}
          placeholder="Add a comment…"
          className="flex-1 rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-brand focus:ring-2 focus:ring-brand/20"
        />
        <button
          disabled={saving}
          className="rounded-lg bg-brand px-4 py-2 text-sm font-semibold text-white hover:bg-brand-dark disabled:opacity-60"
        >
          {saving ? "…" : "Post"}
        </button>
      </form>
    </div>
  );
}