Skip to content

Add holiday menu support for pre-orders#301

Open
kylefritz wants to merge 43 commits intomasterfrom
holiday_menus2
Open

Add holiday menu support for pre-orders#301
kylefritz wants to merge 43 commits intomasterfrom
holiday_menus2

Conversation

@kylefritz
Copy link
Owner

@kylefritz kylefritz commented Feb 23, 2026

Summary

Holiday menu support for Motzi — the long-awaited ability to run special pre-order menus (like Passover) alongside the regular weekly menu. Plus significant admin dashboard improvements.

What's New

Holiday Menus (ordering)

  • New menu_type field on menus: regular or holiday
  • Holiday menus can share a week with regular menus (compound unique index on week_id + menu_type)
  • "Open for Pre-Orders" workflow — no email blast; bakers announce via the regular menu's subscriber note
  • Tabbed ordering UI: "Weekly Menu" + holiday menu name (e.g. "Passover Specials 2026")
  • "New" badge on holiday tab until user places an order
  • "Skip this week?" hidden for holiday menus
  • Holiday orders tracked separately (holidayMenu / holidayOrder in API response)
  • Marketplace users can place holiday orders too
  • Date-based route (/admin/pickup_list/:date) that aggregates orders across all menus sharing a pickup date
  • Non-admin users cannot order from arbitrary menu IDs (allowlist: current + current holiday)

Admin Dashboard Improvements

  • Orders table: shows Subscribers, Marketplace, and Holiday rows with credits-used column; holiday row shows em-dashes for N/A fields
  • Sales panel: regular sales + holiday sales side-by-side (holiday skips Credit Sales row since credits are per-week)
  • What-to-bake: merged cards — one card per pickup day with a table per menu
  • Credit balance < 4: filtered to subscribers who ordered in last 30 days (was showing 369 inactive users, now ~22), with last-ordered menu column
  • Dashboard layout: reorganized — credits row first, then special requests/new users/opt-in
  • Holiday status tags: extracted repeated inline styles to .holiday-tag CSS class

Delete Menu Feature

  • Danger Zone panel on menu show page (red header, between Orders and Emails)
  • Delete button for menus with no orders; "Show N orders" link for menus with orders
  • Safety guards: can't delete current menu (UI hides button + server rejects), cascades to pickup_days/menu_items/comments
  • Full workflow: delete orders first, then delete menu

Menu Form Improvements

  • Week selector shows all available weeks with annotations (e.g. "has regular") instead of filtering by menu_type — fixes stale options when switching between Regular and Holiday in the dropdown
  • copy_from clears sold-out limits (0 → nil) so copied menus start fresh

Consumer UI Accessibility

  • Full ARIA tabs pattern: role="tabpanel", aria-controls, aria-labelledby on panels and tabs
  • Arrow-key keyboard navigation between tabs with focus management
  • :focus-visible outline on tab buttons
  • Holiday badge marked aria-hidden (decorative)
  • Tab font bumped 0.85→0.95rem, badge 0.6→0.7rem for WCAG legibility
  • Mobile overflow handling (text-overflow ellipsis for long menu names)
  • Badge color corrected to match $motzi-red; border-radius aligned with Bootstrap default

Review App Safety

  • app.json for Heroku review apps with safe defaults
  • bin/seed_review_app script seeds real production data
  • Mail interceptor: emails are only delivered to admin users on the review app (filters to/cc/bcc); all other recipients silently dropped
  • Environment banners: sticky purple banner on consumer site, flash alerts on admin dashboard and menu email pages — visible in both review app and local dev
  • Email footers: all emails include a footer noting they're from the review app or local dev
  • SendGrid credentials configured for review apps so the full email flow can be tested end-to-end
  • Heroku Scheduler & Monitor: auto-provisioned via app.json for review apps

Analytics Cleanup

  • cleanup:trim_analytics rake task trims ahoy_events and ahoy_visits older than 90 days
  • Logs before/after row counts for each table
  • Intended for daily Heroku Scheduler job

Docs & Tooling

  • Consolidated agents.md into CLAUDE.md (single source of truth for AI agents)
  • Simplified README.md with Heroku infrastructure docs (addons, scheduled jobs, review apps)
  • New bin/seed_local script to pull prod data for local development

Files Changed

70+ files changed, ~2800 insertions, ~290 deletions across:

  • Backend: models, controllers, views, SQL queries, migrations, routes
  • Frontend: React components (App, Layout, MenuTabs, HolidayMenuTab), TypeScript types
  • Admin: dashboard, menus, pickup days, stylesheets
  • Tests: 205 runs, 735 assertions, 0 failures; 46 JS tests passing

Test Coverage

  • Model tests: menu_type enum, current_holiday, open_for_orders!, uniqueness constraints, copy_from sold-out limits
  • Controller tests: holiday menu in JSON response, admin CRUD, delete workflow, dashboard panels, wrong-menu guard, danger zone UI states
  • Integration test: full Passover pre-order flow (create menu → open → order → confirm)
  • JS tests: HolidayMenuTab (confirmation, form, edit-reset), MenuTabs (tab switching, badge)
  • Mail interceptor tests: admin delivery, non-admin blocking, mixed recipients, cc/bcc filtering
  • Pickup list tests: date-based routing, cross-menu aggregation, 404/400 handling
  • Fixtures: Passover 2026 menu, pickup days, items, menu items, orders

Review App

https://motzi-pr-301.herokuapp.com — seeded with production data, emails delivered to admin users only.

🤖 Generated with Claude Code

- Add menu_type enum (regular/holiday) with compound unique index
  (week_id, menu_type), allowing one regular + one holiday menu per week
- Add Setting.holiday_menu_id; Menu.current_holiday, open_for_orders!
  (no email), updated make_current!/current?/can_publish? for both types
- orders_controller accepts menu_id param for holiday orders;
  render_current_order concern fetches holiday menu/order
- show.json.jbuilder refactored to partials; adds holidayMenu/holidayOrder
- Admin: menu_type select, holiday badge, open_for_orders action,
  _holiday_open partial with clear no-email messaging
- Frontend: extract Layout.tsx, new MenuTabs.tsx (MUI Tabs, ARIA-compliant,
  New badge, handles null regular menu) and HolidayMenuTab.tsx
- 22 new tests + Passover 2026 fixtures (178 runs, 0 failures)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52c128c493

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

def create
if current_user&.current_order
logger.warn "user=#{current_user.email} already placed an order. returning that order"
target_menu = params[:menu_id].present? ? Menu.find(params[:menu_id]) : Menu.current

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict menu_id to active orderable menus

This accepts any menu_id from the request and only rejects when that menu is already past deadline, which means clients can create orders for menus that were never opened as current/holiday (e.g., future regular menus or unopened holiday menus) by posting directly to /orders.json. Because resources :menus, only: [:show] exposes menu IDs publicly, this bypasses the new open_for_orders! gating and can create unintended orders; menu_id should be validated against the currently orderable menus only.

Useful? React with 👍 / 👎.

kylefritz and others added 4 commits February 23, 2026 00:16
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- copy_from: don't copy limit=0 (sold-out state) to new menu — reset to nil
  so items start as available on the copied menu
- Admin: week options now future-only (0..10 range); hide test email button
  for holiday menus with explanatory note instead
- MenuTabs: warm bakery-themed styling, "Holiday" badge instead of "New"
- CLAUDE.md: note to use jq for JSON parsing in shell

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kylefritz kylefritz temporarily deployed to motzi-holiday-menus2-vubwz3ftm February 23, 2026 05:54 Inactive
- Use Raleway/Oswald fonts matching rest of site
- Active color: brand purple #352c63, inactive: warm taupe #9e8c7a
- Replace amber box badge with small Oswald uppercase "HOLIDAY" subtitle
  in site's rust accent color #d54a2c — no box, just typography
- Bottom-align tabs so menu names share the same baseline
- HOLIDAY label sits above the menu name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kylefritz kylefritz temporarily deployed to motzi-holiday-menus2-vubwz3ftm February 23, 2026 06:00 Inactive
Replaces the full-bleed two-column purple header approach with a simple
tab bar (two equal bordered buttons) sitting above the original menu name
header. Subscriber credits stay at the top, tabs below them, then the
unchanged purple menu name. HideMenuNameContext retained in Contexts/Title
for potential future use.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- app.json: inherit AWS, HASHID_SALT from pipeline; default Stripe test keys
- Disable email delivery in production when SendGrid credentials are absent
- Add bin/seed_review_app to copy prod DB and restart dynos

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kylefritz and others added 2 commits February 24, 2026 02:52
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use render_current_order in orders#create so holiday order response
  returns the regular menu as primary (was returning holiday menu as both)
- Always show Holiday badge on tab (was hidden after placing order)
- Increase tab bar bottom margin from 4px to 16px
- Remove unused HideMenuNameContext
- Add Rails test for holiday order response and JS tests for badge

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kylefritz and others added 14 commits February 24, 2026 03:29
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the old per-pickup-day show page with /admin/pickup_lists/:date
that aggregates orders from all pickup days on a given calendar date
(e.g., regular + holiday menus with pickups on the same day). Includes
orders tab and by-item tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update credit assertions (kyle: 23→20, ljf: 0→-2) to reflect new
order_items consuming credits. Fix order count (kyle: 2→4). Switch
passover preorder test to use jess (kyle already has a fixture order).
Restore direct render in orders#create so marketplace orders appear
in the JSON response — render_current_order's order_for_menu excludes
marketplace orders by design.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
order_for_menu excludes marketplace orders (via .subscriber scope),
so pass the just-created order directly instead of re-fetching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same fix as create — pass the known order directly rather than
re-fetching through order_for_menu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: Two issues prevented holiday order updates from working.

1) Frontend: HolidayMenuTab managed its own isEditingOrder state but
   never reset it after a successful save. The Menu component fires
   onCreateOrder without awaiting the promise. Fix: wrap the handler
   to reset isEditingOrder on success.

2) Backend: render_current_order put all orders into @order (regular
   slot) regardless of menu type. A holiday order ended up in the
   wrong JSON slot. Fix: in create, move holiday orders to
   @holiday_order before rendering. Use ||= in render_current_order
   so pre-set ivars are preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of separate rows for regular and holiday bake lists,
each day gets one card containing a labeled table per menu.
Also show holiday menu link in the Menu panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Holiday orders appear as a row in the existing Orders table.
Holiday sales appear below regular sales in the same Sales panel.
No more separate Holiday Orders/Holiday Sales panels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kylefritz and others added 3 commits February 24, 2026 04:12
Verifies exact cell values for subscribers (ordered=1, not_ordered=3),
marketplace (0), and holiday (ordered=2). Confirms both sales tables
show qty=0 because no fixtures have stripe_charge_amount — the
identical values are correct, not a bug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CreditItem.for_menu queries by week_id time range, so both menus
in the same week return identical credit data. Credits are per-week
not per-menu, so only show them in the regular sales section.
Holiday sales section shows only marketplace orders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…to single row

- Pickup list now uses ActiveAdmin's show page with proper tabs, table_for,
  and the existing _order_items partial (matching the original production view)
- Date-based route (/admin/pickup_lists/:date) redirects to the ActiveAdmin
  pickup day show page
- Sales panel uses a single table with Holiday as a row (like Orders panel)
  instead of rendering a separate holiday sales table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "Credits used" column to Orders table (replaces separate panel)
- Rename columns: Ordered→Orders, Skipped→Skip
- Revert Sales partial to master style, show duplicate for holiday menu
- Skip Credits row in holiday sales (credits are per-week, not per-menu)
- Rename Credits to Credit Sales in sales table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kylefritz and others added 4 commits February 24, 2026 10:34
- Menu show page has a Danger Zone section (red header) between Orders and Emails
- Delete button shown when menu has no orders, with confirmation dialog
- "Show N orders" link when menu has orders, links to filtered orders page
- Cleans up ActiveAdmin comments on delete, preserves PaperTrail versions
- Styled with action-danger (red) and action-disabled (gray) button classes
- Full workflow test: delete orders, then delete menu

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Block deleting the current regular or holiday menu
- Test cascade: deleting menu destroys pickup_days and menu_items
- Test sales partial: holiday menu hides Credit Sales row
- Test current menu guard for both regular and holiday menus

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Test marketplace holiday order creation returns holidayOrder JSON slot
- Add HolidayMenuTab JS tests: confirmation display, form display,
  and regression test for editing state reset after save
- Pickup list overlapping dates already covered by existing fixtures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…er dashboard panels

Credit balance panel now uses SQL-level filtering (ordered_within_days param)
to show only subscribers who ordered in the last 30 days, reducing the list
from 369 to ~22. Reorganized dashboard: credits row first, then special
requests/new users/opt-in row.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Filters to/cc/bcc on all outgoing emails to admin-only allow list when
REVIEW_APP is set. Shows banner on dashboard and menu email pages.
Adds SendGrid credentials to app.json for review apps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Consumer site: sticky purple banner at top of page
Admin dashboard + menu email section: flash alert with context
Email layouts: footer noting review app or local dev origin
All banners hidden in production.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kylefritz kylefritz temporarily deployed to motzi-pr-301 February 24, 2026 17:51 Inactive
kylefritz and others added 2 commits February 24, 2026 12:56
These vars should be set in the Heroku pipeline config and inherited
by review apps. Marking them required blocks review app creation if
they're not explicitly provided at creation time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copies AWS, HASHID_SALT, and SendGrid credentials from production
to the review app before seeding the database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trims ahoy_events and ahoy_visits older than 90 days.
Run daily via Heroku Scheduler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge agents.md into CLAUDE.md, simplify README with Heroku
infrastructure docs (addons, scheduled jobs, review apps).
Add scheduler and scheduler-monitor to app.json for auto-provisioning.
Add bin/seed_local for pulling prod data locally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kylefritz and others added 4 commits February 24, 2026 16:03
… copy_from limits test

Show all weeks in the dropdown with annotations for which types already exist,
instead of filtering by a single menu_type that goes stale on dropdown change.
Add test verifying copy_from clears sold-out (0) limits to nil.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… slotting

- Hide delete button for current menu, show message instead
- Extract repeated holiday status_tag inline style to .holiday-tag CSS class
- Add em-dashes for N/A cells in holiday orders row
- Make orders#update explicitly slot holiday orders (match create path)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Complete ARIA tabs pattern: tabpanel, aria-controls, aria-labelledby
- Add arrow-key navigation between tabs with focus management
- Add :focus-visible outline for keyboard users
- Bump tab font 0.85→0.95rem, badge 0.6→0.7rem for legibility
- Fix badge color to match $motzi-red (#d5482c)
- Widen tab gap 4→8px, border-radius 3→4px to match Bootstrap
- Add overflow/ellipsis handling for long menu names on mobile
- Mark HolidayBadge as aria-hidden (decorative)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant