Fazekit

Code

"use client";
import * as React from "react";
import { MotionConfig, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
import { defaultHotelData, type HotelData } from "./data";
import type { StaySearch } from "./sections/booking";
import { Faq, Footer, Gallery, Location, Reviews } from "./sections/closing";
import { Amenities, Offers, Wellness } from "./sections/features";
import { Hero, Intro } from "./sections/hero";
import { Navbar } from "./sections/navbar";
import { Rooms, type ReservationRequest } from "./sections/rooms";
import { scrollToId, useMoney } from "./sections/ui";

export type { HotelData, StaySearch, ReservationRequest };
export { defaultHotelData };

export interface HotelTemplateProps {
  /** Override any top-level content group (brand, rooms, offers…). Missing groups fall back to the demo content. */
  data?: Partial<HotelData>;
  /** Called when a guest searches dates from the booking bar. */
  onSearch?: (search: StaySearch) => void;
  /** Called when a guest requests a room. Return a promise to keep the loading state until your API answers. */
  onReserve?: (request: ReservationRequest) => void | Promise<void>;
  onSubscribe?: (email: string) => void | Promise<void>;
  className?: string;
}

/**
 * Maris House — a boutique seaside hotel landing page.
 * Animated sea hero with a date-range booking bar, rooms with photo carousels and a reservation dialog,
 * amenities, spa & dining tabs, offers with live countdowns, reviews, an illustrated map, a lightbox gallery, FAQ and footer.
 */
export function HotelTemplate({ data, onSearch, onReserve, onSubscribe, className }: HotelTemplateProps) {
  const d: HotelData = React.useMemo(() => ({ ...defaultHotelData, ...data }), [data]);
  const reduce = useReducedMotion();
  const money = useMoney(d.brand.currency);
  const [search, setSearch] = React.useState<StaySearch | null>(null);
  const go = React.useCallback((id: string) => scrollToId(id, reduce), [reduce]);

  const bookTop = React.useCallback(() => {
    go("top");
    window.setTimeout(
      () => document.querySelector<HTMLButtonElement>('form[aria-label="Check availability"] button')?.focus({ preventScroll: true }),
      reduce ? 0 : 600,
    );
  }, [go, reduce]);

  return (
    <MotionConfig reducedMotion="user">
      <div className={cn("relative w-full bg-background text-foreground antialiased selection:bg-[#c9895c]/25", className)}>
        <Navbar data={d} onBook={bookTop} />
        <main>
          <Hero
            data={d}
            onSearch={(s) => {
              setSearch(s);
              onSearch?.(s);
              window.setTimeout(() => go("rooms"), 50);
            }}
          />
          <Intro data={d} />
          <Rooms data={d} money={money} search={search} onReserve={onReserve} />
          <Amenities items={d.amenities} />
          <Wellness data={d} money={money} />
          <Offers offers={d.offers} onBook={bookTop} />
          <Reviews reviews={d.reviews} />
          <Location data={d} />
          <Gallery items={d.gallery} />
          <Faq items={d.faq} />
        </main>
        <Footer data={d} onBook={bookTop} onNav={go} onSubscribe={onSubscribe} />
      </div>
    </MotionConfig>
  );
}

More in Landing Pages

View all →