YSBDesigns

← All work

Own product

MasjidOne

Next.js 15, static export · TypeScript · Tailwind · shadcn/ui · Supabase Postgres with Row Level Security · Stripe · OneSignal · Cloudflare Worker → Resend

Problem

A masjid runs its week across half a dozen systems that have never heard of each other — prayer times in one, the website in another, the madrasah register on paper, fees in a book, donations somewhere else again. The office knows the same family three separate times and can only join them up by remembering; right now the masjid is the integration. Plenty of products do the congregation side well. The part nobody does is the madrasah’s daily operations — the register marked each evening, the sabaq heard, the fee due — in the same system, and then giving a parent a view of their own child.

Approach

One record of one family, reachable from both sides. A masjid on the full plan gets five finished things — a website, a congregation app, unlimited in-mosque screens off the same timetable, the madrasah portal for the office and its teachers, and a parent portal inside the app — all on one Supabase Postgres, where every table carries a masjid_id and every policy and function filters on it. A database per customer would have meant a separate migration, key set and auth setup each time, and the thing that makes the product work happens inside one masjid rather than between them, so the separation would have bought nothing and cost a great deal to run. Row Level Security and SECURITY DEFINER functions scope every query to one masjid; Stripe runs an account per masjid at 0% commission on donations.

The doors are deliberately separate rather than one portal behind a permissions matrix. A teacher sees their own classes and nothing else — not the roll, not fees, not another teacher’s register. And a parent is not a smaller administrator: they arrive to answer one question about their own child, and should never land on a screen implying the rest of the madrasah is theirs to look at.

Outcome

The madrasah portal is built — registers, fees, Hifz and sabaq progress, and parent access — and it holds a Bolton masjid’s full roll. The two halves are at different stages and the copy says so separately, because they are not in the same place: the congregation side runs every day, with a year of prayer times published and jamāʿah notifications firing off the timetable on their own, while the madrasah side is built and loaded but the office has not started marking registers on it — so it is not described as running. One piece is genuinely unbuilt and still labelled as such: knowing whether a hall screen is switched on and talking back.

Getting that right needed a rule rather than a glance. Features were being tagged in development because their tables were empty — but zero rows means nobody has used it yet, not that it does not exist. The test is whether the functions exist and enforce, not whether rows do.

Alongside the public site sit five module pages, a demonstration tenant anyone can walk through on invented data, and a support console that is the one page touching the real platform. Every gate on it is in Postgres rather than in the page: the anonymous role holds no EXECUTE, a platform admin needs a completed second factor, and entering a masjid you do not belong to writes a support-access row into their audit trail.

The screens

The MasjidOne home page: the madrasah and the congregation, on one system
The marketing site · every interface on it labelled as a preview
A teacher's register in the demonstration tenant, with invented pupils marked in, late or absent
Tonight’s register · a teacher sees their own classes and nothing else
Parent access in the demonstration tenant: two invented children, their attendance for the week and a button to report an absence
Parent access · their own children, their attendance, and a way to report an absence
The MasjidOne site on a phone
On a phone

If this looks like your problem

Tell me about it and I'll tell you what it takes.