Features: - Complete multi-step booking flow with service selection, date/time picker, auth (login/signup with email verification), payment, and confirmation - Business hours settings page for defining when business is open - TimeBlock purpose field (BUSINESS_HOURS, CLOSURE, UNAVAILABLE) - Service resource assignment with prep/takedown time buffers - Availability checking respects business hours and service buffers - Customer registration via email verification code UI/UX: - Full dark mode support for all booking components - Separate first/last name fields in signup form - Back buttons on each wizard step - Removed auto-redirect from confirmation page API: - Public endpoints for services, availability, business hours - Customer verification and registration endpoints - Tenant lookup from X-Business-Subdomain header 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
import React from "react";
|
|
import type { Config } from "@measured/puck";
|
|
import BookingWidget from "./components/booking/BookingWidget";
|
|
import { ArrowRight } from "lucide-react";
|
|
|
|
type Props = {
|
|
Hero: {
|
|
title: string;
|
|
subtitle: string;
|
|
align: "left" | "center" | "right";
|
|
ctaText?: string;
|
|
ctaLink?: string;
|
|
};
|
|
TextSection: { heading: string; body: string };
|
|
Booking: { headline: string; subheading: string };
|
|
};
|
|
|
|
export const config: Config<Props> = {
|
|
components: {
|
|
Hero: {
|
|
fields: {
|
|
title: { type: "text" },
|
|
subtitle: { type: "text" },
|
|
align: {
|
|
type: "radio",
|
|
options: [
|
|
{ label: "Left", value: "left" },
|
|
{ label: "Center", value: "center" },
|
|
{ label: "Right", value: "right" },
|
|
],
|
|
},
|
|
ctaText: { type: "text", label: "Button Text" },
|
|
ctaLink: { type: "text", label: "Button Link" },
|
|
},
|
|
defaultProps: {
|
|
title: "Welcome to our site",
|
|
subtitle: "We provide great services",
|
|
align: "center",
|
|
ctaText: "Book Now",
|
|
ctaLink: "/book",
|
|
},
|
|
render: ({ title, subtitle, align, ctaText, ctaLink }) => (
|
|
<section className="relative bg-gradient-to-br from-gray-50 to-white dark:from-gray-900 dark:to-gray-800 py-20 sm:py-28">
|
|
<div className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]"></div>
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className={`relative z-10 ${align === 'center' ? 'text-center' : align === 'right' ? 'text-right' : 'text-left'}`}>
|
|
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-bold text-gray-900 dark:text-white mb-6 tracking-tight">
|
|
{title}
|
|
</h1>
|
|
<p className="text-xl sm:text-2xl text-gray-600 dark:text-gray-300 mb-10 max-w-3xl mx-auto leading-relaxed">
|
|
{subtitle}
|
|
</p>
|
|
{ctaText && ctaLink && (
|
|
<a
|
|
href={ctaLink}
|
|
className="inline-flex items-center px-8 py-4 bg-indigo-600 dark:bg-indigo-500 text-white text-lg font-semibold rounded-xl shadow-lg hover:bg-indigo-700 dark:hover:bg-indigo-600 hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200"
|
|
>
|
|
{ctaText}
|
|
<ArrowRight className="ml-2 w-5 h-5" />
|
|
</a>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
),
|
|
},
|
|
TextSection: {
|
|
fields: {
|
|
heading: { type: "text" },
|
|
body: { type: "textarea" },
|
|
},
|
|
defaultProps: {
|
|
heading: "About Us",
|
|
body: "Enter your text here...",
|
|
},
|
|
render: ({ heading, body }) => (
|
|
<section className="py-16 sm:py-20 bg-white dark:bg-gray-900">
|
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<h2 className="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white mb-6">
|
|
{heading}
|
|
</h2>
|
|
<div className="text-lg text-gray-600 dark:text-gray-300 leading-relaxed whitespace-pre-wrap">
|
|
{body}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
),
|
|
},
|
|
Booking: {
|
|
fields: {
|
|
headline: { type: "text" },
|
|
subheading: { type: "text" },
|
|
},
|
|
defaultProps: {
|
|
headline: "Schedule Your Appointment",
|
|
subheading: "Choose a service and time that works for you",
|
|
},
|
|
render: ({ headline, subheading }) => (
|
|
<section className="py-16 sm:py-20 bg-gray-50 dark:bg-gray-800">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="text-center mb-12">
|
|
<h2 className="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
|
{headline}
|
|
</h2>
|
|
<p className="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
|
|
{subheading}
|
|
</p>
|
|
</div>
|
|
<BookingWidget
|
|
headline={headline}
|
|
subheading={subheading}
|
|
accentColor="#4f46e5"
|
|
buttonLabel="Book Now"
|
|
/>
|
|
</div>
|
|
</section>
|
|
),
|
|
},
|
|
},
|
|
};
|