Fazekit

Code

"use client";
import * as React from "react";
import { MotionConfig, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
import { defaultYogaData, type YogaData } from "./data";
import { Faq, Footer, Reviews, Visit, type ContactMessage, type ContactPrefill } from "./sections/closing";
import { Hero, Styles } from "./sections/hero";
import { Navbar } from "./sections/navbar";
import { AppPromo, IntroOffer, Pricing, Teachers, Workshops } from "./sections/offers";
import { Schedule, type BookingEvent, type ScheduleFocus } from "./sections/schedule";
import { brandProps, scrollToId, useMoney, useOpenStatus } from "./sections/ui";

export type { YogaData, BookingEvent, ContactMessage };
export { defaultYogaData };

export interface YogaStudioTemplateProps {
  /** Override any top-level content group (brand, schedule, teachers, memberships…). Missing groups fall back to data.ts. */
  data?: Partial<YogaData>;
  /** Called whenever a visitor books, cancels or joins a waitlist — wire it to your booking API. */
  onBooking?: (event: BookingEvent) => void;
  /** Called when the contact form is sent. Return a promise to keep the loading state until your API answers. */
  onContact?: (message: ContactMessage) => void | Promise<void>;
  className?: string;
}

/**
 * Stillpoint — a calm, airy website for a yoga & pilates studio.
 * Floating pill navbar, breathing-orb hero with live "next class", class styles, a filterable weekly timetable
 * with day tabs and book-a-spot (spots left, waitlist, undo toast), intro offer, memberships with monthly/annual
 * toggle, teachers (jump to their classes), workshops, app promo, reviews, FAQ, hours with "open now", map + contact form.
 */
export function YogaStudioTemplate({ data, onBooking, onContact, className }: YogaStudioTemplateProps) {
  const d: YogaData = React.useMemo(() => ({ ...defaultYogaData, ...data }), [data]);
  const reduce = useReducedMotion();
  const money = useMoney(d.brand.currency, d.brand.locale);
  const status = useOpenStatus(d.hours);
  const brand = brandProps(d.brandColors);
  const [focus, setFocus] = React.useState<ScheduleFocus>({ nonce: 0 });
  const [prefill, setPrefill] = React.useState<ContactPrefill>({ nonce: 0 });
  const go = React.useCallback((id: string) => scrollToId(id, reduce), [reduce]);

  const schedule = (f: Omit<ScheduleFocus, "nonce"> = {}) => {
    setFocus((c) => ({ ...f, nonce: c.nonce + 1 }));
    go("schedule");
  };
  const contact = (topic: string, message: string) => {
    setPrefill((c) => ({ nonce: c.nonce + 1, topic, message }));
    go("visit");
  };

  return (
    <MotionConfig reducedMotion="user">
      <div className={cn("relative w-full bg-background text-foreground antialiased selection:bg-primary/20", brand.className, className)} style={brand.style}>
        <Navbar data={d} status={status} onBook={() => schedule()} />
        <main>
          <Hero data={d} status={status} onIntro={() => go("intro")} onSchedule={(day) => schedule(day === undefined ? {} : { day })} />
          <Styles data={d} onPick={(styleId) => schedule({ styleId, teacherId: undefined })} />
          <Schedule data={d} status={status} focus={focus} onBooking={onBooking} />
          <IntroOffer data={d} money={money} onClaim={() => contact("Intro offer", `I'd like to start the ${d.intro.period} intro offer.`)} />
          <Pricing data={d} money={money} onChoose={(plan) => contact("Membership", `I'd like to join the ${plan} membership.`)} />
          <Teachers data={d} onClasses={(teacherId) => schedule({ teacherId })} />
          <Workshops data={d} money={money} />
          <AppPromo data={d} />
          <Reviews data={d} />
          <Faq data={d} />
          <Visit data={d} status={status} prefill={prefill} onSubmit={onContact} />
        </main>
        <Footer data={d} onNav={go} />
      </div>
    </MotionConfig>
  );
}

More in Landing Pages

View all →