A React Native (Expo) starting point for the agent-facing mobile app: view shifts, request shift swaps, bid on overtime, submit holiday requests, and receive push notification broadcasts.
This connects to the same live Supabase project the existing web app uses — same tables, same auth users, same data. It is not a mockup.
agent_profiles lookup pattern the web app uses (including a
documented, real gotcha — see below)schedule_runs.agent_schedules, correctly finds today’s run, correctly
maps dayIndex to calendar datesorg_settings,
not a standalone table — see below), place/withdraw bidsnpm run
typecheck)supabase/functions/send-push-broadcast/) that looks up stored
device tokens for a target audience (everyone / a specific line of
business / a specific department) and sends via Expo’s push API in
batches of 100, matching Expo’s own documented limit. Wired to a real
admin UI: OrgSettings.html → Notifications tab → “Send Push
Broadcast” now exists in the web app and calls this function
directly.The push pipeline is code-complete but not yet deployed or tested against a live device. Everything described above exists as real, working code — the function’s logic (lookup → chunk → POST to Expo’s documented API) is correct — but it has never actually fired a notification, because that needs:
device_push_tokens table actually created in your Supabase
project (supabase/migrations/001_device_push_tokens.sql — not yet
run)supabase functions deploy
send-push-broadcast — not yet run)None of this is guesswork or a stub — it’s real code waiting on deployment and credentials only you can provide. Budget time for the deploy-and-test cycle specifically, since the first real send is where integration issues (a wrong project ref, a missing permission, a malformed token) tend to surface, not in the code itself.
Time Off approval-rules engine. The web app runs a leave request
through notice-period and capacity checks before deciding whether it
auto-approves. This app submits every request as pending and lets your
existing supervisor approval screen handle the decision — correct and
safe, just without the web app’s auto-approval fast path. Documented
inline in TimeOffScreen.tsx if you want to add it later.
App icons, splash screen, real branding assets. app.json points at
placeholder paths (./assets/icon.png etc.) that don’t exist yet.
Bundle identifiers. app.json has com.CHANGEME.workforcepilot for
both iOS and Android — replace with your actual reverse-DNS identifier
before building for real.
npm install
npx expo start
Scan the QR code with Expo Go on a physical phone (fastest way to see it
running), or press i/a for a simulator — though remember push
notifications need a real device regardless.
# One-time: link this project to your actual Supabase project
npx supabase login
npx supabase link --project-ref hajeuznaysjkvoykalfo
# Run the migration (creates device_push_tokens)
npx supabase db push
# Deploy the Edge Function
npx supabase functions deploy send-push-broadcast
After that, the “Send Push Broadcast” card in OrgSettings.html’s Notifications tab will actually reach real devices — once the mobile app itself has a real EAS/Apple/Firebase setup and at least one agent has signed in and granted notification permission on a physical phone.
Never select('*') on agent_profiles. This is not a style
preference — it’s a documented bug the web app’s own code already hit.
The _password column has SELECT revoked for anon/authenticated roles,
and Postgres rejects the entire query if any requested column is
forbidden, not just that one. The web app’s login silently fell back to
role 'agent' for every user because of exactly this, caught by an empty
catch block. src/lib/auth.tsx lists columns explicitly for this exact
reason — if you add a column to that list, make sure it isn’t
permission-restricted first.
Overtime opportunities are not a table. They live as a JSON array
inside org_settings under the key wfm_ot_opportunities. Only
individual bids (overtime_bids) are a real table. This is confirmed
directly against the web app’s own code, not assumed — don’t “fix” this
into a proper table without checking whether the web app (and any admin
tooling that manages opportunities) needs updating too.
Schedule days use dayIndex, not a date field. A day in
agent_schedules[].schedule[] is {dayIndex, working, activities} — the
actual calendar date is computed as run.start_date + dayIndex days, not
stored directly. ShiftsScreen.tsx does this correctly; if you build
more schedule-reading screens, copy that pattern rather than looking for
a date field that doesn’t exist.
The 'Aptos' font isn’t available on mobile out of the box. The web
app’s font stack is 'Aptos', 'Segoe UI', system-ui, sans-serif — Aptos
is a Microsoft font, not something you can assume is on a phone. This
project falls back to each platform’s native system font (same category
of fallback the web app’s own CSS already does). If pixel-identical type
matters, you’ll need to license and bundle Aptos via expo-font.
src/
theme/ Design tokens (ported from the real web app CSS) + light/dark context
lib/ Supabase client, auth, push notification registration
navigation/ Root navigator (login vs. main app) + bottom tabs
screens/ The five screens: Login, Shifts, ShiftSwap, OvertimeBid, TimeOff
components/ Shared UI: Button, Card, StatusPill, EmptyState, Screen wrapper
types/ TypeScript types, confirmed against real Supabase queries
eas build for an internal TestFlight / Play internal testing build