Case Study — Nonprofit Community Platform

Nobody arrives here
wanting to use software.

A small nonprofit supports women through the loss of a parent — community events, grief support groups, subsidized therapy, and the anniversaries that come around every year. It had no product at all. We built both halves: the public site that has to earn an application, and the members' app and back office the whole organization now runs on.

At A Glance ● built
47 screens across the public site,
the member app and the back office
Sector
Nonprofit — grief support
Audience
Members in grief, often on a phone
Scope
Public site · member app · back office
Stack
Laravel 13 · Vue 3 · Inertia
Memorial dates
Hebrew calendar, recomputed each year
What It Does

Both halves,
built at once.

A public face and an operating system for the organization behind it — because a site that converts strangers into applicants is worth very little if nobody has anywhere to review the applications.

× No product surface at all — the story, the applications and the community lived in conversations
A public site that tells it and an application funnel that ends in a reviewed cohort
× Nowhere for members to find each other between events
A member app: calendar, registration, paid events, and a waitlist that promotes people on its own
× No way to hold the anniversaries that matter most to the people they serve
A memorial wall built on Hebrew-calendar dates, with reminders and community wishes before the day
× No way to see who nobody had checked in on lately
Assignable reach-out tasks that surface exactly that, with a workload view per staff member
× No system for giving, receipts, or annual statements
One-off and recurring donations with dedications, PDF receipts, and giving statements
× No record of who did what to whose account
An audit log throughout, with lifecycle and destructive actions gated to a single role

What the audience
changed about the build.

01

The software disappears around the need

Members arrive grieving, often distressed, frequently on a phone, sometimes not especially technical. Nobody came here wanting to use software. So the arrival flow asks one group of questions at a time, leaving a memorial wish offers preset words before it offers a blank box, and nothing important is ever a single unguarded click.

02

A religious calendar is a real domain problem

A Yahrtzeit — the anniversary of a death — is recorded as a Hebrew date, so its date in the civil calendar moves every year and has to be recomputed. In a leap year, an anniversary in plain Adar is observed in Adar II. That is domain logic, not a formatting detail, and it is handled natively rather than approximated.

03

Dignity written into the permissions

Her record is hers. Staff who need to see the member app on live data impersonate a dedicated test account and nothing else, behind three separate guards. Closing an account is a request rather than a button. Erasing the personal details is a different, deliberate action — and it leaves the memorial standing.

Warmth and dignity, without slipping into either sterility or sentimentality.

The design system names what it is not as carefully as what it is: not clinical, not corporate, not greeting-card, not funereal. Two registers meet at the login — a warm public voice that has to persuade someone to apply, and a calmer interior built for repeat use — sharing one hue doing two jobs, so the accent colour is spent only on the action that ends a form. Crossing the login is a change of register, not a change of brand.

Inside The Build

Where the care
turns into code.

Every one of these started as a sentence about a person — what she should never accidentally do, what should survive her leaving, what a staff member needs to see without being able to read someone's private record.

Hebrew dates, computed not approximated

Anniversaries are found in two steps: narrow by day of month in the database, then confirm each candidate by converting it into the target year's Hebrew calendar — including the leap-year rule that moves a plain-Adar date into Adar II. It uses the calendar extension already in the language rather than a dependency, and the logic was extracted once a second command needed it.

Impersonation that cannot reach a member

Admins can view the member experience on live data, but only as a dedicated test account — guarded three ways, and the only place in the codebase that logs a user in directly, so that account can hold a password nobody knows. Every entry and exit is written to the audit log.

Closure and erasure are different acts

A member asks to close her account; an admin erases the personal details later, and only on an already-closed account. The erasure anonymizes her without tearing down what she left behind — the parent she commemorated and the wishes written for her stay. The audit entry records her name and email as they go, because that becomes the last place she is named at all.

Email in lanes she controls

Bulk email is split into categories a member can decline and one she cannot, because transactional mail is not marketing. Unsubscribing asks for confirmation and doesn't promise an instant effect it can't deliver. Every send records who it reached and what the mail provider did with it.

A schedule shaped by the hosting bill

The cluster scales to zero, and waking it holds it awake for a full sleep timeout — so a check every fifteen minutes bills around the clock. Reminders run hourly instead, and the command is written to be idempotent and to catch up, so a longer interval can delay a reminder but never skip one.

Payments swapped mid-project

The processor changed late, which meant reworking refunds into voids of unsettled payments and simplifying the donation types. Recurring billing runs in the application rather than at the processor, because each cycle needs its own record to produce a receipt and an annual statement — and it runs without overlapping, since two concurrent passes could charge the same donor twice.

How It Holds Up

A team of four
can't debug this at midnight.

A small nonprofit has no engineering staff and no appetite for surprises. Everything that could be made a rule the machine enforces was made one, so the software doesn't need supervision to stay correct.

Coverage slips a little at a time until nobody trusts it
The gate demands exactly 100% line coverage and 100% type coverage — not a minimum, an equality — on every push
A link goes stale after a route is renamed
Routes generate typed functions for the frontend, so a URL is never written by hand
Contrast quietly fails on the text nobody checks
Nothing renders below 4.5:1 — including placeholders and disabled labels — and hue is never the only signal
A scheduled job double-charges or silently skips
Billing runs without overlapping, and reminders are idempotent and catch up rather than assuming they ran
An admin action can't be traced afterwards
Audit logging throughout, with destructive and lifecycle actions restricted to the highest role
— The gate every change clears

Four gates, all of which must pass before anything merges: coverage at exactly 100% by line and by type, three formatters with zero diff, and static analysis with a single documented exception. Alongside the feature tests are architectural ones — conventions the codebase must keep obeying, including a test that the design system's own typography stays as specified.

Coverage as an equality rather than a minimum is a deliberately awkward rule. It is also the only version that doesn't quietly erode.

— And one word we removed everywhere

The platform described the community as "peer-led" until we looked at what that sentence was doing. It described how the organization is run rather than what a visitor gets — and it undersold the programme, because support groups and subsidized therapy are not peer-led. It came out of every page.

The client's own rewrite of the public copy was then taken wholesale. They know how to say this to the person arriving; our job was to make sure the software didn't contradict them.

This is web application development with UI and UX design done as one job, on the timeline of an MVP build.

Software For People, Not Users

Build it like someone is having a hard day.

Tell us who arrives at your product and what state they arrive in. That answer changes more of the build than the feature list does — and it is the part most software gets wrong before a line is written.