Overview
Every listing in English and Farsi. Co-built with a collaborator: Google sign-in, an owner portal and an admin dashboard, with review stats that only the server is allowed to write.
Who it’s for. Persian-Americans and visitors looking for restaurants, markets, salons, doctors and other businesses, and the owners who manage their listings.
My role. Co-builder (with a collaborator).
Built with a collaborator in a shared repository with feature branches and pull requests: 54 of the 94 commits are mine.
Mine: the initial app and Firebase setup, business detail pages, sign-in and routing, the review system and badges, the submission moderation queue, Firestore security rules, admin authorization with Firebase custom claims, favorites, and the accessibility and launch-readiness pass.
My collaborator's: the business owner portal, the map page, the admin AI auto-fill Cloud Functions, admin search, and much of the visual redesign.
The interesting parts
- Review stats and trust badges are written only by a Cloud Function trigger; Firestore rules deny them to every client, including administrators.
- A business with no reviews has a rating of null, not zero — the structured data omits the rating rather than claim one.
- Build-time SEO pass: sitemap and prerendered listing pages with per-route metadata and JSON-LD.
The problem
Southern California has a large Persian community, but its businesses are spread across social media pages, word of mouth and general-purpose directories that don't speak Farsi. Mahaleh puts them in one place, with every name in English and Farsi, and reviews people can trust.
The product

Reviews people can trust
- Review statistics and trust badges (Mahaleh Verified and مامانپسند, “mom-approved”) are written only by a Cloud Function trigger. Firestore rules deny them to every client, administrators included.
- A business with no reviews has a rating of null, not 0. The page and its structured data leave the rating out instead of showing a score nobody gave.
- User submissions go through a moderation queue before they appear.
Access control
Administrators are identified by Firebase custom claims set on the server, not by a flag in a document the client could edit. App Check limits the backend to the real app.
Getting ready to launch
- Named every control, trapped focus in dialogs, and gave each route its own title and metadata; the skip link was verified in a real browser.
- Cleared every dependency advisory and fixed measured contrast failures.
- Split the bundle and added a build-time SEO pass: sitemap and prerendered listing pages with JSON-LD.
Built & next
Built and working
- Bilingual listings with English and Farsi names
- Business pages with reviews, badges and a gallery
- Google sign-in, favorites, moderation queue and admin tools
Not built yet
- Public launch (pre-launch today; the repository is private)
- A Google Maps billing error found on the final browser pass, recorded in the launch audit
- A full Farsi interface: translation and right-to-left support exist in the code, but the interface is currently fixed to English
What I learned
- Sharing a codebase means agreeing on who owns what and reviewing each other's branches before they land.
- Trust rules belong in the database rules and on the server; the interface is only a view of them.
