DocHub
Menu items, categories, customizations, image uploads, and pricing

Menu System

Each restaurant has its own menu with items organized into categories. Items support customization groups (e.g., size, toppings) and allergen tags.

Data Model

Core fields: name, price_cents, currency (HNL/USD), image_url, category_id, description, is_active.

Items store customizations two ways:

  • Inline: customizations JSONB field — item-specific options
  • Linked: linked_customization_group_ids — references to reusable groups shared across items

Customization Groups

Each group has: name, min_selections, max_selections, and an array of options (label, price_cents, allergen_tags).

Examples:

  • “Size” — min: 1, max: 1 → must choose exactly one
  • “Toppings” — min: 0, max: 3 → optional, up to 3
  • “Sauces” — min: 1, max: null → must choose at least 1, no limit

Categories

Simple grouping: name, display_order, restaurant_id. Items reference a category via category_id. Deleting a category unlinks all its items (sets category_id: null).

Pricing & Tax

  • All prices stored in cents (price_cents)
  • Currency per item (usually HNL, some items in USD)
  • USD items converted to HNL at display time using exchange rate from config
  • Tax applied per category via restaurant_category_tax_mappings table
  • Fallback: direct tax_id on item, then 0%

Customization option prices are added to the base item price. Tax applies to the total (base + customizations).

Image Uploads

POST /api/uploads (multipart form-data):

  1. Validates file is an image, under 20MB
  2. Optimizes: auto-rotate EXIF, resize to max 800×800, JPEG quality 80
  3. Uploads to Cloudflare R2 at menu-items/{restaurantId}/{timestamp}-{name}.jpg
  4. Returns public URL at images.ricoya.net

Manager Console — Menu Tab

Four sub-tabs:

Items

  • List all items grouped by category
  • Quick add form: name, price, category, description, allergen tags
  • Per-item: inline edit name/price/description, toggle active, upload image
  • Full editor modal: all fields including customizations

Customizations

  • Create/edit reusable customization groups
  • Groups can be linked to multiple items
  • Each group: name, min/max selections, options with prices

Categories

  • Create, rename, reorder, delete categories
  • Assign tax rates per category
  • View item count per category

Import

  • Bulk menu import functionality

Customer-Facing Menu

/menu?restaurantId=X renders via MenuPageContent.tsx:

  • Items grouped by category, sorted by display_order
  • Prices shown tax-inclusive in HNL
  • Customization groups merged (inline + linked)
  • Allergen tags displayed per item
  • Only active items shown

API Routes

Route Method Auth Purpose
/api/menu-items GET Public List active items for restaurant
/api/categories GET Public List categories for restaurant
/api/customization-groups GET Public List reusable groups
/api/customization-groups POST Auth Create group
/api/customization-groups PATCH Auth Update group
/api/customization-groups DELETE Auth Delete group
/api/admin/categories GET Manager+ List categories
/api/admin/categories POST Manager+ Create category
/api/admin/categories/[id] PATCH Manager+ Update category
/api/admin/categories/[id] DELETE Manager+ Delete category
/api/uploads POST Auth Upload menu item image

Key Files

File Purpose
src/data/models.ts MenuItem, RestaurantCategory, customization interfaces
src/repositories/menuItemsRepo.ts Menu item CRUD
src/repositories/categoriesRepo.ts Category queries
src/repositories/menuCustomizationGroupsRepo.ts Customization group CRUD
src/hooks/useMenuItems.ts SWR hook with optimistic updates
src/app/menu/MenuPageContent.tsx Customer menu display
src/components/manager/ManagerConsole.tsx Manager menu management UI
src/app/api/uploads/route.ts Image upload + R2 storage