Pavol Rašev
Open to remote work
All work

FirmFlow

A white-label app for employees and the backend behind it. People see the week’s menu, book lunch and pick it up at the counter with a barcode card on their phone; each company gets its own branding and only the modules it pays for.

Client
Fellow Bytes, own product
My role
Everything: mobile app, API, company admin
Timeline
March 2026 to now
Status
In development, preparing the first release
Stack
Expo SDK 57, React Native 0.86, TanStack Query, Symfony 8, API Platform 4, Next.js 16, PostgreSQL, Redis, RabbitMQ
FirmFlow: mobile screen 1
FirmFlow: mobile screen 2
FirmFlow: mobile screen 3

What it’s for

FirmFlow started as employee administration for companies: people, roles, groups, notifications and an audit trail. The first module that employees use every day is lunch. The app talks to Canteen, which owns menus, orders and credit, and FirmFlow owns who the person is.

The phone replaces the plastic canteen card. It shows what’s on today, what you’ve booked for the week, and a barcode the counter can scan, even in a basement canteen with no signal.

Home: today, tomorrow, credit and lunches this week
Daily menu with a calendar strip, soups and mains with allergens
Employee card with a Code 128 barcode for the counter
Orders this month with totals and pickup status

Four screens employees actually use

Expo and React Native with native tabs: Liquid Glass on iOS, the Material bar on Android.

How it fits together

Two backends, two jobs, deliberately not merged. FirmFlow signs the employee in and mints a short-lived diner token. With that token the app calls Canteen directly, so FirmFlow stays out of the lunchtime peak between 11:30 and 13:00.

Mobile appExpo, React NativeCompany adminNext.js 16FirmFlow APISymfony 8, API Platformidentity, roles, pushCanteen APINestJSmenus, orders, creditPostgreSQL, RedisRabbitMQorders, directdiner token, 15 minsigned webhooksdiner provisioning

Decisions worth mentioning

  • It works in a basement canteen.The query cache is saved to SQLite, so today’s order shows up without signal. It’s an allowlist, and the card number is never written to disk, because it opens the till and the snapshot is plain text.
  • Face ID is decided by the OS, not the app.The refresh token is stored a second time in a keychain item created with requireAuthentication. iOS and Android won’t release it without biometrics, so there is no “passed” flag in app code to bypass.
  • The diner token never touches the disk.It lives about fifteen minutes, has no refresh and stays in memory. The app renews it once, shared across requests, a minute before it expires.
  • The server owns the rules.Cut-off times, prices and subsidies come from the API. A local copy of “two days before, in the canteen’s timezone” drifts at daylight saving and shows a button that fails.
  • No double lunches.Every order carries an Idempotency-Key. Money is handled as integer cents with BigInt, never as floats.
  • Tenants and permissions on the backend.A Doctrine filter scopes every query to the company. Permissions are (role, module, action) tuples checked by a voter and cached in Redis; the app builds its tab bar from what the company bought and what the role allows.
  • Webhooks you can trust.Canteen’s notifications are signed with HMAC-SHA256 over timestamp and body, with one secret per company and a five-minute window. Each Idempotency-Key is remembered for 30 days.

In numbers

  • Mobile app: about 15,600 lines of TypeScript and 189 tests, with Slovak, Czech and English kept in parity by a test.
  • Backend: 186 PHP classes, 300 PHPUnit tests across unit, integration and functional suites, three RabbitMQ transports in production.
  • Company admin: Next.js 16 with about 100 tests and CI on every push.

Distribution on iOS is planned as a private Apple Business custom app with redemption codes, since employees use their own phones.