Two people, one household, one shopping list - always in sync. DuoCart is a mobile-first PWA where a couple (or a whole household) shares a live shopping list, browses the current week's discounts from Kaufland, Lidl, Auchan, Mega Image and Penny in one place, and sees the running total per store before reaching the till. Add an item on one phone, it appears instantly on the other.
What it does
Real-time shared lists
- Firestore-backed, grouped by store with per-store subtotals and live savings. Add, check off, adjust quantity; every change syncs to all members in the household.
Daily offers from 5 chains
- a GitHub Actions cron scrapes each retailer every morning at 07:00 (VTEX/GraphQL APIs where available, HTML fixtures where not) and publishes a normalized offer feed.
Full-catalog live search
- for Auchan and Mega Image (which expose their whole stock), search hits their APIs live for ~60k in-stock products, not just the weekly deals.
Cross-store price comparison
- tap any offer to see the same product at other chains, matched on name tokens and unit, cheapest first.
Unit prices & smart filters
- price-per-kg/l parsed from titles, filter by store / category / price range / discount state, sort by price, discount, or unit price.
Multiple households
- create and switch between groups; owners can rename the group and manage members; invites are a single shareable link.
Attribution
- see who added each item and who checked it off, by avatar.
Shopping sessions & history
- finishing a shop archives the checked items into a dated session with the total spent and who took part.
Installable PWA
- custom Firebase auth handler served from the app's own domain, offline-capable, home-screen install on iOS/Android.
Built with
React 19 + TypeScript + Vite, Tailwind v4 + shadcn/ui (Base UI), custom "Forest" design system
Firebase Auth (Google) + Firestore with membership-based security rules; persistent offline cache
Vercel serverless functions proxy the live retailer catalogs and the Firebase auth handler
GitHub Actions cron for the daily scrape; a standalone Node scraper package with fixture-based tests (Vitest)
Engineering notes
Cost-aware data layer
- offers are read from the persistent cache and refreshed at most once per day per user; the scraper diffs against existing docs and only writes what changed, keeping Firestore well within the free quota despite thousands of offers.
Race-safe cart
- adding the same offer repeatedly increments a single deterministic document inside a Firestore transaction, so rapid taps become a quantity, not duplicate rows.
Honest scraping
- each retailer was reverse-engineered from its own frontend; where a chain exposes only its weekly leaflet (Penny/Kaufland/Lidl) the app is explicit about it, and only the two chains with real catalog APIs power full-stock search.