diff --git a/app.js b/app.js index 5afdb46..35bebc5 100644 --- a/app.js +++ b/app.js @@ -25,7 +25,7 @@ var app = express() // session cookies app.use(cookieSession({ - name: 'mangrove-retreat-session', + name: 'mangrove-retreat-session', keys: _.filter([process.env.SECRET_KEY, 'M@ngR0ve']), maxAge: 30 * 24 * 3600 * 1000 // 30 days })) @@ -54,17 +54,17 @@ app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use('/stylesheets', sassMiddleware({ - src: __dirname + '/public/sass', - dest: __dirname + '/public/stylesheets', - debug: true, - outputStyle: 'expanded' + src: __dirname + '/public/sass', + dest: __dirname + '/public/stylesheets', + debug: true, + outputStyle: 'expanded' })) app.use('/stylesheets', postcssMiddleware({ - src: function(req) { - return path.join(__dirname, 'public', 'stylesheets', req.path) - }, - plugins: [autoprefixer()] + src: function(req) { + return path.join(__dirname, 'public', 'stylesheets', req.path) + }, + plugins: [autoprefixer()] })) app.use(express.static(path.join(__dirname, 'public'))) @@ -73,25 +73,25 @@ app.use('/', index) // catch 404 and forward to error handler app.use(function(req, res, next) { - var err = new Error('Not Found') - err.status = 404 - next(err) + var err = new Error('Not Found') + err.status = 404 + next(err) }) // error handler app.use(function(err, req, res, next) { - // set locals, only providing error in development - const isDev = req.app.get('env') === 'development' + // set locals, only providing error in development + const isDev = req.app.get('env') === 'development' - // render the error page - res.status(err.status || 500) - res.render('error', {error: isDev ? err : null}) + // render the error page + res.status(err.status || 500) + res.render('error', {error: isDev ? err : null}) }) // proper logging of UnhandledPromiseRejection process.on('unhandledRejection', function(reason, p) { - console.log("Possibly Unhandled Rejection at: Promise ", - p, " reason: ", reason, reason ? reason.message : null) + console.log("Possibly Unhandled Rejection at: Promise ", + p, " reason: ", reason, reason ? reason.message : null) }) module.exports = app diff --git a/helpers/airtable.js b/helpers/airtable.js index 11ae27c..b141e5d 100644 --- a/helpers/airtable.js +++ b/helpers/airtable.js @@ -2,8 +2,8 @@ var Airtable = require('airtable') var base = new Airtable({apiKey: process.env.AIRTABLE_API_KEY}).base(process.env.AIRTABLE_BASE_KEY) module.exports = { - retreat: base('Retreats'), - participants: base('Retreat Participants'), - faq: base('Retreat FAQ'), - members: base('Members') + retreat: base('Retreats'), + participants: base('Retreat Participants'), + faq: base('Retreat FAQ'), + members: base('Members') } diff --git a/helpers/bedsCounter.js b/helpers/bedsCounter.js index effed26..2b5c658 100644 --- a/helpers/bedsCounter.js +++ b/helpers/bedsCounter.js @@ -1,35 +1,35 @@ function haveDaysInCommon(days1, days2) { - for (var i = 0; i < days1.length; i++) { - var day1 = days1[i] - for (var j = 0; j < days2.length; j++) { - var day2 = days2[j] - if (day1.id === day2.id) { - return true - } - } - } - return false + for (var i = 0; i < days1.length; i++) { + var day1 = days1[i] + for (var j = 0; j < days2.length; j++) { + var day2 = days2[j] + if (day1.id === day2.id) { + return true + } + } + } + return false } function addBedsCountPerWeek(retreat, participants) { - for (var i = 0; i < retreat.weeks.length; i++) { - var bedsAvailable = retreat.house.beds - var week = retreat.weeks[i] + for (var i = 0; i < retreat.weeks.length; i++) { + var bedsAvailable = retreat.house.beds + var week = retreat.weeks[i] - // Remove a bed each time a participant is found in the week - for (var j = 0; j < participants.length; j++) { - var participant = participants[j] - if (haveDaysInCommon(week.days, participant.days)) { - bedsAvailable -= 1 - } - } + // Remove a bed each time a participant is found in the week + for (var j = 0; j < participants.length; j++) { + var participant = participants[j] + if (haveDaysInCommon(week.days, participant.days)) { + bedsAvailable -= 1 + } + } - // Add beds count to week - retreat.weeks[i].beds = bedsAvailable - } - return retreat + // Add beds count to week + retreat.weeks[i].beds = bedsAvailable + } + return retreat } module.exports = { - addBedsCountPerWeek: addBedsCountPerWeek + addBedsCountPerWeek: addBedsCountPerWeek } diff --git a/helpers/dateFormatter.js b/helpers/dateFormatter.js index 179e876..f034654 100644 --- a/helpers/dateFormatter.js +++ b/helpers/dateFormatter.js @@ -1,51 +1,51 @@ function formatDay(date) { - const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Nov', 'Dec'] - return { - id: months[date.getMonth()] + date.getDate(), - name: weekDays[date.getDay()], - number: date.getDate(), - month: months[date.getMonth()], - date: date - } + const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Nov', 'Dec'] + return { + id: months[date.getMonth()] + date.getDate(), + name: weekDays[date.getDay()], + number: date.getDate(), + month: months[date.getMonth()], + date: date + } } function formatWeeks(beginDate, endDate) { - var days = [] - var day = new Date(beginDate) - - while(day.getTime() < new Date(endDate).getTime()) { - days.push(formatDay(day)) - day = new Date(day.getTime() + 24 * 60 * 60 * 1000) - } - days.push(formatDay(new Date(endDate))) - - var weeks = [] - for (var i = 0; i < days.length; i++) { - if (i % 7 == 0) { - weeks.push({days: []}) - } - var weekIndex = parseInt(i / 7) - weeks[weekIndex].days.push(days[i]) - } - - return weeks + var days = [] + var day = new Date(beginDate) + + while(day.getTime() < new Date(endDate).getTime()) { + days.push(formatDay(day)) + day = new Date(day.getTime() + 24 * 60 * 60 * 1000) + } + days.push(formatDay(new Date(endDate))) + + var weeks = [] + for (var i = 0; i < days.length; i++) { + if (i % 7 == 0) { + weeks.push({days: []}) + } + var weekIndex = parseInt(i / 7) + weeks[weekIndex].days.push(days[i]) + } + + return weeks } function formatDays(beginDate, endDate) { - var days = [] - var day = new Date(beginDate) + var days = [] + var day = new Date(beginDate) - while(day.getTime() < new Date(endDate).getTime()) { - days.push(formatDay(day)) - day = new Date(day.getTime() + 24 * 60 * 60 * 1000) - } - days.push(formatDay(new Date(endDate))) + while(day.getTime() < new Date(endDate).getTime()) { + days.push(formatDay(day)) + day = new Date(day.getTime() + 24 * 60 * 60 * 1000) + } + days.push(formatDay(new Date(endDate))) - return days + return days } module.exports = { - formatDays: formatDays, - formatWeeks: formatWeeks + formatDays: formatDays, + formatWeeks: formatWeeks } diff --git a/mangrove_retreat.textClipping b/mangrove_retreat.textClipping deleted file mode 100644 index b3b5b90..0000000 Binary files a/mangrove_retreat.textClipping and /dev/null differ diff --git a/middleware/auth.js b/middleware/auth.js index f3bbde7..da78f2e 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -6,54 +6,54 @@ const airtable = require('../helpers/airtable.js') function checkCode(code) { - return new Promise(function (resolve, reject) { - request.get('https://slack.com/api/oauth.access?code=' + code + '&client_id=' + clientId - + '&client_secret=' + clientSecret + '&redirect_uri=' + slackRedirectUri, - function (error, response, body) { - if (!error && response.statusCode == 200) { - body = JSON.parse(body) - if (body.error) return reject(body.error) + return new Promise(function (resolve, reject) { + request.get('https://slack.com/api/oauth.access?code=' + code + '&client_id=' + clientId + + '&client_secret=' + clientSecret + '&redirect_uri=' + slackRedirectUri, + function (error, response, body) { + if (!error && response.statusCode == 200) { + body = JSON.parse(body) + if (body.error) return reject(body.error) // resolve with user AccessToken and Slack ID resolve({ slackId: body.user_id, accessToken: body.access_token, }) - } else { reject(error) } - }) - }) + } else { reject(error) } + }) + }) } // retrieves details about a Member from Airtable based on their Slack ID function getMemberBySlackId(slackId) { - return new Promise(function (resolve, reject) { - airtable.members.select({ + return new Promise(function (resolve, reject) { + airtable.members.select({ maxRecords: 10, - filterByFormula: `{Slack ID} = '${slackId}'`, - }).firstPage(function(err, records) { - if (err) { reject(err); return } - if (typeof records[0] !== 'undefined') { - resolve(formatMember(records[0])) - } else { - reject('No members found matching username : ' + username) - } - }) - }) + filterByFormula: `{Slack ID} = '${slackId}'`, + }).firstPage(function(err, records) { + if (err) { reject(err); return } + if (typeof records[0] !== 'undefined') { + resolve(formatMember(records[0])) + } else { + reject('No members found matching username : ' + username) + } + }) + }) - function formatMember(currentUser) { - const tw = currentUser.get('Twitter') && currentUser.get('Twitter').length ? currentUser.get('Twitter') : null - const img = tw ? 'https://twitter.com/' + tw + '/profile_image?size=original' : null + function formatMember(currentUser) { + const tw = currentUser.get('Twitter') && currentUser.get('Twitter').length ? currentUser.get('Twitter') : null + const img = tw ? 'https://twitter.com/' + tw + '/profile_image?size=original' : null - return { - id: currentUser.id, - name: currentUser.get('Name'), + return { + id: currentUser.id, + name: currentUser.get('Name'), slackId: currentUser.get('Slack ID'), - color: 'black', - avatar_url: img - } - } + color: 'black', + avatar_url: img + } + } } module.exports = { - checkCode: checkCode, - getMemberBySlackId: getMemberBySlackId -} + checkCode: checkCode, + getMemberBySlackId: getMemberBySlackId +} \ No newline at end of file diff --git a/middleware/charge.js b/middleware/charge.js index eebb7c4..e8990dd 100644 --- a/middleware/charge.js +++ b/middleware/charge.js @@ -5,36 +5,36 @@ const price = require('./price.js') const participants = require('./participants.js') function charge(memberAirtableId, params) { - return new Promise(function (resolve, reject) { - price.compute(params) - .then(function (result) { - if (result.canBook) { - participants.getParticipantByMemberId(memberAirtableId).then(function (participant) { - stripe.customers.create({ - email: params.email, - source: params.tokenId, - description: (`Member ID: ${memberAirtableId}`), - }).then(function (customer) { - const amountInCents = result.price * 100 - stripe.charges.create({ - amount: amountInCents, - description: result.description, - currency: "eur", - customer: customer.id - }).then(function (charge) { - participants.add(participant.id, params.retreatId, params.firstNight, params.lastNight).then(function () { - resolve() - }) - }) - }) - }, function (error) { reject(error) }) - } else { - reject('You cannot book this retreat for the desired dates. Week is either full or you chose custom dates.') - } - }, function (error) { reject(error) }) - }) + return new Promise(function (resolve, reject) { + price.compute(params) + .then(function (result) { + if (result.canBook) { + participants.getParticipantByMemberId(memberAirtableId).then(function (participant) { + stripe.customers.create({ + email: params.email, + source: params.tokenId, + description: (`Member ID: ${memberAirtableId}`), + }).then(function (customer) { + const amountInCents = result.price * 100 + stripe.charges.create({ + amount: amountInCents, + description: result.description, + currency: "eur", + customer: customer.id + }).then(function (charge) { + participants.add(participant.id, params.retreatId, params.firstNight, params.lastNight).then(function () { + resolve() + }) + }) + }) + }, function (error) { reject(error) }) + } else { + reject('You cannot book this retreat for the desired dates. Week is either full or you chose custom dates.') + } + }, function (error) { reject(error) }) + }) } module.exports = { - charge: charge + charge: charge } diff --git a/middleware/faq.js b/middleware/faq.js index b9db045..2b77398 100644 --- a/middleware/faq.js +++ b/middleware/faq.js @@ -1,43 +1,43 @@ var airtable = require('../helpers/airtable.js') function getFormattedFAQ(retreatId) { - return new Promise(function (resolve, reject) { - getFAQ(retreatId).then(function (faq) { - resolve(formatFAQ(faq)) - }) - }) + return new Promise(function (resolve, reject) { + getFAQ(retreatId).then(function (faq) { + resolve(formatFAQ(faq)) + }) + }) - function getFAQ(retreatId) { - return new Promise(function (resolve, reject) { - airtable.faq.select({ - filterByFormula: "{Retreat Id} = '" + retreatId + "'" - }).firstPage(function(err, records) { - if (err) return reject(err) + function getFAQ(retreatId) { + return new Promise(function (resolve, reject) { + airtable.faq.select({ + filterByFormula: "{Retreat Id} = '" + retreatId + "'" + }).firstPage(function(err, records) { + if (err) return reject(err) if (!records.length) return reject("missing FAQ") resolve(formatFAQ(records[0])) - }) - }) - } + }) + }) + } - function formatFAQ(faq) { - var formattedFAQ = [] + function formatFAQ(faq) { + var formattedFAQ = [] - const fields = faq.fields - for (var key in fields) { - if (fields.hasOwnProperty(key)) { - if (key !== 'Name' && key !== 'Retreat' && key !== 'Retreat Id') { - formattedFAQ.push({ - question: key, - answer: fields[key] - }) - } - } - } + const fields = faq.fields + for (var key in fields) { + if (fields.hasOwnProperty(key)) { + if (key !== 'Name' && key !== 'Retreat' && key !== 'Retreat Id') { + formattedFAQ.push({ + question: key, + answer: fields[key] + }) + } + } + } - return formattedFAQ - } + return formattedFAQ + } } module.exports = { - get: getFormattedFAQ + get: getFormattedFAQ } diff --git a/middleware/participants.js b/middleware/participants.js index 00bbaf5..6ae0525 100644 --- a/middleware/participants.js +++ b/middleware/participants.js @@ -4,177 +4,177 @@ var dateFormatter = require('../helpers/dateFormatter.js') var bedsCounter = require('../helpers/bedsCounter.js') function getParticipants(retreatId) { - return new Promise(function (resolve, reject) { - var participants = [] - - airtable.participants.select({ - filterByFormula: "{Retreat Id} = '" + retreatId + "'" - }).eachPage(function page(records, fetchNextPage) { - records.forEach(function(record) { - participants.push(record) - }) - - fetchNextPage() - }, function done(err) { - if (err) { reject(err); return } - resolve(participants) - }) - }) + return new Promise(function (resolve, reject) { + var participants = [] + + airtable.participants.select({ + filterByFormula: "{Retreat Id} = '" + retreatId + "'" + }).eachPage(function page(records, fetchNextPage) { + records.forEach(function(record) { + participants.push(record) + }) + + fetchNextPage() + }, function done(err) { + if (err) { reject(err); return } + resolve(participants) + }) + }) } function getFormattedParticipants(retreatId) { - return new Promise(function (resolve, reject) { - getParticipants(retreatId).then(function (participants) { - resolve(formatParticipants(participants)) - }) - }) - - function formatParticipants(participants) { - var formattedParticipants = [] - - participants.forEach(function(record) { - var id = '' - if (typeof record.get('Participant') !== 'undefined') { - id = record.get('Participant')[0] - } - formattedParticipants.push({ - days: dateFormatter.formatDays(record.get('First Night'), record.get('Last Night')), - id: id - }) - }) - - return formattedParticipants - } + return new Promise(function (resolve, reject) { + getParticipants(retreatId).then(function (participants) { + resolve(formatParticipants(participants)) + }) + }) + + function formatParticipants(participants) { + var formattedParticipants = [] + + participants.forEach(function(record) { + var id = '' + if (typeof record.get('Participant') !== 'undefined') { + id = record.get('Participant')[0] + } + formattedParticipants.push({ + days: dateFormatter.formatDays(record.get('First Night'), record.get('Last Night')), + id: id + }) + }) + + return formattedParticipants + } } function getFormattedParticipantsIncludingDetails(retreatId) { - return new Promise(function (resolve, reject) { - getFormattedParticipants(retreatId).then(function (formattedParticipants) { - getParticipantsDetail(formattedParticipants).then(function (detailedParticipants) { - resolve(getCombinedParticipants(formattedParticipants, detailedParticipants)) - }) - }) - }) - - function getParticipantsDetail(participants) { - return new Promise(function (resolve, reject) { - var detailedParticipants = [] - airtable.members.select({ - filterByFormula: getFormulaForParticipantsDetail(participants), - }).eachPage(function page(records, fetchNextPage) { - records.forEach(function(record) { - detailedParticipants.push(record) - }) - - fetchNextPage() - }, function done(err) { - if (err) { reject(err); return } - resolve(detailedParticipants) - }) - }) - - function getFormulaForParticipantsDetail(participants) { - var formula = 'OR(' - - for (var i = 0; i < participants.length; i++) { - var participant = participants[i] - formula += "RECORD_ID() = '" + participant.id + "'" - if (i < participants.length - 1) { - formula += ', ' - } - } - - formula += ')' - return formula - } - } - - function getCombinedParticipants(formattedParticipants, detailedParticipants) { - var combinedParticipants = [] - const colors = getColors(detailedParticipants.length) - - for (var i = 0; i < detailedParticipants.length; i++) { - var detailedParticipant = detailedParticipants[i] - for (var j = 0; j < formattedParticipants.length; j++) { - var formattedParticipant = formattedParticipants[j] - if (formattedParticipant.id === detailedParticipant.id) { - var tw = detailedParticipant.get('Twitter') && detailedParticipant.get('Twitter').length ? detailedParticipant.get('Twitter') : null - var img = tw ? 'https://twitter.com/' + tw + '/profile_image?size=original' : null - - combinedParticipants.push({ - id: formattedParticipant.id, - days: formattedParticipant.days, - name: detailedParticipant.get('Name'), - username: detailedParticipant.get('Slack Handle'), - color: colors[i], - avatar_url: img - }) - break - } - } - } - - return combinedParticipants - - function getColors(size) { - const colors = ['#FF7900', '#FF00FF', '#7F00FF', '#0075FF', '#00CBFF', '#FF9800', '#FF00A1', - '#7B00FF', '#006BFF', '#0096FF', '#FFBB00'] - const indexes = getShuffledIndexesOfSize(size) - var randomColors = [] - - for (var i = 0; i < indexes.length; i++) { - var index = indexes[i] - randomColors.push(colors[index % colors.length]) - } - - return randomColors - - function getShuffledIndexesOfSize(size) { - var indexes = Array.apply(null, {length: size}).map(Number.call, Number) - shuffle(indexes) - return indexes - - function shuffle(array) { - var m = array.length, t, i - while (m) { - i = Math.floor(Math.random() * m--); - t = array[m]; - array[m] = array[i]; - array[i] = t; - } - return array; - } - } - } - } + return new Promise(function (resolve, reject) { + getFormattedParticipants(retreatId).then(function (formattedParticipants) { + getParticipantsDetail(formattedParticipants).then(function (detailedParticipants) { + resolve(getCombinedParticipants(formattedParticipants, detailedParticipants)) + }) + }) + }) + + function getParticipantsDetail(participants) { + return new Promise(function (resolve, reject) { + var detailedParticipants = [] + airtable.members.select({ + filterByFormula: getFormulaForParticipantsDetail(participants), + }).eachPage(function page(records, fetchNextPage) { + records.forEach(function(record) { + detailedParticipants.push(record) + }) + + fetchNextPage() + }, function done(err) { + if (err) { reject(err); return } + resolve(detailedParticipants) + }) + }) + + function getFormulaForParticipantsDetail(participants) { + var formula = 'OR(' + + for (var i = 0; i < participants.length; i++) { + var participant = participants[i] + formula += "RECORD_ID() = '" + participant.id + "'" + if (i < participants.length - 1) { + formula += ', ' + } + } + + formula += ')' + return formula + } + } + + function getCombinedParticipants(formattedParticipants, detailedParticipants) { + var combinedParticipants = [] + const colors = getColors(detailedParticipants.length) + + for (var i = 0; i < detailedParticipants.length; i++) { + var detailedParticipant = detailedParticipants[i] + for (var j = 0; j < formattedParticipants.length; j++) { + var formattedParticipant = formattedParticipants[j] + if (formattedParticipant.id === detailedParticipant.id) { + var tw = detailedParticipant.get('Twitter') && detailedParticipant.get('Twitter').length ? detailedParticipant.get('Twitter') : null + var img = tw ? 'https://twitter.com/' + tw + '/profile_image?size=original' : null + + combinedParticipants.push({ + id: formattedParticipant.id, + days: formattedParticipant.days, + name: detailedParticipant.get('Name'), + username: detailedParticipant.get('Slack Handle'), + color: colors[i], + avatar_url: img + }) + break + } + } + } + + return combinedParticipants + + function getColors(size) { + const colors = ['#FF7900', '#FF00FF', '#7F00FF', '#0075FF', '#00CBFF', '#FF9800', '#FF00A1', + '#7B00FF', '#006BFF', '#0096FF', '#FFBB00'] + const indexes = getShuffledIndexesOfSize(size) + var randomColors = [] + + for (var i = 0; i < indexes.length; i++) { + var index = indexes[i] + randomColors.push(colors[index % colors.length]) + } + + return randomColors + + function getShuffledIndexesOfSize(size) { + var indexes = Array.apply(null, {length: size}).map(Number.call, Number) + shuffle(indexes) + return indexes + + function shuffle(array) { + var m = array.length, t, i + while (m) { + i = Math.floor(Math.random() * m--); + t = array[m]; + array[m] = array[i]; + array[i] = t; + } + return array; + } + } + } + } } function addParticipant(id, retreatId, firstNight, lastNight) { - return new Promise(function (resolve, reject) { - airtable.participants.create({ - Participant: [id], - Retreat: [retreatId], - 'First Night': firstNight, - 'Last Night': lastNight - }, function(err, record) { - if (err) { reject(err); return; } - resolve() - }) - }) + return new Promise(function (resolve, reject) { + airtable.participants.create({ + Participant: [id], + Retreat: [retreatId], + 'First Night': firstNight, + 'Last Night': lastNight + }, function(err, record) { + if (err) { reject(err); return; } + resolve() + }) + }) } function getParticipantByMemberId(memberAirtableId) { - return new Promise(function (resolve, reject) { - airtable.members.find(memberAirtableId, function(err, record) { - if (err) return reject(err) - if (!record) return reject(`no Member with id=${memberAirtableId}`) - resolve(record) - }) - }) + return new Promise(function (resolve, reject) { + airtable.members.find(memberAirtableId, function(err, record) { + if (err) return reject(err) + if (!record) return reject(`no Member with id=${memberAirtableId}`) + resolve(record) + }) + }) } module.exports = { - get: getFormattedParticipantsIncludingDetails, - add: addParticipant, - getParticipantByMemberId: getParticipantByMemberId + get: getFormattedParticipantsIncludingDetails, + add: addParticipant, + getParticipantByMemberId: getParticipantByMemberId } diff --git a/middleware/price.js b/middleware/price.js index 05eb3f1..c84c01b 100644 --- a/middleware/price.js +++ b/middleware/price.js @@ -6,116 +6,116 @@ var lastNight var retreat Date.prototype.isSameDateAs = function(aDate) { - return ( - this.getFullYear() === aDate.getFullYear() && - this.getMonth() === aDate.getMonth() && - this.getDate() === aDate.getDate() - ) + return ( + this.getFullYear() === aDate.getFullYear() && + this.getMonth() === aDate.getMonth() && + this.getDate() === aDate.getDate() + ) } function compute(params) { - return new Promise(function (resolve, reject) { - firstNight = new Date(params.firstNight) - lastNight = new Date(params.lastNight) - const retreatId = params.retreatId - if (typeof firstNight === 'undefined' || typeof lastNight === 'undefined' - || retreatId === 'undefined') { - reject('Params are not correct. You should send retreatId, firstNight and lastNight') - return - } - - - getRetreat(retreatId).then(function () { - if (isFullWeekStay()) { - resolve(getPricesForFullWeekStay()) - } else { - resolve(getPricesForCustomStay()) - } - }) - - function getRetreat(retreatId) { - return new Promise(function (resolve, reject) { - airtable.retreat.find(retreatId, function(err, record) { - if (err) { reject(err); return } - retreat = record - resolve() - }) - }) - } - - function isFullWeekStay() { - const weeks = dateFormatter.formatWeeks(retreat.get('First Night'), retreat.get('Last Night')) - var firstNightMatches = false - var isFullWeek = false - - for (var i = 0; i < weeks.length; i++) { - var week = weeks[i] - if (week.days[0].date.isSameDateAs(firstNight)) { - firstNightMatches = true - } - - if (firstNightMatches && week.days[week.days.length - 1].date.isSameDateAs(lastNight)) { - isFullWeek = true - } - } - - return isFullWeek - } - - function getPricesForFullWeekStay() { - const weeksCount = getWeeksCount() - - return { - price: retreat.get('Price Per Week') * weeksCount, - discount: retreat.get('Week Discount') * weeksCount, - canBook: true, - description: getDescription(weeksCount) - } - - function getWeeksCount() { - const weeks = dateFormatter.formatWeeks(retreat.get('First Night'), retreat.get('Last Night')) - var firstNightWeekIndex = -1 - var lastNightWeekIndex = -1 - - for (var i = 0; i < weeks.length; i++) { - var week = weeks[i] - if (week.days[0].date.isSameDateAs(firstNight)) { - firstNightWeekIndex = i - } - - if (week.days[week.days.length - 1].date.isSameDateAs(lastNight)) { - lastNightWeekIndex = i - } - } - - return (1 + lastNightWeekIndex - firstNightWeekIndex) - } - - function getDescription(weeksCount) { - var description = 'Booking ' + weeksCount + ' week' - if (weeksCount > 1) { - description += 's' - } - description += ' for ' + retreat.get('Name') - return description - } - } - - function getPricesForCustomStay() { - const timeDifference = lastNight.getTime() - firstNight.getTime() - const numberOfDays = Math.ceil(timeDifference / (1000 * 3600 * 24)) + 1 - const pricePerNight = retreat.get('Price Per Night') - - return { - price: pricePerNight * numberOfDays, - discount: 0, - canBook: false, - description: '' - } - } - }) + return new Promise(function (resolve, reject) { + firstNight = new Date(params.firstNight) + lastNight = new Date(params.lastNight) + const retreatId = params.retreatId + if (typeof firstNight === 'undefined' || typeof lastNight === 'undefined' + || retreatId === 'undefined') { + reject('Params are not correct. You should send retreatId, firstNight and lastNight') + return + } + + + getRetreat(retreatId).then(function () { + if (isFullWeekStay()) { + resolve(getPricesForFullWeekStay()) + } else { + resolve(getPricesForCustomStay()) + } + }) + + function getRetreat(retreatId) { + return new Promise(function (resolve, reject) { + airtable.retreat.find(retreatId, function(err, record) { + if (err) { reject(err); return } + retreat = record + resolve() + }) + }) + } + + function isFullWeekStay() { + const weeks = dateFormatter.formatWeeks(retreat.get('First Night'), retreat.get('Last Night')) + var firstNightMatches = false + var isFullWeek = false + + for (var i = 0; i < weeks.length; i++) { + var week = weeks[i] + if (week.days[0].date.isSameDateAs(firstNight)) { + firstNightMatches = true + } + + if (firstNightMatches && week.days[week.days.length - 1].date.isSameDateAs(lastNight)) { + isFullWeek = true + } + } + + return isFullWeek + } + + function getPricesForFullWeekStay() { + const weeksCount = getWeeksCount() + + return { + price: retreat.get('Price Per Week') * weeksCount, + discount: retreat.get('Week Discount') * weeksCount, + canBook: true, + description: getDescription(weeksCount) + } + + function getWeeksCount() { + const weeks = dateFormatter.formatWeeks(retreat.get('First Night'), retreat.get('Last Night')) + var firstNightWeekIndex = -1 + var lastNightWeekIndex = -1 + + for (var i = 0; i < weeks.length; i++) { + var week = weeks[i] + if (week.days[0].date.isSameDateAs(firstNight)) { + firstNightWeekIndex = i + } + + if (week.days[week.days.length - 1].date.isSameDateAs(lastNight)) { + lastNightWeekIndex = i + } + } + + return (1 + lastNightWeekIndex - firstNightWeekIndex) + } + + function getDescription(weeksCount) { + var description = 'Booking ' + weeksCount + ' week' + if (weeksCount > 1) { + description += 's' + } + description += ' for ' + retreat.get('Name') + return description + } + } + + function getPricesForCustomStay() { + const timeDifference = lastNight.getTime() - firstNight.getTime() + const numberOfDays = Math.ceil(timeDifference / (1000 * 3600 * 24)) + 1 + const pricePerNight = retreat.get('Price Per Night') + + return { + price: pricePerNight * numberOfDays, + discount: 0, + canBook: false, + description: '' + } + } + }) } module.exports = { - compute: compute + compute: compute } diff --git a/middleware/retreat.js b/middleware/retreat.js index 3cd9316..30bcaee 100644 --- a/middleware/retreat.js +++ b/middleware/retreat.js @@ -5,14 +5,14 @@ var dateFormatter = require('../helpers/dateFormatter.js') var weather = require('./weather.js') function getRetreat(slug) { - return new Promise(function (resolve, reject) { - airtable.retreat.select({ - maxRecords: 1, - filterByFormula: `{Slug}='${slug}'`, - }).firstPage(function(err, records) { - if (err) return reject(err) - // TODO: properly handle when no ongoing retreat in Airtable - if (!records.length) return reject("found no ongoing retreats on Airtable") + return new Promise(function (resolve, reject) { + airtable.retreat.select({ + maxRecords: 1, + filterByFormula: `{Slug}='${slug}'`, + }).firstPage(function(err, records) { + if (err) return reject(err) + // TODO: properly handle when no ongoing retreat in Airtable + if (!records.length) return reject("found no ongoing retreats on Airtable") resolve(records[0]) }) }) @@ -43,37 +43,37 @@ function getRetreat(slug) { } function getOrganizer(retreat) { - return new Promise(function (resolve, reject) { - const organizerId = retreat.get('Organizer')[0] - if (typeof organizerId !== 'undefined') { - airtable.retreat.find(organizerId, function(err, organizer) { - if (err) { reject(err); return } - resolve(formatOrganizer(organizer)) - }) - } - }) + return new Promise(function (resolve, reject) { + const organizerId = retreat.get('Organizer')[0] + if (typeof organizerId !== 'undefined') { + airtable.retreat.find(organizerId, function(err, organizer) { + if (err) { reject(err); return } + resolve(formatOrganizer(organizer)) + }) + } + }) - function formatOrganizer(organizer) { - return { - id: organizer.id, - name: organizer.get('Name'), - username: organizer.get('Slack Handle') - } - } + function formatOrganizer(organizer) { + return { + id: organizer.id, + name: organizer.get('Name'), + username: organizer.get('Slack Handle') + } + } } function getCurrent() { return new Promise(function (resolve, reject) { - airtable.retreat.select({ + airtable.retreat.select({ // TODO: this fetches the 10 most recent retreats which is not technically // guaranteed to include the one closest to today, but looks good enough - maxRecords: 10, + maxRecords: 10, sort: [{field: 'First Night', direction: 'desc'}], - }).firstPage(function(err, records) { - if (err) return reject(err) + }).firstPage(function(err, records) { + if (err) return reject(err) if (!records.length) return reject(`no retreats found on Airtable`) - // pick retreat closest to today + // pick retreat closest to today const now = moment() const closest = _.first(_.orderBy(records, function(r) { return Math.abs(now.diff(r.get('First Night'))) @@ -85,62 +85,62 @@ function getCurrent() { function formatRetreat(retreat) { - return { - id: retreat.id, + return { + id: retreat.id, slug: retreat.get('Slug'), - weeks: dateFormatter.formatWeeks(retreat.get('First Night'), retreat.get('Last Night')), - name: retreat.get('Name'), - description: retreat.get('Description'), - channel: retreat.get('Channel'), - house : formatHouse(retreat), - price: formatPrice(retreat), - totalPrice: retreat.get('Total Price'), + weeks: dateFormatter.formatWeeks(retreat.get('First Night'), retreat.get('Last Night')), + name: retreat.get('Name'), + description: retreat.get('Description'), + channel: retreat.get('Channel'), + house : formatHouse(retreat), + price: formatPrice(retreat), + totalPrice: retreat.get('Total Price'), generated: retreat.get('Generated'), - location: formatLocation(retreat), - } + location: formatLocation(retreat), + } - function formatHouse(retreat) { - return { - url: retreat.get('House Url'), - beds: retreat.get('Beds'), - pictures: formatPictures(retreat.get('Pictures')), - rentPrice: retreat.get('House Rent Price') - } + function formatHouse(retreat) { + return { + url: retreat.get('House Url'), + beds: retreat.get('Beds'), + pictures: formatPictures(retreat.get('Pictures')), + rentPrice: retreat.get('House Rent Price') + } - function formatPictures(pictures) { - var formattedPictures = [] + function formatPictures(pictures) { + var formattedPictures = [] - if (typeof pictures !== 'undefined') { - for (var i = 0; i < pictures.length; i++) { - var picture = pictures[i] - formattedPictures.push(picture.url) - } - } + if (typeof pictures !== 'undefined') { + for (var i = 0; i < pictures.length; i++) { + var picture = pictures[i] + formattedPictures.push(picture.url) + } + } - return formattedPictures - } - } + return formattedPictures + } + } - function formatPrice(retreat) { - return { - perWeek: retreat.get('Price Per Week'), - perNight: retreat.get('Price Per Night'), - weekDiscount: retreat.get('Week Discount') - } - } + function formatPrice(retreat) { + return { + perWeek: retreat.get('Price Per Week'), + perNight: retreat.get('Price Per Night'), + weekDiscount: retreat.get('Week Discount') + } + } - function formatLocation(retreat) { - return { - latitude: retreat.get('Latitude'), - longitude: retreat.get('Longitude'), - fullAddress: retreat.get('Address'), - city: retreat.get('City'), - country: retreat.get('Country') - } - } + function formatLocation(retreat) { + return { + latitude: retreat.get('Latitude'), + longitude: retreat.get('Longitude'), + fullAddress: retreat.get('Address'), + city: retreat.get('City'), + country: retreat.get('Country') + } + } } module.exports = { - get: getRetreat, + get: getRetreat, getCurrent, } diff --git a/middleware/weather.js b/middleware/weather.js index 704e65b..acd5f1c 100644 --- a/middleware/weather.js +++ b/middleware/weather.js @@ -2,23 +2,23 @@ var request = require('request') var moment = require('moment-timezone'); function getTemperatureAndLocalTime(latitude, longitude) { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve, reject) { if (!latitude || !longitude) return reject(`missing latitude and/or longitude`) - const url = 'https://api.darksky.net/forecast/0fe81fed15cafca7534a3b3d7e921229/' - + latitude + ',' + longitude + '?exclude=minutely,hourly,daily,alerts,flags&units=si' - request.get(url, function (error, response, body) { - if (!error && response.statusCode == 200) { - resolve({ - temperature: JSON.parse(body).currently.temperature.toFixed(1), - localTime: moment().tz(JSON.parse(body).timezone).format('HH:mm') - }) - } else { - reject(error || `status ${response.statusCode} ${body}`) - } - }) - }) + const url = 'https://api.darksky.net/forecast/0fe81fed15cafca7534a3b3d7e921229/' + + latitude + ',' + longitude + '?exclude=minutely,hourly,daily,alerts,flags&units=si' + request.get(url, function (error, response, body) { + if (!error && response.statusCode == 200) { + resolve({ + temperature: JSON.parse(body).currently.temperature.toFixed(1), + localTime: moment().tz(JSON.parse(body).timezone).format('HH:mm') + }) + } else { + reject(error || `status ${response.statusCode} ${body}`) + } + }) + }) } module.exports = { - getTemperatureAndLocalTime: getTemperatureAndLocalTime + getTemperatureAndLocalTime: getTemperatureAndLocalTime } diff --git a/public/javascripts/main.js b/public/javascripts/main.js index 6b4c863..e908f1f 100644 --- a/public/javascripts/main.js +++ b/public/javascripts/main.js @@ -9,192 +9,192 @@ $('body').on('click', function() { var organiserUsername = $('#retreat_organizer_username').val() var stripeParams = { - key: config.stripePublishableKey, - amount: 2000, - description: 'Retreat' + key: config.stripePublishableKey, + amount: 2000, + description: 'Retreat' } $('.booking-board-row').on('click', function(){ - $(this).toggleClass('is-active') + $(this).toggleClass('is-active') }) $('.booking-board-footer.step-1 .booking-board-btn').on('click', function(){ - $(this).unbind('click') - - var selectedDaysIds = $(this).data('days').split(' ').slice(0, -1) - computePrice($(this), selectedDaysIds) - var firstDaySelected = false - var lastDaySelected = false - $(this).addClass('is-active') - $(this).find('span').html('Pay $200') - $('.booking-board-footer').removeClass('step-1').addClass('step-2') - for (var i = 0, len = selectedDaysIds.length; i < len; i++) { - $('#' + selectedDaysIds[i]).addClass('is-active') - } - - var divs = $('.booking-board-days-selector .day-item.is-active') - var index = 0; - - var delay = setInterval( function(){ - if ( index <= divs.length ){ - $( divs[ index ] ).addClass( 'is-visible' ); - index += 1; - }else{ - clearInterval( delay ); - } - }, i * 2); - - $('.booking-board-days-selector .day-item').on('click', function() { - if (lastDaySelected) { - $('.booking-board-days-selector .day-item').removeClass('is-active') - } - if($('.booking-board-days-selector .day-item').hasClass('is-active')) { - var divs = [] - - if($('.booking-board-days-selector .day-item.is-active').attr('id') == $(this).attr('id')) return - - selectedDaysIds = []; - selectedDaysIds.push($(this).attr('id')) - selectedDaysIds.push($('.booking-board-days-selector .day-item.is-active').attr('id')) - - if(isBefore($('.booking-board-days-selector .day-item.is-active'), $(this))) { - divs = $(this).prevUntil($('.booking-board-days-selector .day-item.is-active')).addClass('is-active is-visible') - } else { - divs = $(this).nextUntil($('.booking-board-days-selector .day-item.is-active')).addClass('is-active is-visible') - } - for (var i = 0, len = divs.length; i < len; i++) { - selectedDaysIds.push(divs[i].getAttribute('id')) - } - $(this).addClass('is-active is-visible') - - lastDaySelected = true - firstDaySelected = false - computePrice($('.booking-board-btn.is-active'), selectedDaysIds) - } else { - $(this).addClass('is-active is-visible') - firstDaySelected = true - lastDaySelected = false - } - - }) - - $('.booking-board-days-selector').on('mouseenter', function() { - if(!firstDaySelected) { - $('.booking-board-days-selector .day-item').removeClass('is-active') - } - }).mouseleave(function(){ - if(!firstDaySelected) { - lastDaySelected = false - - $('.booking-board-days-selector .day-item').removeClass('is-active') - for (var i = 0, len = selectedDaysIds.length; i < len; i++) { - $('#' + selectedDaysIds[i]).addClass('is-active is-visible') - } - - } - }) - - $(this).on('click', function() { - if($(this).hasClass('can-book')) { - openStripeCheckout(stripeParams, function(token) { - selectedDays = datify(selectedDaysIds) - postStripeTokenAndInfos(token, config.retreatId, selectedDays[0], selectedDays[selectedDays.length - 1]) - }) - } else { - // 👦 need le slack id du retreat organiser, dynamiquement (ci dessous @ben en brut: U1NK4E3QE) - window.open('slack://user?team=T0QJH8NJK&id=U1NK4E3QE') - } - }) + $(this).unbind('click') + + var selectedDaysIds = $(this).data('days').split(' ').slice(0, -1) + computePrice($(this), selectedDaysIds) + var firstDaySelected = false + var lastDaySelected = false + $(this).addClass('is-active') + $(this).find('span').html('Pay $200') + $('.booking-board-footer').removeClass('step-1').addClass('step-2') + for (var i = 0, len = selectedDaysIds.length; i < len; i++) { + $('#' + selectedDaysIds[i]).addClass('is-active') + } + + var divs = $('.booking-board-days-selector .day-item.is-active') + var index = 0; + + var delay = setInterval( function(){ + if ( index <= divs.length ){ + $( divs[ index ] ).addClass( 'is-visible' ); + index += 1; + }else{ + clearInterval( delay ); + } + }, i * 2); + + $('.booking-board-days-selector .day-item').on('click', function() { + if (lastDaySelected) { + $('.booking-board-days-selector .day-item').removeClass('is-active') + } + if($('.booking-board-days-selector .day-item').hasClass('is-active')) { + var divs = [] + + if($('.booking-board-days-selector .day-item.is-active').attr('id') == $(this).attr('id')) return + + selectedDaysIds = []; + selectedDaysIds.push($(this).attr('id')) + selectedDaysIds.push($('.booking-board-days-selector .day-item.is-active').attr('id')) + + if(isBefore($('.booking-board-days-selector .day-item.is-active'), $(this))) { + divs = $(this).prevUntil($('.booking-board-days-selector .day-item.is-active')).addClass('is-active is-visible') + } else { + divs = $(this).nextUntil($('.booking-board-days-selector .day-item.is-active')).addClass('is-active is-visible') + } + for (var i = 0, len = divs.length; i < len; i++) { + selectedDaysIds.push(divs[i].getAttribute('id')) + } + $(this).addClass('is-active is-visible') + + lastDaySelected = true + firstDaySelected = false + computePrice($('.booking-board-btn.is-active'), selectedDaysIds) + } else { + $(this).addClass('is-active is-visible') + firstDaySelected = true + lastDaySelected = false + } + + }) + + $('.booking-board-days-selector').on('mouseenter', function() { + if(!firstDaySelected) { + $('.booking-board-days-selector .day-item').removeClass('is-active') + } + }).mouseleave(function(){ + if(!firstDaySelected) { + lastDaySelected = false + + $('.booking-board-days-selector .day-item').removeClass('is-active') + for (var i = 0, len = selectedDaysIds.length; i < len; i++) { + $('#' + selectedDaysIds[i]).addClass('is-active is-visible') + } + + } + }) + + $(this).on('click', function() { + if($(this).hasClass('can-book')) { + openStripeCheckout(stripeParams, function(token) { + selectedDays = datify(selectedDaysIds) + postStripeTokenAndInfos(token, config.retreatId, selectedDays[0], selectedDays[selectedDays.length - 1]) + }) + } else { + // 👦 need le slack id du retreat organiser, dynamiquement (ci dessous @ben en brut: U1NK4E3QE) + window.open('slack://user?team=T0QJH8NJK&id=U1NK4E3QE') + } + }) }) function computePrice(that, selectedDays) { - that.addClass('is-loading') - $('.booking-board-footer-title').addClass('is-loading') + that.addClass('is-loading') + $('.booking-board-footer-title').addClass('is-loading') - selectedDays = datify(selectedDays) + selectedDays = datify(selectedDays) - $.post('/' + config.retreatSlug + '/computeprice', - { + $.post('/' + config.retreatSlug + '/computeprice', + { retreatId: config.retreatId, - firstNight: selectedDays[0], - lastNight: selectedDays[selectedDays.length - 1] - }, function(data){ - that.removeClass('is-loading') - $('.booking-board-footer-title').removeClass('is-loading') - - stripeParams.amount = data.price - stripeParams.description = data.description - var text - if(data.canBook) { - text = 'Pay ' + data.price + "€" - that.addClass('can-book') - that.removeClass('redirect-to-slack') - } else { - text = "DM " + organiserUsername - that.removeClass('can-book') - that.addClass('redirect-to-slack') - - } - that.find('span').text(text) - $('.booking-board-footer-title .data-item-header').html(data.price + '€') - $('.booking-board-footer-title .data-item-footer').html(selectedDays.length + ' nights') - }) + firstNight: selectedDays[0], + lastNight: selectedDays[selectedDays.length - 1] + }, function(data){ + that.removeClass('is-loading') + $('.booking-board-footer-title').removeClass('is-loading') + + stripeParams.amount = data.price + stripeParams.description = data.description + var text + if(data.canBook) { + text = 'Pay ' + data.price + "€" + that.addClass('can-book') + that.removeClass('redirect-to-slack') + } else { + text = "DM " + organiserUsername + that.removeClass('can-book') + that.addClass('redirect-to-slack') + + } + that.find('span').text(text) + $('.booking-board-footer-title .data-item-header').html(data.price + '€') + $('.booking-board-footer-title .data-item-footer').html(selectedDays.length + ' nights') + }) } function datify(array) { - array = array.map(function(item, i) { - return $('#' + item).data('date') - }) + array = array.map(function(item, i) { + return $('#' + item).data('date') + }) - array = array.sort(function(a,b) { - return new Date(a).getTime() - new Date(b).getTime() - }); + array = array.sort(function(a,b) { + return new Date(a).getTime() - new Date(b).getTime() + }); - return array + return array } function isBefore(el1, el2) { - return el1.nextAll().filter(el2).length !== 0; + return el1.nextAll().filter(el2).length !== 0; } var touch = 'ontouchstart' in document.documentElement - || navigator.maxTouchPoints > 0 - || navigator.msMaxTouchPoints > 0; + || navigator.maxTouchPoints > 0 + || navigator.msMaxTouchPoints > 0; if (touch) { // remove all :hover stylesheets - try { // prevent exception on browsers not supporting DOM styleSheets properly - for (var si in document.styleSheets) { - var styleSheet = document.styleSheets[si]; - if (!styleSheet.rules) continue; - - for (var ri = styleSheet.rules.length - 1; ri >= 0; ri--) { - if (!styleSheet.rules[ri].selectorText) continue; - - if (styleSheet.rules[ri].selectorText.match(':hover')) { - styleSheet.deleteRule(ri); - } - } - } - } catch (ex) {} + try { // prevent exception on browsers not supporting DOM styleSheets properly + for (var si in document.styleSheets) { + var styleSheet = document.styleSheets[si]; + if (!styleSheet.rules) continue; + + for (var ri = styleSheet.rules.length - 1; ri >= 0; ri--) { + if (!styleSheet.rules[ri].selectorText) continue; + + if (styleSheet.rules[ri].selectorText.match(':hover')) { + styleSheet.deleteRule(ri); + } + } + } + } catch (ex) {} } var ink, d, x, y; $(".ripplelink").click(function(e){ - if($(this).find(".ink").length === 0){ - $(this).prepend(""); - } + if($(this).find(".ink").length === 0){ + $(this).prepend(""); + } - ink = $(this).find(".ink"); - ink.removeClass("animate"); + ink = $(this).find(".ink"); + ink.removeClass("animate"); - if(!ink.height() && !ink.width()){ - d = Math.max($(this).outerWidth(), $(this).outerHeight()); - ink.css({height: d, width: d}); - } + if(!ink.height() && !ink.width()){ + d = Math.max($(this).outerWidth(), $(this).outerHeight()); + ink.css({height: d, width: d}); + } - x = e.pageX - $(this).offset().left - ink.width()/2; - y = e.pageY - $(this).offset().top - ink.height()/2; + x = e.pageX - $(this).offset().left - ink.width()/2; + y = e.pageY - $(this).offset().top - ink.height()/2; - ink.css({top: y+'px', left: x+'px'}).addClass("animate"); + ink.css({top: y+'px', left: x+'px'}).addClass("animate"); }) diff --git a/public/javascripts/stripe.js b/public/javascripts/stripe.js index 6e1cb61..6ca2a21 100644 --- a/public/javascripts/stripe.js +++ b/public/javascripts/stripe.js @@ -1,32 +1,32 @@ function openStripeCheckout(stripeParams, tokenCallback) { - var handler = StripeCheckout.configure({ - key: stripeParams.key, - image: '/images/mangrove_logo.png', - locale: 'auto', - token: tokenCallback - }) + var handler = StripeCheckout.configure({ + key: stripeParams.key, + image: '/images/mangrove_logo.png', + locale: 'auto', + token: tokenCallback + }) - // Open Checkout with further options: - handler.open({ - name: 'Mangrove', - description: stripeParams.description, - currency: 'eur', - amount: stripeParams.amount * 100 - }) + // Open Checkout with further options: + handler.open({ + name: 'Mangrove', + description: stripeParams.description, + currency: 'eur', + amount: stripeParams.amount * 100 + }) } function postStripeTokenAndInfos(token, retreatId, firstNight, lastNight) { - $.post('/' + config.retreatSlug + '/charge', { - tokenId: token.id, - email: token.email, - retreatId: retreatId, - firstNight: firstNight, - lastNight: lastNight - }, function(data) { - if (data.success) { - window.location = '/booked' - } else { - window.location = '/booking_error?error=' + data.error - } - }) + $.post('/' + config.retreatSlug + '/charge', { + tokenId: token.id, + email: token.email, + retreatId: retreatId, + firstNight: firstNight, + lastNight: lastNight + }, function(data) { + if (data.success) { + window.location = '/booked' + } else { + window.location = '/booking_error?error=' + data.error + } + }) }