Fazekit

Code

"use client";
import * as React from "react";
import { MotionConfig, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
import { defaultSalonData, type SalonData } from "./data";
import { Booking, type BookingPreset, type BookingRequest } from "./sections/booking";
import { Faq } from "./sections/faq";
import { Footer } from "./sections/footer";
import { Hero } from "./sections/hero";
import { Navbar } from "./sections/navbar";
import { BeforeAfter, Promos } from "./sections/offers";
import { Reviews } from "./sections/reviews";
import { Services } from "./sections/services";
import { Team } from "./sections/team";
import { scrollToId, ThemeStyle, useOpenStatus, VS_DISPLAY_STACK } from "./sections/ui";
import { Visit } from "./sections/visit";
import { Vouchers, type VoucherRequest } from "./sections/vouchers";

export type { SalonData, BookingRequest, VoucherRequest };
export { defaultSalonData };

export interface BeautySalonTemplateProps {
  /** Override any top-level content group (business, services, stylists, hours…). Missing groups fall back to the demo content. */
  data?: Partial<SalonData>;
  /** Called when a client confirms an appointment. Return a promise to keep the loading state until your API answers. */
  onBook?: (booking: BookingRequest) => void | Promise<void>;
  /** Called when someone buys a gift card. */
  onVoucher?: (voucher: VoucherRequest) => void | Promise<void>;
  className?: string;
}

const SCOPE = "vs-theme";

export function BeautySalonTemplate({ data, onBook, onVoucher, className }: BeautySalonTemplateProps) {
  const d = React.useMemo(() => ({ ...defaultSalonData, ...data }), [data]);
  const reduce = useReducedMotion();
  const status = useOpenStatus(d.visit.hours, d.business.timeZone);
  const [preset, setPreset] = React.useState<BookingPreset | null>(null);
  const toBook = React.useCallback(() => scrollToId("book", reduce), [reduce]);
  const bookWith = (p: Omit<BookingPreset, "nonce">) => {
    setPreset((prev) => ({ ...p, nonce: (prev?.nonce ?? 0) + 1 }));
    toBook();
  };

  return (
    <MotionConfig reducedMotion="user">
      <ThemeStyle scope={SCOPE} theme={d.theme} />
      <div className={cn(SCOPE, "relative w-full bg-background text-foreground antialiased selection:bg-primary/20", className)} style={{ "--vs-display": VS_DISPLAY_STACK } as React.CSSProperties}>
        <Navbar business={d.business} links={d.nav} status={status} onBook={toBook} />
        <main>
          <Hero hero={d.hero} status={status} onBook={toBook} onServices={() => scrollToId("services", reduce)} />
          <Services services={d.services} business={d.business} onBookService={(serviceId) => bookWith({ serviceId })} />
          <Promos promos={d.promos} />
          <BeforeAfter data={d.transformation} />
          <Team team={d.team} categories={d.services.categories} onBookStylist={(stylistId) => bookWith({ stylistId })} />
          <Booking booking={d.booking} categories={d.services.categories} stylists={d.team.stylists} hours={d.visit.hours} business={d.business} preset={preset} onBook={onBook} />
          <Vouchers vouchers={d.vouchers} business={d.business} onVoucher={onVoucher} />
          <Reviews reviews={d.reviews} />
          <Faq faq={d.faq} business={d.business} />
          <Visit visit={d.visit} business={d.business} status={status} />
        </main>
        <Footer footer={d.footer} business={d.business} />
      </div>
    </MotionConfig>
  );
}

More in Landing Pages

View all →