diff --git a/application/controllers/Pages.php b/application/controllers/Pages.php index 3fed32f..82a0c43 100755 --- a/application/controllers/Pages.php +++ b/application/controllers/Pages.php @@ -276,6 +276,34 @@ class Pages extends CI_Controller { } + /** + * Render a guest-facing error page instead of raw JSON. The public booking + * form is a normal full-page POST, so whatever this outputs is what the + * customer actually sees in their browser. + */ + private function _booking_error($errorCode, $statusCode = 409){ + $this->load->helper('url'); + + $lang = isset($_POST['lang']) && in_array($_POST['lang'], array('no', 'en', 'hu')) ? $_POST['lang'] : 'en'; + $subpage = isset($_POST['subpage']) && $_POST['subpage'] != '' ? $_POST['subpage'] : 'barber'; + + $data = array( + 'pageTitle' => '', + 'selectedLang' => $lang, + 'subpage' => $subpage, + 'bookingErrorCode' => $errorCode + ); + + $this->output->set_status_header($statusCode); + + if($subpage == 'beauty'){ + $this->load->view('pages/beauty-booking-error', $data); + } + else{ + $this->load->view('pages/barber-booking-error', $data); + } + } + public function booking_process(){ $this->load->helper('url'); $this->load->model('User_model'); @@ -355,26 +383,17 @@ class Pages extends CI_Controller { $schedule = $this->Service_model->getWorkerScheduleByDay($bookingArray['worker_id'], $weekday); if (!$schedule) { - $this->output - ->set_status_header(403) - ->set_content_type('application/json') - ->set_output(json_encode(['error' => 'Worker is not available on this day.'])); + $this->_booking_error('worker_unavailable', 403); return; } if ($bookingTime < $schedule->start_time || $bookingTime >= $schedule->end_time) { - $this->output - ->set_status_header(403) - ->set_content_type('application/json') - ->set_output(json_encode(['error' => 'Booking time is outside worker schedule.'])); + $this->_booking_error('outside_schedule', 403); return; } if (!$this->Service_model->isWorkerAvailableThisWeek($bookingArray['worker_id'], $bookingDate)) { - $this->output - ->set_status_header(403) - ->set_content_type('application/json') - ->set_output(json_encode(['error' => 'Worker only works every second week.'])); + $this->_booking_error('alternate_week', 403); return; } @@ -386,12 +405,27 @@ class Pages extends CI_Controller { ); if (!is_array($available) || !in_array($bookingArray['booking_start_time'], $available)) { - $this->output - ->set_status_header(409) - ->set_content_type('application/json') - ->set_output(json_encode([ - 'error' => 'Conflict. Timeslot is taken or does not fit the service.' - ])); + // The guest may have submitted twice: the booking POST stays open for + // several seconds while Google Calendar and the confirmation e-mail + // run, so an impatient second tap arrives after the first already + // saved. If their own booking for this exact slot exists, that is a + // duplicate submit rather than a real conflict - finish normally. + // Guard on a non-empty e-mail: admin-created block bookings are stored + // with an empty guest_email, and must never be mistaken for the guest's + // own duplicate submit. + $guestEmail = trim($bookingArray['guest_email']); + $ownBooking = $guestEmail !== '' ? $this->Service_model->getBookingBySlotAndGuest( + $bookingArray['worker_id'], + $bookingArray['booking_date'], + $bookingArray['booking_start_time'], + $guestEmail + ) : false; + if($ownBooking){ + header('Location:'.SITEURL.$_POST['lang'].'/booking-finished/'.$_POST['subpage']); + return; + } + + $this->_booking_error('slot_taken', 409); return; } @@ -407,12 +441,7 @@ class Pages extends CI_Controller { $closingTime = new DateTime('18:00'); if ($bookingEnd > $closingTime) { - $this->output - ->set_status_header(403) - ->set_content_type('application/json') - ->set_output(json_encode([ - 'error' => 'Selected time exceeds business hours.' - ])); + $this->_booking_error('after_hours', 403); return; } @@ -421,12 +450,7 @@ class Pages extends CI_Controller { $selectedDate = new DateTime($bookingArray['booking_date']); if ($selectedDate > $maxDate) { - $this->output - ->set_status_header(403) - ->set_content_type('application/json') - ->set_output(json_encode([ - 'error' => 'Bookings can only be made up to 3 months in advance.' - ])); + $this->_booking_error('too_far', 403); return; } diff --git a/application/models/Service_model.php b/application/models/Service_model.php index 70b64e7..62127dc 100755 --- a/application/models/Service_model.php +++ b/application/models/Service_model.php @@ -596,6 +596,12 @@ class Service_model extends CI_Model { return $query->num_rows() > 0 ? $query->result()[0] : false; } + public function getBookingBySlotAndGuest($worker_id, $booking_date, $booking_start_time, $guest_email){ + $this->load->database(); + $query = $this->db->query('SELECT * FROM bookings WHERE worker_id = ? AND booking_date = ? AND booking_start_time = ? AND guest_email = ? ORDER BY booking_id DESC LIMIT 1', array($worker_id, $booking_date, $booking_start_time, $guest_email)); + return $query->num_rows() > 0 ? $query->row() : false; + } + public function updateBookingCalEvents($booking_id, $worker_event_id, $owner_event_id){ $this->load->database(); $this->db->query("UPDATE bookings SET gcal_event_id_worker = ?, gcal_event_id_owner = ? WHERE booking_id = ?", array($worker_event_id, $owner_event_id, $booking_id)); diff --git a/application/views/pages/barber-booking-error.php b/application/views/pages/barber-booking-error.php new file mode 100755 index 0000000..96211ef --- /dev/null +++ b/application/views/pages/barber-booking-error.php @@ -0,0 +1,12 @@ + diff --git a/application/views/pages/beauty-booking-error.php b/application/views/pages/beauty-booking-error.php new file mode 100755 index 0000000..ca0abfe --- /dev/null +++ b/application/views/pages/beauty-booking-error.php @@ -0,0 +1,12 @@ + diff --git a/application/views/pages/includes/booking-error-form-en.php b/application/views/pages/includes/booking-error-form-en.php new file mode 100755 index 0000000..f64d4fe --- /dev/null +++ b/application/views/pages/includes/booking-error-form-en.php @@ -0,0 +1,25 @@ + 'This time slot was taken while you were filling in the form. Please choose another time.', + 'worker_unavailable' => 'The selected staff member is not working on this day. Please choose another day.', + 'outside_schedule' => 'The selected time is outside the staff member\'s working hours. Please choose another time.', + 'alternate_week' => 'The selected staff member only works every second week. Please choose another day.', + 'after_hours' => 'The treatment would not finish before closing time. Please choose an earlier time.', + 'too_far' => 'Bookings can only be made up to 3 months in advance.', + 'default' => 'Something went wrong with your booking. Please try again.', + ); + + $shownError = isset($bookingErrorCode) && isset($bookingErrorMessages[$bookingErrorCode]) + ? $bookingErrorMessages[$bookingErrorCode] + : $bookingErrorMessages['default']; + + $backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber'; + $backUrl = SITEURL.'en/booking/'.$backSubpage; +?> +