Minisites — Code Map
App root
apps/minisites/astro.config.mjs-output: 'server', dual adapter (Vercel / Node).apps/minisites/vercel.json- Vercel rewrites & domains.apps/minisites/package.json- Astro 5 + minimal section components.
Pages
apps/minisites/src/pages/[...slug].astro- multi-page catch-all router. Resolves the host, fetchespublishedContent, resolves the URL slug to a page (findPage), composes that page’s sections, and renders the lead popup. Unknown/disabled slug returns 404.apps/minisites/src/pages/404.astro- fallback when the host resolves but the site isn’t published.apps/minisites/src/pages/courses/- per-course detail landing pages (/courses/[id]).apps/minisites/src/pages/robots.txt.ts- robots dynamic endpoint.
Layout
apps/minisites/src/layouts/modern.astro- outer shell. Premium editorial token layer (charcoal + copper + cream). Injects theme CSS variables (incl.headingFontFamily,cornerStyle,sectionDensity— all three now wired into--font-display/--radius-*/--section-padas of FIT-287), normalizes legacy primary colors to the copper default, renders the floating pill top nav (page links vs in-page anchors; CTA prefersjoinUrlwhen present), locale + direction, SEO tags, hero preload, comprehensive dark footer, floating WhatsApp button, and (FIT-287) the inline<script>block that setshtml.jsand runs the oneIntersectionObserver-based scroll-reveal system for.reveal/.reveal-fadeelements — there is no separatereveal.tsfile.
Section components
apps/minisites/src/components/sections/Hero.astroapps/minisites/src/components/sections/About.astroapps/minisites/src/components/sections/Classes.astroapps/minisites/src/components/sections/Schedule.astroapps/minisites/src/components/sections/Pricing.astro- card anatomy since FIT-287: highlight/spots-left/sold-out badges, intro-aware price block, three-branch CTA (joinUrlpresent-and-open / present-and-sold-out / absent). ReadsplatformData.plans[].introPriceInCents/introDiscountPercent/introDurationCycles/seatsLeft/soldOutandplatformData.joinUrl, all optional.apps/minisites/src/components/sections/Courses.astroapps/minisites/src/components/sections/Trainers.astroapps/minisites/src/components/sections/Contact.astroapps/minisites/src/components/sections/Gallery.astroapps/minisites/src/components/sections/Testimonials.astroapps/minisites/src/components/sections/FAQ.astro- renders as a native<details>accordion since FIT-287 (no JS).apps/minisites/src/components/sections/Social.astro- manual social channels + curated post thumbnails.apps/minisites/src/components/sections/Events.astro- auto-populated dated events fromplatformData.events[]; lead-capture CTA.
All 14 section components plus apps/minisites/src/pages/courses/[id].astro (the per-course detail page) got the FIT-287 token/radius/type-scale sweep (var(--radius-card), var(--font-display), the desaturated image filter, .reveal/.reveal-fade classes) — a reskin, not a re-architecture; prop contracts and hasDataMap behavior are unchanged.
Other components
apps/minisites/src/components/LeadPopup.astro- timed lead-capture modal (delay + exit intent,localStoragedismissal, posts to/leads/organization/:orgId).
Lib
apps/minisites/src/lib/i18n.ts-resolveLocale(input)normalizes draft/org locale to one ofhe | en | ru, falling back toen(nothe).mt(locale, key)flat lookup (locale → en → raw key).mtp(locale, baseKey, count)(FIT-287) — CLDR-plural-aware lookup on top ofmt, used by the pricing section’s “spot(s) left” / “first N payments” copy.apps/minisites/src/lib/pages.ts- multi-page resolution:getPages(shims legacysectionsinto a home page),findPage(slug -> enabled page),getNavLinks(page links for multi-page, empty for single-page). Mirrors the sharedgetMinisitePages/findPageBySlugshim so the Astro build stays decoupled from@taikan/shared.
Shared
libs/shared/src/lib/minisite.ts- Zod schemas and helpers:MinisiteContent(withpages[],leadPopup),MinisitePage,MinisiteSection,MinisiteTheme, section data schemas (incl.SocialSectionData,EventsSectionData, andPricingSectionDatawhich since FIT-287 carriesctaMode: 'lead' | 'purchase', default'lead'),MinisiteEventInput,MinisitePlatformData(since FIT-287: optional per-planintroPriceInCents/introDiscountPercent/introDurationCycles/seatsLeft/soldOutand a top-level optionaljoinUrl),getMinisitePages/findPageBySlug(back-compat shim),createDefaultPages/createDefaultSections.libs/shared/src/lib/constants/feature-flags.ts-FeatureFlags.MINISITE_PLAN_PURCHASE('minisite-plan-purchase', FIT-287) gatesjoinUrlemission. The intro/seat fields are ungated —FeatureFlags.PLAN_INTRO_PRICINGwas deleted (2026-08-19, as if permanently true) and no constant for it remains.libs/shared/src/lib/schemas/plan-group.schema.ts- (FIT-289)showCapToMemberson the group create/update/response schemas — the per-group owner toggle that decides whether a MEMBER-facing payload carriesseatsLeftalongsidesoldOut.
Schema
libs/db/src/lib/schema/minisites.ts-minisite_contenttable + newminisite_eventstable (dated promotable events/workshops).libs/db/drizzle/0066_lying_siren.sql- migration creatingminisite_events.
Editor (dashboard surface, apps/web)
Under apps/web/src/app/[lang]/(protected)/dashboard/minisite/:
page.tsx- tabbed editor: Overview, Pages, Events, Branding, SEO. Overview tab (FIT-287) shows a “Plan purchase: live / blocked - join link disabled / blocked - payments not active” status row whenever the published pricing section’sctaMode === 'purchase'— readsGET /organizations/:id/join-link+GET /organizations/:orgId/payment-configclient-side, mirroringMinisitesService.publishedPricingCtaMode/thejoinUrlgate (the API doesn’t expose that private helper, so the client re-derives it offpublishedContent).components/page-manager.tsx- page manager (enable/disable, reorder, nav label/visibility).components/section-manager.tsx- per-page section manager; section ops are page-scoped via the?page=query.components/events-manager.tsx- events CRUD UI.components/theme-editor.tsx- theme controls (colors, fonts, button/corner style, density) plus the lead-popup editor.cornerStyle/sectionDensity/headingFontFamilycontrols were already here pre-FIT-287; the renderer just didn’t read them until now.components/lead-popup-editor.tsx- lead-popup config editor.components/section-editors/pricing-editor.tsx- (FIT-287)ctaModeradio (lead/purchase) + non-blocking readiness hints (join-link enabled? payments active?) shown only in purchase mode; per-planIntro/Cap: nchips, ungated, driven by what each plan carries.components/section-editors/social-editor.tsx,components/section-editors/events-editor.tsx- section editors (alongside the existing per-type editors).
API endpoints used
Public (consumed by the Astro app):
GET /minisites/resolve?host=<host>- resolver consumed by the catch-all;platformDataincludesevents[], the per-plan intro/seat fields (FIT-287, ungated) and a top-leveljoinUrl(gated onminisite-plan-purchase).POST /leads/organization/:orgId- contact form + lead-popup submissions.
Related, not part of this module but read by the FIT-287 purchase chain once joinUrl is emitted: GET /join/:token and the shop deep-link flow in apps/web (request-invite/acceptJoinLink in apps/api/src/memberships/memberships.service.ts).
Editor endpoints (dashboard surface, not consumed by the Astro app directly), in apps/api/src/organizations/minisites.controller.ts:
PATCH /minisites/:orgId/content- whole-blob multi-page save.PATCH /minisites/:orgId/lead-popup- lead-popup config.PATCH /minisites/:orgId/theme- theme tokens.PATCH /minisites/:orgId/pages/reorder,/pages/:pageKind/toggle,/pages/:pageKind- page ops.PATCH /minisites/:orgId/sections/reorder,/sections/:type/toggle,/sections/:type- section ops (page-scoped via?page=, defaulthome).GET/POST /minisites/:orgId/events,PATCH/DELETE /minisites/:orgId/events/:eventId- events CRUD.PATCH /minisites/:orgId/publishand/unpublish- publish workflow.
Cross-module dependency (FIT-287)
MinisitesService.getPlatformData (apps/api/src/organizations/minisites.service.ts) is not a self-contained module for the pricing DTO — it imports:
getSeatsTakenByPlan,loadGroupCaps,computeSeatFieldsfromapps/api/src/plans/plan-capacity.util.ts(the plans module) — the same pure seat-computation helpersPlansServiceuses for dashboard listing and purchase enforcement (FIT-289), extracted so the two call sites can never disagree about a plan’s effective seats.PlansService.loadGroupCaps/seatFieldsare now thin delegates over these exports.PaymentProviderConfigService.getActiveConfigfrom the payments module (added toOrganizationsModule’s imports —apps/api/src/organizations/organizations.module.ts) for thejoinUrlgate’s “org can actually charge” condition.EventTrackingService(global module, no explicit import needed) for both flag evaluations.