The React rewrite of Vital Registry. Same business logic as v1 (Streamlit version), redesigned from scratch in React + TypeScript, and running in production at vital-registry.com/contracts.
Live production app — non-technical user (our Mum) runs her BEMER rental business on it daily.
Why a v2
v1 shipped fast on Streamlit because rendering a working UI in Python was the shortest path to a production app for a non-technical user. It worked — but Streamlit’s page-rerun model and limited component ergonomics made mobile responsiveness and UX polish a constant fight. v2 is the same domain, same rules, done right in React.
What changed
- Frontend: React + TypeScript replaces Streamlit’s server-rendered UI. Component boundaries are explicit, state is local, and the mobile view is first-class.
- Backend: Firebase Firestore stays. The v1 data model, including the dual-inventory Possession Envelope Rule, ports directly — core business logic was already centralised in the DAO layer.
- Auth: Firebase Auth with Google/Facebook OAuth, migrated from v1.
- Billing: Stripe Checkout + Customer Portal, plan-based feature gating preserved.
- Multi-environment pipeline: dev / UAT / prod Firebase projects, deploy gates, separate Stripe keys per env. v1 ran on a single Streamlit Cloud instance; v2 is structured for real release management.
Shared with v1
Every business rule that was explicitly covered by the 391-test suite in v1 maps to v2:
- Dual inventory — owned vs partner assets, with the Possession Envelope Rule enforced before form submission.
- Multi-entity billing — rental and sale transactions tied to specific legal entities, with validation against deleted entities.
- Optimistic UI pattern — ported from Streamlit’s
st.session_state.task_overridesidea to React state, so task completions feel instant without round-trip to Firestore. - Client-side PDF contracts — same html2pdf approach, now using React’s component tree to build the HTML before passing to the PDF renderer.
Tech Stack
| Layer | v1 (Streamlit) | v2 (React) |
|---|---|---|
| UI | Streamlit | React + TypeScript |
| State | @st.cache_data + st.session_state | Local component state + React Query |
| Database | Firebase Firestore | Firebase Firestore (same schema) |
| Auth | google-auth-oauthlib | Firebase Auth SDK |
| Payments | Stripe API via Python | Stripe API via TypeScript SDK |
| html2pdf.js injected | html2pdf.js native | |
| Deployment | Streamlit Cloud (single env) | Firebase Hosting (dev/UAT/prod) |
Built With
Co-developed with my brother Zsombor, who set the visual system and component language. I did almost all of the frontend redesign on top of it, plus the data-layer port from v1, multi-environment deployment setup, and the test harness that carries over from v1.
Case study: czaban.dev/portfolio (MedKölcsön v2).
Running it in production
The app runs across three environments gated by GitHub Actions: feature branches to dev, PRs to UAT, main to prod. All wired up on the Firebase Spark (personal) plan with Gitleaks on every run and weekly scheduled CVE scans. Full story: Three Environment GitOps on a Firebase Personal Plan.
With 20 active users and hard Spark plan quota limits, the monitoring setup is simple but deliberate: PostHog WAU tracking, Firebase alerts that fire before the limits hit, and one rule — know your quota headroom, WAU, and error rate before your users do. Details: The Three Numbers That Matter: PostHog + Firebase Monitoring.