An Instacart‑inspired grocery & convenience delivery app — Expo · React Native · NativeWind, with a real Prisma + Postgres backend.
Two parts: the Expo app (repo root) and the backend (
/server, Docker + Postgres). The app runs on its own with a bundled mock catalog, so you can skip the backend for a quick look — but auth, the admin portal, orders, and Stripe need the server.
# 1. Clone
git clone https://github.com/devpayoub/GrabBag-App.git
cd grabbag-app
# 2. App (Expo)
npm install
cp .env.example .env
npx expo start # press i / a, or scan in Expo Go
# 3. Backend (new terminal) — Postgres via Docker, then API on :4000
cd server
npm install
cp .env.example .env
docker compose up -d # start Postgres
npx prisma migrate dev --name init # create schema
npm run seed # load catalog + demo users
npm run dev # API → http://localhost:4000Demo logins (seeded): admin@basket.dev / admin1234 (ADMIN) · demo@basket.dev / demo1234 (USER).
📖 Full backend & API reference → server/README.md
![]() Home — light |
![]() Home — dark |
![]() Store · slots + shelves |
![]() Product detail |
![]() Cart · free‑delivery meter |
![]() Checkout · 5 steps |
![]() Stripe payment sheet |
Live order tracking |
![]() Admin · products |
| Area | What's inside |
|---|---|
| Onboarding | Produce‑photo welcome, address step (seeded + add‑your‑own), email/password auth or continue as guest. Guests browse + cart; checkout/orders are gated. |
| Home | Address selector, category tiles, promo cards, store sections (near you / popular / deals) with ETA, delivery fee, deal badges. Pull‑to‑refresh + skeletons. |
| Store | Delivery/pickup toggle + time‑slot picker, in‑store search, sticky scroll‑spy department tabs, department shelves of quick‑add cards, sticky free‑delivery meter. |
| Department | Full grid with sort + dietary/price filters and infinite scroll. |
| Search | Debounced product + store search with recent & popular suggestions. |
| Product | Swipeable image gallery, price (+ deal), add/stepper, details & nutrition accordions, related shelves. |
| Cart | Grouped by store, item‑replacement preferences, promo code, live summary + meter. |
| Checkout | Slot → Address → Tip → Payment → Review → Place order, with Stripe (simulated until keys are set — never collects real card data), animated success → live tracking. |
| Order tracking | Shopper timeline (Received → Shopping → Checkout → On the way → Delivered) that advances on a timer, with live found / replaced / refunded updates and a map placeholder. History + reorder. |
| Lists | Saved lists + buy‑it‑again derived from order history. |
| Account | Profile, appearance (system / light / dark), addresses, payment methods, membership, sign out. |
| Admin portal | Role‑gated CRUD for products, categories, users, and orders, plus a database overview — backed by the real API. |
| Everywhere | Full light + dark theming, reduced‑motion‑safe animations, screen‑reader labels, 44×44 targets, AA contrast, and on‑device persistence (carts / lists / orders survive restarts). |
App
- Expo SDK 56 · React Native 0.85 · React 19.2 — Hermes, New Architecture, React Compiler
- Expo Router — file‑based, typed routes
- NativeWind v4 + Tailwind CSS v3 — the design system is the Tailwind theme; semantic tokens (
bg,surface,fg,accent…) via CSS variables inglobal.css, automaticdark: - Reanimated v4 — add‑to‑cart fly, free‑delivery meter, scroll‑spy tabs, order stepper, filter sheet, press micro‑interactions
- Zustand (persisted via AsyncStorage) — session, carts (keyed by store), lists, theme, UI
- zod validation · TypeScript strict (no
any)
Backend (/server)
- Node + TypeScript · Express 4 · Prisma 6 + PostgreSQL (Docker)
- JWT auth (bcrypt) with
USER/ADMINroles · zod request validation - Stripe PaymentIntents (falls back to a simulated intent when no key is set)
- Responses match the app's exact type shapes, so the client swaps mock ⇄ server with no shape changes.
basket-app/
├── app/ # Expo Router screens (file-based, typed routes)
│ ├── _layout.tsx # imports global.css; theme + session + cart providers, fonts, splash
│ ├── (auth)/ # welcome · address · sign-in
│ ├── (tabs)/ # Home · Search · Lists · Cart(badge) · Account
│ ├── admin/ # role-gated: products · categories · users · orders · database
│ ├── store/[id].tsx # store view (departments, delivery/pickup, slots)
│ ├── store/[id]/dept/[slug] # department listing (filters + sort + infinite scroll)
│ ├── product/[id].tsx # product detail (gallery, nutrition, related)
│ ├── checkout.tsx # 5-step checkout → Stripe → place order
│ ├── order/[id].tsx # live shopper tracking + order detail
│ ├── orders.tsx # order history
│ └── payment-methods.tsx # saved payment methods
│
├── src/
│ ├── components/ # NativeWind component library (className + cva) — StoreCard,
│ │ # ProductCard, QtyStepper, SlotPicker, OrderStepper, FilterSheet,
│ │ # FreeDeliveryBar, Button, TabBar, StripePaymentSheet …
│ ├── features/ # auth · cart · catalog feature modules
│ ├── services/ # ← SWAP POINTS between mock and real backend
│ │ ├── api/ # catalog/api — hits the server, falls back to local mock offline
│ │ ├── auth/ # register / login / me (needs backend)
│ │ ├── admin/ # admin CRUD (needs backend, ADMIN role)
│ │ ├── orders/ # place order + simulated shopper-status engine (timer)
│ │ ├── payments/ # Stripe intent + simulated authorize (success/decline)
│ │ └── storage/ # AsyncStorage JSON wrapper
│ ├── store/ # Zustand stores — session, carts (by store), lists, theme, payments, ui
│ ├── mocks/ # seed data: stores, taxonomy, base catalog → generated products
│ ├── lib/ # money/cart math (integer minor units), slots, theme, cn(), zod
│ ├── hooks/ # useAsync …
│ └── types/ # shared domain types
│
├── server/ # backend — see server/README.md
│ ├── src/
│ │ ├── index.ts # Express app + middleware
│ │ ├── auth.ts # JWT sign/verify + guards
│ │ ├── db.ts # Prisma client
│ │ ├── catalog-logic.ts # filter/sort/paginate (mirrors the app's mock)
│ │ ├── serialize.ts # DB rows → app-shaped JSON
│ │ └── routes/ # auth · catalog · products · orders · payments · admin
│ ├── prisma/ # schema.prisma · migrations · seed.ts · data/catalog.json
│ └── docker-compose.yml # Postgres
│
├── scripts/ # export-catalog.ts (mock → server seed) · generate-icons.mjs
├── screenshots/ # app screenshots + banner
├── global.css # @tailwind + :root / .dark CSS variables (imported once)
├── tailwind.config.js # semantic color tokens, radii, type scale
├── metro.config.js · babel.config.js
├── BUILD-GUIDE.md # step-by-step tutorial script (the YouTube build)
└── CLAUDE.md # project rules / conventions
Screens talk only to the service layer (src/services/*), the Zustand stores, and the Tailwind
theme — never to the network directly.
src/services/apitries the backend first and falls back to the bundled mock catalog when the server is unreachable, so browsing always works offline.- Auth, admin, orders, and Stripe run against the real Express + Prisma backend in
/server. - Product & store IDs are shared between the mock and the DB (the catalog is exported from the mock via
scripts/export-catalog.tsintoserver/prisma/data/catalog.json), so nothing breaks when you switch.
- Node.js 20+ and npm
- Docker (for the Postgres database)
- Expo Go on your phone, or an iOS Simulator / Android emulator
npm install
cp .env.example .env # EXPO_PUBLIC_API_URL defaults to http://localhost:4000
npx expo start # then press i (iOS) / a (Android), or scan the QR in Expo Go
npx expo start --web # or run it in the browsercd server
npm install
cp .env.example .env
docker compose up -d # Postgres
npx prisma migrate dev --name init # schema + client
npm run seed # catalog + admin/demo users + demo orders
npm run dev # http://localhost:4000From the repo root you can also drive the backend via passthrough scripts:
npm run backend:setup(install + generate + migrate + seed) thennpm run server.
Leave the Stripe keys empty to run checkout in simulated mode. To use real test PaymentIntents, set
STRIPE_SECRET (and STRIPE_PUBLISHABLE_KEY) in server/.env, and optionally
EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY in the root .env.
App (root)
| Command | What it does |
|---|---|
npx expo start |
start Metro (--web / --ios / --android) |
npm run typecheck |
tsc --noEmit |
npm run lint |
expo lint |
npm run server |
run the backend dev server |
npm run backend:setup |
install + generate + migrate + seed the backend |
npm run seed · npm run studio |
seed the DB · open Prisma Studio |
npm run db:up · db:migrate · db:reset |
Postgres up · migrate · reset |
npm run catalog:export |
regenerate server/prisma/data/catalog.json from the mock |
Backend (/server) — full table in server/README.md.
The design language is Tailwind‑native: every color, radius, and type step is a token in
tailwind.config.js, resolved from CSS variables in global.css. Components use semantic classes
(bg-bg, bg-surface, text-fg, text-muted, bg-accent, border-line) — no raw hex, no
StyleSheet.create — so dark: swaps the whole theme cleanly. Money is stored as integer minor
units (never floats); cart math lives in src/lib/cart.
- Copy
.env.example→.envin both the root and/server..envfiles are git‑ignored. - Anything prefixed
EXPO_PUBLIC_is bundled into the client — never put a secret there. Stripe's secret key lives only inserver/.env. - Change
JWT_SECRETto a long random value before any real deployment.
devpayoub
Released under the MIT License — see LICENSE.
Design language abstracted from Instacart on Mobbin — inspiration only. All copy, branding, colors, and assets here are original. Product photos are royalty‑free (Unsplash) with blurhash fallbacks so cards look right offline.







