Last active
October 6, 2017 22:53
-
-
Save lucas-pelton/256e2c435a4d623d2670d1e672192bcb to your computer and use it in GitHub Desktop.
FullSlate front end API WP plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// http://jillix.github.io/jQuery-sidebar/ | |
(function($){$.fn.sidebar=function(options){var self=this;if(self.length>1){return self.each(function(){$(this).sidebar(options)})}var width=self.outerWidth();var height=self.outerHeight();var settings=$.extend({speed:200,side:"left",isClosed:false,close:true},options);self.on("sidebar:open",function(ev,data){var properties={};properties[settings.side]=0;settings.isClosed=null;self.stop().animate(properties,$.extend({},settings,data).speed,function(){settings.isClosed=false;self.trigger("sidebar:opened")})});self.on("sidebar:close",function(ev,data){var properties={};if(settings.side==="left"||settings.side==="right"){properties[settings.side]=-self.outerWidth()}else{properties[settings.side]=-self.outerHeight()}settings.isClosed=null;self.stop().animate(properties,$.extend({},settings,data).speed,function(){settings.isClosed=true;self.trigger("sidebar:closed")})});self.on("sidebar:toggle",function(ev,data){if(settings.isClosed){self.trigger("sidebar:open",[data])}else{self.trigger("sidebar:close",[data])}});function closeWithNoAnimation(){self.trigger("sidebar:close",[{speed:0}])}if(!settings.isClosed&&settings.close){closeWithNoAnimation()}$(window).on("resize",function(){if(!settings.isClosed){return}closeWithNoAnimation()});self.data("sidebar",settings);return self};$.fn.sidebar.version="3.3.2"})(jQuery); | |
(function($){ | |
$(".sidebar.right", '.off-canvas').sidebar({side: "right"}); | |
if (window.location.hash) { | |
var user = JSON.parse(decodeURIComponent(window.location.hash).slice(1)); | |
for (var key in user) { | |
jQuery('#'+key,"#customer-info").val(user[key]); | |
} | |
} | |
//SELECT SERVICE | |
$('.toggler').on('click',function(e){ | |
$(".sidebar.right", '.off-canvas').trigger("sidebar:toggle"); | |
}); | |
$(document).keyup(function(e) { | |
if (e.keyCode == 27) { // escape key maps to keycode `27` | |
$(".sidebar.right", '.off-canvas').trigger("sidebar:close"); | |
} | |
}); | |
$('a[href*="fullslate.com"]', '#booking-sidebar').add('li', '#booking-sidebar').on('click',function(e){ | |
e.stopPropagation(); | |
e.preventDefault(); | |
if ($(this).hasClass('selected') || $(this).closest('li').hasClass('selected')) | |
{ | |
$('li:not(.selected)','#booking-services').slideDown(); | |
$('.selected','#booking-services').removeClass('selected'); | |
$('#response, #response-wrapper span').empty(); | |
$('#show-more, #response, #customer-info', '#booking-form').slideUp(); | |
} | |
else { | |
if ($(this).is('a')) { | |
var theService=$(this).attr('href').split('/').slice(-1); | |
// hide non-selected services | |
$(this).closest('li').add($(this).closest('ul').closest('li')).addClass('selected'); | |
$('#booking-services').find('li:not(.selected)').slideUp(); | |
// hide user form, in case they've already had a chance to open user form | |
$('label[class*="show-"]:not(.show-'+theService+'), #book-now, #customer-info, #show-more, #response ul').hide(); | |
$('#response-wrapper span').html($(this).attr('title')); | |
// set form field value for ajax post | |
$('#service', '#booking-form').val(theService); | |
// fetch times for selected service | |
$('#show-soonest').click(); | |
} | |
} | |
}); | |
// NAVIGATE WEEKS | |
$('#show-soonest, #show-more').click(function(e,isAutoClick){ | |
var $this = $(this), | |
ajaxurl = '/wp-admin/admin-ajax.php', | |
data = { | |
action: 'fullslate', | |
request: 'openings', | |
service: $('#service','#booking-form').val() | |
}; | |
$('#response-wrapper').addClass('ajax'); | |
if ("show-more" == $this.attr('id')) data.after=sundayMorning; | |
if ("show-more" == $this.attr('id') && !isAutoClick) data.slideDown=true | |
jQuery.post(ajaxurl, data, function(response){ | |
var responseObj=JSON.parse(response), | |
sunday; | |
errorState=0, | |
theService=$('#service', '#booking-form').val(); | |
if (responseObj.success!==true) { | |
/* Error handling ****** | |
noResponse("FullSlate returned failure") | |
set errorState = 1 (for use by booking to know if we're sending a live request) | |
return | |
Live times unavailable now. What time works for you? | |
<input type="date" id="service-date" name="service-date" /> | |
Retry for live times | |
************************/ | |
console.log(responseObj); | |
} | |
var times=responseObj.openings; | |
$('#at', '#booking-form').val(''); //clear the hidden date field that is used to store selected appointment time | |
if (0 === times.length) sunday = comingSunday(new Date(),0); //get the first Sunday after today | |
else sunday = comingSunday(calenDate(times[times.length-1]),0); // get the first Sunday after the last available time | |
sundayMorning = new Date(sunday.setHours(0,0,1)); | |
sundayMorning = sundayMorning.toISOString().replace(/[\-\.:]/g,"").replace(/\d{3}Z/g,"Z"); | |
if (0 === times.length) { $('#show-more').trigger('click',[true]); return;} | |
$('#response-wrapper').removeClass('ajax'); | |
if ($('#response ul').length) $('#response ul').slideUp('fast',function(theTimes=times){buildTimesUI(theTimes);}); | |
else buildTimesUI(times); | |
true === data.slideDown ? $('#show-soonest').slideDown('fast') : $('#show-soonest').slideUp('fast'); | |
$('#show-more, #response, .show-'+theService, '#booking-form').slideDown(); | |
}).fail(function(){ | |
alert('The request for openings was not able to be made'); | |
/* Error handling ****** | |
set errorState = 1 (for use by booking to know if we're sending a live request) | |
return | |
Live times unavailable now. What time works for you? | |
<input type="date" id="service-date" name="service-date" /> | |
Retry for live times | |
************************/ | |
}); | |
}); | |
// TOGGLE OPEN DAY | |
$('#response').on('click','.day',function(e) { | |
e.stopPropagation(); | |
var $this=$(this); | |
if ($this.hasClass('selected')) { | |
$('h3 span',$this).remove(); | |
$('li.selected',this).add($this).removeClass('selected'); | |
$this.siblings().add('#show-more').slideDown('fast'); | |
} | |
$('li',this).slideToggle('fast'); | |
}); | |
// SELECT TIME | |
$('#response').on('click','.day li',function(e){ | |
e.stopPropagation(); | |
var $this=$(this); | |
$('#at', '#booking-form').val($('span',this).attr('data-at')); | |
$('li, ul','#response').removeClass('selected'); | |
$this.add($this.parent()).addClass('selected'); | |
$('#response .day.selected h3').append('<span> @'+$this.html()+'</span>'); | |
$('#response').find('li,ul:not(.selected)').add('#show-more').slideUp(); | |
$('#book-now, #customer-info').slideDown('fast'); | |
}); | |
// BOOK APPOINTMENT | |
$('#book-now').click(function(){ | |
var now=Date.now(); | |
var data = { | |
action: 'fullslate', | |
request: 'book', | |
at: $('#at','#booking-form').val(), | |
service: $('#service','#booking-form').val(), | |
first_name: $('#first_name','#booking-form').val(), | |
last_name: $('#last_name','#booking-form').val(), | |
email: $('#email','#booking-form').val(), | |
phone_number: $('#phone_number','#booking-form').val() | |
}, | |
ajaxurl = '/wp-admin/admin-ajax.php'; | |
if ($('.more-info, label.show-'+data.service,'#booking-form').length) { | |
data['custom-notes'] = ""; | |
$('.more-info, label.show-'+data.service,'#booking-form').each(function(){ | |
var $input=$('input',this); | |
data['custom-notes']+=$input.attr('name').replace(/_/g," ")+": "+$input.val()+"|"; | |
}); | |
} | |
if (0 === errorState){ // have we set a local error anywhere along the way? | |
/*jQuery.post(ajaxurl, data, function(response){ | |
var responseObj=JSON.parse(response); | |
if (true === responseObj.failure ) console.log(responseObj); //handle error | |
});*/ | |
//console.log (data); | |
jQuery.post('https://hook.integromat.com/lslfodxrp0kbt7kxyek66bd8ceqk2q8n', data, function(response){ | |
console.log(response); | |
console.log((Date.now()-now)/1000 + " Sec."); | |
var responseObj=JSON.parse(response); | |
console.log(responseObj); //handle error | |
}); | |
} | |
else {}//send someone an email saying appointment request api endpoint broken | |
}); | |
function buildTimesUI (times){ | |
$('#response').empty(); | |
var adjustedTimes=[], | |
i=0; | |
times.forEach(function(time){ | |
var openingTime=calenDate(time); | |
openingTime.setHours(openingTime.getHours() - openingTime.getTimezoneOffset() / 60); | |
adjustedTimes.push(openingTime.format('fullSlateDateTime')); | |
}); | |
var openingsObj = groupByArray(adjustedTimes,function(o){return o.substring(0,8);}); | |
openingsObj.forEach(function(day){ | |
var titleDate=calenDate(day.key+"T000000Z"); | |
$('#response').append('<ul class="day"><h3>'+titleDate.format("ddd. mmm dd")+'</h3></ul>'); | |
var $today=$('.day','#response').last(); | |
day.values.forEach(function(time){ | |
var localTime=calenDate(time), | |
fsTime=calenDate(time); | |
fsTime.setHours(fsTime.getHours() + fsTime.getTimezoneOffset() / 60); | |
$today.append('<li><span data-at="'+fsTime.format("fullSlateDateTime")+'">'+localTime.format("shortTime")+'</span></li>'); | |
}); | |
}); | |
$('#response ul').slideDown('slow'); | |
} | |
function groupByArray(xs, key) { | |
return xs.reduce(function (rv, x) { | |
let v = key instanceof Function ? key(x) : x[key]; | |
let el = rv.find((r) => r && r.key === v); | |
if (el) { | |
el.values.push(x); | |
} | |
else { | |
rv.push({ | |
key: v, | |
values: [x] | |
}); | |
} | |
return rv; | |
}, []); | |
} | |
function comingSunday(d, dow){ | |
d.setDate(d.getDate() + (dow+(7-d.getDay())) % 7); | |
return d; | |
} | |
function calenDate(icalStr) { | |
var strYear = icalStr.substr(0,4); | |
var strMonth = parseInt(icalStr.substr(4,2),10)-1; | |
var strDay = icalStr.substr(6,2); | |
var strHour = icalStr.substr(9,2); | |
var strMin = icalStr.substr(11,2); | |
var myDate = new Date(strYear,strMonth, strDay, strHour, strMin); | |
return myDate; | |
} | |
/* | |
* Date Format 1.2.3 | |
* (c) 2007-2009 Steven Levithan <stevenlevithan.com> | |
* MIT license | |
* | |
* Includes enhancements by Scott Trenda <scott.trenda.net> | |
* and Kris Kowal <cixar.com/~kris.kowal/> | |
* | |
* Accepts a date, a mask, or a date and a mask. | |
* Returns a formatted version of the given date. | |
* The date defaults to the current date/time. | |
* The mask defaults to dateFormat.masks.default. | |
*/ | |
var dateFormat = function () { | |
var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, | |
timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, | |
timezoneClip = /[^-+\dA-Z]/g, | |
pad = function (val, len) { | |
val = String(val); | |
len = len || 2; | |
while (val.length < len) val = "0" + val; | |
return val; | |
}; | |
// Regexes and supporting functions are cached through closure | |
return function (date, mask, utc) { | |
var dF = dateFormat; | |
// You can't provide utc if you skip other args (use the "UTC:" mask prefix) | |
if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { | |
mask = date; | |
date = undefined; | |
} | |
// Passing date through Date applies Date.parse, if necessary | |
date = date ? new Date(date) : new Date; | |
if (isNaN(date)) throw SyntaxError("invalid date"); | |
mask = String(dF.masks[mask] || mask || dF.masks["default"]); | |
// Allow setting the utc argument via the mask | |
if (mask.slice(0, 4) == "UTC:") { | |
mask = mask.slice(4); | |
utc = true; | |
} | |
var _ = utc ? "getUTC" : "get", | |
d = date[_ + "Date"](), | |
D = date[_ + "Day"](), | |
m = date[_ + "Month"](), | |
y = date[_ + "FullYear"](), | |
H = date[_ + "Hours"](), | |
M = date[_ + "Minutes"](), | |
s = date[_ + "Seconds"](), | |
L = date[_ + "Milliseconds"](), | |
o = utc ? 0 : date.getTimezoneOffset(), | |
flags = { | |
d: d, | |
dd: pad(d), | |
ddd: dF.i18n.dayNames[D], | |
dddd: dF.i18n.dayNames[D + 7], | |
m: m + 1, | |
mm: pad(m + 1), | |
mmm: dF.i18n.monthNames[m], | |
mmmm: dF.i18n.monthNames[m + 12], | |
yy: String(y).slice(2), | |
yyyy: y, | |
h: H % 12 || 12, | |
hh: pad(H % 12 || 12), | |
H: H, | |
HH: pad(H), | |
M: M, | |
MM: pad(M), | |
s: s, | |
ss: pad(s), | |
l: pad(L, 3), | |
L: pad(L > 99 ? Math.round(L / 10) : L), | |
t: H < 12 ? "a" : "p", | |
tt: H < 12 ? "am" : "pm", | |
T: H < 12 ? "A" : "P", | |
TT: H < 12 ? "AM" : "PM", | |
Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), | |
o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), | |
S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] | |
}; | |
return mask.replace(token, function ($0) { | |
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); | |
}); | |
}; | |
}(); | |
// Some common format strings | |
dateFormat.masks = { | |
"default": "ddd mmm dd yyyy HH:MM:ss", | |
shortDate: "m/d/yy", | |
mediumDate: "mmm d, yyyy", | |
longDate: "mmmm d, yyyy", | |
fullDate: "dddd, mmmm d, yyyy", | |
shortTime: "h:MM TT", | |
mediumTime: "h:MM:ss TT", | |
longTime: "h:MM:ss TT Z", | |
isoDate: "yyyy-mm-dd", | |
isoTime: "HH:MM:ss", | |
isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", | |
isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'", | |
fullSlateDateTime: "yyyymmdd'T'HHMMss'Z'" | |
}; | |
// Internationalization strings | |
dateFormat.i18n = { | |
dayNames: [ | |
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", | |
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" | |
], | |
monthNames: [ | |
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", | |
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" | |
] | |
}; | |
// For convenience... | |
Date.prototype.format = function (mask, utc) { | |
return dateFormat(this, mask, utc); | |
}; | |
})(jQuery); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* The plugin bootstrap file | |
* | |
* This file is read by WordPress to generate the plugin information in the plugin | |
* admin area. This file also includes all of the dependencies used by the plugin, | |
* registers the activation and deactivation functions, and defines a function | |
* that starts the plugin. | |
* | |
* @link https://lucasbalzer.com | |
* @since 1.0.0 | |
* @package Wp_Full_Slate | |
* | |
* @wordpress-plugin | |
* Plugin Name: Full Slate | |
* Plugin URI: https://lucasbalzer.com | |
* Description: This is a short description of what the plugin does. It's displayed in the WordPress admin area. | |
* Version: 1.0.0 | |
* Author: Lucas Balzer | |
* Author URI: https://lucasbalzer.com | |
* License: GPL-2.0+ | |
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt | |
* Text Domain: full-slate | |
* Domain Path: /languages | |
*/ | |
/** | |
* Send a POST requst using cURL | |
* @param string $url to request | |
* @param array $post values to send | |
* @param array $options for cURL | |
* @return string | |
*/ | |
function fs_post($url, array $post = NULL, array $options = array()) | |
{ | |
$defaults = array( | |
CURLOPT_POST => 1, | |
CURLOPT_HEADER => 0, | |
CURLOPT_URL => $url, | |
CURLOPT_FRESH_CONNECT => 1, | |
CURLOPT_RETURNTRANSFER => 1, | |
CURLOPT_FORBID_REUSE => 1, | |
CURLOPT_TIMEOUT => 4, | |
CURLOPT_POSTFIELDS => http_build_query($post) | |
); | |
$ch = curl_init(); | |
curl_setopt_array($ch, ($options + $defaults)); | |
if( ! $result = curl_exec($ch)) | |
{ | |
trigger_error(curl_error($ch)); | |
} | |
curl_close($ch); | |
return $result; | |
} | |
function get_openings() { | |
$params=array( | |
'service'=>$_POST['service'], | |
'window' => 'week' | |
); | |
if(isset($_POST['after'])) $params['after'] = $_POST['after']; | |
echo fs_post("https://XXX.fullslate.com/api/openings",$params); | |
} | |
function book () { | |
$params=array( | |
'at'=>$_POST['at'], | |
'service'=>$_POST['service'], | |
'first_name'=>$_POST['first_name'], | |
'last_name'=>$_POST['last_name'], | |
'email'=>$_POST['email'], | |
'phone_number'=>$_POST['phone_number']/*, | |
'custom-notes' => $_POST['custom-notes']*/ | |
); | |
$result=fs_post("https://XXX.fullslate.com/api/bookings/",$params); | |
// JSON Parse $result to check for success/failure | |
// if (success) ping webhook endpoint with data | |
echo $result; | |
} | |
function fullslate(){ | |
switch ($_POST['request']) { | |
case 'openings': get_openings(); break; | |
case 'book': book(); break; | |
} | |
wp_die(); | |
} | |
add_action('wp_ajax_fullslate', 'fullslate' ); // executed when logged in | |
add_action('wp_ajax_nopriv_fullslate', 'fullslate' ); // executed when logged out | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment