/* =============================================================
* Action pages: Donate, Get Involved, Contact, Events, Team Login
* ============================================================= */
/* ---------- Donate ---------- */
function DonatePage() {
const [path, setPath] = useState("momo"); // momo | card
const [amount, setAmount] = useState(30000);
const [custom, setCustom] = useState("");
const [freq, setFreq] = useState("monthly");
const [form, setForm] = useState({
name: "",
email: "",
phone: "",
network: "MTN",
cardNumber: "",
cardExpiry: "",
cardCVV: "",
cardType: "visa",
anon: false
});
const [errors, setErrors] = useState({});
const [stage, setStage] = useState("form"); // form | processing | done
const presets = path === "momo"
? [{ v: 30000, l: "USh 30,000", h: "1 father · 1 month" }, { v: 90000, l: "USh 90,000", h: "1 father · 3 months" }, { v: 360000, l: "USh 360,000", h: "1 Forge · 1 month" }]
: [{ v: 25, l: "$25", h: "1 father · 1 month" }, { v: 75, l: "$75", h: "1 father · 3 months" }, { v: 250, l: "$250", h: "1 Forge · 1 month" }];
const total = custom ? Number(custom) : amount;
const detectCardType = (number) => {
const cleaned = number.replace(/\s/g, '');
if (/^4/.test(cleaned)) return 'visa';
if (/^5[1-5]/.test(cleaned)) return 'mastercard';
if (/^3[47]/.test(cleaned)) return 'amex';
if (/^6(?:011|5)/.test(cleaned)) return 'discover';
return 'visa';
};
const formatCardNumber = (value) => {
const cleaned = value.replace(/\s/g, '');
const type = detectCardType(cleaned);
setForm({ ...form, cardType: type });
// Format with spaces every 4 digits
const formatted = cleaned.match(/.{1,4}/g)?.join(' ') || cleaned;
return formatted.slice(0, 19); // Max 16 digits + 3 spaces
};
const formatExpiry = (value) => {
const cleaned = value.replace(/\D/g, '');
if (cleaned.length >= 2) {
return cleaned.slice(0, 2) + '/' + cleaned.slice(2, 4);
}
return cleaned;
};
const validate = () => {
const e = {};
if (!form.name.trim() && !form.anon) e.name = "Required";
if (!form.email.trim() || !/^[^@]+@[^@]+\.[^@]+$/.test(form.email)) e.email = "Valid email required";
if (path === "momo") {
if (!/^[+0-9 -]{7,}$/.test(form.phone)) e.phone = "Mobile number required";
} else {
// Card validation
const cardNum = form.cardNumber.replace(/\s/g, '');
if (cardNum.length < 13 || cardNum.length > 19) e.cardNumber = "Invalid card number";
if (!/^\d{2}\/\d{2}$/.test(form.cardExpiry)) e.cardExpiry = "MM/YY format required";
if (!/^\d{3,4}$/.test(form.cardCVV)) e.cardCVV = "Invalid CVV";
}
if (!total || total <= 0) e.amount = "Pick or enter an amount";
setErrors(e);
return Object.keys(e).length === 0;
};
// Phase 3.9: store receipt details after successful payment for download
const [receipt, setReceipt] = useState(null);
// Save donation to Supabase + build a receipt object for the success screen.
const recordDonation = async (txResponse) => {
const txRef = (txResponse && (txResponse.tx_ref || txResponse.transaction_id)) || ('FA-' + Date.now());
const currency = path === "momo" ? "UGX" : "USD";
const donor = form.anon ? "Anonymous" : form.name;
const rec = {
tx_ref: txRef,
donor_name: donor,
donor_email: form.email,
donor_phone: form.phone || null,
amount: total,
currency: currency,
frequency: freq,
payment_method: path === "momo" ? "Mobile Money" : "Card",
anonymous: !!form.anon,
date: new Date().toISOString()
};
setReceipt(rec);
// Best-effort Supabase insert into existing 'donations' table.
// Extra fields (tx_ref, email, phone, payment_method, anon flag) are folded into `notes`
// as a pipe-separated string so the OS can parse them back when displaying donation history.
try {
if (window.sb) {
const noteParts = [
'tx_ref:' + rec.tx_ref,
'method:' + rec.payment_method,
rec.donor_email ? 'email:' + rec.donor_email : null,
rec.donor_phone ? 'phone:' + rec.donor_phone : null,
rec.anonymous ? 'anon:true' : null
].filter(Boolean);
await window.sb.from('donations').insert({
donor_name: rec.donor_name,
amount: rec.amount,
amount_ugx: rec.currency === 'UGX' ? rec.amount : null,
donation_date: rec.date.slice(0, 10),
is_recurring: rec.frequency === 'monthly',
frequency: rec.frequency,
notes: noteParts.join(' | ')
});
}
} catch (e) { console.warn('Donation record save failed:', e && e.message); }
};
// Build the receipt as a printable HTML window — user can save as PDF via browser print.
const downloadReceipt = () => {
if (!receipt) return;
const dateLabel = new Date(receipt.date).toLocaleDateString(undefined, { year:'numeric', month:'long', day:'numeric' });
const amountFmt = receipt.currency === 'UGX' ? 'USh ' + Number(receipt.amount).toLocaleString() : '$' + Number(receipt.amount).toLocaleString();
const html = `
Donation Receipt — Fathers Arise
Donation Receipt
Receipt no. ${receipt.tx_ref}
Date ${dateLabel}
Donor ${receipt.donor_name || '—'}
Email ${receipt.donor_email || '—'}
Payment method ${receipt.payment_method}
Frequency ${receipt.frequency === 'monthly' ? 'Monthly recurring' : 'One-time'}
Amount ${amountFmt}
"Asante! Every shilling you give places a father into a Forge group — a circle of brothers who keep him present, honest, and in his children's lives."
Print / Save as PDF
`;
const w = window.open('', '_blank');
if (!w) { alert('Please allow popups to view your receipt'); return; }
w.document.write(html);
w.document.close();
};
const initFlutterwavePayment = async () => {
setStage("processing");
try {
// Flutterwave payment configuration
const config = {
public_key: window.FLUTTERWAVE_PUBLIC_KEY || "FLWPUBK-YOUR-PUBLIC-KEY-HERE",
tx_ref: 'FA-' + Date.now(),
amount: total,
currency: path === "momo" ? "UGX" : "USD",
payment_options: path === "momo" ? "mobilemoneyuganda" : "card",
customer: {
email: form.email,
phone_number: form.phone,
name: form.anon ? "Anonymous" : form.name,
},
customizations: {
title: "Fathers Arise",
description: `${freq === 'monthly' ? 'Monthly' : 'One-time'} donation`,
logo: window.location.origin + "/uploads/logo.png",
},
callback: async function(response) {
console.log(response);
if (response && response.status === "successful") {
await recordDonation(response);
setStage("done");
} else {
setStage("form");
alert("Payment failed. Please try again.");
}
},
onclose: function() {
if (stage !== "done") setStage("form");
}
};
// Initialize Flutterwave
if (window.FlutterwaveCheckout) {
window.FlutterwaveCheckout(config);
} else {
// Fallback: load script and retry
const script = document.createElement('script');
script.src = 'https://checkout.flutterwave.com/v3.js';
script.onload = () => window.FlutterwaveCheckout(config);
document.body.appendChild(script);
}
} catch (error) {
console.error('Payment error:', error);
setStage("form");
alert("Payment initialization failed. Please try again.");
}
};
const submit = (e) => {
e.preventDefault();
if (!validate()) return;
initFlutterwavePayment();
};
// Phase 3.7: editable site content
const [cmsDonate, setCmsDonate] = useState({});
useEffect(() => {
if (window.cms && typeof window.cms.getSiteContent === "function") {
window.cms.getSiteContent("donate").then(setCmsDonate);
}
}, []);
const dHero = cmsDonate.hero || {};
return (
{[["momo", "Mobile Money"], ["card", "Card Payment"]].map(([k, l]) => (
{ setPath(k); setStage("form"); setAmount(k === "momo" ? 30000 : 25); setCustom(""); setErrors({}); }}
className={`p-4 font-display font-bold uppercase text-sm tracking-wider border-b-2 ${path === k ? "border-gold text-navy bg-paper" : "border-transparent text-ink/50"}`}>
{l}
))}
{stage === "done" ? (
Asante! You just helped restore a father.
{receipt && (
Receipt
No. {receipt.tx_ref}
Amount: {receipt.currency === 'UGX' ? 'USh ' + Number(receipt.amount).toLocaleString() : '$' + Number(receipt.amount).toLocaleString()}
Donor: {receipt.donor_name}
)}
{receipt && (
Download Receipt
)}
{ setStage("form"); setReceipt(null); }} className="text-sm font-display font-bold uppercase tracking-wider text-sky hover:text-navy">Make another gift
A copy has also been saved in our records.
) : stage === "processing" ? (
Processing payment...
Please complete the payment in the popup window
) : (
)}
Where it goes
{[["68%", "Forge groups & curriculum"], ["18%", "Mobile Kanaabe outreach"], ["10%", "Field staff & training"], ["4%", "Admin & overhead"]].map(([p, l]) => (
{p}
{l}
))}
"My monthly gift is the smallest line on my budget and the loudest line on my conscience."
— Asha M., Forge donor since 2022
Other ways to give
· Bank transfer (UG, UK, US)
· Stocks & estate gifts
· Sponsor a Forge group
· Corporate matching
Talk to our team
);
}
/* ---------- Get Involved (brief: 4 pathways + Donation) ---------- */
const PATHWAYS = [
{ k: "register", color: "sky", title: "Register as a Father", desc: "Join a Forge group and begin the 12-week journey. Fill in a short registration form and we will connect you to a group.", cta: "Register Now", img: "uploads/TRG_Father_sBreakfast125.jpg" },
{ k: "chapter", color: "navy", title: "Start a Be A Man Chapter", desc: "Are you a teacher, chaplain, or youth leader? Bring Be A Man to your school or community. We provide the curriculum and training.", cta: "Apply to Start a Chapter", img: "uploads/FA-09310.jpg" },
{ k: "volunteer", color: "gold", title: "Become a Volunteer Mentor", desc: "If you are a stable, mature man willing to invest in the next generation, we need you. Training provided.", cta: "Volunteer Application", img: "uploads/FATHERS ARISE MEETING-66.jpg" },
{ k: "partner", color: "forest", title: "Partner or Donate", desc: "Churches, NGOs, corporates, and individuals — partner with the movement. Your support fuels school chapters, Forge groups, retreats, and media production.", cta: "Give / Partner", img: "uploads/FATHERS ARISE-135-9f499005.jpg" },
];
function GetInvolvedPage() {
const [active, setActive] = useState(null);
// Phase 3.7: editable site content
const [cms, setCms] = useState({});
useEffect(() => {
if (window.cms && typeof window.cms.getSiteContent === "function") {
window.cms.getSiteContent("getinvolved").then(setCms);
}
}, []);
const hero = cms.hero || {};
return (
Get Involved
{hero.headline || <>There Is a Place for You >}
{hero.subhead || "Whether you want to register, volunteer, partner, or give — the movement needs you."}
{PATHWAYS.map((p, i) => {
const accent = p.color === "sky" ? "border-sky" : p.color === "gold" ? "border-gold" : p.color === "forest" ? "border-forest" : "border-navy";
const txt = p.color === "sky" ? "text-sky" : p.color === "gold" ? "text-[#A07700]" : p.color === "forest" ? "text-forest" : "text-navy";
return (
{String(i + 1).padStart(2, "0")}
{p.title}
{p.desc}
{
// "Register as a Father" → full OS father form (writes to fa_father_inquiries)
// "Start a Be A Man Chapter" → full OS BEAM form (writes to fa_beam_students)
// Volunteer & Partner → lightweight modal (writes to fa_volunteer_applications / fa_partner_inquiries)
if (p.k === "register") { window.location.href = "father-registration.html"; return; }
if (p.k === "chapter") { window.location.href = "beam-registration.html"; return; }
setActive(p.k);
}} className={`mt-6 inline-flex items-center gap-2 rounded-full px-6 py-3 font-display font-bold uppercase tracking-wider text-sm ${p.color === "gold" ? "btn-gold" : p.color === "sky" ? "bg-sky text-white hover:bg-navy" : p.color === "forest" ? "btn-green" : "btn-navy"}`}>
{p.cta}
);
})}
{/* Impact Report — downloadable */}
PDF · 2025
Annual Report
Transparency
See what your support builds.
A year of fathers restored, families rebuilt, and Forge groups planted across Uganda — in the numbers, the photos, and the testimonies. Download the full report and see exactly where every shilling goes.
547+
Fathers
82
Forge Groups
25
Communities
Last updated: 2025 · 20 pages · Free to share with churches, partners, and donors.
{/* Donation block */}
Donate
Your gift fuels the movement.
Mobile Money support is critical for Uganda. We also accept bank transfer and international cards.
{[
{ name: "Mobile Money", desc: "MTN MoMo / Airtel Money", detail: "Quick local giving", color: "#FFCC00" },
{ name: "Bank Transfer", desc: "UG / UK / US accounts", detail: "Details on request", color: "#2E96D4" },
{ name: "PayPal / Card", desc: "International donors", detail: "Visa, Mastercard, Amex", color: "#2ECC71" },
].map((m) => (
{m.name}
{m.desc}
{m.detail}
))}
Give Now
Digital receipt provided for every gift.
{active &&
setActive(null)} />}
);
}
function PathwayModal({ pathway, onClose }) {
const p = PATHWAYS.find((x) => x.k === pathway);
const fields = {
register: ["Full name", "Phone (WhatsApp)", "Email", "City / location", "Number of children"],
chapter: ["School / institution name", "Your name (role)", "Phone (WhatsApp)", "Email", "Number of students"],
volunteer: ["Your name", "Phone (WhatsApp)", "Email", "City / region", "Skills / availability"],
partner: ["Organisation name", "Contact person", "Email", "Type of partnership", "Brief about your org"],
};
const [vals, setVals] = useState(Array(5).fill(""));
const [done, setDone] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState(null);
// Map pathway → Supabase table + row shape (only volunteer & partner use the modal now)
const submitPathway = async (e) => {
e.preventDefault();
if (!window.sb) { setError("Connection error. Please reload."); return; }
setSubmitting(true);
setError(null);
try {
let table = null, row = null;
if (pathway === "volunteer") {
table = "fa_volunteer_applications";
row = { full_name: vals[0], phone: vals[1], email: vals[2], city: vals[3], skills: vals[4], source: "website" };
} else if (pathway === "partner") {
table = "fa_partner_inquiries";
row = { organisation: vals[0], contact_name: vals[1], email: vals[2], partnership_type: vals[3], about: vals[4], source: "website" };
} else {
// Fallback for any future pathway — generic registrations table
table = "registrations";
row = { pathway, name: vals[0], phone: vals[1], email: vals[2], city: vals[3], data: { extra: vals[4] }, source: "website" };
}
const { error: insertError } = await window.sb.from(table).insert(row);
if (insertError) throw insertError;
setDone(true);
} catch (err) {
console.error("Pathway submit failed:", err);
setError("We couldn't send your request. Please try again or WhatsApp +256 700 669 586.");
} finally {
setSubmitting(false);
}
};
return (
e.stopPropagation()} className="w-full max-w-2xl bg-white rounded-3xl shadow-2xl overflow-hidden max-h-[90vh] overflow-y-auto">
{done ? (
Asante! We'll be in touch.
A team member will reach out via WhatsApp or email within five working days.
Close
) : (
)}
);
}
function PathwayIcon({ name }) {
const p = { width: 22, height: 22, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
switch (name) {
case "church": return ;
case "handshake": return ;
case "user": return ;
default: return null;
}
}
function PathwayForm({ pathway }) {
const titles = {
church: "Plant Forge groups in your church",
partner: "Build something with us",
volunteer: "Show up where it counts",
};
const fields = {
church: ["Church name", "Your name (Pastor/Elder)", "Phone (WhatsApp)", "Approx. men in congregation"],
partner: ["Organisation name", "Your name", "Email", "How would you like to partner?"],
volunteer: ["Your name", "Phone (WhatsApp)", "City / region", "Skills you'd bring"],
};
const [vals, setVals] = useState(["", "", "", ""]);
const [done, setDone] = useState(false);
if (done) return (
Got it. We'll be in touch this week.
A team member will reach out via WhatsApp or email within five working days.
);
return (
);
}
/* ---------- Contact (per brief) ---------- */
function ContactPage() {
const [form, setForm] = useState({ name: "", email: "", phone: "", subject: "General", msg: "" });
const [done, setDone] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState(null);
const submitContact = async (e) => {
e.preventDefault();
if (!window.sb) { setError("Connection error. Please reload the page."); return; }
setSubmitting(true);
setError(null);
try {
const { error: insertError } = await window.sb.from("fa_contact_messages").insert({
name: form.name,
email: form.email,
phone: form.phone,
subject: form.subject,
message: form.msg,
language: (window.getCurrentLang && window.getCurrentLang()) || "en"
});
if (insertError) throw insertError;
setDone(true);
} catch (err) {
console.error("Contact submit failed:", err);
setError("We couldn't send your message. Please try again or WhatsApp +256 700 669 586.");
} finally {
setSubmitting(false);
}
};
// Phase 3.7: editable site content
const [cmsContact, setCmsContact] = useState({});
useEffect(() => {
if (window.cms && typeof window.cms.getSiteContent === "function") {
window.cms.getSiteContent("contact").then(setCmsContact);
}
}, []);
const hero = cmsContact.hero || {};
return (
Contact
{hero.headline || <>Get in Touch >}
{hero.subhead || "A real person will reply. WhatsApp is fastest."}
{/* Form */}
{done ? (
Asante! Message received.
A real person will reply within two business days.
) : (
)}
{/* Direct Contact */}
{[
{ ic: "phone", label: "Phone / WhatsApp", val: "+256 700 669 586", note: "Mon–Sat · 9am–6pm EAT" },
{ ic: "mail", label: "Email", val: "hello@fathersarize.org", note: "We reply within 2 business days" },
{ ic: "pin", label: "Location", val: "Kampala, Uganda", note: "By appointment" },
].map((c, i) => (
{c.label}
{c.val}
{c.note}
))}
{/* Social */}
Follow the Movement
{[
{ name: "Instagram", href: "https://www.instagram.com/fathersariseofficial/?__pwa=1#", svg:
},
{ name: "Facebook", href: "#", svg:
},
{ name: "TikTok", href: "#", svg:
},
{ name: "X", href: "#", svg:
},
{ name: "YouTube", href: "https://www.youtube.com/@FathersArse", svg:
},
].map((s) => (
{s.svg}
{s.name}
))}
{/* Map */}
Kampala, Uganda
Google Map embed
);
}
function ContactIcon({ name }) {
const p = { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
switch (name) {
case "phone": return ;
case "mail": return ;
case "pin": return ;
default: return null;
}
}
/* ---------- Events ---------- */
function EventsPage() {
const [view, setView] = useState("list");
const [filter, setFilter] = useState("All");
const [events, setEvents] = useState([]);
const [loading, setLoading] = useState(true);
// Phase 3.7: editable site content
const [cms, setCms] = useState({});
useEffect(() => {
window.cms.getEvents().then((e) => { setEvents(e); setLoading(false); });
if (window.cms && typeof window.cms.getSiteContent === "function") {
window.cms.getSiteContent("events").then(setCms);
}
}, []);
const hero = cms.hero || {};
const types = ["All", ...Array.from(new Set(events.map((e) => e.type)))];
const filtered = filter === "All" ? events : events.filter((e) => e.type === filter);
return (
Upcoming Events
{hero.headline || "Join Us in Action"}
{hero.subhead || "From Forge gatherings to community celebrations, these are the moments where fatherhood is celebrated, strengthened, and passed forward."}
{types.map((t) => (
setFilter(t)}
className={`px-3 py-1.5 rounded-full text-xs font-display font-bold uppercase tracking-widest ${filter === t ? "bg-navy text-white" : "bg-paper text-ink/65 border border-navy/10 hover:border-navy/30"}`}>{t}
))}
{[["list", "List"], ["calendar", "Calendar"]].map(([k, l]) => (
setView(k)}
className={`px-4 py-1.5 rounded-full text-sm font-display font-bold ${view === k ? "bg-navy text-white" : "text-ink/55"}`}>{l}
))}
{loading ? (
{Array.from({ length: 4 }).map((_, i) => (
))}
) : view === "list" ? (
{filtered.map((e) => )}
) : (
)}
);
}
function EventListCard({ event }) {
const date = new Date(event.date);
return (
{event.image ? (
) : (
)}
{event.type}
{date.toLocaleDateString("en", { day: "numeric", month: "long", year: "numeric" })}
{event.title}
{event.summary}
{event.cta}
Add to calendar
);
}
function CalendarView({ events }) {
// Simple month grid for May/June/July 2026 with event chips
const months = [
{ name: "May 2026", year: 2026, month: 4, days: 31, startDay: 5 },
{ name: "June 2026", year: 2026, month: 5, days: 30, startDay: 1 },
{ name: "July 2026", year: 2026, month: 6, days: 31, startDay: 3 },
];
const lookup = (year, month, day) => events.filter((e) => {
const d = new Date(e.date);
return d.getFullYear() === year && d.getMonth() === month && d.getDate() === day;
});
return (
{months.map((m) => (
{m.name}
{["S", "M", "T", "W", "T", "F", "S"].map((d, i) =>
{d}
)}
{Array.from({ length: m.startDay }).map((_, i) =>
)}
{Array.from({ length: m.days }).map((_, i) => {
const day = i + 1;
const evs = lookup(m.year, m.month, day);
return (
{day}
{evs.length > 0 && (
{evs[0].type}
)}
);
})}
))}
);
}
/* ---------- Team Login — doorway to the Fathers Arise OS ---------- */
// Same-origin: works on the temp Hostinger preview AND on the real domain
// when it goes live. One config, no edits later.
const FA_OS_URL = "/os/";
function TeamLoginPage() {
return (
Internal Tool
Fathers Arise OS
The internal Operating System for the Fathers Arise team. Sign in to access tasks, finance, monthly reports, the website editor, and live form submissions.
For Fathers Arise staff, program leads, and Forge captains only.
Not a team member?{" "}
Get involved →
The OS lives at a separate, secure address.
);
}
window.DonatePage = DonatePage;
window.GetInvolvedPage = GetInvolvedPage;
window.ContactPage = ContactPage;
window.EventsPage = EventsPage;
window.TeamLoginPage = TeamLoginPage;