A mobile-first, offline-first Progressive Web App (PWA) for inventory and sales management, designed for small stores (1-5 users).
- Products — Full CRUD with transactional stock management
- Sales — Cart-based sale creation with automatic stock deduction
- Consignments — Deliver products to customers, track returns
- Dashboard — Real-time metrics (total products, low stock alerts, recent sales, open consignments)
- Offline-first — Works 100% offline using IndexedDB; syncs automatically when online
- Bilingual — English and Spanish with auto-detection and manual toggle
- Installable PWA — Add to home screen, service worker caching
| Layer | Technology |
|---|---|
| Framework | React 18 + TypeScript |
| Build | Vite |
| Styling | TailwindCSS |
| Local DB | Dexie (IndexedDB) |
| State | Zustand |
| Forms | React Hook Form + Zod |
| i18n | i18next |
| Backend | Supabase (Postgres + Auth) |
| PWA | vite-plugin-pwa (Workbox) |
The app follows a strict offline-first architecture:
- UI reads/writes only from IndexedDB (via Dexie) — never directly from Supabase
- Supabase is used exclusively for synchronization — the app works fully without network
- All stock updates are transactional —
product.stockupdate +inventory_movementinsert happen inside a single Dexie transaction - Soft deletes — records are marked
deleted = true, never physically removed
The synchronization engine runs bidirectionally:
Push phase:
- Scans all tables for records where
synced = false - Upserts them to Supabase
- On success, marks
synced = trueand updatesupdated_at
Pull phase:
- Fetches records from Supabase where
updated_at > lastSync - Merges into IndexedDB using a last-write-wins strategy
Triggers:
- On app startup
- When the browser goes back online (
onlineevent) - Every 60 seconds while online
The lastSync timestamp is stored in localStorage.
Every entity includes synchronization metadata:
| Field | Description |
|---|---|
id |
UUID generated client-side |
created_at |
ISO timestamp |
updated_at |
ISO timestamp |
synced |
Whether the record has been pushed to Supabase |
deleted |
Soft delete flag |
Entities: Products, Inventory Movements, Sales, Sale Items, Consignments, Consignment Items.
Stock is stored as a field on products.stock. Every modification:
- Runs inside a Dexie transaction
- Updates
product.stock - Inserts a corresponding
inventory_movementrecord - Marks both records as
synced = false
Movement types: IN, OUT, ADJUSTMENT, CONSIGNMENT_OUT, CONSIGNMENT_RETURN.
src/
app/ # App component, layout, routing
core/
db/ # Dexie schema, types, database initialization
sync/ # Sync engine (push, pull, orchestration)
supabase/ # Supabase client configuration
features/
products/ # Product CRUD, stock adjustments
sales/ # Sale creation, history
consignments/ # Consignment delivery and returns
inventory/ # Inventory movement types
dashboard/ # Dashboard metrics
settings/ # Language toggle, manual sync
components/ # Shared UI components (PageHeader, EmptyState, etc.)
hooks/ # Custom React hooks
stores/ # Zustand stores (UI state, sync status)
i18n/ # i18next config + locale files (en, es)
utils/ # Utility functions (currency formatting)
- Node.js >= 18
- npm >= 9
-
Clone the repository:
git clone <repo-url> cd inventory
-
Install dependencies:
npm install
-
Configure environment variables:
cp .env.example .env
Edit
.envwith your Supabase credentials:VITE_SUPABASE_URL=https://your-project.supabase.co VITE_SUPABASE_ANON_KEY=your-anon-key-here -
Set up Supabase database:
- Create a new Supabase project
- Run the migration file at
supabase/migrations/00001_initial_schema.sqlin the SQL editor - This creates all tables with Row Level Security policies scoped by
organization_id
-
Start development server:
npm run dev
-
Build for production:
npm run build
-
Preview production build:
npm run preview
| Command | Description |
|---|---|
npm run dev |
Start dev server with HMR |
npm run build |
Type-check + production build |
npm run preview |
Preview production build locally |
npm run lint |
Run ESLint |
npm run format |
Format code with Prettier |
The app is configured as an installable PWA with:
- Auto-update — new versions are applied automatically
- Offline fallback — all app routes work offline via
navigateFallback - Runtime caching — Supabase API responses cached with NetworkFirst strategy; images cached with CacheFirst strategy
- Precaching — all JS, CSS, HTML, and static assets are precached
To install: visit the app in Chrome/Edge and use the "Install" prompt, or "Add to Home Screen" on mobile.
- Two languages: English (
en) and Spanish (es) - Language is auto-detected from browser settings
- Users can manually switch via Settings
- Selection is persisted in
localStorage - All UI strings use
t()from i18next — no hardcoded text
Translation files: src/i18n/locales/{en,es}/common.json
The app is configured for deployment on Vercel:
- Connect your repository to Vercel
- Set environment variables (
VITE_SUPABASE_URL,VITE_SUPABASE_ANON_KEY) - Deploy — Vite's build output is automatically detected
| Variable | Description |
|---|---|
VITE_SUPABASE_URL |
Your Supabase project URL |
VITE_SUPABASE_ANON_KEY |
Your Supabase anonymous/public API key |
Private — all rights reserved.