Happy am Meer - Advanced Vacation Rental Platform with Real-Time Booking System

- Published on
- Duration
- 2 Months
- Role
- Full-Stack Developer, System Architect, UI/UX Designer




Happy am Meer Vacation Rental Platform
The Happy am Meer platform represents a sophisticated vacation rental management system built for a German vacation rental company expanding into vacation rentals. This production-ready platform features a comprehensive booking system with real-time availability, external calendar integration, calendar validation system, and mobile booking experience.
The system is designed for scalability and performance, handling complex booking scenarios with automated external calendar synchronization (Airbnb, Booking.com, VRBO) on a Vercel cron schedule (every 15 minutes) combined with per-property sync intervals, plus real-time calendar validation with auto-correction capabilities. The mobile-first design provides an exceptional user experience with a scroll-based sticky booking bar, full-screen modal calendar interface, and comprehensive touch-optimized interactions.
This platform demonstrates advanced headless CMS architecture with real-time booking capabilities, calendar validation system, and mobile optimization, achieving 92-95 Lighthouse performance scores while keeping listings, availability, and inquiries anchored in Payload collections.
The implementation showcases modern web development practices with Payload CMS, Next.js 15, and React 19, delivering a vacation rental experience that significantly exceeds industry standards through automated data integrity, mobile UX, optimized background processing, and typed REST-style route handlers that wrap Payload’s find, create, and update APIs.
Key Features
- Advanced real-time booking calendar with multi-mode interface
- Automated external calendar synchronization (Airbnb, Booking.com, VRBO)
- Advanced calendar validation system with auto-correction
- Mobile booking experience with scroll-based sticky bar
- Comprehensive property management with seasonal pricing
- Block-based content management system with 13 flexible blocks
- Advanced caching system with LRU eviction for optimal performance
- Optimized background job processing with Vercel cron jobs (scheduled every 15 minutes; per-property sync frequency respected)
- Individual calendar processing with error isolation
- Cross-page session persistence for booking data
- Email automation for booking inquiries and notifications
- Real-time validation alerts and monitoring
- SEO optimization with vacation rental structured data
- Database schema enhancements for validation tracking
- Payload-backed property catalog and detail APIs with
unstable_cacheand tag-based revalidation - Per-slug booking inquiry
POSTthat resolves theferienhaeuserdocument, checksavailability, thenpayload.createonbooking-inquiries - Multi-property
POSTavailability endpoint aggregatingavailabilityandferienhaeuserdocs for range queries - iCal sync that merges into each
availabilitydocument’sexternalBookingsviapayload.update, with manual sync and background batch jobs
Tech Stack Overview
This project leverages a modern, production-ready technology stack:
- Next.js 15: App Router with server-side rendering for optimal SEO
- React 19.1.0: Latest React with server components and enhanced performance
- TypeScript 5.6.3: Strict type checking throughout the codebase
- Payload CMS 3.36.0: Headless CMS with block-based content architecture
- MongoDB Atlas: Document database for flexible content and booking data
- S3 Storage: Media asset management with automatic optimization
- TailwindCSS 3.4.3: Utility-first CSS with custom design system
- Shadcn UI: Radix UI-based component library for accessibility
- Vercel: Deployment platform with cron job support for background processing
- Framer Motion 12.9.2: Performance-optimized animations
- React Hook Form 7.45.4: Form handling with validation
- Sharp 0.32.6: Image optimization and processing
- Nodemailer: Email automation for booking notifications
Project Structure
The project follows a sophisticated, scalable architecture:
.
├── public/ # Static assets and media
│ ├── media/ # Uploaded content via Payload CMS
│ └── favicon.ico # Site favicon
├── src/ # Source code
│ ├── app/ # Next.js app router
│ │ ├── (frontend)/ # Public-facing pages
│ │ │ ├── ferienhaeuser/ # Vacation rental pages
│ │ │ ├── verfuegbarkeit/ # Availability calendar
│ │ │ └── ... # Other public pages
│ │ ├── (payload)/ # Payload CMS admin panel
│ │ └── api/ # API routes
│ │ ├── ferienhaeuser-api/ # Property management APIs
│ │ ├── booking-inquiry/ # Booking system APIs
│ │ ├── ical-sync/ # External calendar sync
│ │ ├── cron/ # Scheduled entry (forwards to background sync)
│ │ └── ... # Other API endpoints
│ ├── utilities/ # Shared helpers (URLs, email, cron helpers, rate limiting)
│ ├── blocks/ # Content blocks for CMS
│ │ ├── ArchiveBlock/ # Content listings
│ │ ├── Banner/ # Promotional content
│ │ ├── CallToAction/ # Conversion elements
│ │ └── ... # 13 total content blocks
│ ├── collections/ # Payload CMS collections
│ │ ├── Ferienhaeuser/ # Property management
│ │ ├── Availability/ # Booking availability
│ │ ├── BookingInquiries/ # Guest inquiries
│ │ └── ... # 10 total collections
│ ├── components/ # React components
│ │ ├── booking-calendar/ # Booking system components
│ │ ├── ui/ # UI components (Shadcn)
│ │ └── ... # Feature components
│ ├── lib/ # Utility functions and configurations
│ │ ├── booking-data.ts # Booking data types
│ │ ├── calendar-cache.ts # Advanced caching system
│ │ ├── server-booking-inquiry-pricing.ts # Server-side inquiry totals
│ │ ├── stayAvailabilityCheck.ts # Server-side availability checks
│ │ └── ... # Other utilities
│ └── payload.config.ts # Payload CMS configuration
Advanced Booking System Architecture
The booking system features a sophisticated multi-layered architecture:
Real-Time Availability Checking
- Live availability queries with < 200ms response times
- Conflict detection with external booking platforms
- Advanced caching with LRU eviction for optimal performance
- 5-minute availability cache, 30-minute pricing cache
Advanced Calendar Validation System
- Real-time validation after every sync operation
- External calendar dominance with auto-correction
- Periodic validation via independent Vercel cron jobs
- Comprehensive monitoring and alert system
External Calendar Integration
- Automated synchronization with Airbnb, Booking.com, VRBO
- Individual calendar processing with error isolation
- Background sync triggered on a Vercel cron schedule (every 15 minutes) with per-property sync frequency settings
- Smart scheduling based on sync frequency settings
Mobile Booking Experience
- Scroll-based sticky booking bar (400px threshold)
- Full-screen modal calendar with comprehensive scroll prevention
- Reset functionality and image placeholder system
- Touch-optimized interactions for mobile devices
- Cross-page session persistence for booking data
Property Management System
- Complete property listings with detailed information
- Seasonal pricing with "highest price wins" logic
- Image galleries and amenity management
- German localization for vacation rental market
- Validation status tracking and monitoring
Payload CMS backend: properties, availability, and calendar sync
Public and admin-adjacent flows read and write MongoDB exclusively through Payload’s Local API inside Next.js route handlers (getPayload + collection slugs). The vacation-rental surface is split across ferienhaeuser, availability, and booking-inquiries, with sync state stored on the availability record so the booking UI and APIs stay aligned.
Property catalog (GET /api/ferienhaeuser-api/ferienhaeuser)
- Uses
payload.findonferienhaeuserwith_status: 'published',status: 'active', configurabledepth(nested media and relations), sort (-featured,title), and a sensiblelimit. - Wraps the query in
unstable_cache(10-minute revalidation,tags: ['ferienhaeuser']) so the listing page stays fast; filter query parameters (regions, guest counts, date ranges, property types) refine the in-memory result set after the cached fetch.
Single property (GET and POST /api/ferienhaeuser-api/ferienhaeuser/[slug])
- GET: Cached
payload.findbyslugwith higherdepthso galleries, amenities, pricing blocks, and SEO fields hydrate in one trip. OptionalcheckIn/checkOutoravailability=trueruns a follow-uppayload.findonavailabilityforproperty: { equals: id }and merges blocked dates, synced external bookings (date ranges only in the public JSON), and minimum-stay overrides into the response. - POST (booking inquiry): Uncached
payload.findfor the same slug constraints, server-side overlap checks against that availability document, thenpayload.createonbooking-inquirieslinkingpropertyto the resolved id. Unavailable stays return 409 with conflict detail instead of creating a document.
Multi-property availability (POST /api/ferienhaeuser-api/ferienhaeuser/availability)
- Cached
payload.findacross activeavailabilityrows (optionalpropertyfilter) plus matchingferienhaeuserdocs; application code applies overlap rules so multi-unit search respects the same blocked dates and external bookings stored in Payload.
Calendar synchronization (iCal → Payload)
- Each property’s feeds are configured on
availability.syncSettings.externalCalendars(platform,icalUrl,enabled, and per-calendar sync metadata). - The sync library downloads feeds, normalizes events, and persists the merged booking list with
payload.updateonavailability(externalBookingsand per-calendarlastSync/lastStatus). POST /api/ical-syncacceptsavailabilityIdorpropertyId; whenpropertyIdis used, apayload.findresolves the active availability row first, then callssyncPropertyCalendars. After a successful sync,revalidateTag('availability')andrevalidateTag('ferienhaeuser')keep Next.js data and the booking calendar cache in sync with the database.- Background processing:
/api/cron/ical-syncforwards to/api/ical-sync/background, which batches eligible availability ids viagetPropertiesForSync/syncMultiplePropertiesso many properties sync without blocking the admin UI.
// Patterns used in the Ferienhaeuser API routes (simplified)
const property = await payload.find({
collection: 'ferienhaeuser',
where: {
slug: { equals: slug },
_status: { equals: 'published' },
status: { equals: 'active' },
},
depth: 3,
limit: 1,
});
const availability = await payload.find({
collection: 'availability',
where: {
property: { equals: property.docs[0].id },
status: { equals: 'active' },
},
depth: 0,
limit: 1,
});
const inquiry = await payload.create({
collection: 'booking-inquiries',
data: {
property: property.docs[0].id,
guestDetails,
stayDetails,
inquiryDetails,
status: 'new',
priority: 'normal',
},
});
Payload CMS Configuration
The Payload CMS configuration demonstrates advanced headless CMS architecture:
// Core Collections Configuration
collections: [
// Original Collections
Pages, Posts, Products, Media, ImageSlider, Categories, Users,
// Vacation Rental Collections
Ferienhaeuser, PropertyAmenities, Availability, BookingInquiries,
],
// Live Preview Configuration
livePreview: {
breakpoints: [
{ label: 'Mobile', width: 375, height: 667 },
{ label: 'Tablet', width: 768, height: 1024 },
{ label: 'Desktop', width: 1440, height: 900 },
],
url: 'https://happy-am-meer.vercel.app/',
collections: ['pages', 'posts', 'products', 'ferienhaeuser'],
},
// Storage Configuration
storage: s3Storage({
collections: {
media: {
prefix: 'media',
bucket: process.env.S3_BUCKET,
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION,
},
},
}),
Booking Calendar API Implementation
The booking system features comprehensive API endpoints:
// Availability Checking API
export async function POST(request: NextRequest) {
try {
const { propertyIds, checkIn, checkOut, guests } = await request.json();
// Real-time availability checking
const availability = await checkAvailability({
propertyIds,
checkIn,
checkOut,
guests,
});
// Conflict detection with external calendars
const conflicts = await detectConflicts(availability);
// Dynamic pricing calculation
const pricing = await calculatePricing({
propertyIds,
checkIn,
checkOut,
guests,
});
return NextResponse.json({
success: true,
data: { availability, conflicts, pricing },
cache: { ttl: 300, hit: true },
});
} catch (error) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}
Advanced Calendar Synchronization System
The platform features a sophisticated multi-layered calendar synchronization system:
// Core Calendar Sync Library - src/lib/syncCalendars.ts
interface SyncCalendarResult {
success: boolean;
syncedBookings: number;
errors: number;
message?: string;
data?: any;
}
interface CalendarEvent {
summary: string;
dtstart: Date;
dtend: Date;
uid: string;
description: string;
}
/**
* Parse iCal data and extract events with robust error handling
*/
const parseICalData = (icalData: string): CalendarEvent[] => {
const events: CalendarEvent[] = [];
const lines = icalData.split('\n').map((line) => line.trim());
let currentEvent: Partial<CalendarEvent> | null = null;
let isInEvent = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line === 'BEGIN:VEVENT') {
isInEvent = true;
currentEvent = {
summary: '',
dtstart: new Date(),
dtend: new Date(),
uid: '',
description: '',
};
} else if (line === 'END:VEVENT' && isInEvent && currentEvent) {
if (currentEvent.dtstart && currentEvent.dtend) {
events.push(currentEvent as CalendarEvent);
}
currentEvent = null;
isInEvent = false;
} else if (isInEvent && currentEvent) {
if (line.startsWith('SUMMARY:')) {
currentEvent.summary = line.substring(8);
} else if (line.startsWith('DTSTART')) {
const dateValue =
line.split(':')[1] || line.split('=')[line.split('=').length - 1];
currentEvent.dtstart = parseICalDate(dateValue);
} else if (line.startsWith('DTEND')) {
const dateValue =
line.split(':')[1] || line.split('=')[line.split('=').length - 1];
currentEvent.dtend = parseICalDate(dateValue);
} else if (line.startsWith('UID:')) {
currentEvent.uid = line.substring(4);
} else if (line.startsWith('DESCRIPTION:')) {
currentEvent.description = line.substring(12);
}
}
}
return events;
};
/**
* Fetch iCal data with timeout and proper error handling
*/
const fetchICalData = async (
url: string,
timeoutMs: number = 15000
): Promise<string> => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Happy-am-Meer Calendar Sync/1.0',
Accept: 'text/calendar,application/calendar,text/plain',
},
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type') || '';
if (!contentType.includes('calendar') && !contentType.includes('text')) {
throw new Error(`Invalid content type: ${contentType}`);
}
return await response.text();
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request timeout after ${timeoutMs}ms`);
}
throw new Error(`Failed to fetch iCal data: ${error.message}`);
}
throw new Error('Failed to fetch iCal data: Unknown error');
}
};
/**
* Sync calendars for a single property with enhanced logging
*/
export async function syncPropertyCalendars(
payload: Payload,
availabilityId: string,
syncType: 'manual' | 'background' | 'scheduled' = 'manual',
triggeredBy: string = 'system'
): Promise<SyncCalendarResult> {
const syncStartTime = Date.now();
try {
// Check if auto-sync is enabled for background/scheduled syncs
if (syncType !== 'manual') {
const autoSyncEnabled = await isAutoSyncEnabled(payload, availabilityId);
if (!autoSyncEnabled) {
await logSyncEvent(payload, {
availabilityId,
syncType,
platform: 'system',
success: false,
error: 'Auto-sync disabled for this property',
details: 'Skipping sync - auto-sync not enabled',
triggeredBy,
duration: Date.now() - syncStartTime,
});
return {
success: false,
syncedBookings: 0,
errors: 0,
message: 'Auto-sync disabled for this property',
};
}
}
// Get availability record
const availability = await payload.findByID({
collection: 'availability',
id: availabilityId,
depth: 1,
});
if (!availability || !availability.syncSettings?.externalCalendars) {
await logSyncEvent(payload, {
availabilityId,
syncType,
platform: 'system',
success: false,
error: 'No external calendars configured',
details: 'Property has no external calendar URLs configured',
triggeredBy,
duration: Date.now() - syncStartTime,
});
return {
success: false,
syncedBookings: 0,
errors: 1,
message: 'No external calendars configured',
};
}
const externalBookings: any[] = [];
const syncResults: any[] = [];
let totalSyncedBookings = 0;
let totalErrors = 0;
// Process each external calendar
for (const calendar of availability.syncSettings.externalCalendars) {
if (!calendar.enabled || !calendar.icalUrl) {
continue;
}
const calendarSyncStart = Date.now();
const platformName = calendar.platformName || calendar.platform;
try {
console.log(
`🔄 Syncing calendar from ${platformName}: ${calendar.icalUrl}`
);
// Fetch iCal data with timeout
const icalData = await fetchICalData(calendar.icalUrl);
// Parse events
const events = parseICalData(icalData);
let calendarBookingsCount = 0;
// Convert events to external bookings
for (const event of events) {
// Skip past events (older than 1 day ago to handle timezone issues)
const oneDayAgo = new Date();
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
if (new Date(event.dtend) < oneDayAgo) {
continue;
}
externalBookings.push({
platform: platformName,
bookingId: event.uid || `${calendar.platform}-${Date.now()}`,
startDate: event.dtstart.toISOString(),
endDate: event.dtend.toISOString(),
guestName: event.summary || 'External Booking',
lastUpdated: new Date().toISOString(),
});
calendarBookingsCount++;
}
// Log successful calendar sync
await logSyncEvent(payload, {
availabilityId,
syncType,
platform: platformName,
success: true,
syncedBookings: calendarBookingsCount,
details: `Successfully synced ${calendarBookingsCount} bookings from ${platformName}`,
triggeredBy,
duration: Date.now() - calendarSyncStart,
});
syncResults.push({
platform: platformName,
success: true,
syncedBookings: calendarBookingsCount,
});
totalSyncedBookings += calendarBookingsCount;
// Update individual calendar sync status
calendar.lastSync = new Date().toISOString();
calendar.lastStatus = 'success';
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Unknown sync error';
console.error(`❌ Error syncing calendar ${platformName}:`, error);
// Log failed calendar sync
await logSyncEvent(payload, {
availabilityId,
syncType,
platform: platformName,
success: false,
error: errorMessage,
details: `Failed to sync calendar from ${platformName}: ${errorMessage}`,
triggeredBy,
duration: Date.now() - calendarSyncStart,
});
syncResults.push({
platform: platformName,
success: false,
error: errorMessage,
});
totalErrors++;
calendar.lastStatus = 'failed';
}
}
// Update availability record with new bookings
const allSuccessful = totalErrors === 0;
await payload.update({
collection: 'availability',
id: availabilityId,
data: {
name: availability.propertyName || 'Unknown Property',
externalBookings,
syncSettings: {
...availability.syncSettings,
externalCalendars: availability.syncSettings.externalCalendars,
lastSync: new Date().toISOString(),
lastSyncStatus: allSuccessful ? 'success' : 'failed',
},
},
});
return {
success: allSuccessful,
syncedBookings: totalSyncedBookings,
errors: totalErrors,
message: allSuccessful
? `Successfully synced ${totalSyncedBookings} bookings from ${syncResults.length} calendars`
: `Sync completed with ${totalErrors} errors`,
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Unknown error';
await logSyncEvent(payload, {
availabilityId,
syncType,
platform: 'system',
success: false,
error: errorMessage,
details: `Global sync error: ${errorMessage}`,
triggeredBy,
duration: Date.now() - syncStartTime,
});
return {
success: false,
syncedBookings: 0,
errors: 1,
message: errorMessage,
};
}
}
Background Processing with Vercel Cron
The platform utilizes Vercel cron jobs for automated operations:
{
"crons": [
{
"path": "/api/cron/ical-sync",
"schedule": "*/15 * * * *"
}
]
}
// Background Sync Endpoint - src/app/api/ical-sync/background/route.ts
interface SyncJobResult {
jobId: string;
timestamp: number;
totalProperties: number;
successfulSyncs: number;
failedSyncs: number;
totalBookingsProcessed: number;
executionTimeMs: number;
results: Array<{
propertyId: string;
propertyName: string;
availabilityId: string;
success: boolean;
syncedBookings: number;
errors: number;
lastSync: string;
}>;
globalErrors: string[];
}
/**
* Execute background sync for all active properties using enhanced sync library
*/
async function executeBackgroundSync(
forceSync: boolean = false,
maxProperties: number = 30
): Promise<SyncJobResult> {
const startTime = Date.now();
const jobId = `bg_sync_${startTime}_${Math.random().toString(36).substr(2, 6)}`;
const result: SyncJobResult = {
jobId,
timestamp: startTime,
totalProperties: 0,
successfulSyncs: 0,
failedSyncs: 0,
totalBookingsProcessed: 0,
executionTimeMs: 0,
results: [],
globalErrors: [],
};
try {
const payload = await getPayload({ config: configPromise });
// Get properties that need syncing using the enhanced library
const syncIntervalMinutes = forceSync ? 0 : 30; // Force sync ignores time interval
const availabilityIds = await getPropertiesForSync(
payload,
syncIntervalMinutes,
maxProperties
);
result.totalProperties = availabilityIds.length;
if (ENV.debugCalendar) {
console.log(
`🔄 Starting background sync for ${result.totalProperties} properties (forceSync: ${forceSync})`
);
}
if (availabilityIds.length === 0) {
if (ENV.debugCalendar) {
console.log('ℹ️ No properties need syncing at this time');
}
result.executionTimeMs = Date.now() - startTime;
return result;
}
// Use the enhanced batch sync function
const batchResult = await syncMultipleProperties(
payload,
availabilityIds,
'background',
jobId,
3 // Max 3 concurrent syncs
);
// Convert batch result to SyncJobResult format
result.successfulSyncs = batchResult.successfulSyncs;
result.failedSyncs = batchResult.failedSyncs;
result.totalBookingsProcessed = batchResult.totalBookings;
// Convert results format
result.results = batchResult.results.map((r) => ({
propertyId: r.availabilityId,
propertyName: r.propertyName,
availabilityId: r.availabilityId,
success: r.success,
syncedBookings: r.syncedBookings,
errors: r.errors,
lastSync: new Date().toISOString(),
}));
// Collect error messages
result.globalErrors = batchResult.results
.filter((r) => !r.success && r.message)
.map((r) => `${r.propertyName}: ${r.message}`);
result.executionTimeMs = Date.now() - startTime;
// Log sync summary
if (ENV.debugCalendar) {
console.log(`✅ Background sync completed:`, {
jobId: result.jobId,
duration: `${result.executionTimeMs}ms`,
successful: result.successfulSyncs,
failed: result.failedSyncs,
totalBookings: result.totalBookingsProcessed,
});
}
// Store sync job result for monitoring
await storeSyncJobResult(result, payload);
return result;
} catch (error) {
result.executionTimeMs = Date.now() - startTime;
result.globalErrors.push(
`Global sync error: ${error instanceof Error ? error.message : 'Unknown error'}`
);
console.error('❌ Background sync failed:', error);
return result;
}
}
This sophisticated system enables:
- Automated external calendar synchronization on a Vercel cron schedule (every 15 minutes via
/api/cron/ical-sync, forwarding to the background sync worker) - Integrated calendar validation with real-time auto-correction
- Smart scheduling based on property sync frequency settings
- Individual calendar processing with error isolation per property
- Concurrent processing with rate limiting (max 3 concurrent syncs)
- Comprehensive error handling with retry logic and exponential backoff
- Validation monitoring with divergence detection and alerts
- Detailed monitoring and logging for all sync and validation operations
- Conflict detection and resolution for double bookings
- Performance optimization with timeout handling and request limiting
- Enhanced database schema with validation status tracking
- Payload-local persistence so listings, availability, inquiries, and post-sync cache tags all reflect the same MongoDB documents the admin UI edits
Performance Optimization
The platform achieves exceptional performance through:
- Advanced Caching: Multi-layer cache with LRU eviction
- Image Optimization: Automatic resizing and WebP conversion
- Code Splitting: Dynamic imports for booking components
- Server-Side Rendering: SEO optimization for German market
- Core Web Vitals: All metrics within excellent targets
Current Performance Scores:
- Performance: 92-95/100 (Lighthouse)
- Accessibility: 95-98/100
- Best Practices: 100/100
- SEO: 100/100
⭐ NEW: Enhanced Mobile Booking Experience
The mobile booking system provides an exceptional user experience with advanced features:
// Enhanced Mobile Booking Bar Component
const MobileBookingBar = ({ property, bookingData }) => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const handleScroll = () => {
setIsVisible(window.scrollY > 400);
};
window.addEventListener('scroll', handleScroll, { passive: true });
handleScroll(); // Initial check
return () => window.removeEventListener('scroll', handleScroll);
}, []);
if (!isVisible) return null;
return (
<div className="fixed bottom-0 left-0 right-0 bg-white border-t shadow-lg sm:hidden">
<div className="flex items-center justify-between p-4">
<PropertyInfo property={property} />
<BookingSummary data={bookingData} />
<ActionButton onClick={openModal} />
</div>
</div>
);
};
This implementation provides:
- Scroll-based visibility with 400px threshold for non-intrusive experience
- Full-screen modal calendar with complete booking functionality
- Reset functionality for clearing calendar selections and guest data
- Image placeholder system with consistent fallbacks for all property images
- Comprehensive scroll prevention for focused booking experience
- Touch-optimized interactions for mobile devices
- Performance optimizations with conditional rendering and efficient state management