A React + TypeScript web interface for collecting restaurant menu data and sending it to the SoundFood API.
SoundFood creates tailored music experiences for restaurants to enhance the perceived flavors of dishes. This app focuses on data collection: menus, dishes, and dish-specific sensory attributes (tastes, colors, textures, shapes, emotions, etc.).
- Features
- Tech Stack
- Project Structure
- Getting Started
- Development
- Deployment
- Architecture
- Documentation
- Contributing
- License
| Category | Features |
|---|---|
| π Authentication | Register, login, logout with token persistence |
| π€ Profile | View profile, update email, change password |
| π Menus | Create, edit, delete, save as draft, submit |
| π½οΈ Dishes | Add, edit, delete, reorder via drag & drop |
| π¨ Sensory Attributes | Taste sliders, colors (up to 3), textures, shapes, emotions |
| π i18n | English and Italian with language switcher |
| Category | Technology |
|---|---|
| Framework | React 18 + TypeScript |
| Build Tool | Vite 5 |
| UI Library | Chakra UI 2.x |
| State | React Context + Reducers |
| i18n | i18next |
| HTTP Client | Fetch API (custom wrapper) |
| Unit Testing | Vitest |
| E2E Testing | Playwright |
| Deployment | Docker + Nginx / GitHub Pages |
src/
βββ api/ # API client, services, and types
β βββ client.ts # HTTP client with auth handling
β βββ services.ts # API service functions
β βββ types.ts # TypeScript interfaces
βββ features/ # Feature-based modules
β βββ auth/ # Authentication (context + UI)
β β βββ model/ # AuthContext
β β βββ ui/ # LoginPage, ProfilePage
β βββ menu/ # Menu management
β βββ hooks/ # useAttributes, etc.
β βββ model/ # Dish types
β βββ ui/ # MenuForm, DishCard, etc.
βββ shared/ # Shared utilities and components
β βββ config/ # Constants
β βββ lib/ # Utilities (storage, helpers)
β βββ ui/ # Reusable UI components
βββ hooks/ # Global custom hooks
βββ test/ # Test setup and utilities
βββ App.tsx # Main app component
βββ i18n.ts # i18next configuration
βββ theme.ts # Chakra UI theme
e2e/ # Playwright E2E tests
public/locales/ # Translation files (EN/IT)
docs/ # Documentation
More details in src/README.md.
- Node.js 20+ (CI uses Node 20)
- Yarn (recommended) or npm
- Access to a compatible SoundFood API
# Clone the repository
git clone https://github.com/SoundFoodPhygital/menu-client.git
cd menu-client
# Install dependencies
yarn install
# Start development server
yarn devCreate a .env file from the template:
cp .env.example .env| Variable | Description | Default |
|---|---|---|
VITE_API_BASE_URL |
SoundFood API base URL | http://localhost:5000 |
VITE_BASE_PATH |
Base path for hosting (production) | / |
| Command | Description |
|---|---|
yarn dev |
Start development server |
yarn build |
Build for production |
yarn preview |
Preview production build |
yarn lint |
Run ESLint |
yarn test |
Run unit tests (watch mode) |
yarn test:run |
Run unit tests once |
yarn test:coverage |
Run tests with coverage |
yarn test:e2e |
Run Playwright E2E tests |
yarn test # Watch mode
yarn test:run # Single run
yarn test:coverage # With coverage reportyarn test:e2e # Headless
yarn test:e2e:headed # With browser UI
yarn test:e2e:debug # Debug modeNote: Full E2E coverage requires a reachable API or mock server.
Build and run with Docker:
# Build image
docker build \
-t menu-client \
--build-arg VITE_BASE_PATH=/ \
--build-arg VITE_API_BASE_URL=http://localhost:5000 \
.
# Run container
docker run --rm -p 8080:80 menu-clientOr use Docker Compose:
docker compose up --buildOpen http://localhost:8080.
The production build defaults to VITE_BASE_PATH=/menu-client/ for GitHub Pages:
yarn build
yarn deployflowchart TB
subgraph Client["π₯οΈ Frontend (React + Vite)"]
UI["UI Components"]
Auth["Auth Feature"]
Menu["Menu Feature"]
API["API Client"]
I18n["i18n"]
end
subgraph Backend["βοΈ SoundFood API"]
AuthAPI["Auth Service"]
MenuAPI["Menu Service"]
AttrAPI["Attributes Service"]
end
User((π€ Restaurant User)) --> UI
UI --> Auth
UI --> Menu
UI --> I18n
Auth --> API
Menu --> API
API --> AuthAPI
API --> MenuAPI
API --> AttrAPI
sequenceDiagram
autonumber
actor U as π€ User
participant FE as Frontend
participant API as SoundFood API
U->>FE: Fill menu details
U->>FE: Add dishes with attributes
U->>FE: Click Submit
FE->>API: POST /api/menus
API-->>FE: { id: menuId }
loop Each dish
FE->>API: POST /api/menus/{menuId}/dishes
API-->>FE: { id: dishId }
end
FE->>API: POST /api/menus/{menuId}/submit
API-->>FE: { status: 'submitted' }
FE-->>U: β
Success notification
FE-->>U: Redirect to menus list
sequenceDiagram
autonumber
actor U as π€ User
participant FE as Frontend
participant API as SoundFood API
Note over U,API: List Menus
U->>FE: Open "My Menus"
FE->>API: GET /api/menus
API-->>FE: ApiMenu[]
FE-->>U: Display menu list
Note over U,API: View Details
U->>FE: Select menu
FE->>API: GET /api/menus/{id}
FE->>API: GET /api/menus/{id}/dishes
API-->>FE: Menu + Dishes
FE-->>U: Display details
Note over U,API: Delete Menu
U->>FE: Delete menu
FE->>API: DELETE /api/menus/{id}
API-->>FE: { success }
FE-->>U: Remove from list
classDiagram
class User {
+number id
+string username
+string role
+datetime created_at
}
class Menu {
+number id
+string title
+string description
+MenuStatus status
+datetime updated_at
}
class Dish {
+number id
+string name
+string section
+TasteProfile tastes
+string[] colors
}
class TasteProfile {
+number sweet
+number bitter
+number sour
+number salty
+number umami
+number piquant
+number fat
+number temperature
}
class Attribute {
+number id
+string description
}
class MenuStatus {
<<enumeration>>
draft
submitted
}
User "1" --> "*" Menu : owns
Menu "1" --> "*" Dish : contains
Menu --> MenuStatus
Dish "1" --> "1" TasteProfile
Dish "*" --> "*" Attribute : emotions
Dish "*" --> "*" Attribute : textures
Dish "*" --> "*" Attribute : shapes
stateDiagram-v2
[*] --> Draft: Create menu
Draft --> Draft: Edit / Save
Draft --> Submitted: Submit
Submitted --> Draft: Revert
Submitted --> Submitted: Edit
state Draft {
[*] --> Valid
Valid: β Title required
Valid: β Dishes optional
}
state Submitted {
[*] --> Complete
Complete: β Title required
Complete: β At least 1 dish
}
stateDiagram-v2
[*] --> Checking: App loads
Checking --> Authenticated: Token valid
Checking --> Unauthenticated: No token
Unauthenticated --> Authenticating: Login/Register
Authenticating --> Authenticated: Success
Authenticating --> Unauthenticated: Failure
Authenticated --> Unauthenticated: Logout
| Document | Description |
|---|---|
| README.md | This file β project overview |
| src/README.md | Source code architecture details |
| docs/REQUIREMENTS.md | User stories and functional requirements |
| .github/copilot-instructions.md | Development guidelines for Copilot |
Contributions are welcome! Please follow the guidelines in .github/copilot-instructions.md:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Follow DRY and SOLID principles
- Write tests for new features
- Add translations (EN + IT)
- Commit your changes
- Open a Pull Request
This project is licensed under the Apache-2.0 License β see the LICENSE file for details.
SoundFood β Enhancing flavors through music π΅π½οΈ