context = window rest = context.JK.Rest() logger = context.JK.logger UserStore = context.UserStore @LessonBooking = React.createClass({ mixins: [ @PostProcessorMixin, Reflux.listenTo(AppStore, "onAppInit"), Reflux.listenTo(UserStore, "onUserChanged") ] onAppInit: (@app) -> @app.bindScreen('jamclass/lesson-booking', {beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide, navName: 'Lesson Booking'}) onUserChanged: (userState) -> @setState({user: userState?.user}) onSlotDecision: (slot_decision) -> @setState({slot_decision: slot_decision?.slot_decision}) onUpdateAllDecision: (update_all) -> @setState({update_all: update_all?.update_all}) componentWillMount: () -> componentDidMount: () -> @checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}] @root = $(@getDOMNode()) componentDidUpdate: () -> # add friendly helpers to a slot processSlot: (slot, booking) -> if !slot? return slot.slotTime = @slotTime(slot, booking) slot.is_recurring = @isRecurring() if slot['is_teacher_created?'] slot.creatorRole = 'teacher' slot.creatorRoleRelative = 'teacher' else slot.creatorRole = 'student' slot.creatorRoleRelative = 'student' if context.JK.currentUserId == slot.proposer_id slot.creatorRoleRelative = "your" slot.mySlot = @mySlot(slot) componentWillUpdate: (nextProps, nextState) -> if nextState.booking? booking = nextState.booking if !booking.post_processed booking.post_processed = true for slot in booking.slots @processSlot(slot, booking) @processSlot(booking.counter_slot, booking) @processSlot(booking.default_slot, booking) @processSlot(booking.alt_slot, booking) getInitialState: () -> { user: null, booking: null, updating: false, updatingLesson: false } beforeHide: (e) -> beforeShow: (e) -> afterShow: (e) -> @setState({updating: true, counterErrors: null, cancelErrors: null}) rest.getLessonBooking({ id: e.id, }).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError) hasFocusedLesson: () -> this.state.booking.focused_lesson?.id? focusedLesson: () -> this.state?.booking?.focused_lesson updateBookingState: (booking) -> console.log("updating booking state", booking) if booking.counter_slot? startSlotDecision = booking.counter_slot.id else if booking.accepter_id? startSlotDecision = 'counter' else startSlotDecision = booking.default_slot.id update_all = !booking.focused_lesson?.id? if booking.focused_lesson? #booking.focused_lesson.lesson_booking = booking @postProcessLesson(booking.focused_lesson) if booking.next_lesson? #booking.next_lesson.lesson_booking = booking @postProcessLesson(booking.next_lesson) @setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false, update_all: update_all}) getLessonBookingDone: (response) -> @updateBookingState(response) toJamClassMain: (e) -> e.preventDefault() window.location.href = '/client#/jamclass' onCancel: () -> # what to do? window.location.href = '/client#/jamclass' onAccept: () -> @setState({updatingLesson: true, counterErrors: null, cancelErrors: null}) if @state.slot_decision == 'counter' request = @getSlotData(0) request.id = this.state.booking.id request.timezone = window.jstz.determine().name() request.message = @getMessage() request.lesson_session_id = @focusedLesson()?.id rest.counterLessonBooking(request).done((response) => @counterLessonBookingDone(response)).fail((response) => @counterLessonBookingFail(response)) else if @state.slot_decision == 'decline' request = {} request.message = @getMessage() request.id = this.state.booking.id request.lesson_session_id = @focusedLesson()?.id rest.cancelLessonBooking(request).done((response) => @cancelLessonBookingDone(response)).fail((response) => @cancelLessonBookingFail(response)) else if @state.slot_decision request = {} request.message = @getMessage() request.id = this.state.booking.id request.slot = this.state.slot_decision rest.acceptLessonBooking(request).done((response) => @acceptLessonBookingDone(response)).fail((response) => @acceptLessonBookingFail(response)) # {"errors":{"lesson_booking_slots":["is invalid"]},"_children":{"lesson_booking_slots":[{"errors":{}},{"errors":{}},{"errors":{"day_of_week":["must be specified"]}}]}} dayOfWeekMissing: (errors) -> console.log("errors", errors) childErrors = errors._children if childErrors for key, errorData of childErrors for slotErrors in errorData if slotErrors.errors?.day_of_week? return true return false acceptLessonBookingDone: (response ) -> logger.debug("accept lesson booking done") @updateBookingState(response) window.location.href = '/client#/jamclass' cancelLessonBookingDone: (response ) -> context.JK.Banner.showNotice("Lesson Canceled", "Your lesson has been canceled.

We've taken you back to the JamClass home page.") logger.debug("cancel lesson booking done") @updateBookingState(response) window.location.href = '/client#/jamclass' counterLessonBookingDone: (response ) -> context.JK.Banner.showNotice("Lesson Change Requested", "Your request for a time has been sent.

We've taken you back to the JamClass home page.") logger.debug("counter lesson booking done") @updateBookingState(response) window.location.href = '/client#/jamclass' counterLessonBookingFail: (jqXHR ) -> @setState({updatingLesson: false}) logger.debug("counter lesson booking failed") handled = false if jqXHR.status == 422 errors = JSON.parse(jqXHR.responseText) if @dayOfWeekMissing(errors) handled = true @setState({counterErrors: {errors: {day_of_week: ["must be specified"]}}}) if !handled @app.ajaxError(arguments[0], arguments[1], arguments[2]) acceptLessonBookingFail: (response ) -> @setState({updatingLesson: false}) logger.debug("accept lesson booking failed " + response.responseText) @app.ajaxError(arguments[0], arguments[1], arguments[2]) cancelLessonBookingFail: (jqXHR) -> @setState({updatingLesson: false}) logger.debug("cancel lesson booking failed", jqXHR) handled = false if jqXHR.status == 422 errors = JSON.parse(jqXHR.responseText) if errors.errors?.base? handled = true window.JK.Banner.showAlert("Unable to Cancel Lesson", errors.errors?.base[0]) if !handled @app.ajaxError(arguments[0], arguments[1], arguments[2]) getMessage: () -> @root.find('textarea.message').val() getSlotData: (position) -> $slot = @root.find('.slot-' + (position + 1)) picker = $slot.find('.date-picker') hour = $slot.find('.hour').val() minute = $slot.find('.minute').val() am_pm = $slot.find('.am_pm').val() update_all = $slot.find('input.update-all').is(':checked') && @isRecurring() if hour? and hour != '' hour = new Number(hour) if am_pm == 'PM' hour += 12 else hour = null if minute? and minute != '' minute = new Number(minute) else minute = null if !@isRecurring() date = picker.datepicker("getDate") if date? date = context.JK.formatDateYYYYMMDD(date) else day_of_week = $slot.find('.day_of_week').val() {hour: hour, minute: minute, date: date, day_of_week: day_of_week, update_all: update_all} student: () -> @state.booking?.user teacher: () -> @state.booking?.teacher otherRole: () -> if @teacherViewing() 'student' else 'teacher' other: () -> if @teacherViewing() @student() else if @studentViewing() @teacher() else null myself: () -> if @teacherViewing() @teacher() else if @studentViewing() @student() else null neverAccepted: () -> !this.state.booking?.accepter_id? defaultSlot: () -> @state.booking?.default_slot counteredSlot: () -> @state.booking?.counter_slot canceler: () -> if @student().id == this.state.booking?.canceler_id @student() else @teacher() counterer: () -> if @counteredSlot()?['is_teacher_created?'] @teacher() else @student() otherCountered: () -> @myself().id == @counterer().id studentCanceled: () -> @canceler().id == @student().id selfCanceled: () -> @canceler().id == @myself().id studentMadeDefaultSlot: () -> @student()?.id == @defaultSlot()?.proposer_id teacherViewing: () -> !@studentViewing() studentViewing: () -> @state.booking? && @state.booking.user_id == context.JK.currentUserId isActive: () -> @state.booking?.active == true isRequested: () -> @state.booking?.status == 'requested' && !@isCounter() isSuccessful: () -> @displayableLesson().success isCounter: () -> @counteredSlot()? && !@isCanceled() && !@isSuspended() isInitialCounter: () -> @isCounter() && !@isActive() isActiveCounter: () -> @isCounter() && @isActive() isCompleted: () -> if @state.booking? @displayableLesson().status == 'completed' else false isApproved: () -> @state.booking?.status == 'approved' isCanceled: () -> @state.booking?.status == 'canceled' isSuspended: () -> @state.booking?.status == 'suspended' isStudentCountered: () -> @counteredSlot()?['is_student_created?'] isTeacherCountered: () -> @counteredSlot()?['is_teacher_created?'] isTestDrive: () -> @state.booking?.lesson_type == 'test-drive' isRecurring: (booking = this.state.booking) -> booking?.recurring lessonLength: () -> @state.booking?.lesson_length lessonDesc: () -> if @isRecurring() lessonType = "weekly recurring #{this.lessonLength()}-minute lesson" else lessonType = "single #{this.lessonLength()}-minute lesson" bookedPrice: () -> price = this.state.booking?.booked_price if price? if typeof price == "string" price = new Number(price) return price.toFixed(2) else return price lessonPaymentAmt: () -> console.log("lessonPaymentAmt") if @state.booking?.payment_style == 'elsewhere' '$10' else if @state.booking?.payment_style == 'single' "$#{this.bookedPrice()}" else if @state.booking?.payment_style == 'weekly' "at $#{this.bookedPrice()} per lesson" else if @state.booking?.payment_style == 'monthly' "monthly at $#{this.bookedPrice()} per month" else "$???" selfLastToAct: () -> if @isRequested() @studentViewing() else if @isCounter() @counterer().id == @myself().id else if @isCanceled() @canceler().id == @myself().id else if @isSuspended() @studentViewing() else false mySlot: (slot) -> slot.proposer_id == context.JK.currentUserId slotsDescription: (defaultSlot, altSlot) -> text = "Preferred day/time for lesson is #{this.slotTime(defaultSlot)}. Secondary option is #{this.slotTime(altSlot)}." slotTime: (slot, booking = this.state.booking) -> if @isRecurring(booking) "#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}" else slot.pretty_start_time slotTimePhrase: (slot) -> if @isRecurring() "each " + @slotTime(slot) else @slotTime(slot) slotMessage: (slot, qualifier = '') -> @messageBlock(slot.mySlot,slot[qualifier + 'message'] ) messageBlock: (selfWroteMessage, message) -> if selfWroteMessage whoSaid = "You said:" else whoSaid = "Message from #{this.other().first_name}:" if message && message.length > 0 description = `
{whoSaid} {message}
` description dayTime: (slot) -> if slot.hour > 11 hour = slot.hour - 12 if hour == 0 hour = 12 am_pm = 'pm' else hour = slot.hour if hour == 0 hour = 12 am_pm = 'am' "#{context.JK.padString(hour.toString(), 2)}:#{context.JK.padString(slot.minute.toString(), 2)}#{am_pm} (#{slot.pretty_timezone})" displayableLesson: () -> lesson = @focusedLesson() if !lesson? lesson = @state.booking.next_lesson lesson isStartingSoonOrNow: () -> # 60 minutes before startTime = new Date(@displayableLesson().scheduled_start).getTime() endTime = (startTime + @lessonLength() * 60) now = new Date().getTime() now > startTime && now < endTime isNow: () -> startTime = new Date(@displayableLesson().scheduled_start).getTime() endTime = (startTime + @lessonLength() * 60) now = new Date().getTime() now > startTime && now < endTime isPast: () -> new Date().getTime() > new Date(@displayableLesson().scheduled_start).getTime() isMissed: () -> @displayableLesson().missed sessionLink: () -> link = "/client#/session/#{this.displayableLesson().music_session_id}" `JOIN SESSION` nextLessonSummaryWithAvatar: () -> `
{this.userHeader(this.other())} {this.nextLessonSummary()}
` nextLessonSummaryRow: () -> if @isActive() if @isNow() data =`

You should join this session immediately: {this.sessionLink()}

` else if @isPast() data =`

This lesson is over.

` else data = `

This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}

` else if @isRequested() data = `

This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}

` `
{data}
` nextLessonSummary: () -> if @isActive() if @isNow() `

You should join this session immediately: {this.sessionLink()}

` else if @isPast() `

This lesson is over.

` else `

This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}

` else if @isRequested() `

This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}

` renderCancelLesson: () -> `

Cancel this Lesson

If you would like to cancel this lesson, you have to do so 24 hours in advance.

CANCEL LESSON
` dayOfWeek: (slot) -> switch slot.day_of_week when 0 then "Sunday" when 1 then "Monday" when 2 then "Tuesday" when 3 then "Wednesday" when 4 then "Thursday" when 5 then "Friday" when 6 then "Saturday" userHeader: (user) -> photo_url = user?.photo_url if !photo_url? photo_url = '/assets/shared/avatar_generic.png' `
{user.name}
` decisionProps: (slots) -> { onSlotDecision: this.onSlotDecision, onUpdateAllDecision: this.onUpdateAllDecision, initial: this.neverAccepted(), counter: this.isCounter(), is_recurring: this.isRecurring(), slot_decision: this.state.slot_decision, update_all: this.state.update_all, slots: slots, otherRole: this.otherRole(), onUserDecision: this.onAccept, onUserCancel: this.onCancel, disabled: this.state.updatingLesson, selfLastToAct: this.selfLastToAct(), counterErrors: this.state.counterErrors, cancelErrors: this.state.cancelErrors, focusedLesson: this.focusedLesson() } render: () -> if @state.updating return @renderLoading() else if @teacherViewing() return @renderTeacher() else if @studentViewing() return @renderStudent() else return @renderLoading() completedHeader: () -> if @isNow() header = 'the lesson is scheduled for right now!' else if @isPast() if @isMissed() header = "this lesson was #{this.displayableLesson().displayStatus.toLowerCase()}" else if @isSuspended() header = 'this lesson was suspended due to billing issues' else header = 'this lesson is over' else header = 'this lesson is over' header approvedHeader: () -> if @isCompleted() if @isMissed() header = "this lesson was #{this.displayableLesson().displayStatus.toLowerCase()}" else if @isSuspended() header = 'this lesson was suspended due to billing issues' else header = 'this lesson is over' else if @isNow() header = 'the lesson is scheduled for right now!' else if @isPast() header = 'this lesson is over' else header = 'this lesson is coming up soon' header renderTeacher: () -> if @isRequested() header = 'respond to lesson request' content = @renderTeacherRequested() else if @isCounter() if @isTeacherCountered() header = 'your proposed alternate day/time is still pending' else header = 'student has proposed an alternate day/time' content = @renderTeacherCountered() else if @isApproved() header = @approvedHeader() content = @renderTeacherApproved() else if @isCompleted() header = @completedHeader() content = @renderTeacherComplete() else if @isCanceled() header = "this lesson was #{this.displayableLesson()?.displayStatus?.toLowerCase()}" content = @renderTeacherCanceled() else if @isSuspended() header = 'This lesson has been suspended' content = @renderTeacherSuspended() return `
` renderStudent: () -> if @isRequested() header = 'your lesson has been requested' content = @renderStudentRequested() else if @isCounter() if @isTeacherCountered() header = 'teacher has proposed an alternate day/time' else header = 'your proposed alternate day/time is still pending' content = @renderTeacherCountered() else if @isApproved() header = @approvedHeader() content = @renderStudentApproved() else if @isCompleted() header = @completedHeader() content = @renderStudentComplete() else if @isCanceled() if @neverAccepted() && @studentViewing() && !@studentCanceled() header = "we're sorry, but your lesson request has been declined" else header = "this lesson was #{this.displayableLesson()?.displayStatus?.toLowerCase()}" content = @renderStudentCanceled() else if @isSuspended() header = 'this lesson has been suspended' content = @renderStudentSuspended() `
` renderLoading: () -> header = 'Loading ...' `
` renderStudentRequested: () -> `
{this.userHeader(this.myself())} Your request has been sent. You will receive an email when {this.teacher().name} responds.
` joinSessionNow: (e) -> e.preventDefault() SessionActions.enterSession(@displayableLesson().music_session.id) updateCreditCard: (e) -> window.location.href="/client#/account/paymentHistory" renderStudentComplete: () -> @renderStudentApproved() renderTeacherComplete: () -> @renderTeacherApproved() renderStudentApproved: () -> if @isCompleted() if @isMissed() whatNow = null summary = `
{this.userHeader(this.displayableLesson().missedUser)}

This lesson was missed by the {this.displayableLesson().missedRole}.

` else if @isSuspended() whatNow = `

What Now?

You should update your credit card info. update credit card

` else summary = null else if @isNow() if @studentMadeDefaultSlot() message = this.slotMessage(this.state.booking.default_slot, 'accept') if @isRecurring() detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` else detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` summary = `
{this.userHeader(this.teacher())}

Has accepted your lesson request.

{detail} {message}
` whatNow = `

What Now?

You should join the lesson session as soon as possible. join session now

{this.nextLessonSummary()}
` else if @isPast() if @studentMadeDefaultSlot() message = this.slotMessage(this.state.booking.default_slot, 'accept') if @isRecurring() detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` else detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` summary = `
{this.userHeader(this.teacher())}

Has accepted your lesson request.

{detail} {message}
` else decision = `` `
{summary} {whatNow} {decision}
` renderStudentCanceled: () -> @renderCanceled() renderStudentSuspended: () -> #

Message from {this.other().first_name}:

#
renderStudentCountered: () -> @renderCountered() renderTeacherRequested: () -> if @isTestDrive() action = `

Has requested a TestDrive {this.lessonLength()}-minute lesson, for which you will be paid $10.

` else action = `

Has requested a {this.lessonDesc()} lesson, for which you will be paid {this.lessonPaymentAmt()}.

` `
{this.userHeader(this.other())} {action} {this.slotMessage(this.state.booking.default_slot)}
` renderTeacherApproved: () -> if @isCompleted() if @isMissed() whatNow = null summary = `
{this.userHeader(this.displayableLesson().missedUser)}

This lesson was missed by the {this.displayableLesson().missedRole}.

` else if @isSuspended() whatNow = `

What Now?

You should update your credit card info. update credit card

` else summary = null else if @isNow() if @studentMadeDefaultSlot() message = this.slotMessage(this.state.booking.default_slot, 'accept') if @isRecurring() detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` else detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` summary = `
{this.userHeader(this.student())}

Is ready to take the lesson.

{detail} {message}
` whatNow = `

What Now?

You should join the lesson session as soon as possible. join session now

{this.nextLessonSummary()}
` else if @isPast() else decision = `` `
{summary} {whatNow} {decision}
` renderTeacherCanceled: () -> @renderCanceled() renderTeacherSuspended: () -> renderTeacherCountered: () -> @renderCountered() renderCanceled: () -> canceler = @canceler() myself = @myself() initial = @neverAccepted() if initial if @studentViewing() if @studentCanceled() action = `

You canceled this lesson request.

` else action = `

Has declined your lesson request.

` else if @studentCanceled() action = `

Has canceled this lesson request.

` else action = `

You declined this lesson request.

` if @studentViewing() if @studentCanceled() blurb = `

We're sorry this scheduling attempt did not working out for you. Please search our community of instructors to find someone else who looks like a good fit for you, and submit a new lesson request

` else blurb = `

We're sorry this instructor has declined your request. Please search our community of instructors to find someone else who looks like a good fit for you, and submit a new lesson request

` whatNow = `

What Now?

{blurb} SEARCH INSTRUCTORS NOW
` `
{this.userHeader(canceler)} {action} {this.messageBlock(this.selfCanceled(), this.state.booking.cancel_message)}
{whatNow}
` renderCountered: () -> counterer = @counterer() myself = @myself() phrase = this.slotTimePhrase(this.counteredSlot()) action = `

Has suggested a different time for your lesson.

` detail = `

Proposed alternate day/time is {phrase}

` `
{this.userHeader(counterer)} {action} {detail} {this.slotMessage(this.counteredSlot())}
` })