Files
hope-events/frontend/src/app/legal/privacy/page.tsx
T
joshuaandClaude Sonnet 5 86af7093ac Add admin-configurable branding (colors, logo, favicon) and generic default fallbacks
Site Settings -> Branding now supports a Primary/Secondary/Accent brand color
system applied site-wide (buttons, nav, hover states, links) and to outgoing
email header/CTA colors, plus a favicon upload alongside the existing logo
upload, a live preview panel (website/email x desktop/mobile), and
logo-based color suggestions. The setup wizard's Branding step got the same
treatment. Fixes two related bugs found along the way: the setup wizard's
logo/favicon upload was missing its auth token, and a static favicon.ico in
Next's special app/ convention path was silently overriding the dynamic one.

Also replaces every "Hope Events"/"Hope Family Church" default (org name,
email subjects, WhatsApp messages, report metadata, API docs) with a neutral
"Cross Code" placeholder, and the optional legal settings (operator name, IO
details, website URL, effective date) with obviously-generic placeholders
instead of defaulting to real personal/organisational details -- since this
platform is deployed for multiple organisations. Adds SETTINGS.md documenting
every setting's default behaviour.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 14:49:51 +02:00

255 lines
16 KiB
TypeScript

"use client";
import { Navbar } from "@/components/layout/Navbar";
import { Footer } from "@/components/layout/Footer";
import { useScrollSpy } from "@/hooks/useScrollSpy";
import { useSiteSettings } from "@/contexts/SiteSettingsContext";
const sections = [
{ id: "summary", title: "Plain English Summary" },
{ id: "introduction",title: "1. Introduction" },
{ id: "information", title: "2. Information We Collect" },
{ id: "use", title: "3. How We Use Your Information" },
{ id: "whatsapp", title: "4. WhatsApp Communications" },
{ id: "storage", title: "5. Where We Store Your Data" },
{ id: "retention", title: "6. Data Retention" },
{ id: "children", title: "7. Children and Minors" },
{ id: "sharing", title: "8. Sharing of Information" },
{ id: "rights", title: "9. Your Rights (under POPIA)" },
{ id: "cookies", title: "10. Cookies and Local Storage" },
{ id: "security", title: "11. Security" },
{ id: "changes", title: "12. Changes to This Policy" },
{ id: "officer", title: "13. Information Officer" },
{ id: "contact", title: "14. Contact" },
];
export default function PrivacyPolicyPage() {
const activeId = useScrollSpy(sections.map((s) => s.id));
const { settings } = useSiteSettings();
// orgName follows the sitewide org_name default (Cross Code) since org_name is a
// required field at setup — this fallback is practically unreachable in production.
const orgName = settings.org_name || "Cross Code";
// The fields below are all OPTIONAL legal-specific settings that can realistically
// stay unset on a live site. Defaulting them to a real person/org's identity would be
// actively misleading on someone else's deployment, so these are generic placeholders
// that obviously need to be filled in via Admin → Site Settings → Legal, rather than
// silently showing Cross Code's or any other real organisation's details.
const orgEmail = settings.org_email || "privacy@example.com";
const websiteUrl = settings.legal_website_url || "example.com";
const ioName = settings.legal_io_name || "[not yet configured]";
const ioEmail = settings.legal_io_email || "io@example.com";
const effectiveDate = settings.legal_effective_date || "[not yet set]";
return (
<div className="min-h-screen flex flex-col bg-gray-50">
<Navbar />
<main className="flex-1 max-w-6xl mx-auto px-6 py-12 grid grid-cols-1 lg:grid-cols-[250px_1fr] gap-10">
{/* Sidebar */}
<aside className="hidden lg:block text-sm text-gray-600 space-y-2 self-start sticky top-24 h-fit">
<h3 className="font-semibold text-gray-800 mb-3">On this page</h3>
<ul className="space-y-2 border-l border-gray-300 pl-3">
{sections.map((section) => (
<li key={section.id}>
<a
href={`#${section.id}`}
className={`block hover:text-brand-600 transition-colors ${
activeId === section.id ? "text-brand-600 font-semibold" : ""
}`}
>
{section.title}
</a>
</li>
))}
</ul>
</aside>
{/* Main Content */}
<article className="bg-white rounded-2xl shadow p-8 text-gray-700 leading-relaxed">
<h1 className="text-4xl font-bold text-brand-700 mb-2">Privacy Policy</h1>
<p className="text-sm text-gray-500 mb-6">Effective Date: {effectiveDate}</p>
<div className="border-l-4 border-brand-600 pl-4 mb-8 text-gray-700">
<p>
<strong>Website:</strong>{" "}
<a href={`https://${websiteUrl}`} className="text-brand-600 hover:underline">
{websiteUrl}
</a>
</p>
<p><strong>Responsible Party:</strong> {orgName}</p>
<p>
<strong>Email:</strong>{" "}
<a href={`mailto:${orgEmail}`} className="text-brand-600 hover:underline">
{orgEmail}
</a>
</p>
</div>
<section id="summary" className="mb-10">
<h2 className="text-2xl font-semibold text-brand-600 mb-4">🟢 Plain English Summary</h2>
<p>We respect your privacy here&apos;s what you need to know:</p>
<ul className="list-disc ml-6 space-y-1">
<li>We only collect info needed to register you for events and process payments.</li>
<li>We store your info safely on South African servers.</li>
<li>We don&apos;t sell or share your data with anyone.</li>
<li>You can ask us what info we have about you or request deletion at any time.</li>
<li>Under 18s must register with a parent&apos;s permission.</li>
<li>Payments go through Yoco; we never see your card details.</li>
<li>We may send you security alerts (e.g. new login notifications) via email and/or WhatsApp to keep your account safe.</li>
<li>If you opt in to WhatsApp notifications, we use your mobile number to send you event updates, tickets, and account alerts via WhatsApp (powered by WAWP).</li>
<li>We keep your data for up to 5 years after your last event registration.</li>
</ul>
</section>
{sections.slice(1).map((section) => (
<Section key={section.id} id={section.id} title={section.title}
orgName={orgName} orgEmail={orgEmail} ioName={ioName} ioEmail={ioEmail} />
))}
</article>
</main>
<Footer />
</div>
);
}
function Section({
id, title, orgName, orgEmail, ioName, ioEmail,
}: {
id: string;
title: string;
orgName: string;
orgEmail: string;
ioName: string;
ioEmail: string;
}) {
const content: Record<string, string[]> = {
introduction: [
`${orgName} ('we', 'us', 'our') values your privacy and is committed to protecting your personal information in accordance with the Protection of Personal Information Act (POPIA).`,
"This policy explains what data we collect, how we use it, and your rights.",
],
information: [
"We collect the following information when you interact with our platform:",
"• Account Information: name, email address, phone number, and password (encrypted).",
"• Event Information: events you register for, ticket selections, and related preferences.",
"• Payment Information: processed by Yoco; we receive only payment status and reference numbers.",
"• Communication Data: emails or messages exchanged with our team.",
"We do not collect or store credit card numbers or bank details.",
"Administrator-created accounts (e.g. walk-in event registrations) may be created with a mobile number only. These accounts use a placeholder email address and can be activated by the account holder at any time.",
],
use: [
"We use your information to:",
"• Process event registrations and payments.",
"• Communicate event updates, confirmations, or changes via email and/or WhatsApp (based on your preference).",
"• Send security notifications for new logins, password changes, and account actions on your account.",
"• Deliver your event tickets via email attachment and/or WhatsApp PDF (based on your preference).",
"• Manage user accounts and event attendance records.",
"• Improve our website and event experience.",
"• Comply with legal obligations.",
],
whatsapp: [
"If you choose 'WhatsApp' or 'Both' as your notification preference, we will use your South African mobile number to send you messages via WhatsApp. These messages may include:",
"• Event registration confirmations and updates.",
"• Payment receipts.",
"• Your event tickets (as a PDF document).",
"• Security alerts such as new login notifications, password changes, and account closure confirmations.",
"WhatsApp communications are delivered through WAWP (wawp.net), a third-party WhatsApp Business API provider. Your mobile number is transmitted to WAWP solely for the purpose of delivering your messages. WAWP does not retain your data beyond what is necessary for message delivery.",
"Security-critical messages (login alerts, password resets, account closures) are always sent via email regardless of your preference, and additionally sent via WhatsApp if your preference includes it.",
"You may change your notification preference at any time from the Profile & Security page in your account. Selecting 'Email only' will stop WhatsApp messages from being sent to you.",
"For accounts registered without a valid email address (e.g. walk-in or at-the-door registrations), WhatsApp will be used as the primary delivery channel for payment confirmations and ticket delivery, regardless of notification preference, since email is unavailable.",
"If your account requires activation and no email address is on file, the activation link will be sent to your WhatsApp number instead.",
"WhatsApp is a product of Meta Platforms, Inc. By receiving WhatsApp messages from us, you are subject to WhatsApp's own Terms of Service and Privacy Policy.",
],
storage: [
"All data is securely stored on cloud servers hosted in South Africa.",
"We use appropriate technical and organisational measures to protect your information from unauthorised access, alteration, or disclosure.",
],
retention: [
"We retain your personal data for up to 5 years after your last event registration, or until you request deletion of your account — whichever comes first.",
"You can delete your account at any time from the Profile & Security page in your account. Deletion anonymises your personal information (name, email address, and phone number are replaced with placeholder values) while retaining event and payment records in anonymised form for financial record-keeping purposes.",
"An administrator may also anonymise your account data on your request. To request this, contact our Information Officer (see section 13).",
],
children: [
"Users under 18 may only create an account or register for events with the consent and supervision of a parent or guardian.",
"We do not knowingly collect or process personal information of minors without such consent.",
],
sharing: [
"We do not sell, rent, or share your personal information with any third parties.",
"The only exceptions are:",
"• Processing payments securely through Yoco (a South African payment processor).",
"• Delivering WhatsApp messages through WAWP (wawp.net), a WhatsApp Business API provider — only your mobile number and message content are shared, solely for delivery purposes.",
"• Where required by South African law.",
],
rights: [
"Under POPIA, you have the right to:",
"• Access your personal information.",
"• Request correction or deletion of your data.",
"• Withdraw consent for processing.",
"• Lodge a complaint with the Information Regulator of South Africa.",
"To contact the Information Regulator:",
"• Website: www.inforeg.org.za",
"• Email: enquiries@inforeg.org.za",
"To exercise your rights directly with us, contact our Information Officer using the details in section 13 below.",
],
cookies: [
"This website uses browser localStorage to store your login session token and user interface preferences (such as dismissed notifications). No third-party cookies, tracking pixels, or analytics tools are used.",
"You can clear your browser's local storage at any time through your browser settings, which will log you out of the site.",
],
security: [
"Your personal data is encrypted during transmission and stored using industry-standard security protocols.",
"Passwords are encrypted and cannot be viewed by anyone, including administrators.",
"In the event of a data breach that poses a risk to your rights, we will notify you and the Information Regulator of South Africa as required under POPIA section 22, without unreasonable delay.",
],
changes: [
"We may update this Privacy Policy periodically.",
"Any changes will be posted on this page with an updated effective date.",
],
officer: [
`The Information Officer responsible for overseeing compliance with POPIA on behalf of ${orgName} is:`,
`• Name: ${ioName}`,
`• Email: ${ioEmail}`,
"You may contact the Information Officer for any POPIA-related requests, including access to your personal data, requests for correction or deletion, and formal complaints.",
],
contact: [
"For general privacy-related questions or requests, you can also contact us at:",
`📧 ${orgEmail}`,
],
};
return (
<section id={id} className="mb-10 scroll-mt-24">
<h3 className="text-xl font-semibold text-brand-600 mb-3">{title}</h3>
{content[id]?.map((line, i) => {
// Render email addresses as links
const emailMatch = line.match(/[\w.+-]+@[\w-]+\.[\w.]+/);
if (emailMatch) {
const email = emailMatch[0];
const parts = line.split(email);
return (
<p key={i} className="mb-2">
{parts[0]}
<a href={`mailto:${email}`} className="text-brand-600 hover:underline">
{email}
</a>
{parts[1] || ""}
</p>
);
}
if (line.includes("www.inforeg.org.za")) {
return (
<p key={i} className="mb-2">
{line.replace("www.inforeg.org.za", "")}
<a href="https://www.inforeg.org.za" target="_blank" rel="noopener noreferrer" className="text-brand-600 hover:underline">
www.inforeg.org.za
</a>
</p>
);
}
return (
<p key={i} className="mb-2">
{line}
</p>
);
})}
</section>
);
}