jam-cloud/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee

558 lines
19 KiB
CoffeeScript

context = window
rest = context.JK.Rest()
logger = context.JK.logger
UserStore = context.UserStore
@BookLesson = React.createClass({
mixins: [
ICheckMixin,
Reflux.listenTo(AppStore, "onAppInit"),
Reflux.listenTo(UserStore, "onUserChanged")
]
onAppInit: (@app) ->
@app.bindScreen('jamclass/book-lesson',
{beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide})
onUserChanged: (userState) ->
@setState({user: userState?.user})
checkboxChanged: (e) ->
checked = $(e.target).is(':checked')
value = $(e.target).val()
@setState({ recurring: value })
componentDidMount: () ->
@checkboxes = [{selector: 'input.lesson-frequency', stateKey: 'lesson-frequency'}]
@root = $(@getDOMNode())
@slot1Date = @root.find('.slot-1 .date-picker')
@slot2Date = @root.find('.slot-2 .date-picker')
@slot1Date.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
@slot2Date.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
@iCheckify()
componentDidUpdate:() ->
@iCheckify()
@slot1Date = @root.find('.slot-1 .date-picker')
@slot2Date = @root.find('.slot-2 .date-picker')
@slot1Date.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
@slot2Date.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
toggleDate: (e) ->
isNormal: () ->
@state.type == 'normal'
isTestDrive: () ->
@state.type?.indexOf('test-drive') == 0
parseId:(id) ->
if !id?
{id: null, type: null}
else
bits = id.split('_')
if bits.length == 2
{id: bits[1], type: bits[0]}
else
{id: null, type: null}
beforeHide: (e) ->
logger.debug("BookLesson: beforeHide")
@resetErrors()
beforeShow: (e) ->
afterShow: (e) ->
logger.debug("BookLesson: afterShow", e.id)
parsed = @parseId(e.id)
id = parsed.id
@setState({teacherId: id, type: parsed.type})
@resetErrors()
rest.getUserDetail({
id: id,
show_teacher: true
}).done((response) => @userDetailDone(response)).fail(@app.ajaxError)
userDetailDone: (response) ->
if response.id == @state.teacherId
school_on_school = response.teacher.school_id? && @state.user?.school_id? && response.teacher.school_id == @state.user.school_id
@setState({teacher: response, isSelf: response.id == context.JK.currentUserId, school_on_school: school_on_school})
else
logger.debug("BookLesson: ignoring teacher details", response.id, @state.teacherId)
getInitialState: () ->
{
user: null,
teacher: null,
teacherId: null,
generalErrors: null,
descriptionErrors: null,
bookedPriceErrors: null,
slot1Errors: null,
slot2Errors: null
updating: false,
recurring: 'single'
}
jamclassPolicies: (e) ->
e.preventDefault()
context.JK.popExternalLink($(e.target).attr('href'))
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}
resetErrors: () ->
@setState({generalErrors: null, slot1Errors: null, slot2Errors: null, descriptionErrors: null, bookedPriceErrors: null})
isRecurring: () ->
@state.recurring == 'recurring'
isMonthly: () ->
if !@isRecurring()
return false
parsed = @bookingOption()
return parsed? && parsed.frequency == 'monthly'
bookingOption: () ->
select = @root.find('.booking-options-for-teacher')
value = select.val()
@parseBookingOption(value)
# select format = frequency|lesson_length , where frequency is 'monthly' or 'weekly'
parseBookingOption: (value) ->
if !value?
return null
bits = value.split('|')
if !bits? || bits.length != 2
return null
return {frequency: bits[0], lesson_length: bits[1]}
onBookLesson: (e) ->
e.preventDefault()
logger.debug("user requested to book lesson")
if $(e.target).is('.disabled')
return
options = {}
options.teacher = this.state.teacher.id
options.slots = [@getSlotData(0), @getSlotData(1)]
options.timezone = window.jstz.determine().name()
description = @root.find('textarea.user-description').val()
if description == ''
description == null
options.description = description
if @isTestDrive()
options.payment_style = 'elsewhere'
options.lesson_type = 'test-drive'
else if @isNormal()
options.lesson_type = 'paid'
if @isRecurring()
if @isMonthly()
options.payment_style = 'monthly'
else
options.payment_style = 'weekly'
else
options.payment_style = 'single'
options.recurring = @isRecurring()
parsed = @bookingOption()
if parsed?
options.lesson_length = parsed.lesson_length
else
throw "Unable to determine lesson type"
logger.debug("lesson booking data: " + JSON.stringify(options))
@resetErrors()
@setState({updating: true})
rest.bookLesson(options).done((response) => @booked(response)).fail((jqXHR) => @failedBooking(jqXHR))
booked: (response) ->
@setState({updating: false})
UserActions.refresh()
if response.user['has_stored_credit_card?'] || @state.school_on_school
context.JK.Banner.showNotice("Lesson Requested","The teacher has been notified of your lesson request, and should respond soon.<br/><br/>We've taken you back to the JamClass home page, where you can check the status of this lesson, as well as any other past and future lessons.")
url = "/client#/jamclass/lesson-booking/#{response.id}"
url = "/client#/jamclass"
context.location = url
else
context.location = "/client#/jamclass/lesson-payment/lesson-booking_#{response.id}"
failedBooking: (jqXHR) ->
@setState({updating: false})
if jqXHR.status == 422
logger.debug("unable to book lesson: " + jqXHR.responseText)
body = JSON.parse(jqXHR.responseText)
generalErrors = {errors: {}}
for errorType, errors of body.errors
if errorType == 'description'
@setState({descriptionErrors: errors})
else if errorType == 'booked_price'
@setState({bookedPriceErrors: errors})
else if errorType == 'lesson_length'
# swallow, because 'booked_price' covers this
else if errorType == 'lesson_booking_slots'
# do nothing. these are handled better by the _children errors
else
generalErrors.errors[errorType] = errors
for childErrorType, childErrors of body._children
if childErrorType == 'lesson_booking_slots'
slot1Errors = childErrors[0]
slot2Errors = childErrors[1]
if Object.keys(slot1Errors["errors"]).length > 0
@setState({slot1Errors: slot1Errors})
if Object.keys(slot2Errors["errors"]).length > 0
@setState({slot2Errors: slot2Errors})
if Object.keys(generalErrors.errors).length > 0
@setState({generalErrors: generalErrors})
onCancel: (e) ->
e.preventDefault()
window.history.go(-1);
isNormal: () ->
@state.type == 'normal'
constructBookingOptions: () ->
results = []
if !@state.teacher?
return results
teacher = @state.teacher.teacher
enabledMinutes = []
for minutes in [30, 45, 60, 90, 120]
duration_enabled = teacher["lesson_duration_#{minutes}"]
if duration_enabled
enabledMinutes.push(minutes)
if !@isRecurring()
for minutes in enabledMinutes
lesson_price = teacher["price_per_lesson_#{minutes}_cents"]
value = "single|#{minutes}"
display = "#{minutes} Minute Lesson for $#{(lesson_price / 100).toFixed(2)}"
results.push(`<option value={value}>{display}</option>`)
else
for minutes in enabledMinutes
lesson_price = teacher["price_per_lesson_#{minutes}_cents"]
value = "single|#{minutes}"
display = "#{minutes} Minute Lesson Each Week - $#{(lesson_price / 100).toFixed(2)} Per Week"
results.push(`<option value={value}>{display}</option>`)
for minutes in enabledMinutes
monthly_price = teacher["price_per_month_#{minutes}_cents"]
value = "monthly|#{minutes}"
display = "#{minutes} Minute Lesson Each Week - $#{(monthly_price / 100).toFixed(2)} Per Month"
results.push(`<option value={value}>{display}</option>`)
if results.length == 0
results.push(`<option value=''>This teacher has no pricing options</option>`)
else
results.unshift(`<option value=''>Please choose an option...</option>`)
results
render: () ->
teacher = @state.teacher
photo_url = teacher?.photo_url
if !photo_url?
photo_url = '/assets/shared/avatar_generic.png'
if teacher?
name = `<div className="teacher-name">{teacher.name}</div>`
teacher_first_name = teacher.first_name
else
name = `<div className="teacher-name">Loading...</div>`
teacher_first_name = '...'
hours = []
for hour in ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
if hour == '12'
key = '00'
else
key = hour
hours.push(`<option key={key} value={key}>{hour}</option>`)
minutes = []
for minute in ['00', '15', '30', '45']
minutes.push(`<option key={minute} value={minute}>{minute}</option>`)
am_pm = [`<option key="AM" value="AM">AM</option>`, `<option key="PM" value="PM">PM</option>`]
bookLessonClasses = classNames({"button-orange": true, 'book-lesson-btn': true, disabled: !this.state.teacher? && !@state.updating})
cancelClasses = classNames({"button-grey": true, disabled: !this.state.teacher? && !@state.updating})
descriptionErrors = context.JK.reactSingleFieldErrors('description', @state.descriptionErrors)
bookedPriceErrors = context.JK.reactSingleFieldErrors('booked_price', @state.bookedPriceErrors)
slot1Errors = context.JK.reactErrors(@state.slot1Errors, {preferred_day: 'Date', day_of_week: 'Day'})
slot2Errors = context.JK.reactErrors(@state.slot2Errors, {preferred_day: 'Date', day_of_week: 'Day'})
generalErrors = context.JK.reactErrors(@state.generalErrors, {user: 'You'})
bookedPriceClasses = classNames({booked_price: true, error: bookedPriceErrors?, field: true, 'booking-options': true})
descriptionClasses = classNames({description: true, error: descriptionErrors?})
slot1Classes = classNames({slot: true, 'slot-1': true, error: slot1Errors?})
slot2Classes = classNames({slot: true, 'slot-2': true, error: slot2Errors?})
generalClasses = classNames({general: true, error: generalErrors?})
if !@isRecurring()
slots =
`<div className="slots">
<div className={slot1Classes}>
<div className="slot-prompt">What date/time do you prefer for your lesson?</div>
<div className="field date">
<label>Date:</label>
<input className="date-picker" name="slot-1-date" type="text" data-slot="1"></input>
</div>
<div className="field time">
<label>Time:</label>
<select className="hour">{hours}</select> : <select className="minute">{minutes}</select> <select
className="am_pm">{am_pm}</select>
</div>
{slot1Errors}
</div>
<div className={slot2Classes}>
<div className="slot-prompt">What is a second date/time option if preferred not available?</div>
<div className="field date">
<label>Date:</label>
<input className="date-picker" name="slot-2-date" type="text" data-slot="2"></input>
</div>
<div className="field time">
<label>Time:</label>
<select className="hour">{hours}</select> : <select className="minute">{minutes}</select> <select
className="am_pm">{am_pm}</select>
</div>
{slot2Errors}
</div>
</div>`
else
days = []
days.push(`<option value=''>Choose a day of the week...</option>`)
days.push(`<option value="0">Sunday</option>`)
days.push(`<option value="1">Monday</option>`)
days.push(`<option value="2">Tuesday</option>`)
days.push(`<option value="3">Wednesday</option>`)
days.push(`<option value="4">Thursday</option>`)
days.push(`<option value="5">Friday</option>`)
days.push(`<option value="6">Saturday</option>`)
slots =
`<div className="slots">
<div className={slot1Classes}>
<div className="slot-prompt">What day/time do you prefer for your lesson?</div>
<div className="field date">
<label>Day:</label>
<select name="day-of-week-1" className="day_of_week" data-slot="1">{days}</select>
</div>
<div className="field time">
<label>Time:</label>
<select className="hour">{hours}</select> : <select className="minute">{minutes}</select> <select
className="am_pm">{am_pm}</select>
</div>
{slot1Errors}
</div>
<div className={slot2Classes}>
<div className="slot-prompt">What is a second day/time option if preferred not available?</div>
<div className="field date">
<label>Day:</label>
<select name="day-of-week-2" className="day_of_week" data-slot="2">{days}</select>
</div>
<div className="field time">
<label>Time:</label>
<select className="hour">{hours}</select> : <select className="minute">{minutes}</select> <select
className="am_pm">{am_pm}</select>
</div>
{slot2Errors}
</div>
</div>`
if @isTestDrive()
header = `<h2>book testdrive lesson</h2>`
if @state.user?.remaining_test_drives == 1
testDriveLessons = "1 TestDrive lesson credit"
else
testDriveLessons = "#{this.state.user.remaining_test_drives} TestDrive lesson credits"
actions = `<div className="actions left">
<a className={cancelClasses} onClick={this.onCancel}>CANCEL</a>
<a className={bookLessonClasses} onClick={this.onBookLesson}>BOOK TESTDRIVE LESSON</a>
</div>`
testDriveCredits = 1
if this.state.user.lesson_package_type_id == 'test-drive'
testDriveCredits = 4
else if this.state.user.lesson_package_type_id == 'test-drive-1'
testDriveCredits = 1
else if this.state.user.lesson_package_type_id == 'test-drive-2'
testDriveCredits = 2
if this.state.user.remaining_test_drives > 0
testDriveBookingInfo = `<div className="booking-info">
<p>You are booking a single 30-minute TestDrive session.</p>
<p>You currently have {testDriveLessons} available. If you need to cancel, you must cancel at least 24 hours before the lesson is scheduled to start, or you will be charged 1 TestDrive lesson credit.<br/>
<div className="jamclass-policies"><a href="/corp/terms" rel="external" onClick={this.jamclassPolicies}>jamclass
policies</a></div>
</p>
</div>`
else
testDriveBookingInfo = `<div className="booking-info">
<p>You are booking a single 30-minute TestDrive session.</p>
<p>Once payment is entered on the next screen, the teacher will be notified, and this lesson will then use 1 of {testDriveCredits} TestDrive credits. If you need to cancel, you must cancel at least 24 hours before the lesson is scheduled to start, or you will be charged 1 TestDrive lesson credit.<br/>
<div className="jamclass-policies"><a href="/corp/terms" rel="external" onClick={this.jamclassPolicies}>jamclass
policies</a></div>
</p>
</div>`
columnLeft = `<div className="column column-left">
{header}
{slots}
<div className={descriptionClasses}>
<div className="description-prompt">Tell {teacher_first_name} a little about yourself as a student.</div>
<textarea name="user-description" className="user-description" defaultValue=""/>
{descriptionErrors}
</div>
{actions}
<br className="clearall"/>
<div className={generalClasses}>
{generalErrors}
</div>
</div>`
columnRight = `<div className="column column-right">
<div className="teacher-header">
<div className="avatar">
<img src={photo_url}/>
</div>
{name}
</div>
{testDriveBookingInfo}
</div>`
else if @isNormal()
bookingOptionsForTeacher = @constructBookingOptions()
header = `<h2>book a lesson with {teacher_first_name}</h2>`
outActions = `<div className="actions right ">
<a className={cancelClasses} onClick={this.onCancel}>CANCEL</a>
<a className={bookLessonClasses} onClick={this.onBookLesson}>BOOK LESSON</a>
</div>`
columnLeft = `<div className="column column-left">
{header}
<div className="lesson-frequency">
<div className="field lesson-frequency lesson-frequency-single">
<input type="radio" name="lesson-frequency" value="single" checked={!this.isRecurring()}/><label htmlFor="lesson-frequency">A single lesson</label>
</div>
<div className="field lesson-frequency lesson-frequency-recurring">
<input type="radio" name="lesson-frequency" value="recurring" checked={this.isRecurring()}/><label htmlFor="lesson-frequency">A series of recurring weekly lessons</label>
</div>
</div>
<div className={bookedPriceClasses}>
<label>What lesson length and payment option do you prefer?</label>
<select name="booking-options-for-teacher" className="booking-options-for-teacher">{bookingOptionsForTeacher}</select>
{bookedPriceErrors}
</div>
<div className={descriptionClasses}>
<div className="description-prompt">Tell {teacher_first_name} a little about yourself as a student.</div>
<textarea name="user-description" className="user-description" defaultValue=""/>
{descriptionErrors}
</div>
</div>`
columnRight =
`<div className="column column-right">
{slots}
<br className="clearall"/>
<div className={generalClasses}>
{generalErrors}
</div>
</div>`
`<div className="content-body-scroller">
<Nav/>
<div className="lesson-booking">
{columnLeft}
{columnRight}
<br className="clearall"/>
{outActions}
<br className="clearall"/>
</div>
</div>`
})