Fazekit

Code

"use client";
import * as React from "react";
import { MotionConfig, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
import { defaultWineryData, type ClubTier, type WineryData } from "./data";
import { CartDrawer, type CartLine } from "./sections/cart";
import { Events, Footer, Press } from "./sections/closing";
import { Club } from "./sections/club";
import { Hero } from "./sections/hero";
import { Navbar } from "./sections/navbar";
import { Awards, Story } from "./sections/story";
import { scrollToId, useMoney } from "./sections/ui";
import { Vineyard } from "./sections/vineyard";
import { Visit, type TastingRequest } from "./sections/visit";
import { Wines } from "./sections/wines";

export type { WineryData, CartLine, TastingRequest, ClubTier };
export { defaultWineryData };

export interface WineryTemplateProps {
  /** Override any top-level content group (brand, wines, club…). Missing groups fall back to the demo content. */
  data?: Partial<WineryData>;
  /** Called with the cart lines when the visitor checks out. */
  onCheckout?: (lines: CartLine[]) => void;
  /** Called when a tasting is reserved. Return a promise to keep the loading state until your API answers. */
  onBookTasting?: (request: TastingRequest) => void | Promise<void>;
  onJoinClub?: (tier: ClubTier) => void;
  onSubscribe?: (email: string) => void | Promise<void>;
  className?: string;
}

/**
 * Colline Verdi — a family vineyard & wine club storefront.
 * Animated hillside hero (day in light mode, dusk in dark), story timeline, awards,
 * flip-card wine shop with cart drawer, club tiers with a shipment calendar,
 * tasting-room booking, an interactive vineyard map, events, press and a newsletter footer.
 */
export function WineryTemplate({ data, onCheckout, onBookTasting, onJoinClub, onSubscribe, className }: WineryTemplateProps) {
  const d: WineryData = React.useMemo(() => ({ ...defaultWineryData, ...data }), [data]);
  const reduce = useReducedMotion();
  const money = useMoney(d.brand.currency);
  const [cart, setCart] = React.useState<Record<string, number>>({});
  const [cartOpen, setCartOpen] = React.useState(false);

  const add = React.useCallback((id: string) => setCart((c) => ({ ...c, [id]: (c[id] ?? 0) + 1 })), []);
  const setQty = React.useCallback(
    (id: string, qty: number) =>
      setCart((c) => {
        const next = { ...c };
        if (qty <= 0) delete next[id];
        else next[id] = qty;
        return next;
      }),
    [],
  );
  const lines: CartLine[] = d.wines.items.filter((w) => cart[w.id]).map((w) => ({ wine: w, qty: cart[w.id] }));
  const count = lines.reduce((n, l) => n + l.qty, 0);
  const go = React.useCallback((id: string) => scrollToId(id, reduce), [reduce]);
  const closeCart = React.useCallback(() => setCartOpen(false), []);

  return (
    <MotionConfig reducedMotion="user">
      <div className={cn("relative w-full bg-background text-foreground antialiased selection:bg-[#7a1f2b]/20 dark:selection:bg-[#e7b3a6]/25", className)}>
        <Navbar data={d} cartCount={count} onCart={() => setCartOpen(true)} onJoin={() => go("club")} />
        <main>
          <Hero data={d} onShop={() => go("wines")} onVisit={() => go("visit")} />
          <Story data={d} />
          <Awards awards={d.awards} />
          <Wines data={d} money={money} cart={cart} onAdd={add} />
          <Club data={d} money={money} onJoin={onJoinClub} />
          <Visit data={d} money={money} onBook={onBookTasting} />
          <Vineyard data={d} />
          <Events events={d.events} onReserve={() => go("visit")} />
          <Press quotes={d.press} />
        </main>
        <Footer data={d} onNav={go} onSubscribe={onSubscribe} />
        <CartDrawer
          open={cartOpen}
          onClose={closeCart}
          lines={lines}
          money={money}
          minimumAge={d.footer.minimumAge}
          onQty={setQty}
          onCheckout={(l) => {
            onCheckout?.(l);
            setCart({});
          }}
        />
      </div>
    </MotionConfig>
  );
}

More in E-commerce

View all →