Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Birthday Add-On — Book-Bite API Implementation Plan

Scope: Book-Bite partner-portal integration only. The hh-server backend plan and Pegasus consumer plan are separate documents.

Implementation target repository: hungryhub-team/book-bite.

Goal: Implement and verify the Book-Bite client for birthday add-on list, detail, create, update, and delete flows against the partner API contract.

Status: The hh-server contract for these fields is implemented and documented; the Book-Bite FE implementation remains a pending checklist in this plan.

Canonical wire contract: src/BookBite/superpowers/specs/2026-08-06-birthday-add-on-api-contract.md.

Backend dependency: hh-server owns authorization, validation, persistence, and response shape. Book-Bite must consume the response as implemented and must not recreate backend birthday rules locally as an authority.

Scope

  • Call GET /birthday_add_ons with the selected restaurant’s restaurant_id, pagination, filters, and sorting.
  • Consume the list’s flat restaurant-group response (free, premium, and meta) and the JSON:API detail response.
  • Submit multipart create/update payloads using add_on[...] and the correct nested attributes.
  • Use the authenticated restaurant context for create/update authorization. For create, the server resolves an explicit request-level restaurant_id through default_restaurant when present, or falls back to the staff’s default restaurant; do not nest it under add_on[...].
  • For Partner booking create/update, the portal does not need to send dining_occasion_id; hh-server assigns the Birthday occasion automatically when a birthday add-on is selected. If the booking payload contains reservation[restaurant_id]=null, hh-server normalizes it to the authenticated staff member’s default restaurant.
  • Map itemType to the two backend modes: items_list uses birthday item rows, while items_image uses menu image rows.
  • Treat isFreeBirthdayAddOn as the authoritative free-row indicator. Price inference remains only as temporary compatibility fallback.
  • Render uploader responses as already-resolved URLs. Do not prepend a local base URL to an absolute image URL.
  • Surface 409 BIRTHDAY_FREE_ADDON_MAX_ONE_REACHED as the free-add-on conflict state and preserve backend validation errors for field-level display.

Payment settings parity with Admin

The birthday add-on form must implement the same payment settings contract as the Admin Add-On Settings screen. This is part of the v1 create/edit flow, not a later UI enhancement.

FE state and wire mapping

FE stateAPI fieldBehavior
requirePrepaymentadd_on[require_prepayment]Checkbox; default false.
paymentTypesadd_on[add_on_payment_types_attributes]Array of selected integer payment-type IDs; multiple values are allowed.
loadedPaymentTypeRowsnested row id / payment_type_idKeep the persisted junction ID while editing so removed rows can be marked _destroy.
paymentTypeOptionsGET /restaurant_packages/payment_types response dataOptions shaped as { id, name }; the partner API applies currency filtering. Do not hardcode gateway names or IDs.
minBookingTimeInAdvanceadd_on[minimum_booking_time_in_advance]Integer minutes; 0 is Follow Restaurant Level.

Create must keep restaurant_id at the top level and place these settings under add_on[...]:

{
  restaurant_id: 42,
  add_on: {
    require_prepayment: true,
    minimum_booking_time_in_advance: 30,
    add_on_payment_types_attributes: [
      { payment_type_id: 1 },
      { payment_type_id: 4 }
    ]
  }
}

For update, retain each existing junction row’s id. A removed row is sent as { id, payment_type_id, _destroy: true }; a newly selected row is sent as { payment_type_id }. If prepayment is toggled off, the FE must clear the selection and mark all previously persisted rows for destruction.

UI and validation behavior

  • Render a checkbox labeled by the existing Add-On Settings copy for requirePrepayment.
  • Only show the payment-type multi-select when prepayment is enabled.
  • Fetch payment options from the existing GET /restaurant_packages/payment_types partner endpoint. It returns { id, name } options and applies the restaurant-currency filtering on the server; do not duplicate the Admin payment catalog or hardcode gateway IDs in Birthday Add-On.
  • Block save when prepayment is enabled and no payment type is selected. Show the same validation state as Admin: “Select at least one payment type before saving.”
  • Permit multiple selections; do not reduce the value to a single payment type.
  • Render the time-in-advance selector using the Admin option builder: 0 (Follow Restaurant Level), 15/30/45 minutes, then hourly values in 15-minute increments. Do not add the Admin-only 1000000 (Unlimited) option unless that display mode is explicitly enabled.

File ownership

AreaFiles
API methodssrc/api/birthdayAddOn.js
List normalizationsrc/pages/package/birthday-add-on/helpers/list.js, BirthdayAddOnList.vue
Form mapping and payloadssrc/pages/package/birthday-add-on/helpers/form.js
Create/edit screenssrc/pages/package/birthday-add-on/CreateBirthdayAddOn.vue, EditBirthdayAddOn.vue, components/FormBirthdayAddOn.vue
Contract teststests/birthdayAddOnList.test.js

Implementation checklist

  • Confirm list requests send only populated filters and use restaurant_id only for list selection.
  • Confirm detail reads response.data.attributes and uses response.data.id for restaurant_add_ons_attributes.id.
  • Confirm create/update build birthdayAddOnItemsAttributes or addOnMenusAttributes according to itemType.
  • Confirm free create forces priceCents = 0 and premium create leaves price editable.
  • Confirm image fields are sent as files and displayed from the full URL returned by the API.
  • Confirm active-state updates use the nested restaurant-add-on ID.
  • Confirm delete handles the API success shape { success: true, data: null }.
  • Confirm the explicit free indicator is preferred over price inference in list grouping.
  • Add requirePrepayment, paymentTypes, loadedPaymentTypeRows, paymentTypeOptions, and minBookingTimeInAdvance to create/edit form state.
  • Submit multiple add_on_payment_types_attributes rows using integer payment_type_id values.
  • Preserve existing payment junction IDs and submit _destroy=true for removed rows or when prepayment is disabled.
  • Match Admin’s currency-specific payment option filtering and its 0/15-minute time-in-advance option values.
  • Block save when prepayment is enabled with zero selected payment types.
  • Load payment options from GET /restaurant_packages/payment_types and use the returned IDs in the nested payment-type payload.

Verification

node --test tests/birthdayAddOnList.test.js

Also run the repository lint/build commands before merging. Manual verification must cover list, detail, premium create, free create, edit, delete, validation 422, and free-conflict 409. For payment settings, verify create with zero/one/multiple types, edit add/remove without duplicate junctions, toggling prepayment off, the no-type validation error, currency-specific options, and time values 0, 15, 30, 45, and an hourly value.

Out of scope

  • hh-server migrations, serializers, reservation validation, or feature flags.
  • Pegasus V5 list/detail/availability consumption.
  • A new client-side birthday API contract that diverges from the canonical spec.