# beenvoice Mobile Expo companion for [beenvoice](../beenvoice) — dashboard, time clock, invoices, clients, businesses, and settings. Shares the **same tRPC API** and **better-auth** sessions as the web app. **Architecture (dense):** [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) ## Prerequisites - [Bun](https://bun.sh) 1.3+ - beenvoice API running ([setup](../beenvoice/README.md)) - Xcode + iOS Simulator (or device) for native dev build - **Not Expo Go** — widgets, SecureStore auth, and biometrics need `expo-dev-client` ## Setup ```bash cd beenvoice-app bun install cp .env.example .env ``` `.env`: ```env # Simulator EXPO_PUBLIC_API_URL=http://localhost:3000 # Physical iPhone — Mac LAN IP EXPO_PUBLIC_API_URL=http://192.168.1.42:3000 ``` Omit `EXPO_PUBLIC_API_URL` in production builds to default to `https://beenvoice.soconnor.dev`. Server must enable `@better-auth/expo` in `beenvoice/src/lib/auth.ts` with `beenvoice://` in `trustedOrigins`. ## Run ```bash # Terminal 1 — API cd ../beenvoice && bun run dev # Terminal 2 — mobile (builds native app if needed) cd beenvoice-app && bun run ios ``` Metro uses port **8082** (avoids other Expo projects on 8081). Metro only (app already installed): ```bash bun run start -- --clear ``` Open the **beenvoice** dev build on the simulator — not Expo Go. ### After native changes Icon (`assets/beenvoice.icon`), widgets, or new native modules: ```bash bunx expo prebuild --platform ios --clean bun run ios ``` ## Features | Area | Details | |------|---------| | **Auth** | Sign in, register, forgot/reset password; official or self-hosted server | | **Multi-account** | Bitwarden-style switcher; per-account session in SecureStore | | **Dashboard** | Revenue, pending, overdue, running timer, recent invoices | | **Timer** | Clock in/out, client + invoice + rate; optional description (default "Clock In"); iOS Live Activity | | **Entities** | Clients and businesses — list, create, edit | | **Invoices** | List, filter, create, edit, status updates | | **Settings** | Profile, accounts, theme, per-account app lock (PIN + Face ID), sign out | | **App lock** | Per-account; locks on background return | ## Auth & accounts (summary) - **Guest** auth storage: `beenvoice:guest` until first successful login - **Per account**: `beenvoice:auth:{host::userId}` in SecureStore - After login, `finalizeAuthenticatedAccount()` migrates session keys before activating the account (avoids double login) - **Server picker**: Official (`beenvoice.soconnor.dev`) or custom URL on auth screens Full flow: [docs/ARCHITECTURE.md#multi-account-model](./docs/ARCHITECTURE.md#multi-account-model) ## Deep links | Scheme | Screen | |--------|--------| | `beenvoice://reset-password?token=…` | Reset password | | `beenvoice://timer` | Timer tab (from Live Activity) | ## Project layout ``` app/ _layout.tsx # Providers, auth guard (auth)/ # sign-in, register, password flows (app)/ # tab shell + nested stacks components/ # UI, forms, chrome, time clock contexts/ # Auth, Accounts, AppLock, Theme lib/ # tRPC, auth storage, config, theming widgets/ # iOS Live Activity (TimeClockActivity) ``` ## Troubleshooting | Issue | Fix | |-------|-----| | `PlatformConstants` / runtime not ready | Stop other Metro on 8081/8082; rebuild with `prebuild --clean` | | Expo Go | Use `bun run ios` dev build | | API errors on device | `EXPO_PUBLIC_API_URL` = Mac LAN IP; server `BETTER_AUTH_URL` must match | | Live Activity empty | Rebuild iOS; widget UI must live inside `"widget"` function | | Login twice | Server + app versions with session migration (`lib/auth-storage.ts`) | ## Related - [beenvoice README](../beenvoice/README.md) - [beenvoice ARCHITECTURE](../beenvoice/docs/ARCHITECTURE.md) - [Workspace root README](../README.md)