
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A production-ready, fully customizable React booking widget component for embedding appointment scheduling directly into your website.
npm install @booking/ui
# or
yarn add @booking/ui
# or
pnpm add @booking/ui
This package requires React 18+ and React DOM 18+:
npm install react react-dom
import { BookingStepsWidget } from '@booking/ui';
function App() {
return (
<BookingStepsWidget
baseUrl="https://your-api-url.com"
tenantId="your-tenant-id"
/>
);
}
import { BookingStepsWidget } from '@booking/ui';
<BookingStepsWidget
baseUrl="https://api.example.com"
tenantId="tenant-123"
/>
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',
}}
/>
| 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 |
| 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) |
The widget can be customized in two ways:
<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
}}
/>
<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',
}}
/>
<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',
}}
/>
import { BookingStepsWidget, defaultLabels } from '@booking/ui';
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
labels={defaultLabels} // Optional, already default
/>
import { BookingStepsWidget, swedishLabels } from '@booking/ui';
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
labels={swedishLabels}
weekStartsOn="monday" // Swedish calendar starts Monday
/>
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}
/>
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
}
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
layout="horizontal"
/>
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
layout="vertical"
/>
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
layout="wizard"
spacing="cozy"
/>
'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>
);
}
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>
);
}
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}
/>
);
}
If you're using Tailwind CSS, make sure to include the UI package styles:
// In your app's main CSS file
@import '@booking/ui/styles.css';
// Or import directly in your component
import '@booking/ui/styles.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;
}
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
className="booking-widget"
styles={{
serviceCardClassName: 'custom-service-card',
submitButtonClassName: 'custom-submit-button',
}}
/>
Always use environment variables for API configuration:
// .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 || ''}
/>
The component handles errors internally, but you can wrap it for additional error handling:
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>
The component includes built-in loading states, but you can add a wrapper:
import { Suspense } from 'react';
<Suspense fallback={<div>Loading booking widget...</div>}>
<BookingStepsWidget {...props} />
</Suspense>
The component is accessible by default, but ensure your page has proper structure:
<main>
<h1>Book an Appointment</h1>
<BookingStepsWidget {...props} />
</main>
The widget expects your API to implement these endpoints:
GET /api/services?tenantId={tenantId} - List servicesGET /api/service-availability - Get available time slotsPOST /api/bookings - Create bookingSee the main README for complete API documentation.
baseUrl is correct and accessibletenantId exists in your system@booking/ui/styles.cssweekStartsOn matches your localeFull TypeScript support is included. Import types as needed:
import type {
BookingStepsWidget,
BookingLabels,
BookingWidgetStyles,
} from '@booking/ui';
Contributions welcome! Please see the main Contributing Guide.
MIT License - see LICENSE for details.
@booking/sdk - TypeScript SDK for API integration@booking/core - Core business logic@booking/db - Database schemasNeed Help? Check out the main documentation or open an issue.
FAQs
React booking widget component for ZvenBook booking system
We found that booking-ui demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.