Een dating-app op basis van brieven, niet van gezichten!

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

9.3 kB Ruw bestand
import { useEffect, useState } from "react";
import { INK, RED, BLUE, PAPER, SKY, MUTE, FONT_MONO, FONT_SERIF } from "../../theme";
import { REPORT_REASONS, REPORT_DETAILS_MAX, MODERATION_CONTACT } from "../../constants";
import AirmailEdge from "../../components/AirmailEdge";

/*
 * Melden: dezelfde dialoog voor een persoon en voor één brief (`letter` bepaalt welk van de twee).
 *
 * Melden is niet hetzelfde als blokkeren, en de tekst zegt dat ook: blokkeren regel je voor jezelf
 * en werkt meteen, melden gaat naar de beheerder en die kan iets doen wat jij niet kunt. Wie meldt,
 * wil meestal allebei — vandaar het vinkje, aangevinkt, maar uit te zetten (soms wil je juist nog
 * kunnen zien wat er gebeurt).
 *
 * onSubmit({ reason, details, block }) → Promise<{ id, blocked }>. Daarna blijft de dialoog staan met
 * een bevestiging: melden zonder terugkoppeling voelt als in een put roepen.
 */
export default function ReportDialog({ subject, letter = null, canBlock = false, onSubmit, onClose }) {
  const [reason, setReason] = useState(null);
  const [details, setDetails] = useState("");
  const [block, setBlock] = useState(canBlock);
  const [sending, setSending] = useState(false);
  const [error, setError] = useState(null);
  const [done, setDone] = useState(null); // { blocked } na versturen

  const title = letter ? `Brief van ${subject.name} melden` : `${subject.name} melden`;

  /* Escape sluit; de pagina eronder scrolt intussen niet mee (net als bij de brief-dialoog) */
  useEffect(() => {
    const onKey = (e) => e.key === "Escape" && !sending && onClose();
    document.addEventListener("keydown", onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = prev;
    };
  });

  const submit = async () => {
    if (!reason || sending) return;
    setSending(true);
    setError(null);
    try {
      const result = await onSubmit({ reason, details: details.trim(), block });
      setDone({ blocked: result?.blocked ?? false });
    } catch (e) {
      setError(e.message);
      setSending(false);
    }
  };

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label={title}
      className="dialog-backdrop"
      style={{ background: "rgba(31,44,85,0.55)" }}
      onClick={(e) => e.target === e.currentTarget && !sending && onClose()}
    >
      <div className="dialog-sheet" style={{ background: "#fff" }}>
        <AirmailEdge />
        <div style={{ padding: 20 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4 }}>
            <h2 style={{ margin: 0, fontSize: 20 }}>{done ? "Melding verstuurd" : title}</h2>
            <button
              onClick={() => !sending && onClose()}
              aria-label="Sluiten"
              style={{ background: "none", border: "none", fontSize: 22, color: MUTE, lineHeight: 1 }}
            >
              ×
            </button>
          </div>

          {done ? (
            <>
              <p style={{ fontFamily: FONT_SERIF, fontSize: 15, lineHeight: 1.6, margin: "10px 0 0" }}>
                Dank je. De beheerder van Liefdesbrieven kijkt ernaar en kan de brieven bekijken die bij de melding
                horen. {subject.name} krijgt geen bericht van je melding en ziet niet dat je gemeld hebt.
              </p>
              {done.blocked && (
                <p style={{ fontFamily: FONT_SERIF, fontSize: 15, lineHeight: 1.6, margin: "10px 0 0" }}>
                  {subject.name} is ook geblokkeerd: jullie zien elkaar niet meer en niemand kan de ander nog
                  schrijven. Terugdraaien kan bij Mijn profiel.
                </p>
              )}
              <p style={{ fontFamily: FONT_MONO, fontSize: 11, color: MUTE, margin: "12px 0 0", lineHeight: 1.6 }}>
                Gaat het om acuut gevaar, bel dan 112. Wil je zelf iets kwijt over de afhandeling: {MODERATION_CONTACT}.
              </p>
              <button
                onClick={onClose}
                style={{
                  width: "100%", padding: "14px 0", marginTop: 18, fontSize: 16, fontWeight: 700,
                  border: "none", background: INK, color: "#fff",
                }}
              >
                Sluiten
              </button>
            </>
          ) : (
            <>
              <p style={{ fontFamily: FONT_MONO, fontSize: 11, color: MUTE, margin: "0 0 14px", lineHeight: 1.6 }}>
                gaat naar de beheerder · {subject.name} hoort hier niets van
              </p>

              {/* Waarover gaat het: bij een brief het begin ervan, zodat je meldt wat je denkt te melden */}
              {letter && (
                <blockquote
                  style={{
                    background: PAPER, border: `1px solid ${SKY}`, margin: "0 0 16px", padding: "10px 12px",
                    fontFamily: FONT_SERIF, fontSize: 13.5, lineHeight: 1.6, color: INK,
                  }}
                >
                  {letter.photo && (
                    <span style={{ fontFamily: FONT_MONO, fontSize: 11, color: MUTE, display: "block", marginBottom: 4 }}>
                      met foto: {letter.photo.alt}
                    </span>
                  )}
                  {letter.text.slice(0, 200)}
                  {letter.text.length > 200 && "…"}
                </blockquote>
              )}

              <fieldset style={{ border: "none", padding: 0, margin: "0 0 14px" }}>
                <legend style={{ fontSize: 13, fontWeight: 600, padding: 0, marginBottom: 8 }}>Wat is er aan de hand?</legend>
                {Object.entries(REPORT_REASONS).map(([key, label]) => (
                  <label
                    key={key}
                    style={{
                      display: "flex", alignItems: "center", gap: 10, padding: "9px 12px", marginBottom: 6,
                      border: `2px solid ${reason === key ? INK : SKY}`, background: reason === key ? PAPER : "#fff",
                      fontSize: 14, cursor: "pointer",
                    }}
                  >
                    <input
                      type="radio"
                      name="melding-reden"
                      value={key}
                      checked={reason === key}
                      onChange={() => setReason(key)}
                      style={{ margin: 0, accentColor: RED }}
                    />
                    {label}
                  </label>
                ))}
              </fieldset>

              <label htmlFor="melding-toelichting" style={{ display: "block", fontSize: 13, fontWeight: 600, marginBottom: 4 }}>
                Toelichting <span style={{ fontWeight: 400, color: MUTE }}>(mag leeg, helpt wel)</span>
              </label>
              <textarea
                id="melding-toelichting"
                value={details}
                onChange={(e) => setDetails(e.target.value.slice(0, REPORT_DETAILS_MAX))}
                rows={4}
                placeholder="Wat er gebeurd is, en waar we naar moeten kijken."
                style={{
                  width: "100%", padding: 12, fontSize: 14.5, fontFamily: FONT_SERIF, lineHeight: 1.6,
                  border: `2px solid ${INK}`, background: PAPER, resize: "vertical",
                }}
              />
              <p style={{ fontFamily: FONT_MONO, fontSize: 11, color: MUTE, margin: "6px 0 14px" }}>
                nog {REPORT_DETAILS_MAX - details.length} tekens
              </p>

              {canBlock && (
                <label
                  style={{
                    display: "flex", alignItems: "flex-start", gap: 10, padding: "10px 12px", marginBottom: 14,
                    border: `2px solid ${block ? INK : SKY}`, background: block ? PAPER : "#fff", fontSize: 14, cursor: "pointer",
                  }}
                >
                  <input
                    type="checkbox"
                    checked={block}
                    onChange={(e) => setBlock(e.target.checked)}
                    style={{ margin: "3px 0 0", accentColor: RED }}
                  />
                  <span>
                    {subject.name} ook blokkeren
                    <span style={{ display: "block", fontFamily: FONT_MONO, fontSize: 11, color: MUTE, marginTop: 3, lineHeight: 1.5 }}>
                      verdwijnt uit je postvak en uit Zoeken; jullie kunnen elkaar niet meer schrijven
                    </span>
                  </span>
                </label>
              )}

              {error && (
                <p role="alert" style={{ fontFamily: FONT_MONO, fontSize: 12, color: RED, margin: "0 0 10px" }}>fout: {error}</p>
              )}
              <button
                onClick={submit}
                disabled={!reason || sending}
                style={{
                  width: "100%", padding: "14px 0", fontSize: 16, fontWeight: 700, border: "none",
                  background: reason && !sending ? RED : SKY, color: reason && !sending ? "#fff" : MUTE,
                }}
              >
                {sending ? "Versturen…" : "⚑ Verstuur melding"}
              </button>
              <p style={{ fontFamily: FONT_MONO, fontSize: 11, color: BLUE, margin: "10px 0 0", lineHeight: 1.6 }}>
                Acuut gevaar? Bel 112. Een melding hier wordt gelezen, maar niet meteen.
              </p>
            </>
          )}
        </div>
      </div>
    </div>
  );
}