Changeset 3448790
- Timestamp:
- 01/28/2026 02:33:42 PM (2 months ago)
- Location:
- creavi-booking-service
- Files:
-
- 34 added
- 7 edited
-
tags/1.1.6 (added)
-
tags/1.1.6/assets (added)
-
tags/1.1.6/assets/css (added)
-
tags/1.1.6/assets/css/admin.css (added)
-
tags/1.1.6/assets/css/creavibc-deactivation-feedback.css (added)
-
tags/1.1.6/assets/css/style.css (added)
-
tags/1.1.6/assets/images (added)
-
tags/1.1.6/assets/images/service_placeholder.png (added)
-
tags/1.1.6/assets/js (added)
-
tags/1.1.6/assets/js/admin.js (added)
-
tags/1.1.6/assets/js/booking.js (added)
-
tags/1.1.6/assets/js/cbs-gcal-busy-admin.js (added)
-
tags/1.1.6/assets/js/creavibc-deactivation-feedback.js (added)
-
tags/1.1.6/assets/vendor (added)
-
tags/1.1.6/assets/vendor/flatpickr (added)
-
tags/1.1.6/assets/vendor/flatpickr/flatpickr.min.css (added)
-
tags/1.1.6/assets/vendor/flatpickr/flatpickr.min.js (added)
-
tags/1.1.6/assets/vendor/luxon (added)
-
tags/1.1.6/assets/vendor/luxon/luxon.min.js (added)
-
tags/1.1.6/creavi-booking-service.php (added)
-
tags/1.1.6/includes (added)
-
tags/1.1.6/includes/admin.php (added)
-
tags/1.1.6/includes/ajax-handlers.php (added)
-
tags/1.1.6/includes/cbs-gcal-remote.php (added)
-
tags/1.1.6/includes/deactivation-feedback.php (added)
-
tags/1.1.6/includes/functions.php (added)
-
tags/1.1.6/includes/gcal-freebusy.php (added)
-
tags/1.1.6/includes/meta-boxes.php (added)
-
tags/1.1.6/includes/placeholders.php (added)
-
tags/1.1.6/includes/post-types.php (added)
-
tags/1.1.6/includes/reminders.php (added)
-
tags/1.1.6/includes/render-booking-inline.php (added)
-
tags/1.1.6/includes/save-service.php (added)
-
tags/1.1.6/readme.txt (added)
-
trunk/assets/js/booking.js (modified) (6 diffs)
-
trunk/creavi-booking-service.php (modified) (2 diffs)
-
trunk/includes/functions.php (modified) (2 diffs)
-
trunk/includes/meta-boxes.php (modified) (4 diffs)
-
trunk/includes/render-booking-inline.php (modified) (2 diffs)
-
trunk/includes/save-service.php (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
creavi-booking-service/trunk/assets/js/booking.js
r3445660 r3448790 276 276 } 277 277 278 // -----------------------------278 // ----------------------------- 279 279 // Dynamic availability helpers 280 280 // ----------------------------- … … 331 331 } 332 332 333 function creavibcGetLeadMinutes(instance) { 334 const v = parseInt(instance?.MIN_TIME_BEFORE_BOOKING_MIN || 0, 10); 335 return Number.isFinite(v) ? Math.max(0, v) : 0; 336 } 337 338 function creavibcGetPickerZone(instance, userTz) { 339 const tzMode = instance?.TIMEZONE_MODE || 'localized'; 340 const adminTz = instance?.ADMIN_TIMEZONE || 'UTC'; 341 return (tzMode === 'locked') ? adminTz : userTz; 342 } 343 333 344 334 345 function initInlineCalendar(serviceId) { … … 341 352 const instance = window.CREAVIBC_INSTANCES?.[serviceId]; 342 353 if (!instance) return; 354 355 356 const userTz = window.CREAVIBC_USER_TIMEZONE || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; 357 const { DateTime } = luxon; 358 359 const leadMin = creavibcGetLeadMinutes(instance); 360 const pickerZone = creavibcGetPickerZone(instance, userTz); 361 362 // Earliest selectable day (based on lead time) 363 const earliestISO = DateTime.now().setZone(pickerZone).plus({ minutes: leadMin }).toISODate(); 364 365 366 343 367 344 368 // Sort AVAILABLE_DATES and prefer the first date >= today … … 361 385 dates = dates.slice().sort(); 362 386 363 const todayStr = new Date().toISOString().slice(0, 10); 364 const firstEnabled = dates.find(d => d >= todayStr) || dates[0] || null; 365 366 387 388 //const todayStr = new Date().toISOString().slice(0, 10); 389 //const firstEnabled = dates.find(d => d >= todayStr) || dates[0] || null; 390 391 const firstEnabled = dates.find(d => d >= earliestISO) || dates[0] || null; 367 392 368 393 const fp = flatpickr(calendarEl, { 369 394 inline: true, 370 minDate: "today", 395 //minDate: "today", 396 minDate: earliestISO, 371 397 dateFormat: "Y-m-d", 372 398 enable: dates, … … 440 466 } 441 467 442 /* 443 function renderTimeSlotsForDate(dateStr, serviceId, popup) { 444 const container = popup.querySelector('.creavibc-time-slots'); 445 446 // Smooth clearing (fade old out) 447 container.classList.add('fade-out'); 448 setTimeout(() => { 449 container.innerHTML = ''; 450 container.classList.remove('fade-out'); 451 452 const instance = window.CREAVIBC_INSTANCES?.[serviceId]; 453 if (!instance) return; 454 455 const weekday = new Date(dateStr).toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase(); 456 const slots = instance.WEEKDAY_SLOTS?.[weekday] || []; 457 const duration = parseInt(instance.SERVICE_DURATION || 30, 10); 458 const isLocalized = instance.TIMEZONE_MODE === 'localized'; 459 460 461 const { DateTime } = luxon; 462 463 const userTz = window.CREAVIBC_USER_TIMEZONE || 'UTC'; 464 const adminTz = instance.ADMIN_TIMEZONE || 'UTC'; 465 466 const tzMode = instance.TIMEZONE_MODE || 'localized'; 467 468 // "Now" should be compared in the SAME timezone as slotStart. 469 const now = (tzMode === 'locked') 470 ? DateTime.now().setZone(adminTz) 471 : DateTime.now().setZone(userTz); 472 473 // Identify "today" in the same comparison zone 474 const todayISO = now.toISODate(); 475 476 477 478 if (!slots.length) { 479 container.innerHTML = '<p>No slots available for this date.</p>'; 480 return; 481 } 482 483 fetchBookedSlots(dateStr, serviceId).then(bookedSlots => { 484 slots.sort().forEach((start, i) => { 485 486 // Build slot start in the comparison timezone 487 let slotStart; 488 489 if (tzMode === 'locked') { 490 // Slot times are provider/admin timezone 491 slotStart = DateTime.fromISO(`${dateStr}T${start}`, { zone: adminTz }); 492 } else { 493 // Slot times are displayed/treated as user's timezone 494 // We must interpret the selected date as user-date and compare in userTz. 495 // BUT your slot list "start" is based on admin times. So we convert admin->user for comparison. 496 const adminStart = DateTime.fromISO(`${dateStr}T${start}`, { zone: adminTz }); 497 slotStart = adminStart.setZone(userTz); 498 } 499 500 // Is this slot in the past (only matters for same-day) 501 const isPast = (slotStart.toISODate() === todayISO) && (slotStart < now); 502 503 504 let displayStart = start; 505 let displayEnd = calculateEnd(start, duration); 506 507 if (isLocalized && window.luxon) { 508 const { DateTime } = luxon; 509 const adminZoned = DateTime.fromISO(`${dateStr}T${start}`, { zone: instance.ADMIN_TIMEZONE }); 510 const userZonedStart = adminZoned.setZone(window.CREAVIBC_USER_TIMEZONE); 511 const userZonedEnd = userZonedStart.plus({ minutes: duration }); 512 513 displayStart = userZonedStart.toFormat('HH:mm'); 514 displayEnd = userZonedEnd.toFormat('HH:mm'); 515 } 516 517 const btn = document.createElement('button'); 518 btn.type = 'button'; 519 btn.className = 'creavibc-slot-btn'; 520 btn.textContent = displayStart; 521 btn.dataset.time = start; 522 523 btn.dataset.displayStart = displayStart; 524 btn.dataset.displayEnd = displayEnd; 525 526 if (bookedSlots.includes(start)) { 527 btn.classList.add('creavibc-slot-disabled'); 528 btn.disabled = true; 529 } else { 530 btn.addEventListener('click', () => { 531 popup.querySelectorAll('.creavibc-slot-btn').forEach(b => b.classList.remove('selected')); 532 btn.classList.add('selected'); 533 534 const tzMode = instance.TIMEZONE_MODE || 'localized'; 535 const adminTimezone = instance.ADMIN_TIMEZONE; 536 const userTimezone = window.CREAVIBC_USER_TIMEZONE; 537 const showTz = (tzMode === 'locked') ? adminTimezone : userTimezone; 538 popup.querySelector('.creavibc-summary-time-text').innerHTML = `${displayStart} – ${displayEnd} (${showTz})`; 539 540 popup.querySelector('.creavibc-summary-time').classList.remove('time-hidden'); 541 validateNextButton(serviceId); 542 }); 543 } 544 545 container.appendChild(btn); 546 547 // Animate each with stagger 548 setTimeout(() => btn.classList.add('show'), i * 20); 549 }); 550 551 552 // Auto-select the first enabled slot (after fade-out/stagger are done) 553 // Auto-select the first enabled slot (if any) 554 setTimeout(() => { 555 const firstEnabled = container.querySelector('.creavibc-slot-btn:not(.creavibc-slot-disabled)'); 556 if (firstEnabled) { 557 firstEnabled.click(); // triggers your existing selection logic + validates Next 558 } 559 }, 100); 560 561 562 563 564 565 }); 566 }, 200); // allow fade-out delay 567 } 568 */ 569 570 468 /* 571 469 function renderTimeSlotsForDate(dateStr, serviceId, popup) { 572 470 const container = popup.querySelector('.creavibc-time-slots'); … … 689 587 690 588 }, 200); // allow fade-out delay 691 } 589 } */ 590 591 function renderTimeSlotsForDate(dateStr, serviceId, popup) { 592 const container = popup.querySelector('.creavibc-time-slots'); 593 594 // Smooth clearing (fade old out) 595 container.classList.add('fade-out'); 596 setTimeout(() => { 597 container.innerHTML = ''; 598 container.classList.remove('fade-out'); 599 600 const sid = String(serviceId); 601 const instance = window.CREAVIBC_INSTANCES?.[sid]; 602 if (!instance || !window.luxon) return; 603 604 const { DateTime } = luxon; 605 606 // Safer weekday derivation (matches keys: monday, tuesday...) 607 const weekday = DateTime.fromISO(dateStr).toFormat('cccc').toLowerCase(); 608 609 const slots = (instance.WEEKDAY_SLOTS?.[weekday] || []).slice(); 610 const duration = parseInt(instance.SERVICE_DURATION || 30, 10); 611 const tzMode = instance.TIMEZONE_MODE || 'localized'; 612 const isLocalized = tzMode === 'localized'; 613 614 const userTz = 615 window.CREAVIBC_USER_TIMEZONE || 616 Intl.DateTimeFormat().resolvedOptions().timeZone || 617 'UTC'; 618 619 const adminTz = instance.ADMIN_TIMEZONE || 'UTC'; 620 621 // Compare in the correct "now" timezone context 622 const now = (tzMode === 'locked') 623 ? DateTime.now().setZone(adminTz) 624 : DateTime.now().setZone(userTz); 625 626 // Lead time cutoff (minutes) 627 const leadMinRaw = parseInt(instance.MIN_TIME_BEFORE_BOOKING_MIN || 0, 10); 628 const leadMin = Number.isFinite(leadMinRaw) ? Math.max(0, leadMinRaw) : 0; 629 const cutoff = now.plus({ minutes: leadMin }); 630 631 if (!slots.length) { 632 container.innerHTML = '<p>No slots available for this date.</p>'; 633 return; 634 } 635 636 fetchBookedSlots(dateStr, serviceId).then(bookedSlots => { 637 const booked = Array.isArray(bookedSlots) ? bookedSlots : []; 638 639 slots.sort().forEach((start, i) => { 640 // Build slot start in correct timezone for comparison 641 let slotStart; 642 643 if (tzMode === 'locked') { 644 // Slot times are provider/admin timezone 645 slotStart = DateTime.fromISO(`${dateStr}T${start}`, { zone: adminTz }); 646 } else { 647 // Slot list is based on admin timezone; compare in user timezone 648 const adminStart = DateTime.fromISO(`${dateStr}T${start}`, { zone: adminTz }); 649 slotStart = adminStart.setZone(userTz); 650 } 651 652 // Disable slots earlier than "now + lead time" 653 const isBeforeCutoff = slotStart < cutoff; 654 655 // Display values 656 let displayStart = start; 657 let displayEnd = calculateEnd(start, duration); 658 659 // Localized display: show in user's timezone 660 if (isLocalized) { 661 const adminZoned = DateTime.fromISO(`${dateStr}T${start}`, { zone: adminTz }); 662 const userZonedStart = adminZoned.setZone(userTz); 663 const userZonedEnd = userZonedStart.plus({ minutes: duration }); 664 665 displayStart = userZonedStart.toFormat('HH:mm'); 666 displayEnd = userZonedEnd.toFormat('HH:mm'); 667 } 668 669 const btn = document.createElement('button'); 670 btn.type = 'button'; 671 btn.className = 'creavibc-slot-btn'; 672 btn.textContent = displayStart; 673 btn.dataset.time = start; 674 675 btn.dataset.displayStart = displayStart; 676 btn.dataset.displayEnd = displayEnd; 677 678 const isBooked = booked.includes(start); 679 680 if (isBooked || isBeforeCutoff) { 681 btn.classList.add('creavibc-slot-disabled'); 682 btn.disabled = true; 683 684 // Optional tooltip: 685 // if (isBeforeCutoff) btn.title = 'This time is not available yet'; 686 } else { 687 btn.addEventListener('click', () => { 688 popup.querySelectorAll('.creavibc-slot-btn').forEach(b => b.classList.remove('selected')); 689 btn.classList.add('selected'); 690 691 const showTz = (tzMode === 'locked') ? adminTz : userTz; 692 693 popup.querySelector('.creavibc-summary-time-text').innerHTML = 694 `${displayStart} – ${displayEnd} (${showTz})`; 695 696 popup.querySelector('.creavibc-summary-time').classList.remove('time-hidden'); 697 validateNextButton(serviceId); 698 }); 699 } 700 701 container.appendChild(btn); 702 703 // Animate each with stagger 704 setTimeout(() => btn.classList.add('show'), i * 20); 705 }); 706 707 // Auto-select the first enabled slot (if any) 708 setTimeout(() => { 709 const firstEnabled = container.querySelector('.creavibc-slot-btn:not(.creavibc-slot-disabled)'); 710 if (firstEnabled) { 711 firstEnabled.click(); 712 } 713 }, 100); 714 }); 715 716 }, 200); // allow fade-out delay 717 } 718 692 719 693 720 -
creavi-booking-service/trunk/creavi-booking-service.php
r3445660 r3448790 1 1 <?php 2 2 /** 3 * Plugin Name: Booking Calendar4 * Description: A simple service booking system with popup UI.3 * Plugin Name: Appointment Booking Calendar 4 * Description: Easy appointment booking system. Create services, manage availability, and accept bookings with a simple booking calendar. 5 5 * Text Domain: creavi-booking-service 6 * Version: 1.1. 56 * Version: 1.1.6 7 7 * Author: Creavi 8 8 * License: GPL2 … … 15 15 define('CREAVIBC_PLUGIN_URL', plugin_dir_url(__FILE__)); 16 16 define('CREAVIBC_PLUGIN_PATH', plugin_dir_path(__FILE__)); 17 define('CREAVIBC_VERSION', '1.1. 5');17 define('CREAVIBC_VERSION', '1.1.6'); 18 18 19 19 require_once CREAVIBC_PLUGIN_DIR . 'includes/deactivation-feedback.php'; -
creavi-booking-service/trunk/includes/functions.php
r3445660 r3448790 81 81 $thankyou_text = __( "Thank you for booking\nSee you soon!", 'creavi-booking-service' ); 82 82 } 83 84 $min_time_value = (int) get_post_meta( $post->ID, '_creavibc_min_time_value', true ); 85 $min_time_value = max(0, $min_time_value); 86 87 $min_time_unit = (string) get_post_meta( $post->ID, '_creavibc_min_time_unit', true ); 88 $min_time_unit = in_array($min_time_unit, ['minutes','hours','days'], true) ? $min_time_unit : 'hours'; 89 90 // Convert to minutes for JS (easy to calculate) 91 $min_time_minutes = $min_time_value; 92 if ($min_time_unit === 'hours') { 93 $min_time_minutes = $min_time_value * 60; 94 } elseif ($min_time_unit === 'days') { 95 $min_time_minutes = $min_time_value * 1440; 96 } 97 83 98 84 99 … … 98 113 'MONTHS_AHEAD' => $months_ahead, // 1..12 99 114 'EXCLUDED_DATES' => $excluded, // "YYYY-MM-DD,YYYY-MM-DD" 115 'MIN_TIME_BEFORE_BOOKING_MIN' => (int) $min_time_minutes, 116 100 117 'GCAL_BLOCK_LIVE' => $gcal_block_live ? 1 : 0, 101 118 ]; -
creavi-booking-service/trunk/includes/meta-boxes.php
r3442505 r3448790 122 122 $months_ahead = max(1, min(12, $months_ahead ?: 1)); 123 123 124 // ✅Canonical excluded field name + meta key (do not change!)124 // Canonical excluded field name + meta key (do not change!) 125 125 $excluded_value = (string) get_post_meta($post->ID, '_creavibc_excluded_booking_days', true); 126 126 $excluded_array = array_filter(array_map('trim', explode(',', $excluded_value))); 127 127 $excluded_string = esc_attr(implode(',', $excluded_array)); 128 128 129 // Minimum time before booking (value + unit) 130 $min_time_value = (int) get_post_meta( $post->ID, '_creavibc_min_time_value', true ); 131 $min_time_value = $min_time_value >= 0 ? $min_time_value : 0; 132 133 $min_time_unit = (string) get_post_meta( $post->ID, '_creavibc_min_time_unit', true ); 134 $min_time_unit = in_array( $min_time_unit, ['minutes','hours','days'], true ) ? $min_time_unit : 'hours'; 135 136 129 137 wp_nonce_field('creavibc_save_days', 'creavibc_days_nonce'); 130 138 131 139 $is_dynamic = ($mode === 'dynamic'); 132 140 ?> 141 142 <div class="creavibc-field"> 143 <label><strong><?php esc_html_e('Minimum time before booking', 'creavi-booking-service'); ?></strong></label> 144 <div class="creavibc-help"> 145 <?php esc_html_e('Defines how long before the appointment booking is allowed.', 'creavi-booking-service'); ?> 146 </div> 147 148 <div style="display:flex; gap:8px; align-items:center; max-width:320px;"> 149 <input 150 type="number" 151 min="0" 152 step="1" 153 name="creavibc_min_time_value" 154 value="<?php echo esc_attr($min_time_value); ?>" 155 class="creavibc-input" 156 style="width:120px;" 157 > 158 <select 159 name="creavibc_min_time_unit" 160 class="creavibc-select" 161 style="width:160px;" 162 > 163 <option value="minutes" <?php selected($min_time_unit, 'minutes'); ?>><?php esc_html_e('Minutes', 'creavi-booking-service'); ?></option> 164 <option value="hours" <?php selected($min_time_unit, 'hours'); ?>><?php esc_html_e('Hours', 'creavi-booking-service'); ?></option> 165 <option value="days" <?php selected($min_time_unit, 'days'); ?>><?php esc_html_e('Days', 'creavi-booking-service'); ?></option> 166 </select> 167 </div> 168 </div> 169 133 170 134 171 <div class="creavibc-tabs" data-default="<?php echo esc_attr($mode); ?>"> … … 197 234 <div class="creavibc-help"><?php esc_html_e('Pick one or multiple dates to block. Stored as comma-separated YYYY-MM-DD.', 'creavi-booking-service'); ?></div> 198 235 199 <!-- ✅Keep the SAME ID + NAME so flatpickr init & POST save match -->236 <!-- Keep the SAME ID + NAME so flatpickr init & POST save match --> 200 237 <input 201 238 type="text" … … 689 726 if ( $is_connected ) { 690 727 691 // ✅ Best source of truth:Google userinfo (email behind the token)728 // Google userinfo (email behind the token) 692 729 $connected_email = ''; 693 730 … … 747 784 * NOTE: 748 785 * We pass the CURRENT WP user initiating OAuth (so Google consent is done by the admin who clicks Connect). 749 * Your backend stores wp_user_id for that user, then yourplugin maps it back to connection_id later.786 * backend stores wp_user_id for that user, then plugin maps it back to connection_id later. 750 787 */ 751 788 $connect_url = add_query_arg( -
creavi-booking-service/trunk/includes/render-booking-inline.php
r3445660 r3448790 65 65 } 66 66 67 $min_time_value = (int) get_post_meta( $post->ID, '_creavibc_min_time_value', true ); 68 $min_time_value = max(0, $min_time_value); 69 70 $min_time_unit = (string) get_post_meta( $post->ID, '_creavibc_min_time_unit', true ); 71 $min_time_unit = in_array($min_time_unit, ['minutes','hours','days'], true) ? $min_time_unit : 'hours'; 72 73 // Convert to minutes for JS (easy to calculate) 74 $min_time_minutes = $min_time_value; 75 if ($min_time_unit === 'hours') { 76 $min_time_minutes = $min_time_value * 60; 77 } elseif ($min_time_unit === 'days') { 78 $min_time_minutes = $min_time_value * 1440; 79 } 67 80 68 81 … … 82 95 'EXCLUDED_DATES' => $excluded, // "YYYY-MM-DD,YYYY-MM-DD" 83 96 'GCAL_BLOCK_LIVE' => $gcal_block_live ? 1 : 0, 97 98 'MIN_TIME_BEFORE_BOOKING_MIN' => (int) $min_time_minutes, 84 99 ]; 85 100 ?> -
creavi-booking-service/trunk/includes/save-service.php
r3441012 r3448790 53 53 // (Do nothing) 54 54 } 55 56 // 5) Minimum time before booking (value + unit) 57 if ( isset($_POST['creavibc_min_time_value'], $_POST['creavibc_min_time_unit']) ) { 58 59 $value = max( 0, (int) wp_unslash( $_POST['creavibc_min_time_value'] ) ); 60 $unit = sanitize_text_field( wp_unslash( $_POST['creavibc_min_time_unit'] ) ); 61 62 if ( ! in_array( $unit, ['minutes','hours','days'], true ) ) { 63 $unit = 'hours'; 64 } 65 66 update_post_meta( $post_id, '_creavibc_min_time_value', $value ); 67 update_post_meta( $post_id, '_creavibc_min_time_unit', $unit ); 68 } 69 55 70 } 56 71 -
creavi-booking-service/trunk/readme.txt
r3445660 r3448790 3 3 Tags: appointments, booking, booking calendar, bookings, scheduling 4 4 Requires at least: 6.0 5 Tested up to: 6. 85 Tested up to: 6.7 6 6 Requires PHP: 7.4 7 Stable tag: 1.1. 57 Stable tag: 1.1.6 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 16 16 **Appointment Booking Calendar** 17 17 18 **The #1 Booking Plugin for Your Website**19 18 Booking Calendar is the ultimate all-in-one plugin to add professional bookings and appointments directly to your WordPress website. 20 19 Built natively for WordPress, it’s designed to make online bookings simple, fast, and intuitive - both for you and your clients. … … 86 85 87 86 == Changelog == 87 88 = 1.1.6 = 89 * Added Minimum Time Before Booking option - per service lead-time setting. 88 90 89 91 = 1.1.5 =
Note: See TracChangeset
for help on using the changeset viewer.