Files
hope-events/frontend/src/components/shared/MessageKindPicker.tsx
T
joshuaandClaude Opus 5.5 594cb35a85 POPIA toolkit part 1: privacy page, unsubscribe, WhatsApp STOP, data requests
- Privacy page: news opt-in per channel with history, download my data
  (JSON + PDF), correction and deletion requests
- Bulk messages are an Event update or News & announcements; news only
  reaches people who opted in, with unsubscribe links, one-click
  List-Unsubscribe headers and "Reply STOP" on WhatsApp
- WhatsApp STOP via WAWP's message webhook, checked against a secret in
  the webhook URL
- Sign-up opt-in box; existing accounts kept opted in and asked once
- Admin -> Data requests with 30-day due dates and a daily reminder job
- Deleting an account is a request; anonymising now covers attendees,
  form answers, consent details, payer details in provider payloads, etc.
- Unit tests (privacy.test.js) and e2e (privacy.spec.ts); docs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012mt6TCv85T9VKRq3cV7rXq
2026-10-23 18:13:47 +02:00

45 lines
1.9 KiB
TypeScript

"use client";
import React from "react";
export type MessageKind = "update" | "news";
const OPTIONS: { value: MessageKind; label: string; description: string }[] = [
{
value: "update",
label: "Event update",
description: "About an event the recipients registered for — a reminder, venue or time change, what to bring. Goes to everyone selected.",
},
{
value: "news",
label: "News & announcements",
description: "Promoting events or sharing news. Only goes to people who opted in, and includes an unsubscribe link.",
},
];
/** POPIA: what kind of bulk message this is — decides who it may go to (see utils/privacy.js). */
export function MessageKindPicker({ value, onChange, name }: { value: MessageKind; onChange: (kind: MessageKind) => void; name: string }) {
return (
<fieldset>
<legend className="block text-xs text-gray-600 mb-1">Message type</legend>
<div className="grid sm:grid-cols-2 gap-2">
{OPTIONS.map(o => (
<label key={o.value} className={`flex items-start gap-2 border rounded-lg p-2.5 cursor-pointer ${value === o.value ? "border-brand-500 bg-brand-50" : "border-gray-200 bg-white hover:bg-gray-50"}`}>
<input type="radio" name={name} className="mt-0.5 accent-brand-600" checked={value === o.value} onChange={() => onChange(o.value)} />
<span>
<span className="block text-sm font-medium text-gray-800">{o.label}</span>
<span className="block text-[11px] text-gray-500">{o.description}</span>
</span>
</label>
))}
</div>
</fieldset>
);
}
/** "Matched 12 recipient(s)." plus how many news skipped. */
export function matchedText(res: { matched?: number; skipped?: number } | null | undefined) {
const skipped = res?.skipped ?? 0;
return `Matched ${res?.matched ?? 0} recipient(s).${skipped ? ` ${skipped} skipped — not opted in to news or unsubscribed.` : ""}`;
}