Online Menu

This project is publicly accessible. Click to access.

Published:

9 minute read

Online Menu: Category hierarchy (Chinese)

Online Menu: Category hierarchy (Chinese)

In One Sentence

Online Menu is a bilingual family recipe website I built and deploy myself — browse dishes in English or Chinese, track which recipes are mastered vs aspirational, view cooking tips and sauce formulas, and admins edit everything in the browser without a third-party CMS.

The Problem

Household menus lived in scattered notes and chat messages. Bilingual families need both languages in one place. Recipes evolve over time — a static document goes stale. There was no fun way to track which dishes we had actually mastered vs still wanted to try.


Six Core Capabilities

1. Bilingual by Design — Not Bolted On

Pain point: Translation is usually an afterthought — English UI with Chinese content pasted in, or vice versa.

What Online Menu does:

  • Dual-language data model: every text field stored as { en: string, zh: string } — dish names, notes, category names, subcategory names, ingredient descriptions, sauce recipes.
  • One-tap language toggle in header — switches entire UI chrome and all content simultaneously.
  • Localized chrome: title bar, loading text, buttons, modals, status labels, bottom nav, confirm dialogs all switch with active language.
  • Bilingual edit forms: side-by-side CN/EN inputs; both names required on save with validation errors in active language.
  • Multiline bilingual fields: dish notes, ingredient descriptions, sauce recipes use paired textareas with pre-wrap line break preservation.
  • Pinyin-based ID generation: new items get IDs like dish_{pinyin}_{uuid} using the pinyin library on Chinese names — human-readable slugs in Firestore.

2. Dish Status Game — Unlock, Test, Lock

Pain point: A flat list of recipes does not reflect the family's cooking journey — some are mastered, some are experiments, some are aspirational.

What Online Menu does:

  • Three status states with colour-coded visual encoding:
StatusENCNMeaningColour
unlockedUnlocked已解锁Mastered family favouriteGreen
testingTesting测试中Work in progressOrange
lockedLocked待解锁Not yet tried / aspirationalGray
  • Status legend banner at top of Menu tab — all three badges visible with localized labels.
  • Dish chips use status-coloured background, border, and shadow — scannable at a glance.
  • Sort order: unlocked → testing → locked within each subcategory.
  • Default: new dishes start as locked; admin changes via dropdown in edit form.
  • Detail modal shows status badge next to dish title alongside emoji and rating.

3. Category Hierarchy & Dish Cards

Pain point: A long flat list of dishes is hard to browse in the kitchen; categories need to match how a real menu is organised.

What Online Menu does:

  • Three-level hierarchy: Category (accordion) → Subcategory (heading) → Dish chips — derived at runtime by grouping flat dishes array on categoryId.
  • Expandable category accordion with chevron rotation animation; subcategory headings within each category.
  • Dish chips: pill-shaped buttons showing name + optional emoji + optional star rating (⭐ with numeric score; "分" suffix in Chinese).
  • Dish detail modal: full name, emoji, rating, status badge, bilingual notes section.
  • Admin dish CRUD:
    • Global "Add Dish" button
    • Per-category ➕ and per-subcategory ➕ (pre-fills context)
    • Edit / delete from detail modal with confirmation
    • Category assignment via dropdown of existing categories; subcategory via free-text bilingual fields (typing new subcategory creates it on save)
  • Post-save UX: auto-scroll to saved dish with 3-second orange highlight pulse.
  • Category migration: moving a dish updates local state — removes from old group, adds to new, auto-expands target category.

4. Recipe Secrets Tab — Tips & Sauces

Pain point: Cooking knowledge is not just dish names — ingredient prep tips and sauce formulas are separate knowledge that gets lost.

What Online Menu does:

  • Two collections on Recipe tab (📖 食谱秘籍):
    1. Ingredient Tips (ingredient_tips) — { id, name, description } bilingual; cooking advice and prep notes.
    2. Sauce Recipes (sauce_recipes) — { id, name, recipe } bilingual; step-by-step sauce formulas.
  • Card list display with title + body; line breaks preserved via pre-wrap.
  • Admin CRUD per section: ➕ add, edit button, 🗑️ delete on each card; shared edit modal for create/update.
  • Bottom navigation switches between Menu and Recipe tabs — fixed 60px bar with emoji icons.
  • Full REST: GET all, POST create, PUT update, DELETE for both collections.

5. Google OAuth Admin — CMS Without a CMS

Pain point: Updating a family menu should not require editing JSON files or paying for a headless CMS subscription.

What Online Menu does:

  • Public read access for all visitors — no login required to browse.
  • Google Sign-in via Firebase Authentication popup (signInWithPopup + GoogleAuthProvider).
  • Admin verification: after auth, frontend fetches GET /api/admins and compares signed-in email against Firestore admins collection allowlist.
  • Admin indicator: crown emoji (👑) next to sign-out button when admin.
  • Gated editing UI: isAdmin controls visibility of all add/edit/delete buttons — non-admin signed-in users see the same read-only view as anonymous visitors.
  • In-browser CRUD: all edits via modal forms; changes POST/PUT/DELETE through Express API → Firestore; local React state updated immediately after save.
  • No third-party CMS — the React app itself is the content management interface.

6. Mobile-First Kitchen UX & Deployment

Pain point: Recipes are used on a phone in the kitchen — desktop-first layouts fail on small screens.

What Online Menu does:

  • Mobile-first design: pink/white theme (#fff0f5 background, #e91e63 accent); fixed title bar + bottom nav; content padding adjusts for title bar height (56px desktop / 96px mobile).
  • Responsive title bar: single row on desktop, two-row stack on mobile (<600px).
  • Modal system: overlay modals for dish detail, dish edit, ingredient/sauce edit (z-index 1000).
  • Micro-interactions: hover lift/scale on cards/chips/buttons, staggered fade-in on accordion expand, highlight animation after save.
  • Parallel data fetch on mount — dishes, ingredient tips, sauce recipes, and admin list loaded simultaneously via Axios.
  • Deployment stack:
    • Frontend: React 19 on GitHub Pages at jacky-info.com/menuv2/ (CRA homepage + gh-pages deploy; GitHub Actions CI on push).
    • Backend: Express 5 on Vercel (backend-five-tau-19.vercel.app/api).
    • Database: Firebase Firestore via Firebase Admin SDK (local service account or env vars).
    • Auth: Firebase Auth (Google) client-side only.

Real-World Impact

Used daily at home. Demonstrates full-stack delivery — bilingual i18n, OAuth admin gating, Firestore CRUD, and public HTTPS deployment on personal domain without third-party CMS costs.

Skills Demonstrated

AreaWhat this project shows
Full-stackReact 19 + Express 5 + Firebase Firestore
i18n{ en, zh } data model + one-tap UI toggle
AuthGoogle OAuth, email allowlist admin gating
CMS-like adminIn-browser CRUD without third-party CMS
DeploymentGitHub Pages + Vercel + Firebase, HTTPS on personal domain

How It Works (Plain English)

  1. Visitors browse menu or recipe tab in preferred language — no login needed.
  2. Admin signs in with Google → crown appears → add/edit/delete buttons visible.
  3. Edit dish, tip, or sauce in modal → save → Express API writes to Firestore → UI updates immediately.
  4. Status colours and sort order reflect family's cooking progress at a glance.

Architecture

┌──────────────────────────────────────────────┐
│     React Web App (GitHub Pages · HTTPS)      │
│  Menu · Recipes · EN/CN toggle · Admin modals │
└─────────────────────┬────────────────────────┘
                      │ Axios REST
         ┌────────────┼────────────┐
         ▼            ▼            ▼
  ┌────────────┐ ┌─────────┐ ┌──────────────┐
  │ Express API│ │Firestore│ │ Google OAuth │
  │  (Vercel)  │ │ dishes  │ │  (Firebase)  │
  │            │ │ tips    │ │              │
  │            │ │ sauces  │ │              │
  │            │ │ admins  │ │              │
  └────────────┘ └─────────┘ └──────────────┘

Tech Stack

  • Frontend: React 19, TypeScript, Create React App, Context API (Auth + Language), Axios, UUID, Pinyin
  • Backend: Node.js, Express 5, Firebase Admin SDK, dotenv, CORS
  • Auth: Firebase Authentication (Google Sign-in)
  • Database: Firebase Firestore
  • Deploy: GitHub Actions → GitHub Pages (frontend); Vercel (API)

Our family's menu, online and bilingual.