Customer app

The Customer app is the public digital menu guests open (typically via QR). Its job is to introduce the restaurant, show products, and — later — take orders.

Today guests browse the menu only. Ordering from this app will be added in the future.

Repo: customer (Morvanes Customer)
Stack: React, TypeScript, Vite

Environment

Copy .env.example to .env (or .env.local) and set the API host if needed.

Variable Purpose
VITE_API_BASE_URL Backend origin (no trailing /api)

Local backend: leave the default — the app targets the local API (http://localhost:8000 when the env var is unset in a non-browser context; see src/config/app.ts). See Environments for ports and hosts.

Testing environment: point at the shared test API:

VITE_API_BASE_URL="http://resamenu.morvanes.info"

Full host list: Environments → Testing.

Warning — do not commit secrets
Never commit real .env or .env.local files. They can contain API hosts and other environment-specific values. Keep them local only; .env.example is the file that belongs in git.

The page loads catalog and availability from the shared API, then renders the menu.

Endpoint Purpose
GET /api/menu/products/ All products (name, category, price, image, portions, …)
GET /api/menu/availability/ Per-product availability (disabled, stock, windows)

Flow in code:

  1. CustomerPage uses useMenuAvailability (src/hooks/use-menu-availability.ts).
  2. The hook calls fetchProducts()/api/menu/products/ and fetchAvailability()/api/menu/availability/.
  3. Availability rows are keyed by product_key.
  4. Products are grouped/filtered (src/lib/menu/customer-menu.ts) and unavailable items are hidden.
  5. UI sections (MenuHero, category cards, search, sections) render the result.

Products and availability refresh about every 60 seconds.

Product names, categories, descriptions, and prices come from the API as stored (usually Turkish today). Only the UI chrome is translated — see Internationalization. English (and other) product copy will be added later; language-based naming and related fields are planned to live in the database.

Branding and templates

Right now the menu chrome is static for a single restaurant (logo, hero video, copy, contact details hardcoded in the app).

Later, each restaurant will get a customized menu built from templates (Figma-like design services). That multi-tenant theming layer does not exist yet — keep branding changes local and explicit until then.


Internationalization

UI strings use i18next + react-i18next, with i18next-browser-languagedetector for the initial language. Flags in the switcher come from country-flag-icons.

Libraries

Package Role
i18next Core i18n
react-i18next React bindings (useTranslation, Trans)
i18next-browser-languagedetector Detect language (e.g. localStorage, browser)
country-flag-icons Flags in the language switcher

Fallback language is tr. Supported locales today: tr, en.

Project structure

src/
  i18n.ts                          # init + resources
  locales/
    tr/translation.json
    en/translation.json
  components/layout/
    LanguageSwitcher.tsx           # tr / en selector
  **/*.constants.ts                # dotted keys for t()

main.tsx imports ./i18n before the app mounts. Vite auto-imports useTranslation and Trans.

How translations are used

  1. Put the string in both locale files under the same nested path.
  2. Export a constant with that dotted path in a nearby *.constants.ts.
  3. Call t(CONSTANT) in the component.

Example (MenuHero.constants.ts + component):

export const MENU_HERO_TAGLINE = "components.menu.menuHero.tagline";
const { t } = useTranslation();
return <p>{t(MENU_HERO_TAGLINE)}</p>;

Locale JSON (nested under components.menu.menuHero, etc.):

{
  "components": {
    "menu": {
      "menuHero": {
        "tagline": "Urla · Taste & Atmosphere"
      }
    }
  }
}

Language switcher

LanguageSwitcher lives in the hero (and on non-home error/404 bars). It calls i18n.changeLanguage(code) for tr or en.

Add a new translation key

  1. Add the same key path in src/locales/tr/translation.json and src/locales/en/translation.json.
  2. Export a constant in the relevant *.constants.ts.
  3. Use t(YOUR_CONSTANT) in the component.

Add a new language

  1. Create src/locales/<code>/translation.json (copy structure from en or tr).
  2. Import it in src/i18n.ts and register under resources.
  3. Add an entry to LANGUAGES in LanguageSwitcher.tsx (code, label, flag).

Product catalog languages

UI chrome supports tr / en today. Product names and other catalog fields are still single-language from the API.

Planned: English (and further) product translations — language-based naming and other product info will be stored in the database so the menu can show the right locale for each guest.

What is not translated (today)

  • Product / category / portion names from the API
  • Some hardcoded category labels used for scroll targets
  • Document title / meta in index.html (still fixed Turkish)