context = window rest = context.JK.Rest() logger = context.JK.logger UserStore = context.UserStore @LessonBooking = React.createClass({ mixins: [ 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}) 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) updateBookingState: (booking) -> if booking.counter_slot? startSlotDecision = booking.counter_slot.id else if booking.accepter_id? startSlotDecision = 'counter' else startSlotDecision = booking.default_slot.id @setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false}) 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.update_all = true 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.update_all = true 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) cancelLessonBookingDone: (response ) -> logger.debug("cancel lesson booking done") @updateBookingState(response) counterLessonBookingDone: (response ) -> logger.debug("counter lesson booking done") @updateBookingState(response) 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) @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() 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} 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: () -> @state.booking? && @state.booking.teacher_id == context.JK.currentUserId studentViewing: () -> @state.booking? && @state.booking.user_id == context.JK.currentUserId isActive: () -> @state.booking?.active == true isRequested: () -> @state.booking?.status == 'requested' && !@isCounter() isCounter: () -> @counteredSlot()? && !@isCanceled() && !@isSuspended() isInitialCounter: () -> @isCounter() && !@isActive() isActiveCounter: () -> @isCounter() && @isActive() 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: () -> this.state.booking?.booked_price 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.timezone})" isStartingSoonOrNow: () -> # 60 minutes before startTime = new Date(@state.booking.next_lesson.scheduled_start).getTime() endTime = (startTime + @lessonLength() * 60) now = new Date().getTime() now > startTime && now < endTime isNow: () -> startTime = new Date(@state.booking.next_lesson.scheduled_start).getTime() endTime = (startTime + @lessonLength() * 60) now = new Date().getTime() now > startTime && now < endTime isPast: () -> new Date().getTime() > new Date(@state.booking.next_lesson.scheduled_start).getTime() sessionLink: () -> link = "/client#/session/#{this.stateb.booking.next_lesson.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.state.booking.next_lesson.pretty_scheduled_start}

` else if @isRequested() data = `

This lesson is scheduled to start at {this.state.booking.next_lesson.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.state.booking.next_lesson.pretty_scheduled_start}

` else if @isRequested() `

This lesson is scheduled to start at {this.state.booking.next_lesson.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, initial: this.neverAccepted(), counter: this.isCounter(), is_recurring: this.isRecurring(), slot_decision: this.state.slot_decision, 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 } render: () -> if @state.updating @renderLoading() else if @teacherViewing() @renderTeacher() else if @studentViewing() @renderStudent() else @renderLoading() renderTeacher: () -> if @isRequested() header = 'respond to lesson request' content = @renderTeacherRequested() 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() 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' content = @renderTeacherApproved() else if @isCanceled() header = 'this lesson is canceled' content = @renderTeacherCanceled() else if @isSuspended() header = 'This lesson has been suspended' content = @renderTeacherSuspended() `
` renderStudent: () -> if @isRequested() header = 'your lesson has been requested' content = @renderStudentRequested() 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() 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' content = @renderStudentApproved() else if @isCanceled() if @neverAccepted() && @studentViewing() && !@studentCanceled() header = "we're sorry, but your lesson request has been declined" else header = 'this lesson is canceled' 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.
` renderStudentApproved: () -> 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)}

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

Has accepted your lesson request.

{detail} {message}

What Now?

We strongly recommending adding this lesson to your calendar now so you don't forget it!

You can do this manually today; we will soon add a easier way to do so automatically.

{this.nextLessonSummary()}
` 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: () -> `
{this.nextLessonSummaryWithAvatar()}
` 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())}
` })