Free First Practice — Promo Replacement + Lead Funnel Test
Replace the 50-free-credits trial promo with the existing CHERRY_CITY_FIRST_PRACTICE comp offer across all promo surfaces, and test the free practice as the primary lead-capture CTA on the Cherry City market LP in place of the hero tour form.
The bet
The tour form produces almost no scheduled tours — tour scheduling happens through DM conversations. Meanwhile the 50-credit promo asks prospects to first understand prepaid credits before they can see the value, which is backwards for a first touch. A comped first practice is a concrete, self-explanatory offer, and since a CM is required to show up for every first practice, it is effectively a tour — with a practice session instead of a walkthrough. The hypothesis: on-site free-practice bookings will convert at a higher rate than on-site tour-form submissions (a low bar — near zero) while getting the same or better in-person CM contact.
Scope: Cherry City (MG10) only — the only location with hourly enabled. The comp primitive is location-scoped per offer, so expansion later is a data change.
What already exists (no work)
- Comp primitive (comp-reservations spec): public
POST /api/comp/reservations, Offer-row-driven, stub users by email, no card,PromoRedemptiondedup, account-setup email chain,comp_bookedPostHog event, Meta CAPI + Google Lead events. CHERRY_CITY_FIRST_PRACTICEoffer:FREE_BOOKING, 1/user, 240-min cap, MG10 hourly studios, no end date.- Comp booking flow:
/book?type=hourly&offer=<code>(BookingFlow comp mode with the native studio picker, per the scheduler refactor). The pre-refactorHourlyBookingWithPickercomp modal is deleted. - Hourly staff-notification email fires for comp bookings via
executeCreationSideEffects— CM hears about every booking. AlreadyRedeemedModal→ falls through to paid hourly booking.- Minimum booking lead time is governed by Acuity availability — the scheduler only presents valid slots, so no CM-scramble gate is needed on the comp endpoint.
Workstream A — swap promo surfaces (50 credits → free first practice)
A1. Dashboard callout (/account/overview) — data only, zero code
GET /api/offers/dashboard returns Offer rows with showOnDashboard: true; FREE_BOOKING passes the type filter by default and is hidden once the user has a PromoRedemption row. handleOfferPress in UnifiedDashboardContent.tsx falls through to router.push(offer.ctaUrl) for unknown types.
Script (apps/api/scripts/marketing/, per ops-script convention):
- 50-credit offer:
showOnDashboard: false CHERRY_CITY_FIRST_PRACTICE:showOnDashboard: true+ display fields —title,description,ctaText("Claim your free practice"),ctaUrl: '/lp/salem/free-first-practice',badgeText,priority,variant,imageUrl
A2. Header PromoButton — code change
Current: hardcoded FEATURED_OFFER_ID (CREDIT offer), label {amount} Free Credits, links /promo/free-credits. Eligibility endpoint (/api/promo/eligibility/[offerId]) filters type: 'CREDIT' and requires creditId + amount.
Changes:
- Eligibility route: accept
type IN ('CREDIT', 'FREE_BOOKING'); skip thecreditId/amountgate forFREE_BOOKING(amountnullable in response).PromoRedemptioncheck already covers both types. - Response: add
ctaUrlso the button's destination is offer-driven. PromoButton: label fromoffer.ctaText(drop the{amount} Free Creditstemplate), link fromctaUrl, swapFEATURED_OFFER_IDto the first-practice offer id.
A3. /promo/free-credits retirement
301 → /lp/salem/free-first-practice in next.config.ts. The CREDIT offer row stays (soft, not deleted) so any already-granted balances and history are untouched; it just loses all surfaces. give-50-get-50 referral program is unrelated — untouched.
Workstream B — LP hero (/lp/salem) — SHELVED pending scheduler refactor
The bet framing stands: "first practice on us" is a higher-value bait than "take a tour". But three hero iterations (inline 2-col scheduler, accordion-only card with studio step, inert scheduler-tease + CTA) all failed the look/feel bar — the accordion scheduler is modal-native and can't carry a marketing hero. Decision (2026-07-09): /lp/salem keeps the tour-form hero unchanged; the free-practice hero lands together with a wholesale scheduler refactor (single-column wizard-style component; SchedulerAccordion backs every booking flow incl. reschedule, so it's a dedicated project — HeroUI OSS has no stepper).
Update (2026-07-12): the scheduler refactor landed as PR #880, and this branch is stacked on it. Comp booking now runs on /book?type=hourly&offer=<code>; the legacy modal stack (HourlyBookingWithPicker, HourlyBookingContent, SchedulerAccordion, CmHelpFooter) is deleted. A future hero integration builds on (or links to) the /book flow, not the retained props below. Open: the modal's "call the CM" help footer has no /book successor yet (see open items).
- Offer surface meanwhile: the dedicated LP
/lp/salem/free-first-practice(content-first hero, "Claim your free practice" CTA navigates to/book?type=hourly&offer=<code>). All legacy slugs (/lp/cherry-city-first-practice,/lp/cherry-city-free-hour,/lp/cherry-city/*) 301 there or to the market LP as appropriate. Promo surfaces (A1/A2/A3) point at it. - ~~Retained for the refactor:
schedulerOnly(HourlyBookingContent),studioStep(HourlyBookingWithPicker),stepsCollapsed/onStepExpand(SchedulerAccordionchain),SchedulerStepTitle,CmHelpFooter variant="inline"— all tested, opt-in, currently unused by any caller.~~ Deleted 2026-07-12 with their host components; the refactor they waited on shipped a different shape. - Config: frontend map
marketSlug → compOfferCodeinapps/web/src/lib/config/comp-offers.ts(onlysalem), consumed by the dedicated LP route; the future hero integration reads the same map. - Measurement: unchanged plan —
comp_booked(offerCode) vstour_events, once the offer gets a hero-level surface.
Open items
- Authenticated prefill. Logged-in users clicking the dashboard/header CTA land on the public LP and retype their email. Acceptable v1; prefill from session later if it shows up in friction.
- Existing members can redeem. Any user without a
PromoRedemptionrow is eligible, including long-tenured members. Accepted — dashboard callout implies it; goodwill cost is one comped session each. - CM help contact dropped with the modal. The legacy comp modal's
CmHelpFooter(tel: link to the location's community manager) has no successor on/book;useHourlyResourcestill fetchescommunityManagerName/communityManagerPhonebut nothing renders them. Needs a product call: reintroduce a help affordance on/bookor drop the dead fields.
Related
docs/features/comp-reservations/spec.md— the primitivedocs/marketing/system/3-engine/customer-journey.md— tour funnel baselineapps/web/src/components/molecules/navigation/PromoButton.tsxapps/api/src/app/api/promo/eligibility/[offerId]/route.tsapps/api/src/app/api/offers/dashboard/route.tsapps/web/src/app/(landing)/lp/[marketSlug]/MarketLandingContent.tsx