// Args: user, tutor, startTime, endTime 'use strict'; var ScheduleSessionModal = React.createClass({ displayName: 'ScheduleSessionModal', getInitialState: function getInitialState() { // TODO(gar): allow for loading the modal by tutorId/tutorName return { duration: this.props.user.minutes >= 30 || this.props.user.planMinutesPerDay >= 30 ? 30 : 15, startTime: this.props.startTime, topic: null, choosingTopic: false, productOptions: this.props.products, product: null, error: null, reservationButtonDisabled: false, tag: null }; }, componentWillMount: function componentWillMount() { // Setup IELTS Reservation Logic if (this.props.proType) { this.setState({ topic: "Cambly IELTS", tag: 'ielts' }); var DEFAULT_DURATION = 30; this.setState({ duration: DEFAULT_DURATION }); for (var prod in this.props.products) { if (this.props.products[prod]['productInfo']['minutes'] == DEFAULT_DURATION) { this.setState({ product: this.props.products[prod] }); } } // Build StripeCheckout Handler var StripeObject = StripeCheckout.configure({ key: cambly.STRIPE_KEY, image: 'https://www.cambly.com/static/images/icon128.jpg', locale: 'auto', token: (function (token) { var user = this.props.user.userId; var data = { stripeToken: token.id, userId: user, product: this.state.product['encrypted'] }; var language = this.props.language; CAMBLY.post('/' + language + '/student/subscribe/return', data).done((function (data) { this.saveSession(this.state.startTime, this.state.duration, this.state.topic, this.props.proType, this.state.product._id); }).bind(this)); }).bind(this) }); this.setState({ stripeHandler: StripeObject }); } }, componentDidUpdate: function componentDidUpdate() { $(document.body).addClass('modal-open'); }, updateStartTimes: function updateStartTimes(startTime, endTime) { var minutes = +$('select#minutes option:selected').val(); var selectedStartTime = +$('select#startTime option:selected').attr('value'); var html = reservationStartTimeSelectorTemplate({ minutes: minutes, selectedStartTime: selectedStartTime, startTime: startTime, endTime: endTime }); $('#reservationStartTimeSelectorDiv').html(html); }, reservationError: function reservationError(data) { var MESSAGES = { noMinutes: "You don't have enough minutes to reserve the session. Would you like to buy more?", maxReservations: "You already have the maximum number of upcoming reservations.", tutorConflict: "Your tutor has a conflicting reservation", studentConflict: "You have a conflicting reservation" }; if (data.responseJSON.error in MESSAGES) { this.setState({ error: MESSAGES[data.responseJSON.error] }); } else if (data.responseJSON.error == 'custom' && 'customMessage' in data.responseJSON) { this.setState({ error: data.responseJSON.customMessage }); } else { this.setState({ error: data.responseJSON.error }); } this.setState({ reservationButtonDisabled: true }); }, reserveSession: function reserveSession(e) { var _this = this; e.preventDefault(); var startTime = this.state.startTime; var minutes = this.state.duration; var topic = this.state.topic; var tag = this.state.tag; var proType = this.props.proType || null; if (proType) { var postData = { tutor: this.props.tutor.userId, startTime: startTime / 1000, minutes: minutes, topic: topic, tag: tag, source: 'profile-web', proType: proType, dryRun: true, productId: this.state.product._id }; CAMBLY.post('/reserveSession', postData).done(function (data) { // We have to leave cambly.com to complete the adyen payment, so save // the reservation details in the adyen metadata for after we return. CAMBLY.post('/en/thirdPartyCheckout', { 'product': _this.state.product.originalProduct, 'country': 'cn', // suspicious... 'userId': _this.props.user.userId, 'action': 'minutes', 'paymentProcessor': 'adyen', 'returnUrl': true, 'reservationMetadata': { tutor: _this.props.tutor.userId, startTime: startTime, minutes: minutes, topic: topic, tag: tag, source: 'profile-web', proType: proType, dryRun: false, productId: _this.state.product._id } }).done(function (resp) { window.location = resp.result.redirect; }); }).fail(this.reservationError); } else { this.saveSession(startTime, minutes, topic, proType); } }, toggleChooseTopic: function toggleChooseTopic(show) { this.setState({ choosingTopic: show }); }, setTopic: function setTopic(e) { this.setState({ topic: e }); }, onTopicChange: function onTopicChange(e) { this.setTopic(e.target.value); }, onDurationChange: function onDurationChange(e) { var dur = e.target.value; this.setState({ duration: dur }); if (this.props.proType) { for (var prod in this.props.products) { if (this.props.products[prod]['productInfo']['minutes'] == dur) { this.setState({ product: this.props.products[prod] }); } } } }, onStartTimeChange: function onStartTimeChange(e) { this.setState({ startTime: e.target.value }); this.setState({ reservationButtonDisabled: false }); //in case original error was due to conflict }, onModal: function onModal(modal) { this.modal = modal; if (modal) { $(modal).modal('show'); $(modal).on('hidden.bs.modal', (function (e) { this.props.onHide(); }).bind(this)); } }, saveSession: function saveSession(startTime, minutes, topic, proType, productId) { var postData = { tutor: this.props.tutor.userId, startTime: startTime / 1000, minutes: minutes, topic: topic, tag: this.state.tag, source: 'profile-web', proType: proType, dryRun: false, productId: productId }; CAMBLY.post('/reserveSession', postData).done((function (data) { $('#scheduleSessionModal #success').show(); this.setState({ reservationButtonDisabled: true }); // TODO(gar): show success or failure messages setTimeout((function () { // Reset the Form $('#scheduleSessionModal').modal('hide'); $('.help-block').hide(); if (this.props.proType) { this.props.didDismiss(); } }).bind(this), 1000); }).bind(this)).fail((function (data) { this.reservationError(data); }).bind(this)); }, render: function render() { var _this2 = this; var durationOptions = []; // Always show at least the 15 minute option to avoid form submits with an empty 'minutes' parameter. var durations = [15, 30, 45, 60, 90, 120]; var ieltsDuration = [30, 60]; if (this.props.proType) { var diff = this.props.endTime - this.props.startTime; for (var i = 0; i < ieltsDuration.length; i++) { if ((this.props.user.minutes >= ieltsDuration[i] || this.props.user.planMinutesPerDay >= ieltsDuration[i]) && diff >= ieltsDuration[i] * 60 * 1000) { durationOptions.push(React.createElement("option", { key: ieltsDuration[i], value: ieltsDuration[i] }, "%d Minutes".replace('%d', ieltsDuration[i]))); } } } else { for (var i = 0; i < durations.length; i++) { if (durations[i] == 15 || this.props.user.minutes >= durations[i] || this.props.user.planMinutesPerDay >= durations[i]) { durationOptions.push(React.createElement("option", { key: durations[i], value: durations[i] }, "%d Minutes".replace('%d', durations[i]))); } } } var durationSelector = React.createElement("select", { id: "minutes", name: "minutes", className: "form-control", value: this.state.duration, onChange: this.onDurationChange }, durationOptions); var durationBlock = React.createElement("div", { className: "form-group" }, React.createElement("label", { htmlFor: "minutes", className: "control-label col-sm-3" }, "Duration"), React.createElement("div", { className: "col-sm-9" }, durationSelector)); var startTimeOptions = []; for (var time = this.props.startTime; time + this.state.duration * 60 * 1000 <= this.props.endTime; time += 15 * 60 * 1000) { startTimeOptions.push(React.createElement(ReadableTimeOption, { key: time, time: time })); } var startTimeSelector = React.createElement("select", { id: "startTime", name: "startTime", className: "form-control", value: this.state.startTime, onChange: this.onStartTimeChange }, startTimeOptions); var chooseFromLibrary = null; if (this.props.hasLibrary) { var topicChooser = null; if (this.state.choosingTopic) { topicChooser = React.createElement(ChooseTopicModal, { tutor: this.props.tutor, onChoose: this.setTopic, onHide: this.toggleChooseTopic.bind(this, false) }); } chooseFromLibrary = React.createElement("div", { id: "chooseFromLibraryDiv", className: "text-right" }, React.createElement("a", { href: "#", onClick: function onClick() { CAMBLY.logUIEvent('seeTutorsTopicsClick'); _this2.toggleChooseTopic(true); } }, "See tutor's topics")); } var chooseTopic = null; if (this.props.proType) { chooseTopic = null; } else { chooseTopic = React.createElement("div", { className: "form-group" }, React.createElement("label", { htmlFor: "reservationTopic", className: "control-label col-sm-3" }, "What would you like to work on?", React.createElement("br", null), React.createElement("small", null, "(optional)")), React.createElement("div", { id: "reservationTopicDiv", className: "col-sm-9" }, React.createElement("textarea", { id: "reservationTopic", type: "text", className: "form-control", maxLength: "2000", value: this.state.topic || '', onChange: this.onTopicChange }), chooseFromLibrary)); } var showAlert = null; if (this.state.error) { showAlert = React.createElement("div", { id: "error", className: "row help-block" }, React.createElement("div", { className: "col-xs-12" }, React.createElement("div", { className: "alert alert-danger" }, this.state.error))); } return React.createElement("div", null, React.createElement("div", { className: "modal fade", id: "scheduleSessionModal", tabIndex: "-1", role: "dialog", 'aria-labelledby': "makeReservationHeader", 'aria-hidden': "true", ref: this.onModal }, React.createElement("div", { className: "modal-dialog" }, React.createElement("div", { className: "modal-content" }, React.createElement("div", { className: "modal-header text-center" }, React.createElement("div", { className: "row" }, React.createElement("div", { className: "col-xs-10 col-xs-offset-1" }, React.createElement("h3", { id: "makeReservationHeader" }, "Make a Reservation")), React.createElement("div", { className: "col-xs-1 text-right" }, React.createElement("button", { type: "button", className: "close", 'data-dismiss': "modal", 'aria-label': "Close" }, React.createElement("span", { 'aria-hidden': "true" }, "×"))))), React.createElement("div", { className: "modal-body text-center" }, React.createElement("div", { id: "success", className: "row help-block", style: { display: 'none' } }, React.createElement("div", { className: "col-xs-12" }, React.createElement("div", { className: "alert alert-success" }, "Session Reserved"))), showAlert, React.createElement("form", { className: "form-horizontal" }, React.createElement("input", { id: "tutor", type: "hidden", name: "tutor" }), durationBlock, React.createElement("div", { className: "form-group" }, React.createElement("label", { id: "startTime", htmlFor: "startTime", className: "control-label col-sm-3" }, "Start"), React.createElement("div", { className: "col-sm-9" }, startTimeSelector)), chooseTopic, React.createElement("div", { className: "form-group text-center" }, React.createElement("div", { className: "col-xs-12 col-sm-10 col-sm-offset-1" }, React.createElement("button", { id: "submit", className: "btn btn-accent", onClick: this.reserveSession, disabled: this.state.reservationButtonDisabled }, "Reserve")))))))), topicChooser); } }); // TODO(gar): Date Formatting is actually a slow operation, and this can give the // modal a sluggish feel if there's a lot of date options.... Cache these? var ReadableTimeOption = function ReadableTimeOption(props) { var render = function render(option) { if (option) { $(option).html(new Date(props.time).toLocaleTimeString(navigator.language, { hour: '2-digit', minute: '2-digit' })); } }; return React.createElement("option", { value: props.time, ref: render }); };