A Tinder-style food discovery app. Tap one button, and FoodSwipe finds real places to eat near you — then you swipe right to save and left to skip. Liked spots get a detail view with photos, hours, reviews, and one-tap Google Maps directions.
Built with React Native + Expo (SDK 54), TypeScript, and Supabase (accounts, cloud-synced likes, and a serverless proxy for Google data).
Targets SDK 54 on purpose — that's the version the current App Store / Play Store Expo Go supports, so it runs in the Expo Go you already have. (SDK 55/56 aren't on the App Store yet as of May 2026.)
FoodSwipe pulls food data from one of two places depending on whether you're signed in:
| Guests (no login) | Signed-in users | |
|---|---|---|
| Source | OpenStreetMap (Overpass API) | Google Places API (New) |
| Cost / keys | Free, no key | Paid (billing required), key stays server-side |
| Card art | Warm gradient + food emoji | Real Google photos |
| Open/closed | "Estimated" heuristic | Live open-now + hours |
| Ratings / reviews | — (OSM has almost none) | Google ratings + reviews |
The Google key is never shipped in the app — it lives in a Supabase Edge Function that the app calls (only signed-in users can). Guests stay fully functional for free.
- 🔐 Accounts via Supabase (email + password), with a local guest mode fallback.
- 📍 Location-first onboarding — grant location, then sign in. Units auto-detect from your country (US → miles, Canada/elsewhere → km) with a manual override in Settings.
- 🃏 Smooth swipe deck (UI-thread Reanimated 4 + Gesture Handler): the card follows your finger, rotates, shows green LIKE / red NOPE stamps as it crosses the threshold, and springs back if released early. Plus ✓ / ✗ / undo buttons.
- 📸 Rich place data for signed-in users — Google photos, ratings, live open/closed, hours, and reviews on the detail screen.
- ❤️ Liked list synced to your account (cross-device) — or saved on-device in guest mode.
- 🗺️ Find Directions opens Google Maps straight to the spot (a keyless deep link).
- 🎨 Warm, appetizing design.
You'll need Node 20.19+ and Expo Go (iOS / Android).
npm install
npx expo start # scan the QR with Expo Go (same Wi-Fi as your computer)- Same Wi-Fi required. On a restrictive network, use
npx expo start --tunnel. - After installing native deps or editing
.env, clear the cache:npx expo start -c.
The app runs immediately in guest mode (no setup). Sign-in (Supabase) and the rich Google data are optional upgrades — set them up below.
- Create a free project at supabase.com.
- Run the schema: Dashboard → SQL Editor → New query → paste
supabase/schema.sql→ Run. (Createsprofiles+liked_places, RLS policies, and a trigger that auto-creates a profile on sign-up.) - Add your keys: copy
.env.example→.envand fill in:From Project Settings → API. The anon/publishable key is safe to ship (protected by RLS).EXPO_PUBLIC_SUPABASE_URL=https://YOUR-REF.supabase.co EXPO_PUBLIC_SUPABASE_ANON_KEY=YOUR-anon-or-publishable-key - (For easy testing) Authentication → Providers → Email → turn off "Confirm email." Sign-up then logs you straight in. (Leave it on for production — the app already shows a "check your email" message and handles that flow.)
- Restart:
npx expo start -c.
This is optional and paid (Google requires a billing account). Without it, signed-in users still work — they just fall back to the basic experience.
Google Cloud — create the key
- console.cloud.google.com → create/select a project.
- Billing → link a card (required even for the free tier).
- APIs & Services → Library → enable "Places API (New)".
- Credentials → Create credentials → API key → open it → Restrict key → Places API (New) → Save.
Supabase — store the key & deploy the proxy (the key stays server-side, never in the app)
5. Store the key as a secret — Dashboard → Project Settings → Edge Functions → Secrets,
add GOOGLE_PLACES_API_KEY. (Or supabase secrets set GOOGLE_PLACES_API_KEY=… via CLI.)
6. Deploy the function — Dashboard → Edge Functions (paste
supabase/functions/google-places/index.ts,
name it google-places). (Or supabase functions deploy google-places via CLI.)
💸 Cost: photos, reviews, and current hours are Google's Enterprise-tier fields (~1,000 free calls/month, then paid). Each search also resolves ~1 photo per result. Fine for personal use; watch it at scale. To stretch the free tier, lower
maxResultsor make photos load lazily in the Edge Function.🔒 Security: the key is only ever in the Edge Function (a request from the app that carries the user's Supabase token). It is never bundled into the app, where it could be extracted. The function rejects anyone who isn't a signed-in user.
- Food data — guests: Overpass/OpenStreetMap (src/data/overpass.ts, with mirror failover, caching, and 1500 m → 5000 m auto-widen). Signed-in: Google Places via the Edge Function (src/data/places.ts → supabase/functions/google-places/index.ts). Both compute distance with the Haversine formula and sort nearest-first.
- "How busy is it" — for guests/OSM this is an estimate (no live data exists for free): a stable per-place value seeded from the place id, modulated by local day/time, and clearly labeled "Estimated" (src/data/busyness.ts). Signed-in users instead see Google's real open-now status + hours. For real popular-times, you'd add a paid service like BestTime.app.
- Directions (src/lib/directions.ts) — opens
https://www.google.com/maps/dir/?api=1&destination=<lat>,<lng>, a keyless universal link that launches Google Maps (or the browser) with the destination set.
App.tsx # provider tree (Gesture → SafeArea → Auth → Location → Liked → Toast)
app.json · metro.config.js # Expo config (name/slug, location permission, plugins)
.env / .env.example # Supabase keys (.env is gitignored)
supabase/
schema.sql # tables + RLS + profile trigger
functions/google-places/ # Edge Function: secure Google Places proxy (Deno)
src/
theme.ts · types.ts
data/ overpass · places · geo · busyness
lib/ supabase · env · directions
store/ AuthContext · LocationContext · LikedContext · ToastContext
components/ Card · SwipeDeck · BusyMeter · PrimaryButton · Field · states/
screens/ onboarding/ · auth/ · Home · Swipe · Liked · Detail · Settings
navigation/ RootNavigator · types
- Guest mode lets the app run with zero setup; sign-in + Supabase add cloud sync, and the Google key adds photos/reviews/hours. Each layer is optional.
- Email confirmation is your choice in Supabase; deep-link verification isn't wired (recommended: off for testing, on for production — the app handles both).
- Liked thumbnails use the gradient/emoji art; the full Google photo loads on the detail screen (Google's terms restrict caching photo URLs, so we fetch them live).
- Custom permission strings in
app.jsononly appear in a standalone build; in Expo Go you'll see Expo Go's generic location prompt. Expected. - Location on a simulator often returns nothing unless you set a simulated location — test on a real device.
- Blank screen / "worklet" error:
npx expo start -c(clears Metro cache). - QR won't connect: same Wi-Fi, or
npx expo start --tunnel. - "Supabase not configured" banner: expected until you fill
.env(guest mode still works). - Signed in but no photos/reviews: the
google-placesfunction isn't deployed, theGOOGLE_PLACES_API_KEYsecret is missing, or Google billing/"Places API (New)" isn't enabled. Check the function logs in the Supabase dashboard.
Place data © OpenStreetMap contributors (Overpass) for guests; photos & reviews powered by Google (Places API) for signed-in users.