Safer Inventory
Safer Inventory is a working operations platform that connects Shopify, QuickBooks Online, and the warehouse floor. It's a real shipping product, not a demo, with a marketing site, a web dashboard, and a native mobile app for warehouse staff. It was built to be multi-tenant from day one, and the mobile side keeps working even when the warehouse WiFi drops out.
Under the hood it's a pnpm + Turbo monorepo with three first-party apps and four shared packages, all in TypeScript. The same design tokens render the same surface across web and mobile, which is mostly invisible to the user and a big reason the product feels like one thing instead of three.
apps/web
Marketing site + dashboard
- ▸Next.js 16.2.4 on the App Router with React Server Components
- ▸Turbopack for dev and build. 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 with 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
The shared design system. React components used across web and the mobile webview surfaces. Tokens live in CSS custom properties on web, and the mobile app re-implements the same token table in theme.ts because RN can't consume CSS. Output is byte-for-byte aligned across both.
@safer/types
Cross-surface TypeScript contracts. A Shopify webhook payload type the API publishes is the same type the web dashboard and the mobile app consume. Strict mode end to end. No 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, Tailwind preset, shared Drizzle config. The discipline layer that keeps three apps and four packages from drifting apart over time.
Clerk handles identity. We handle the integration ceremony. OAuth state tokens stored in Redis with 10-minute TTLs. Refresh-token rotation. A branded loader during the cross-domain handoff so users never sit on 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 as serif display (italic for emphasis). Plus Jakarta Sans for body. JetBrains Mono for numbers, SKUs, OAuth state IDs, and metadata.
Surfaces
Glass cards with a hairline gold stripe across the top edge. It reads like real light hitting a card. Multi-layer shadows that mimic how physical surfaces catch light.
Motion
Spring-based: damping 20, stiffness 180. Not bouncy (cheap), not stiff (mechanical). One config across web and mobile so they feel like the same product.
Theme
Dark by default, full light-mode override via html.light 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 is baked into the data model and the auth boundary, not bolted on after the fact. When the proxy.ts migration broke things, we diagnosed it by reading Next.js dist source for the PROXY_FILENAME constant instead of guessing.
The RouteLoaderProvider puts up a Safer-branded full-screen overlay during OAuth redirects. Animated marks, a gold key-flow line, cycling status pills, a 30-second safety auto-dismiss. Most teams ship a blank tab during these transitions. We don't.