Skip to content

About

An Instacart‑inspired grocery & convenience delivery app — Expo · React Native · NativeWind, with a real Prisma + Postgres backend.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

🧺 GrabBag

An Instacart‑inspired grocery & convenience delivery app — Expo · React Native · NativeWind, with a real Prisma + Postgres backend.


GitHub LinkedIn


Expo React Native TypeScript NativeWind Reanimated Zustand Prisma PostgreSQL Stripe

⚡ TL;DR — get it running

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:4000

Demo logins (seeded): admin@basket.dev / admin1234 (ADMIN) · demo@basket.dev / demo1234 (USER).

📖 Full backend & API reference → server/README.md


📸 Screenshots


Home — light

Home — dark

Store · slots + shelves

Product detail

Cart · free‑delivery meter

Checkout · 5 steps

Stripe payment sheet

Live order tracking

Admin · products

✨ Features

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).

🧱 Tech stack

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 in global.css, automatic dark:
  • 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 / ADMIN roles · 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.

🗂️ Project structure

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

How it fits together

Screens talk only to the service layer (src/services/*), the Zustand stores, and the Tailwind theme — never to the network directly.

  • src/services/api tries 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.ts into server/prisma/data/catalog.json), so nothing breaks when you switch.

🚀 Getting started (detailed)

Prerequisites

  • Node.js 20+ and npm
  • Docker (for the Postgres database)
  • Expo Go on your phone, or an iOS Simulator / Android emulator

1 · Run the app

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 browser

2 · Run the backend

cd 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:4000

From the repo root you can also drive the backend via passthrough scripts: npm run backend:setup (install + generate + migrate + seed) then npm run server.

3 · Enable real Stripe payments (optional)

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.


🛠️ Scripts

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.


🎨 Design system

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.


🔐 Environment & security

  • Copy .env.example → .env in both the root and /server. .env files are git‑ignored.
  • Anything prefixed EXPO_PUBLIC_ is bundled into the client — never put a secret there. Stripe's secret key lives only in server/.env.
  • Change JWT_SECRET to a long random value before any real deployment.

👤 Author

devpayoub

GitHub LinkedIn


📄 License

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.

Built with 🧺 and ☕ by devpayoub

About

An Instacart‑inspired grocery & convenience delivery app — Expo · React Native · NativeWind, with a real Prisma + Postgres backend.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages