plans
2026-05-11
2026 05 11 Page Cleanup

Landing Page Cleanup — Extract Inline Sections to Components

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Refactor 7 page.tsx files in apps/frontend/app/[locale]/(landing)/ so each page only composes extracted section components — no inline data arrays, helper functions, or JSX section blocks.

Architecture: Each page's inline sections become a new SectionName component in components/[domain]/. Page.tsx becomes a thin orchestrator. Shared data arrays go to lib/data/. Helper functions with business logic go to lib/utils/.

Tech Stack: Next.js 16 (App Router), Tailwind CSS v4, paraglide-js i18n


Scope: Pages That Need Refactoring

PageLinesIssues
about/page.tsx164Inline content blocks (storyContent, cultureContent, etc.)
venue-rental/page.tsx381Inline data arrays + inline JSX sections
private-events/page.tsx294Inline data arrays + helper functions + inline JSX sections
workshops/page.tsx294Inline data arrays + idealForTitle() + inline JSX sections
artist-proposal/page.tsx319Inline data arrays + performanceTypeTitle() + inline JSX sections
artists/page.tsx127Inline performance-type cards + inline venue feature cards
inquiry/contact/page.tsx114Inline contactItems array

Task 1: Extract about/page.tsx

Target: components/marketing/about/ directory

Files:

  • Create: components/marketing/about/story-section.tsx
  • Create: components/marketing/about/culture-section.tsx
  • Create: components/marketing/about/community-section.tsx
  • Create: components/marketing/about/vision-section.tsx
  • Modify: app/[locale]/(landing)/about/page.tsx

Pattern to extract:

  • storyContent JSX block → StorySection.tsx

  • cultureContent JSX block → CultureSection.tsx

  • communityContent JSX block → CommunitySection.tsx

  • visionContent JSX block → VisionSection.tsx

  • Step 1: Create components/marketing/about/story-section.tsx

Extract storyContent JSX and the surrounding ContentSection into StorySection. Keep the same props: id, title, content, imageSrc, imageAlt, contentClassName, titleAlign, spacing.

// components/marketing/about/story-section.tsx
import { ContentSection } from "~/components/ui/content-section";
import { m } from "~/src/paraglide/messages";
 
const storyContent = (
  <>
    <p>House of Legends was created by a couple...</p>
    {/* rest of storyContent JSX */}
  </>
);
 
export function StorySection() {
  return (
    <ContentSection
      id="our-story"
      title={m.about_ourStory()}
      content={storyContent}
      imageSrc="/images/about-hol-1.jpg"
      imageAlt="House of Legends interior"
      contentClassName="order-1"
      titleAlign="left"
      spacing="lg"
    />
  );
}
  • Step 2: Create remaining section components

Repeat pattern for culture-section.tsx, community-section.tsx, vision-section.tsx.

  • Step 3: Rewrite about/page.tsx
"use client";
 
import { StorySection } from "~/components/marketing/about/story-section";
import { CultureSection } from "~/components/marketing/about/culture-section";
import { CommunitySection } from "~/components/marketing/about/community-section";
import { VisionSection } from "~/components/marketing/about/vision-section";
import { SectionDivider } from "~/components/ui/section-divider";
import { VisitCTASection } from "~/components/marketing/visit-cta-section";
 
export default function AboutPage() {
  return (
    <main className="min-h-screen bg-background text-foreground pt-[var(--header-height)]">
      <StorySection />
      <SectionDivider />
      <CultureSection />
      <SectionDivider />
      <CommunitySection />
      <SectionDivider />
      <VisionSection />
      <SectionDivider />
      <VisitCTASection />
    </main>
  );
}

Task 2: Extract venue-rental/page.tsx

Target: components/marketing/venue-rental/ directory

Files:

  • Create: components/marketing/venue-rental/venue-intro-section.tsx (hero + first content section)
  • Create: components/marketing/venue-rental/venue-form-section.tsx (inquiry form section)
  • Create: components/marketing/venue-rental/venue-showcase-section.tsx (numbered cards + show formats)
  • Modify: app/[locale]/(landing)/inquiry/venue-rental/page.tsx

Inline data: Keep arrays inline in their section components — no lib/data/ files. Only create lib/data/ for truly shared data across multiple pages.

  • Step 1: Extract section components

Create VenueIntroSection, VenueFormSection, VenueShowcaseSection by extracting the relevant inline JSX blocks from page.tsx. Each section should import from lib/data/ directly.

  • Step 4: Rewrite venue-rental/page.tsx

Remove all inline data arrays and inline JSX sections. Import extracted section components. Page.tsx should be under 40 lines.


Task 3: Extract private-events/page.tsx

Target: components/marketing/private-events/ directory

Files:

  • Create: components/marketing/private-events/private-events-experience-section.tsx
  • Create: components/marketing/private-events/private-events-venue-section.tsx
  • Create: components/marketing/private-events/private-events-packages-section.tsx
  • Create: components/marketing/private-events/private-events-form-section.tsx
  • Modify: app/[locale]/(landing)/inquiry/private-events/page.tsx

Inline helpers to remove:

  • packageTitle(), packageFeature(), amenityLabel(), experienceTitle(), experienceDescription() — these map i18n keys. Move the logic into each section component using paraglide m.* calls directly.

  • Step 1: Extract section components

Each section becomes its own component. Pass i18n keys directly as props or use m.* calls inside the section.

  • Step 2: Rewrite private-events/page.tsx

Remove all helper functions and inline data. Page should compose extracted sections.


Task 4: Extract workshops/page.tsx

Target: components/marketing/workshops/ directory

Files:

  • Create: components/marketing/workshops/workshop-organizers-section.tsx
  • Create: components/marketing/workshops/workshop-ideal-for-section.tsx (IconCardsGrid with idealIcons)
  • Create: components/marketing/workshops/workshop-why-host-section.tsx (3 Why Host cards)
  • Create: components/marketing/workshops/workshop-formats-section.tsx
  • Create: components/marketing/workshops/workshop-form-section.tsx
  • Modify: app/[locale]/(landing)/inquiry/workshops/page.tsx

Inline data: Keep arrays inline in section components. Only use lib/data/ for data shared across multiple pages.

Helper to remove:

  • idealForTitle(key: number) → replace with direct m.* calls inside WorkshopIdealForSection

  • Step 1: Create section components following the same extraction pattern.


Task 5: Extract artist-proposal/page.tsx

Target: components/marketing/artist-proposal/ directory

Files:

  • Create: components/marketing/artist-proposal/artist-types-section.tsx
  • Create: components/marketing/artist-proposal/artist-venue-section.tsx
  • Create: components/marketing/artist-proposal/artist-show-formats-section.tsx
  • Create: components/marketing/artist-proposal/artist-highlights-section.tsx
  • Create: components/marketing/artist-proposal/artist-gallery-section.tsx
  • Create: components/marketing/artist-proposal/artist-form-section.tsx
  • Modify: app/[locale]/(landing)/inquiry/artist-proposal/page.tsx

Inline data: Keep arrays inline in section components.

Helper to remove:

  • performanceTypeTitle(key) → replace with direct m.* calls

Task 6: Extract artists/page.tsx

Target: components/marketing/artists/ directory

Files:

  • Create: components/marketing/artists/artists-performance-types-section.tsx
  • Create: components/marketing/artists/artists-venue-section.tsx
  • Create: components/marketing/artists/artists-cta-section.tsx
  • Modify: app/[locale]/(landing)/artists/page.tsx

Inline JSX to extract:

  • Performance types grid (6 cards with emoji) → PerformanceTypesSection
  • Venue features grid (3 cards) → VenueSection

Note: Performance type cards use emoji icons (🎵, 🎭, etc.) — these should be replaced with IconSymbol per the no-emoji rule. Use IconSymbol with appropriate SF Symbol names (music.note, theater.fill, etc.) instead.

  • Step 1: Replace emoji in performance type cards with IconSymbol

In artists-performance-types-section.tsx, replace:

// BAD — emoji
<div className="text-3xl mb-2">🎵</div>
 
// GOOD — IconSymbol
<IconSymbol name="music.note" size={24} className="mb-2" />
  • Step 2: Extract venue features into VenueSection component

  • Step 3: Rewrite artists/page.tsx


Task 7: Extract inquiry/contact/page.tsx

Target: components/marketing/contact/ directory

Files:

  • Create: components/marketing/contact/contact-info-section.tsx

  • Create: components/marketing/contact/contact-map-section.tsx

  • Modify: app/[locale]/(landing)/inquiry/contact/page.tsx

  • Step 1: Extract sections

  • Step 2: Extract sections and rewrite contact/page.tsx


Implementation Notes

Page-level state (what stays in page.tsx)

  • useState for modal control (e.g., french-mentalist)
  • nuqs query params (e.g., wall/page.tsx)
  • CalendarModalProvider context wrapping

What goes into section components

  • All inline JSX section blocks
  • All inline data arrays (keep inline in the section — no lib/data/ unless shared across pages)
  • All helper functions that are purely i18n mappers (inline directly, use m.* calls)

Naming convention for extracted sections

  • components/[domain]/[section-name]-section.tsx
  • Export: export function [SectionName]Section() { ... }
  • Examples: StorySection, CultureSection, VenueIntroSection, WorkshopIdealForSection

Order of extraction (follow this order)

  1. Section components (data arrays stay inline)
  2. Rewrite page.tsx (import sections, remove inline code)

Rollback plan

If a page breaks during refactoring, restore from git:

git checkout -- apps/frontend/app/[locale]/(landing)/[page]/page.tsx

Sections in components/ can be left in place — they're additive improvements.