AI Booking Assistant

Experience the future of appointment booking with our intelligent chatbot

Back to Booking Options

Booking Assistant Online

Hello! I'm your AI booking assistant. I can help you schedule appointments, check availability, and answer questions about our services. How can I assist you today?
// Floating chat widget functionality const chatToggle = document.getElementById('chatToggle'); const chatContainer = document.getElementById('chatContainer'); const closeChat = document.getElementById('closeChat'); const chatMessages2 = document.getElementById('chatMessages2'); const userInput2 = document.getElementById('userInput2'); const sendMessage2 = document.getElementById('sendMessage2'); const chatInputContainer2 = document.getElementById('chatInputContainer2'); // Booking options for the floating widget const aiOption = document.getElementById('aiOption'); const formOption = document.getElementById('formOption'); const whatsappOption = document.getElementById('whatsappOption'); // Booking state for the floating widget let bookingState2 = { step: 0, // 0: initial, 1: service, 2: date, 3: time, 4: details, 5: confirmation service: '', date: '', time: '', name: '', phone: '', email: '' }; // Services data for the floating widget - get from the PHP data let services2 = []; // Available time slots for the floating widget const timeSlots2 = [ '09:00', '09:30', '10:00', '10:30', '11:00', '11:30', '12:00', '12:30', '13:00', '13:30', '14:00', '14:30', '15:00', '15:30', '16:00', '16:30', '17:00' ]; // Initialize floating chat widget function initFloatingChat() { // Initialize services array from the PHP data (same as main chat) services2 = JSON.parse(JSON.stringify(services)); // Copy from main services // Toggle chat window chatToggle.addEventListener('click', function() { chatContainer.style.display = 'block'; }); closeChat.addEventListener('click', function() { chatContainer.style.display = 'none'; }); // Booking option event listeners aiOption.addEventListener('click', function() { // Hide the booking options, show the AI chat interface document.querySelector('.booking-options').style.display = 'none'; chatMessages2.style.display = 'flex'; chatInputContainer2.style.display = 'flex'; addUserMessage2('I want to use the AI Booking Assistant'); startAIChat2(); }); formOption.addEventListener('click', function() { addUserMessage2('I want to use the Traditional Booking Form'); window.location.href = 'booking.php'; }); whatsappOption.addEventListener('click', function() { addUserMessage2('I want to book via WhatsApp'); window.open('https://wa.me/27812451296?text=Hi%2C%20I%20would%20like%20to%20book%20an%20appointment', '_blank'); }); // Send message on button click sendMessage2.addEventListener('click', sendUserMessage2); // Send message on Enter key userInput2.addEventListener('keypress', function(e) { if (e.key === 'Enter') { sendUserMessage2(); } }); } // Show form message for floating widget function showFormMessage2() { addBotMessage2("Great! I'll direct you to our booking form. Please fill out the required information to schedule your appointment."); } // Start AI chat flow for floating widget function startAIChat2() { bookingState2.step = 1; addBotMessage2("Great! Let's book your appointment. What service are you interested in?"); showServices2(); } // Send message from floating chatbot function sendUserMessage2() { const message = userInput2.value.trim(); if (message) { addUserMessage2(message); processMessage2(message); userInput2.value = ''; } } // Add user message to floating chat function addUserMessage2(message) { const messageElement = document.createElement('div'); messageElement.className = 'message user-message'; messageElement.textContent = message; chatMessages2.appendChild(messageElement); chatMessages2.scrollTop = chatMessages2.scrollHeight; } // Add bot message to floating chat function addBotMessage2(message, options = null) { const messageElement = document.createElement('div'); messageElement.className = 'message bot-message'; messageElement.innerHTML = message; if (options) { const quickRepliesDiv = document.createElement('div'); quickRepliesDiv.className = 'quick-replies'; options.forEach(option => { const button = document.createElement('button'); button.className = 'quick-reply'; button.textContent = option.text; button.setAttribute('data-message', option.value); button.addEventListener('click', function() { addUserMessage2(option.value); processMessage2(option.value); }); quickRepliesDiv.appendChild(button); }); messageElement.appendChild(quickRepliesDiv); } chatMessages2.appendChild(messageElement); chatMessages2.scrollTop = chatMessages2.scrollHeight; } // Process message in floating chat function processMessage2(message) { // Convert to lowercase for easier processing const lowerMessage = message.toLowerCase(); // Reset booking if user wants to start over if (lowerMessage.includes('start over') || lowerMessage.includes('new booking')) { bookingState2 = { step: 0, service: '', date: '', time: '', name: '', phone: '', email: '' }; } // Booking flow logic if (bookingState2.step === 1) { // Service selection - match with actual services from the form const selectedService = services2.find(service => message.toLowerCase().includes(service.name.toLowerCase()) || service.name.toLowerCase().includes(message.toLowerCase()) ); if (selectedService) { bookingState2.service = selectedService.name; bookingState2.step = 2; showDateSelection2(); } else { // Show available services if user's selection doesn't match addBotMessage2("I didn't quite catch that. Please select one of our services:", [ { text: "Manicure", value: "Manicure" }, { text: "Pedicure", value: "Pedicure" }, { text: "Nail Art", value: "Nail Art" }, { text: "Acrylic Nails", value: "Acrylic Nails" } ]); } } else if (bookingState2.step === 2) { // Date selection if (lowerMessage.includes('today')) { const today = new Date(); bookingState2.date = formatDate2(today); bookingState2.step = 3; showTimeSelection2(); } else if (lowerMessage.includes('tomorrow')) { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); bookingState2.date = formatDate2(tomorrow); bookingState2.step = 3; showTimeSelection2(); } else if (lowerMessage.includes('next week')) { // Show next week calendar instead of jumping to time slots showNextWeekCalendar2(); } else if (lowerMessage.includes('choose a date')) { showCalendar2(); } else { // Check if it's a valid date format (DD/MM) if (isValidDateFormat(message)) { bookingState2.date = message + '/' + new Date().getFullYear(); bookingState2.step = 3; showTimeSelection2(); } else { // Handle date from calendar (format should be DD/MM/YYYY) // Parse the date to ensure it's in the right format bookingState2.date = message; bookingState2.step = 3; showTimeSelection2(); } } } else if (bookingState2.step === 3) { // Time selection if (timeSlots2.includes(message)) { bookingState2.time = message; bookingState2.step = 4; askForDetails2(); } else { addBotMessage2("Please select an available time slot:", [ { text: "09:00", value: "09:00" }, { text: "10:00", value: "10:00" }, { text: "11:00", value: "11:00" }, { text: "12:00", value: "12:00" } ]); } } else if (bookingState2.step === 4) { // Collecting details - First check if it's phone/email for existing customer lookup if (!bookingState2.phone && !bookingState2.email) { // Check if the message is a phone number or email const isPhone = /^\d{9,}$/.test(message.replace(/\D/g, '')); // At least 9 digits const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(message); if (isPhone) { bookingState2.phone = message; // Check if this is an existing customer checkExistingCustomer(message, null).then(existingCustomer => { if (existingCustomer) { // Pre-fill known details for existing customer bookingState2.name = existingCustomer.name; bookingState2.email = existingCustomer.email; addBotMessage2(`Welcome back, ${existingCustomer.name}! I have your details on file. Your booking for ${bookingState2.service} on ${bookingState2.date} at ${bookingState2.time} is almost complete. Would you like to confirm this booking?`, [ { text: "Yes, confirm", value: "Yes, confirm" }, { text: "No, update details", value: "Update details" } ]); bookingState2.step = 5; // Skip to confirmation for existing customer } else { addBotMessage2("Great! What's your full name?"); } }).catch(error => { console.error('Error checking existing customer:', error); addBotMessage2("Great! What's your full name?"); }); } else if (isEmail) { bookingState2.email = message; // Check if this is an existing customer checkExistingCustomer(null, message).then(existingCustomer => { if (existingCustomer) { // Pre-fill known details for existing customer bookingState2.name = existingCustomer.name; bookingState2.phone = existingCustomer.phone; addBotMessage2(`Welcome back, ${existingCustomer.name}! I have your details on file. Your booking for ${bookingState2.service} on ${bookingState2.date} at ${bookingState2.time} is almost complete. Would you like to confirm this booking?`, [ { text: "Yes, confirm", value: "Yes, confirm" }, { text: "No, update details", value: "Update details" } ]); bookingState2.step = 5; // Skip to confirmation for existing customer } else { addBotMessage2("Great! What's your full name?"); } }).catch(error => { console.error('Error checking existing customer:', error); addBotMessage2("Great! What's your full name?"); }); } else { // Not a phone or email, treat as name bookingState2.name = message; addBotMessage2("Thanks! What's your phone number?"); } } else if (!bookingState2.name) { bookingState2.name = message; addBotMessage2("Great! What's your phone number?"); } else if (!bookingState2.phone) { bookingState2.phone = message; addBotMessage2("Almost done! What's your email address?"); } else if (!bookingState2.email) { bookingState2.email = message; // Store customer for future visits const newCustomer = { name: bookingState2.name, phone: bookingState2.phone, email: bookingState2.email }; storeCustomer(newCustomer); bookingState2.step = 5; showConfirmation2(); } } else if (bookingState2.step === 5) { // Confirmation if (lowerMessage.includes('yes') || lowerMessage.includes('confirm')) { completeBooking2(); } else if (lowerMessage.includes('no') || lowerMessage.includes('cancel')) { addBotMessage2("Booking cancelled. Would you like to start a new booking?", [ { text: "Yes, new booking", value: "New booking" }, { text: "No, thanks", value: "No thanks" } ]); bookingState2 = { step: 0, service: '', date: '', time: '', name: '', phone: '', email: '' }; } } } // Show services in floating chat function showServices2() { let servicesHTML = "We offer these services:
"; services2.forEach(service => { servicesHTML += ``; }); servicesHTML += "
"; addBotMessage2(servicesHTML); // Add event listeners to service options document.querySelectorAll('.service-option').forEach(option => { option.addEventListener('click', function() { const service = this.getAttribute('data-service'); addUserMessage2(service); processMessage2(service); }); }); } // Show date selection for floating widget - using calendar approach function showDateSelection2() { const now = new Date(); const currentHour = now.getHours(); const currentMinute = now.getMinutes(); // Consider the last booking time of the day (15:00 = 15 hours) const lastBookingHour = 15; const today = new Date(); const dates = []; // Check if it's still possible to book today (before 15:00) if (currentHour < lastBookingHour) { // Include today in the list of available dates dates.push(new Date(today)); } // Add the next 7 days for (let i = 1; i <= 7; i++) { const date = new Date(); date.setDate(today.getDate() + i); dates.push(date); } let calendarHTML = `

Select a Date

Sun
Mon
Tue
Wed
Thu
Fri
Sat
`; // Add empty cells for days before the first date const firstDate = dates[0]; const firstDay = firstDate.getDay(); for (let i = 0; i < firstDay; i++) { calendarHTML += `
`; } // Add date cells dates.forEach(date => { const day = date.getDate(); const month = date.getMonth() + 1; const year = date.getFullYear(); const formattedDate = `${day}/${month}/${year}`; // Check if this is today and disable it if too late in the day const isToday = date.toDateString() === today.toDateString(); const canBookToday = currentHour < lastBookingHour; if (isToday && !canBookToday) { calendarHTML += `
${day}
`; } else { calendarHTML += `
${day}
`; } }); calendarHTML += `
`; // Format today's date to match the calendar format (DD/MM/YYYY) const todayDate = new Date(); const todayFormatted = `${todayDate.getDate()}/${todayDate.getMonth() + 1}/${todayDate.getFullYear()}`; // First send the message without the calendar to introduce the date selection addBotMessage2(`Great! You've selected ${bookingState2.service}. When would you like to come in?`); // Then add the calendar HTML directly without putting it inside the bot message text setTimeout(() => { // Create a message element specifically for the calendar const calendarElement = document.createElement('div'); calendarElement.className = 'message bot-message'; calendarElement.innerHTML = calendarHTML; chatMessages2.appendChild(calendarElement); chatMessages2.scrollTop = chatMessages2.scrollHeight; // Add quick replies separately to avoid conflicts with calendar const quickRepliesDiv = document.createElement('div'); quickRepliesDiv.className = 'quick-replies'; const todayOption = document.createElement('button'); todayOption.className = 'quick-reply'; todayOption.textContent = "Today"; todayOption.setAttribute('data-message', todayFormatted); todayOption.addEventListener('click', function() { addUserMessage2(todayFormatted); processMessage2(todayFormatted); }); quickRepliesDiv.appendChild(todayOption); const tomorrowOption = document.createElement('button'); tomorrowOption.className = 'quick-reply'; tomorrowOption.textContent = "Tomorrow"; tomorrowOption.setAttribute('data-message', "Tomorrow"); tomorrowOption.addEventListener('click', function() { addUserMessage2("Tomorrow"); processMessage2("Tomorrow"); }); quickRepliesDiv.appendChild(tomorrowOption); const nextWeekOption = document.createElement('button'); nextWeekOption.className = 'quick-reply'; nextWeekOption.textContent = "Next Week"; nextWeekOption.setAttribute('data-message', "Next week"); nextWeekOption.addEventListener('click', function() { addUserMessage2("Next week"); processMessage2("Next week"); }); quickRepliesDiv.appendChild(nextWeekOption); chatMessages2.appendChild(quickRepliesDiv); chatMessages2.scrollTop = chatMessages2.scrollHeight; }, 100); // Small delay to ensure message is added first // Add event listeners to date options after DOM update setTimeout(() => { document.querySelectorAll('.calendar-date-custom.floating-date:not(.disabled)').forEach(date => { date.addEventListener('click', function() { const selectedDate = this.getAttribute('data-date'); // Add visual feedback this.style.backgroundColor = '#D81B60'; this.style.color = 'white'; this.style.borderColor = '#D81B60'; addUserMessage2(selectedDate); processMessage2(selectedDate); }); }); }, 10); } // Show time selection for floating widget function showTimeSelection2() { let timeHTML = `
`; timeSlots2.forEach(time => { timeHTML += ``; }); timeHTML += `
`; addBotMessage2(`Perfect! We have these time slots available on ${bookingState2.date}: ${timeHTML}`); // Add event listeners to time slots setTimeout(() => { document.querySelectorAll('.floating-time-slot').forEach(slot => { slot.addEventListener('click', function() { const time = this.getAttribute('data-time'); // Add visual feedback document.querySelectorAll('.floating-time-slot').forEach(s => { s.style.backgroundColor = ''; s.style.color = ''; s.style.borderColor = ''; }); this.style.backgroundColor = '#D81B60'; this.style.color = 'white'; this.style.borderColor = '#D81B60'; addUserMessage2(time); processMessage2(time); }); }); }, 10); } // Ask for customer details for floating widget function askForDetails2() { // Check if user might be an existing customer by asking for phone/email first addBotMessage2(`Excellent! Your ${bookingState2.service} is scheduled for ${bookingState2.date} at ${bookingState2.time}. To complete your booking, I'll need your contact details. What's your phone number or email address? (If you've booked with us before, we'll pre-fill your details)`); } // Show confirmation for floating widget function showConfirmation2() { const service = services2.find(s => s.name === bookingState2.service); const total = service ? service.price : 0; const summaryHTML = `
Service: ${bookingState2.service}
Date & Time: ${bookingState2.date} at ${bookingState2.time}
Name: ${bookingState2.name}
Phone: ${bookingState2.phone}
Email: ${bookingState2.email}
Total: R${total}
`; addBotMessage2(`Please confirm your booking details: ${summaryHTML}`, [ { text: "Yes, confirm booking", value: "Yes, confirm booking" }, { text: "No, cancel", value: "Cancel booking" } ]); } // Complete booking for floating widget - similar to main chat but using floating state async function completeBooking2() { // Find the service ID from our services array const service = services2.find(s => s.name === bookingState2.service); if (!service) { addBotMessage2("Error: Could not find the selected service in our system. Please try again."); return; } // Store customer for future visits const newCustomer = { name: bookingState2.name, phone: bookingState2.phone, email: bookingState2.email }; await storeCustomer(newCustomer); // Format date for database (expected format: YYYY-MM-DD) // bookingState2.date format is DD/MM/YYYY or DD/MM/YYYY, so we need to convert it let dbDate = bookingState2.date; if (bookingState2.date.includes('/')) { const dateParts = bookingState2.date.split('/'); if (dateParts.length === 3) { // Format is DD/MM/YYYY, convert to YYYY-MM-DD dbDate = `${dateParts[2]}-${String(dateParts[1]).padStart(2, '0')}-${String(dateParts[0]).padStart(2, '0')}`; } else if (dateParts.length === 2) { // Format might be DD/MM, assume current year const currentYear = new Date().getFullYear(); dbDate = `${currentYear}-${String(dateParts[1]).padStart(2, '0')}-${String(dateParts[0]).padStart(2, '0')}`; } } // Prepare booking data const bookingData = { customer_name: bookingState2.name, customer_email: bookingState2.email, customer_phone: bookingState2.phone, service_id: service.id, booking_date: dbDate, booking_time: bookingState2.time, notes: `Booking made via floating AI Assistant`, iso_date: dbDate, // Add ISO date for internal processing slots_used: Math.ceil(service.duration / 30) || 1, // Calculate time slots needed whatsapp: true, // Default to WhatsApp notification sms: false // Default to no SMS }; // Send booking to server try { const response = await fetch('admin/create_booking.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams(bookingData) }); const result = await response.json(); if (result.success) { const total = service ? service.price : 0; addBotMessage2(`
✅ Booking Request Made!

Booking Reference: BG${Math.floor(1000 + Math.random() * 9000)}
Service: ${bookingState2.service}
Date & Time: ${bookingState2.date} at ${bookingState2.time}
Total: R${total}

Next Steps: Please send proof of payment via WhatsApp to complete your booking. You will receive a confirmation WhatsApp message once payment is verified by our admin.
`); // After successful booking, provide option to book another appointment addBotMessage2("Would you like to book another appointment?", [ { text: "Book another appointment", value: "New booking" }, { text: "No, thanks", value: "No thanks" } ]); } else { addBotMessage2(`⚠️ There was an issue creating your booking: ${result.message || 'Unknown error'}. Please try again or contact us directly.`); } } catch (error) { console.error('Error creating booking:', error); addBotMessage2(`⚠️ There was an issue creating your booking. Please try again or contact us directly.`); } // Reset booking state regardless of success/failure bookingState2 = { step: 0, service: '', date: '', time: '', name: '', phone: '', email: '' }; } // Helper function to format date for floating widget function formatDate2(date) { const day = date.getDate(); const month = date.getMonth() + 1; const year = date.getFullYear(); return `${day}/${month}/${year}`; } // Calendar variables for floating widget let currentMonth2 = new Date().getMonth(); let currentYear2 = new Date().getFullYear(); // Show calendar with month navigation for floating widget function showCalendar2() { const calendarHTML = generateCalendar2(currentMonth2, currentYear2); addBotMessage2(`Please select a date for your ${bookingState2.service}: ${calendarHTML}`); // Add event listeners to calendar dates after DOM update setTimeout(() => { document.querySelectorAll('.calendar-date:not(.disabled)').forEach(date => { date.addEventListener('click', function() { const selectedDate = this.getAttribute('data-date'); addUserMessage2(selectedDate); bookingState2.date = selectedDate; bookingState2.step = 3; showTimeSelection2(); }); }); // Add event listeners to calendar navigation document.querySelectorAll('.calendar-nav').forEach(button => { button.addEventListener('click', function() { const direction = this.getAttribute('data-direction'); if (direction === 'prev') { currentMonth2--; if (currentMonth2 < 0) { currentMonth2 = 11; currentYear2--; } } else { currentMonth2++; if (currentMonth2 > 11) { currentMonth2 = 0; currentYear2++; } } showCalendar2(); }); }); }, 10); } // Show next week's calendar for floating widget function showNextWeekCalendar2() { // Set to next week's starting date const nextWeek = new Date(); nextWeek.setDate(nextWeek.getDate() + 7); currentMonth2 = nextWeek.getMonth(); currentYear2 = nextWeek.getFullYear(); showCalendar2(); } // Generate calendar HTML for floating widget function generateCalendar2(month, year) { const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const today = new Date(); let calendarHTML = `
${monthNames[month]} ${year}
Sun
Mon
Tue
Wed
Thu
Fri
Sat
`; // Add empty cells for days before the first day of the month for (let i = 0; i < firstDay.getDay(); i++) { calendarHTML += `
`; } // Add days of the month for (let day = 1; day <= lastDay.getDate(); day++) { const currentDate = new Date(year, month, day); const isToday = currentDate.toDateString() === today.toDateString(); const isPast = currentDate < today && !isToday; const dateClass = isToday ? 'calendar-date today' : isPast ? 'calendar-date disabled' : 'calendar-date'; calendarHTML += `
${day}
`; } calendarHTML += `
`; return calendarHTML; }

Booking Assistant Online

Hello! I'm your booking assistant. How would you like to book your appointment today?
AI Booking Assistant
Our intelligent chatbot guides you through the booking process with natural conversation.
Traditional Booking Form
Use our standard booking form for a straightforward appointment scheduling experience.
WhatsApp Booking
Message us directly on WhatsApp to book your appointment with our team.