An Expo app for exploring restaurant menus and discovering dishes. Built with a centralized design system based on wireframes in UserInterfaces/.
- Node.js (LTS) and npm
- Expo Go on your phone (iOS / Android) if you want to test on a device
- For iOS Simulator: Xcode (macOS)
- For Android Emulator: Android Studio with a virtual device
npm install
npm startThis starts the Expo dev server (Metro). A QR code and shortcuts appear in the terminal; you can also use the Dev Tools page that opens in the browser.
- Install Expo Go on your phone.
- Ensure the phone and computer are on the same Wi‑Fi (or run
npx expo start --tunnelif they are not). - iPhone: open the Camera app and scan the QR code → open in Expo Go.
Android: open Expo Go and use Scan QR code. - The project loads in Expo Go. If the bundle fails to load, check the firewall and that Metro is reachable from the phone.
- Install Xcode from the App Store and open it once to finish setup.
- Run
npm start, then pressiin the terminal to open the iOS Simulator, or choose Run on iOS simulator from the Dev Tools UI.
- Install Android Studio, create a virtual device (AVD), and start the emulator.
- Run
npm start, then pressain the terminal to install and launch the app on the emulator, or choose Run on Android device/emulator from the Dev Tools UI.
Create a .env file in the project root (see .env.example if present) with your hosted Supabase project values:
EXPO_PUBLIC_SUPABASE_URLEXPO_PUBLIC_SUPABASE_KEY(anon / public key)EXPO_PUBLIC_MENU_API_URL— base URL of the Flask API without a trailing slash (e.g.http://192.168.1.10:8080). On a physical device, use your computer’s LAN IP, notlocalhost. The simulator can usehttp://127.0.0.1:8080.
Apply migrations so the menu-uploads Storage bucket and policies exist (npm run supabase:db:push).
Restart the dev server after changing .env.
Schema and RLS live in supabase/migrations/. This project is intended to use Supabase Cloud (not a local Docker stack).
-
Create a project at supabase.com.
-
Install the CLI and link your project, then push migrations:
npx supabase login npx supabase link --project-ref YOUR_PROJECT_REF npm run supabase:db:push
YOUR_PROJECT_REFis in the dashboard URL:https://supabase.com/dashboard/project/<project-ref>. -
In the Supabase dashboard → Project Settings → API, copy the Project URL and anon public key into
.envasEXPO_PUBLIC_SUPABASE_URLandEXPO_PUBLIC_SUPABASE_KEY.
The app reads these in lib/supabase.ts.
See backend/README.md. Quick start: cd backend, create a venv, pip install -r requirements.txt, cp .env.example .env, python app.py (port 8080 by default). With MOCK_MENU_PARSE=1, POST /v1/parse-menu returns a static ParsedMenu JSON.
Diner flow: Home → camera or photo library → upload to Storage → Processing screen → Flask parse → save rows to diner_menu_scans / sections / dishes → Menu tab shows the new scanId (full menu UI comes next).
| Script | Purpose |
|---|---|
supabase:link |
Link CLI to a remote project (supabase link) |
supabase:db:push |
Push migrations to the linked remote database |
supabase:migration:new |
Create a new empty migration file under supabase/migrations/ |
Design tokens and reusable components live in constants/theme.ts and components/. All screens should use these—no duplicated styles.
Colors
| Token | Value | Usage |
|---|---|---|
primary |
#FF6A3D |
Buttons, links, accents |
background |
#FFFFFF |
Screen background |
text |
#101828 |
Headings, labels |
textSecondary |
#667085 |
Body, subtitles |
textPlaceholder |
#A0AEC0 |
Input placeholders |
error |
#E53E3E |
Error text, validation |
border |
#D0D5DD |
Input borders, dividers |
Spacing (xs 4 → xxxl 40) · Border radius (sm 8 → base 12 → full) · Typography — heading, headingSmall, body, bodyMedium, caption, label, button, small
Users can be a diner, a restaurant owner, or both. The app uses two shell palettes so each mode feels distinct:
| Role | Theme object | Primary | Screen feel |
|---|---|---|---|
| Diner | dinerRoleTheme |
Orange #FF6A3D (matches global brand) |
Warm off-white background (#FFFCFA), orange tabs/CTAs/cards where the diner shell applies |
| Restaurant | restaurantRoleTheme |
Green #059669 |
White background, mint-tinted accents (primaryLight, borders), green active tabs and primary buttons |
Where it shows up: DinerTabScreenLayout / RestaurantTabScreenLayout, DinerBottomNav / RestaurantBottomNav, RoleAppHeader (badges and segmented Diner | Restaurant switch), and role-colored PrimaryButton usage (accentColor / accentShadowRgb) on profile and similar screens.
Shared screens (e.g. login) use the global Colors from theme.ts. See docs/account-roles.md for dual-role behavior.
| Component | Props | Description |
|---|---|---|
PrimaryButton |
text, onPress, … optional accentColor, accentShadowRgb for role-colored CTAs |
Primary filled button |
SecondaryButton |
text, onPress, style, disabled, loading, icon |
Outlined secondary action |
InputField |
label, error, placeholder, style, inputStyle, containerStyle + TextInput props |
Labeled input with optional error |
ScreenContainer |
children, scroll, padding, backgroundColor, centered |
Screen layout with safe area |
ErrorText |
text or children, style |
Red error message |
Divider |
text, style |
Horizontal divider, optional "OR" text |
Usage
import {
Divider,
InputField,
PrimaryButton,
ScreenContainer,
SecondaryButton,
} from "@/components";
import { Colors, Spacing, Typography } from "@/constants/theme";
export default function LoginScreen() {
return (
<ScreenContainer scroll padding="xl">
<Text style={[Typography.heading, { color: Colors.text }]}>
PickMyPlate
</Text>
<InputField label="Email" placeholder="your@email.com" />
<PrimaryButton text="Log In" onPress={() => {}} />
<Divider text="OR" />
</ScreenContainer>
);
}- Diner personalization & smart preference tags — onboarding flow, rule-based tag parsing, and Supabase schema (
diner_*tables, diner-only RLS). - Restaurant owner: login, registration, profile — auth vs
restaurants/restaurant_cuisine_types, restaurant-only RLS. - Dual diner + restaurant accounts —
user_roles, role picker, and switching after login. - Automated LLM PR review — GitHub Actions workflow that runs Gemini on pull requests, posts a PR comment, and keeps human approval required.
- Diner menu scans —
lib/menu-scan-schema.tsdefines theParsedMenuAPI contract andassembleParsedMenufor DB-backed menus. Tables:diner_menu_scans,diner_menu_sections,diner_scanned_dishes,diner_favorite_dishes(seesupabase/migrations/).
PickMyPlate2/
├── app/ # Expo Router screens
│ ├── _layout.tsx
│ └── index.tsx
├── assets/
├── components/ # Reusable UI (design system)
│ ├── PrimaryButton.tsx
│ ├── SecondaryButton.tsx
│ ├── InputField.tsx
│ ├── ScreenContainer.tsx
│ ├── ErrorText.tsx
│ ├── Divider.tsx
│ └── index.ts
├── constants/
│ ├── theme.ts # Global design tokens
│ └── role-theme.ts # Diner vs restaurant shell colors
├── docs/ # Feature / architecture notes
├── lib/ # Supabase client, parsers, etc.
├── hooks/
│ ├── use-color-scheme.ts
│ ├── use-color-scheme.web.ts
│ └── use-theme-color.ts
├── supabase/
│ └── migrations/ # Postgres schema + RLS
├── UserInterfaces/ # Wireframes & assets
└── app-example/ # Starter reference (excluded from build)