Mobile App — DM Portal (Reviewer)
The DM mobile app is a React Native companion to the web-based DM Portal (apps/dm). It lets DM reviewers manage cross-tenant content approvals, review client context and strategy, and monitor agent activity from their phone.
Audience: DM reviewers and account managers only. Tenant users use dashboard-mobile.
Technology
| Layer | Technology |
|---|---|
| Framework | Expo bare workflow + React Native 0.83 |
| Navigation | React Navigation v7 (bottom tabs + native stack) |
| State / data fetching | TanStack Query v5 |
| Token storage | expo-secure-store (iOS Keychain / Android Keystore) |
| Biometrics | expo-local-authentication |
| Offline cache | react-native-mmkv + TanStack Query persist |
| Auth | JWT (access + refresh tokens) stored in SecureStore |
Monorepo placement
apps/
dm/ # Next.js web DM portal
dm-mobile/ # React Native app (Expo bare) — this appShared code:
packages/common— format utilities, badge colours
API prefix: All endpoints are under /dm/v1 on the Fastify API.
Navigation Structure
Bottom Tab Bar
├── 🏠 Overview — Cross-tenant stats + pending approvals + running agents
├── 📝 Content Hub — Tabbed list: Blog | Social posts (across all tenants)
└── ⚙️ More
├── Client Context — Context review + revision request (per tenant)
├── Strategy — Strategy review with version history (per tenant)
├── Deliverables — Deliverable progress view (per tenant)
└── Profile — Account info, theme switcher, sign outAll screens requiring a specific tenant prompt the user to select from their assigned tenants via TenantContext.
Screen Coverage
| Screen | Status | Notes |
|---|---|---|
| Login | ✅ MVP | Email + password; app: "dm" login type |
| Overview | ✅ MVP | Stats (pending approvals, running agents, escalations, active tenants); filterable approval queue |
| Content Hub | ✅ MVP | Tabbed blog + social lists across all tenants; approve / reject / request revision |
| Blog Detail | ✅ MVP | Full content view; DM approve / reject with optional feedback |
| Social Detail | ✅ MVP | Platform preview; DM approve with channel + schedule picker; reject with feedback |
| Client Context | ✅ MVP | Scrollable Markdown; request revision with notes |
| Strategy | ✅ MVP | Scrollable Markdown; version history list |
| Deliverables | ✅ MVP | Period picker; template rows with completion % |
| Profile | ✅ MVP | Name, email, theme (light/dark/system), sign out |
API Layer
Base URL: http(s)://<api-host>/dm/v1
All routes require a valid DM JWT (Authorization: Bearer <token>).
| Method | Path | Description |
|---|---|---|
| GET | /overview-stats | Cross-tenant stats: pending approvals, running agents, escalations, active tenants |
| GET | /overview-approvals | Paginated pending approval queue (filterable by type and platform) |
| GET | /overview-running | Currently running agent jobs |
| GET | /context?tenantId= | Client context content + logs for a tenant |
| POST | /context/revise?tenantId= | Request context revision with notes |
| GET | /strategy?tenantId= | Strategy content + version history + logs for a tenant |
| GET | /blog?tenantId= | Paginated blog post list (filterable by status) |
| GET | /blog/:id | Blog post detail |
| POST | /blog/:id/dm-approve | DM approve a blog post |
| POST | /blog/:id/dm-reject | DM reject a blog post with optional feedback |
| GET | /social?tenantId= | Paginated social post list (filterable by platform and status) |
| GET | /social/:id | Social post detail |
| GET | /social/:id/channels | Available channels for scheduling |
| POST | /social/:id/dm-approve | DM approve + assign channel + optional scheduledAt |
| POST | /social/:id/dm-reject | DM reject with optional feedback |
| GET | /deliverables?tenantId= | Deliverable templates + progress + period options for a tenant |
Auth endpoints shared with all apps:
| Method | Path | Description |
|---|---|---|
| POST | /auth/v1/login | Login with app: "dm" |
| GET | /auth/v1/me | Current user profile |
| GET | /auth/v1/me/tenants | Tenants accessible to the logged-in DM |
| POST | /auth/v1/refresh | Refresh access token |
Auth Flow
- First launch: email + password login (same DM credentials as the web portal)
- On success: biometric (Face ID / fingerprint) prompt to enable quick unlock
- Subsequent opens: biometric unlock (no password re-entry)
- Token storage:
expo-secure-store(iOS Keychain / Android Keystore) - Token refresh: automatic on 401 — in-flight requests resume with the new token
- Login specifies
app: "dm"— only accounts with DM/reviewer access are accepted
Tenant Selection
DMs work across multiple tenants. TenantContext holds the currently selected tenant. Screens that require a tenant (Context, Strategy, Deliverables, Blog, Social) read from this context and let the user switch tenants without logging out.
Development
# From the monorepo root
pnpm --filter @leadmetrics/dm-mobile dev
# Or from apps/dm-mobile
npx expo start # Metro dev server
npx expo run:android # Build + install on Android emulator (requires Android SDK)
npx expo run:ios # Build + install on iOS Simulator (requires Xcode, macOS only)Windows / pnpm notes
Building on Windows with pnpm requires short-path workarounds due to the 260-character MAX_PATH limit.
See the init script at ~/.gradle/init.d/cmake-short-path.gradle for the Gradle + CMake redirects that
make the Android build work on Windows. The script redirects buildDir for pnpm-store modules to
E:/lm-build/<module> and CMake staging to C:/lm-cmake/<module>.
Metro config (metro.config.js) excludes Next.js .next/ build directories from file watching to
prevent ENOENT crashes when sibling web apps run simultaneously.
Environment
| Variable | Description |
|---|---|
EXPO_PUBLIC_API_URL | Fastify API base URL (default: http://localhost:3003) |
Copy .env.example to .env and set EXPO_PUBLIC_API_URL for non-local environments.