Add booking flow, business hours, and dark mode support
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>
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
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"; backgroundColor: string; textColor: string };
|
||||
TextSection: { heading: string; body: string; backgroundColor: string };
|
||||
Booking: { headline: string; subheading: string; accentColor: string; buttonLabel: string };
|
||||
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> = {
|
||||
@@ -22,65 +29,91 @@ export const config: Config<Props> = {
|
||||
{ label: "Right", value: "right" },
|
||||
],
|
||||
},
|
||||
backgroundColor: { type: "text" }, // Puck doesn't have color picker by default? Or use "custom"?
|
||||
textColor: { type: "text" },
|
||||
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",
|
||||
backgroundColor: "#ffffff",
|
||||
textColor: "#000000",
|
||||
ctaText: "Book Now",
|
||||
ctaLink: "/book",
|
||||
},
|
||||
render: ({ title, subtitle, align, backgroundColor, textColor }) => (
|
||||
<div style={{ backgroundColor, color: textColor, padding: "4rem 2rem", textAlign: align }}>
|
||||
<h1 style={{ fontSize: "3rem", fontWeight: "bold", marginBottom: "1rem" }}>{title}</h1>
|
||||
<p style={{ fontSize: "1.5rem" }}>{subtitle}</p>
|
||||
</div>
|
||||
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" },
|
||||
backgroundColor: { type: "text" },
|
||||
},
|
||||
defaultProps: {
|
||||
heading: "About Us",
|
||||
body: "Enter your text here...",
|
||||
backgroundColor: "#f9fafb",
|
||||
},
|
||||
render: ({ heading, body, backgroundColor }) => (
|
||||
<div style={{ backgroundColor, padding: "3rem 2rem" }}>
|
||||
<div style={{ maxWidth: "800px", margin: "0 auto" }}>
|
||||
<h2 style={{ fontSize: "2rem", marginBottom: "1rem" }}>{heading}</h2>
|
||||
<div style={{ whiteSpace: "pre-wrap" }}>{body}</div>
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
),
|
||||
},
|
||||
Booking: {
|
||||
fields: {
|
||||
headline: { type: "text" },
|
||||
subheading: { type: "text" },
|
||||
accentColor: { type: "text" },
|
||||
buttonLabel: { type: "text" },
|
||||
},
|
||||
defaultProps: {
|
||||
headline: "Book an Appointment",
|
||||
subheading: "Select a service below",
|
||||
accentColor: "#2563eb",
|
||||
buttonLabel: "Book Now",
|
||||
headline: "Schedule Your Appointment",
|
||||
subheading: "Choose a service and time that works for you",
|
||||
},
|
||||
render: ({ headline, subheading, accentColor, buttonLabel }) => (
|
||||
<div style={{ padding: "3rem 2rem", textAlign: "center" }}>
|
||||
<BookingWidget
|
||||
headline={headline}
|
||||
subheading={subheading}
|
||||
accentColor={accentColor}
|
||||
buttonLabel={buttonLabel}
|
||||
/>
|
||||
</div>
|
||||
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>
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user