A payment form, not a membership system
Joining used to mean filling in a payment form on the association's WordPress site. It took one-off card payments, with no member accounts and no renewal reminders. Members picked their own dues band, and the mailing list was not connected to anything.
What we built
Join and renew in one flow. A member enters their bar admission date and the portal works out their dues from seven tiers. They pay on the page through Stripe and manage their own card afterwards.
A home for every member. A dashboard, a membership card to download, payment history that goes back before the portal existed, a member directory and messages to the board.
Renewals that run themselves. Reminders go out 30 and 7 days before the due date, on the day, and 7 and 30 days after. Then comes a 30-day grace period, and after that access pauses until the member renews. Members stay in sync with the association's Mailchimp list.
Events. Public and members-only events, with member pricing, capacity limits and a waitlist, paid booking and refunds, attendee exports and calendar feeds.
A board dashboard. Membership and payment figures at a glance, search across the whole system, records that save as you type, an audit log, announcements the board approves, and exports.
Find an attorney. A public directory of members by practice area, so people looking for a lawyer can find one.
How it is built
React Router 8 renders the pages on the server, on Express 5, with React 19, TypeScript and Tailwind. The data sits in PostgreSQL 18 through Drizzle ORM. Better Auth handles sign-in by email link, Google or password, with two-factor sign-in for the board. Stripe takes the payments into the association's own account, Resend sends the email, and Turnstile keeps bots off every form that sends one. It runs in Docker on a server we manage, behind Cloudflare, with weekly backups. The rest of the website stays on WordPress.
The result
The portal went live on Saturday 7 November 2026, two months after work began. Every member and their payment history came across from the old system, and invitations went out in batches over four days. Members now join and renew on their own, and the board sees the whole membership in one place.



