An inventory management system for tracking, organizing, and managing the lifecycle of donated medical supplies. Built for a nonprofit's warehouse operations team to replace ad-hoc spreadsheets with a single source of truth that tracks expiration, location, donation status, and audit history across thousands of SKUs.
Live: https://b4pdatabase.vercel.app
The application provides authenticated, role-aware access to a medical supplies catalog with rich metadata (lot numbers, expiration dates, pallet locations, packaging hierarchy, cost basis, and supplier links). Operators can search, filter, edit, archive, and mark items as donated; admins manage user access through a dedicated admin console.
- Inventory CRUD with strongly-typed fields covering supply type, lot number, expiration, pallet location, packaging hierarchy (units → unit boxes → cardboard boxes → pallets), per-unit cost, weight, and dimensions.
- Lifecycle tabs — active inventory, archived items, and donated items are separated into discrete views so the working set stays focused.
- Expiration tracking with automatic flagging and filterable status (expired / not expired / all).
- Search and filtering by free-text query and supply type.
- Authentication via Supabase Auth (email/password) with rate-limit handling and session persistence.
- Role-based access control — protected routes gate the dashboard, and an admin-only console manages users.
- Responsive UI built on shadcn/ui + Radix primitives with light/dark theme support.
- Toast notifications for every mutation so destructive actions are visible and reversible.
| Layer | Choice | Why |
|---|---|---|
| Framework | React 18 + Vite 6 | Fast HMR, modern build pipeline, minimal config |
| Language | TypeScript (strict) | Compile-time guarantees on a data-heavy schema |
| Styling | Tailwind CSS v4 | Utility-first, zero-runtime, consistent design tokens |
| Components | shadcn/ui + Radix UI | Accessible primitives, owned source, no vendor lock-in |
| Routing | React Router v7 | Nested routes with layout providers |
| Forms | react-hook-form + Zod | Schema-first validation, minimal re-renders |
| Backend | Supabase (Postgres + Auth + RLS) | Managed Postgres with row-level security and JWT auth |
| Notifications | Sonner | Lightweight toast system |
| Hosting | Vercel | Zero-config deploys with preview URLs per branch |
src/
├── pages/ # Route-level views (Auth, MedicalSupplies, AdminUsers, 404)
├── components/
│ ├── layout/ # Shell, navigation, theme toggle
│ ├── ui/ # shadcn primitives (button, dialog, table, ...)
│ └── login-form.tsx
├── context/
│ └── SessionContext.tsx # Auth state + Supabase session subscription
├── router/
│ ├── index.tsx # Route definitions
│ └── AuthProtectedRoute.tsx # Redirect guard for authenticated routes
├── supabase/index.ts # Typed Supabase client singleton
├── hooks/ # Reusable hooks (theme, mobile, etc.)
├── lib/ # Utilities (cn, formatters)
├── config.ts # Env var validation (fails fast on missing config)
└── Providers.tsx # Top-level providers (theme, session, toaster)
- Single source of truth for session state.
SessionContextsubscribes tosupabase.auth.onAuthStateChangeonce at the provider level so every consumer reads from one stream — no duplicate listeners, no race conditions on logout. - Fail-fast configuration.
config.tsthrows synchronously at module load if Supabase env vars are missing, surfacing misconfiguration during boot rather than at the first network call. - Server-side authorization. Auth is enforced in Supabase via row-level security policies. The client guard (
AuthProtectedRoute) is a UX convenience — it redirects unauthenticated users — not the security boundary. - Tab-based lifecycle separation. Active / archived / donated items are filtered server-side and rendered in distinct tabs, keeping payloads small and the active working set unambiguous.
- Node.js 18+
- A Supabase project (supabase.com)
git clone https://github.com/njiedev/b4pdatabase.git
cd b4pdatabase
npm installCreate a .env file in the project root:
VITE_SUPABASE_URL=https://<your-project>.supabase.co
VITE_SUPABASE_ANON_KEY=<your-anon-key>Both variables are required — the app will refuse to boot without them.
| Command | Description |
|---|---|
npm run dev |
Start the Vite dev server with HMR |
npm run build |
Type-check (tsc) then produce a production bundle |
npm run preview |
Serve the production build locally |
npm run lint |
Lint with ESLint (zero warnings allowed) |
The application expects a medical_supplies table and a profiles table (for admin role flags). RLS policies should restrict reads/writes to authenticated users, with admin-only policies on the profiles table for user management. See src/pages/MedicalSuppliesPage.tsx for the canonical field shape.
The app is deployed on Vercel. vercel.json configures SPA fallback routing so client-side routes resolve correctly on hard refresh. Pushes to main deploy to production; every other branch produces a preview URL.
Required Vercel environment variables:
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEY
- Image uploads per SKU (Supabase Storage)
- Excel / CSV import + export
- Custom domain
- Barcode scanning for pallet check-in
- Invite-only registration
See to do.md for the working list.
MIT — see LICENSE.MD.