booking-ui
Version:
React booking widget component for ZvenBook booking system
589 lines (459 loc) • 13.9 kB
Markdown
# @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).