Een dating-app op basis van brieven, niet van gezichten!
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
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>
);
}