UNPKG

booking-ui

Version:

React booking widget component for ZvenBook booking system

589 lines (459 loc) 13.9 kB
# @booking/ui - Booking Widget Component A production-ready, fully customizable React booking widget component for embedding appointment scheduling directly into your website. ## ✨ Features - 🎯 **Complete Booking Flow** - Service selection → Date/Time → Contact details → Confirmation - 🌍 **Internationalization** - Built-in English & Swedish, easy to add more languages - 🎨 **Fully Customizable** - Match your brand with CSS variables and className overrides - 📱 **Mobile Responsive** - Works beautifully on all devices - 🌙 **Dark Mode Support** - Automatic dark mode detection - ✅ **Form Validation** - Real-time email validation and error handling - ⚡ **Loading States** - Smooth loading indicators throughout - ♿ **Accessible** - Built with accessibility in mind ## 📦 Installation ```bash npm install @booking/ui # or yarn add @booking/ui # or pnpm add @booking/ui ``` ### Peer Dependencies This package requires React 18+ and React DOM 18+: ```bash npm install react react-dom ``` ## 🚀 Quick Start ```tsx import { BookingStepsWidget } from '@booking/ui'; function App() { return ( <BookingStepsWidget baseUrl="https://your-api-url.com" tenantId="your-tenant-id" /> ); } ``` ## 📖 Basic Usage ### Minimal Example ```tsx import { BookingStepsWidget } from '@booking/ui'; <BookingStepsWidget baseUrl="https://api.example.com" tenantId="tenant-123" /> ``` ### With Customization ```tsx import { BookingStepsWidget, swedishLabels } from '@booking/ui'; <BookingStepsWidget baseUrl="https://api.example.com" tenantId="tenant-123" weekStartsOn="monday" labels={swedishLabels} layout="wizard" spacing="cozy" radius="lg" styles={{ primaryColor: '#0066FF', accentColor: '#00AA44', serviceCardClassName: 'hover:shadow-lg', submitButtonClassName: 'w-full font-bold', }} /> ``` ## 🔧 Props Reference ### Required Props | Prop | Type | Description | |------|------|-------------| | `baseUrl` | `string` | Base URL of your booking API (e.g., `"https://api.example.com"`) | | `tenantId` | `string` | Your tenant ID from the booking system | ### Optional Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `className` | `string` | - | Additional CSS classes for the root container | | `layout` | `"horizontal" \| "vertical" \| "wizard"` | `"horizontal"` | Layout style for the widget | | `spacing` | `"compact" \| "normal" \| "cozy"` | `"normal"` | Spacing between elements | | `radius` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | Border radius for elements | | `color` | `"accent" \| "primary" \| "muted" \| "transparent"` | `"accent"` | Color scheme variant | | `weekStartsOn` | `"sunday" \| "monday"` | `"sunday"` | Calendar week start day | | `labels` | `BookingLabels` | `defaultLabels` | Translation labels (see Internationalization) | | `styles` | `BookingWidgetStyles` | - | Styling customization (see Styling) | ## 🎨 Styling Customization The widget can be customized in two ways: ### 1. CSS Custom Properties (Theme Colors) ```tsx <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} styles={{ primaryColor: '#0066FF', // Primary buttons, progress bars accentColor: '#00AA44', // Highlights, active states borderColor: '#E0E0E0', // Borders textColor: '#1F2937', // Text color backgroundColor: '#FFFFFF', // Background }} /> ``` ### 2. className Overrides (Fine-grained Control) ```tsx <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} styles={{ // Stepper component stepperClassName: 'custom-stepper', // Calendar component calendarClassName: 'shadow-lg border-2', // Service selection cards serviceCardClassName: 'hover:scale-105 transition-transform', // Time slot buttons timeSlotClassName: 'bg-blue-600 hover:bg-blue-700 text-white', // Form inputs inputClassName: 'font-sans text-base border-2', // Navigation buttons (Back/Next) buttonClassName: 'px-6 py-3 rounded-lg', // Submit button (overrides buttonClassName) submitButtonClassName: 'w-full font-bold uppercase tracking-wide', }} /> ``` ### Complete Styling Example ```tsx <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} styles={{ // Theme colors primaryColor: '#0066FF', accentColor: '#00AA44', // Component styling serviceCardClassName: cn( 'border-2 hover:border-accent transition-all', 'shadow-sm hover:shadow-md' ), timeSlotClassName: 'bg-primary hover:bg-primary/90 text-white', inputClassName: 'border-2 focus:ring-2 focus:ring-primary', submitButtonClassName: 'w-full bg-primary hover:bg-primary/90', }} /> ``` ## 🌍 Internationalization ### Built-in Languages #### English (Default) ```tsx import { BookingStepsWidget, defaultLabels } from '@booking/ui'; <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} labels={defaultLabels} // Optional, already default /> ``` #### Swedish ```tsx import { BookingStepsWidget, swedishLabels } from '@booking/ui'; <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} labels={swedishLabels} weekStartsOn="monday" // Swedish calendar starts Monday /> ``` ### Custom Labels ```tsx import { BookingStepsWidget, defaultLabels, BookingLabels } from '@booking/ui'; const frenchLabels: BookingLabels = { ...defaultLabels, stepService: 'Service', stepDateTime: 'Date et heure', stepDetails: 'Vos coordonnées', chooseService: 'Choisir un service', selectDate: 'Sélectionner une date', loadingTimes: 'Chargement des horaires...', noSlotsAvailable: 'Aucun créneau disponible.', yourDetails: 'Vos coordonnées', firstNamePlaceholder: 'Prénom', lastNamePlaceholder: 'Nom', emailPlaceholder: 'Email', phonePlaceholder: 'Téléphone (optionnel)', back: 'Retour', next: 'Suivant', bookAppointment: 'Réserver', booking: 'Réservation…', bookingConfirmed: 'Réservation confirmée', changeService: 'Service sélectionné • modifier', changeDate: '{date} • modifier', changeTime: '{time} • modifier', dayLabels: { sunday: 'Di', monday: 'Lu', tuesday: 'Ma', wednesday: 'Me', thursday: 'Je', friday: 'Ve', saturday: 'Sa', }, }; <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} labels={frenchLabels} /> ``` ### Label Interface ```typescript interface BookingLabels { // Stepper labels stepService: string; stepDateTime: string; stepDetails: string; // Service selection chooseService: string; // Date & time selection selectDate: string; loadingTimes: string; noSlotsAvailable: string; // Calendar day labels dayLabels: { sunday: string; monday: string; tuesday: string; wednesday: string; thursday: string; friday: string; saturday: string; }; // Contact form yourDetails: string; firstNamePlaceholder: string; lastNamePlaceholder: string; emailPlaceholder: string; phonePlaceholder: string; // Navigation back: string; next: string; bookAppointment: string; booking: string; // Success bookingConfirmed: string; // Change buttons changeService: string; changeDate: string; // Use {date} placeholder changeTime: string; // Use {time} placeholder } ``` ## 📱 Layout Options ### Horizontal Layout ```tsx <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} layout="horizontal" /> ``` ### Vertical Layout ```tsx <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} layout="vertical" /> ``` ### Wizard Layout (Recommended) ```tsx <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} layout="wizard" spacing="cozy" /> ``` ## 💡 Complete Examples ### Basic Integration ```tsx 'use client'; import { BookingStepsWidget } from '@booking/ui'; export default function BookingPage() { return ( <div className="container mx-auto p-6"> <h1 className="text-3xl font-bold mb-6">Book an Appointment</h1> <BookingStepsWidget baseUrl={process.env.NEXT_PUBLIC_API_URL || ''} tenantId={process.env.NEXT_PUBLIC_TENANT_ID || ''} /> </div> ); } ``` ### Custom Styled Widget ```tsx import { BookingStepsWidget } from '@booking/ui'; import { cn } from '@/lib/utils'; export function CustomBookingWidget() { return ( <div className="max-w-2xl mx-auto"> <BookingStepsWidget baseUrl="https://api.example.com" tenantId="tenant-123" layout="wizard" spacing="cozy" radius="lg" styles={{ primaryColor: '#0066FF', accentColor: '#00AA44', serviceCardClassName: cn( 'border-2 border-gray-200', 'hover:border-accent hover:shadow-lg', 'transition-all duration-200' ), timeSlotClassName: 'bg-primary text-white hover:bg-primary/90', inputClassName: 'border-2 focus:ring-2 focus:ring-primary', submitButtonClassName: 'w-full bg-primary hover:bg-primary/90 font-bold', }} /> </div> ); } ``` ### Multi-language Support ```tsx import { BookingStepsWidget, swedishLabels, defaultLabels } from '@booking/ui'; import { useRouter } from 'next/router'; export function LocalizedBookingWidget() { const router = useRouter(); const locale = router.locale || 'en'; const labels = locale === 'sv' ? swedishLabels : defaultLabels; const weekStartsOn = locale === 'sv' ? 'monday' : 'sunday'; return ( <BookingStepsWidget baseUrl={process.env.NEXT_PUBLIC_API_URL || ''} tenantId={process.env.NEXT_PUBLIC_TENANT_ID || ''} labels={labels} weekStartsOn={weekStartsOn} /> ); } ``` ### With Tailwind CSS If you're using Tailwind CSS, make sure to include the UI package styles: ```tsx // In your app's main CSS file @import '@booking/ui/styles.css'; // Or import directly in your component import '@booking/ui/styles.css'; ``` ### Custom CSS Styling ```css /* Override default styles */ .booking-widget { --booking-primary: #0066FF; --booking-accent: #00AA44; } /* Custom service cards */ .custom-service-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } /* Custom buttons */ .custom-submit-button { background: #00AA44; padding: 1rem 2rem; font-weight: bold; text-transform: uppercase; } ``` ```tsx <BookingStepsWidget baseUrl={baseUrl} tenantId={tenantId} className="booking-widget" styles={{ serviceCardClassName: 'custom-service-card', submitButtonClassName: 'custom-submit-button', }} /> ``` ## 🎯 Best Practices ### 1. Environment Variables Always use environment variables for API configuration: ```tsx // .env.local NEXT_PUBLIC_API_URL=https://api.example.com NEXT_PUBLIC_TENANT_ID=your-tenant-id // Component <BookingStepsWidget baseUrl={process.env.NEXT_PUBLIC_API_URL || ''} tenantId={process.env.NEXT_PUBLIC_TENANT_ID || ''} /> ``` ### 2. Error Handling The component handles errors internally, but you can wrap it for additional error handling: ```tsx import { ErrorBoundary } from 'react-error-boundary'; function ErrorFallback({ error }) { return ( <div className="p-4 bg-red-50 border border-red-200 rounded"> <p className="text-red-800">Something went wrong: {error.message}</p> </div> ); } <ErrorBoundary FallbackComponent={ErrorFallback}> <BookingStepsWidget {...props} /> </ErrorBoundary> ``` ### 3. Loading States The component includes built-in loading states, but you can add a wrapper: ```tsx import { Suspense } from 'react'; <Suspense fallback={<div>Loading booking widget...</div>}> <BookingStepsWidget {...props} /> </Suspense> ``` ### 4. Accessibility The component is accessible by default, but ensure your page has proper structure: ```tsx <main> <h1>Book an Appointment</h1> <BookingStepsWidget {...props} /> </main> ``` ## 🔍 API Requirements The widget expects your API to implement these endpoints: - `GET /api/services?tenantId={tenantId}` - List services - `GET /api/service-availability` - Get available time slots - `POST /api/bookings` - Create booking See the main [README](../README.md) for complete API documentation. ## 🐛 Troubleshooting ### Widget Not Loading 1. **Check API URL**: Ensure `baseUrl` is correct and accessible 2. **Check Tenant ID**: Verify `tenantId` exists in your system 3. **Check CORS**: Ensure your API allows requests from your domain 4. **Check Console**: Look for errors in browser console ### Styling Not Applying 1. **Import Styles**: Make sure to import `@booking/ui/styles.css` 2. **Tailwind Config**: Ensure Tailwind is configured correctly 3. **CSS Specificity**: Your custom styles might need higher specificity 4. **Dark Mode**: Check if dark mode classes are interfering ### Calendar Not Showing 1. **Week Start**: Verify `weekStartsOn` matches your locale 2. **Date Format**: Ensure API returns dates in ISO format 3. **Timezone**: Check timezone handling in API responses ## 📚 TypeScript Support Full TypeScript support is included. Import types as needed: ```typescript import type { BookingStepsWidget, BookingLabels, BookingWidgetStyles, } from '@booking/ui'; ``` ## 🤝 Contributing Contributions welcome! Please see the main [Contributing Guide](../../CONTRIBUTING.md). ## 📄 License MIT License - see [LICENSE](../../LICENSE) for details. ## 🔗 Related Packages - [`@booking/sdk`](../sdk/README.md) - TypeScript SDK for API integration - [`@booking/core`](../core/README.md) - Core business logic - [`@booking/db`](../db/README.md) - Database schemas --- **Need Help?** Check out the [main documentation](../../README.md) or [open an issue](https://github.com/yourusername/booking-system/issues).