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.envor.env.localfiles. They can contain API hosts and other environment-specific values. Keep them local only;.env.exampleis the file that belongs in git.
Menu data flow
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:
CustomerPageusesuseMenuAvailability(src/hooks/use-menu-availability.ts).- The hook calls
fetchProducts()→/api/menu/products/andfetchAvailability()→/api/menu/availability/. - Availability rows are keyed by
product_key. - Products are grouped/filtered (
src/lib/menu/customer-menu.ts) and unavailable items are hidden. - 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
- Put the string in both locale files under the same nested path.
- Export a constant with that dotted path in a nearby
*.constants.ts. - 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
- Add the same key path in
src/locales/tr/translation.jsonandsrc/locales/en/translation.json. - Export a constant in the relevant
*.constants.ts. - Use
t(YOUR_CONSTANT)in the component.
Add a new language
- Create
src/locales/<code>/translation.json(copy structure fromenortr). - Import it in
src/i18n.tsand register underresources. - Add an entry to
LANGUAGESinLanguageSwitcher.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)
Related docs
- Home — system overview
- API — Django backend
- Staff app — POS
- Environments — ports and hosts