Safer Inventory
Safer Inventory is a Shopify + QuickBooks Online inventory operations platform — a real, shipping product with a marketing site, a web dashboard, and a native mobile app for warehouse staff. Multi-tenant from day one. Offline-first on mobile.
A pnpm + Turbo monorepo with three first-party apps and four shared packages. TypeScript end-to-end. The same design tokens render byte-for-byte identical surfaces across web and mobile.
apps/web
Marketing site + dashboard
- ▸Next.js 16.2.4 on the App Router with React Server Components
- ▸Turbopack as the dev/build runtime — instant HMR, fast cold starts
- ▸Tailwind v4 with CSS custom-property tokens; zero CSS-in-JS runtime
- ▸Framer Motion for orchestrated reveals and spring micro-interactions
- ▸Next.js 16's new proxy.ts convention mounts clerkMiddleware at the edge
- ▸Auth-sensitive routes pinned to the Node runtime, not edge
- ▸beforeInteractive theme script to prevent flash of wrong theme
apps/mobile
Expo / React Native warehouse app
- ▸Expo SDK with expo-router (file-based, App Router parity)
- ▸expo-camera scanner: EAN-13, UPC, Code128, QR, PDF417, ITF14, Codabar
- ▸Local SQLite via Drizzle ORM caches catalog, inventory, and orders
- ▸pendingMutations queue drains writes when the device returns online
- ▸BullMQ-style sync pump exposes queued / syncing / conflict / failed counts
- ▸App-level lock — biometric or passcode after N minutes backgrounded
- ▸expo-haptics, expo-secure-store, expo-local-authentication baked in
- ▸Sentry @sentry/react-native at the root for crash + font-load capture
apps/api
Backend handlers + workers
- ▸Drizzle ORM end-to-end — same library on server and device, different drivers
- ▸Postgres for the server, SQLite on the device, identical type contracts
- ▸BullMQ workers backed by IORedis for queue processing
- ▸Upstash Redis REST for edge-friendly rate limiting and OAuth state storage
- ▸Per-organization rate limiting with an explicit owner-bypass for trusted operators
- ▸Stripe + RevenueCat reconciled into a unified subscriptionSource on the org
The discipline layer. Code reuse without the lock-in of a framework. Every package is plain TypeScript with explicit boundaries.
@safer/ui
Design system — React components shared between web and the mobile webview surfaces. Tokens defined in CSS custom properties; the mobile app re-implements the exact same token table in theme.ts because RN can't consume CSS, but the visual output is byte-for-byte aligned.
@safer/types
Cross-surface TypeScript contracts. A Shopify webhook payload type used by the API is the same type consumed by the web dashboard and the mobile app. End-to-end strict mode, zero any.
@safer/integrations
Shopify and QuickBooks Online OAuth adapters. Install flows, callback validation, refresh-token rotation, sandbox/production switching, scope negotiation, and webhook signature verification.
Internal config
ESLint, Prettier, TypeScript base config, Tailwind preset, and shared Drizzle config — the discipline layer that keeps three apps and four packages from drifting.
Clerk handles identity. We handle the integration ceremony — OAuth state tokens stored in Redis with 10-minute TTLs, refresh-token rotation, and a branded loader overlay during the cross-domain handoff so users never see a blank tab.
- ✓Clerk v6 on both web (@clerk/nextjs) and mobile (@clerk/clerk-expo)
- ✓Email/password, Sign in with Apple, Google, GitHub OAuth
- ✓Custom JWT template (safer-api) with fallback to default token
- ✓Handles needs_client_trust terminal status alongside complete for device-trust flows
- ✓OAuth state tokens minted, stored in Redis with a 10-minute TTL, validated on callback — no in-flight CSRF window
- ✓AuthRequiredError boundary in install handlers redirects expired sessions to a graceful error UI instead of leaking a 500
Aesthetic
Premium electrical contracting meets editorial magazine — deep navy #0c1e33 backgrounds, gold accents #d6b059 used sparingly, warm cream text #f0ede6 (never pure white).
Typography trio
Cormorant Garamond (serif display, italic for emphasis), Plus Jakarta Sans (geometric sans body), JetBrains Mono (numbers, SKUs, OAuth state IDs, technical metadata).
Surfaces
Glass-card with a hairline gold accent stripe across the top edge — suggests physical lighting on a real card. Multi-layer shadows mimic how real surfaces cast light.
Motion
Spring-based: damping 20, stiffness 180. Never bouncier (cheap), never stiffer (mechanical). One canonical config across web and mobile.
Theme
Dark by default, full light-mode override via html.light class with localStorage persistence and zero-flash hydration.
// Engineering practices
Server Components-first — auth gates resolve on the server, client bundles stay small. Multi-org tenancy baked into the core data model and the auth boundary, not bolted on. Root-cause-first debugging — the proxy.ts migration was diagnosed by reading the Next.js dist source for the PROXY_FILENAME constant rather than guessing.
The RouteLoaderProvider shows a Safer-branded full-screen overlay during OAuth redirects with animated marks, a gold key-flow line, cycling status pills, and a 30-second safety auto-dismiss. Most teams ship a blank tab during these transitions. We don't.