2016-05-30 21:43:55 +00:00
2016-04-06 02:23:15 +00:00
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 ( )
2016-07-12 02:38:08 +00:00
state = { }
state [ ' lesson-frequency ' ] = value
@ setState ( state )
2016-04-06 02:23:15 +00:00
componentDidMount: () ->
2016-07-12 02:38:08 +00:00
@checkboxes = [ { selector: ' input.lesson-freq-field ' , stateKey: ' lesson-frequency ' } ]
2016-04-06 02:23:15 +00:00
@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: () ->
2016-05-05 02:20:38 +00:00
@ state . type ? . indexOf ( ' test-drive ' ) == 0
2016-04-06 02:23:15 +00:00
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
2017-03-22 12:39:06 +00:00
#school_on_school = response.teacher.school_id? && @state.user?.school_id? && response.teacher.school_id == @state.user.school_id && !response.teacher.school.education
2017-07-10 02:21:29 +00:00
@ setState ( { teacher: response , isSelf: response . id == context . JK . currentUserId } )
2016-04-06 02:23:15 +00:00
else
logger . debug ( " BookLesson: ignoring teacher details " , response . id , @ state . teacherId )
getInitialState: () ->
2016-07-12 02:38:08 +00:00
state = {
2016-04-06 02:23:15 +00:00
user: null ,
teacher: null ,
teacherId: null ,
generalErrors: null ,
descriptionErrors: null ,
bookedPriceErrors: null ,
slot1Errors: null ,
slot2Errors: null
updating: false ,
recurring: ' single '
}
2016-07-12 02:38:08 +00:00
state [ ' lesson-frequency ' ] = ' single '
state
2016-04-06 02:23:15 +00:00
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: () ->
2016-07-12 02:38:08 +00:00
@ state [ ' lesson-frequency ' ] == ' recurring '
#@state.recurring == 'recurring'
2016-04-06 02:23:15 +00:00
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 ( )
2016-04-21 14:23:29 +00:00
logger . debug ( " user requested to book lesson " )
2016-04-06 02:23:15 +00:00
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 "
2016-04-21 14:23:29 +00:00
logger . debug ( " lesson booking data: " + JSON . stringify ( options ) )
2016-04-06 02:23:15 +00:00
@ resetErrors ( )
@ setState ( { updating: true } )
rest . bookLesson ( options ) . done ( (response) => @ booked ( response ) ) . fail ( (jqXHR) => @ failedBooking ( jqXHR ) )
booked: (response) ->
@ setState ( { updating: false } )
2016-04-21 20:29:40 +00:00
UserActions . refresh ( )
2017-03-22 12:39:06 +00:00
#if response.user['has_stored_credit_card?'] || @state.school_on_school || response.posa_card_id?
2017-07-10 02:21:29 +00:00
if response . user [ ' has_stored_credit_card? ' ] || ( response . posa_card_id ? && response . posa_card_purchased )
2016-05-19 18:26:37 +00:00
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. " )
2016-04-25 13:09:00 +00:00
url = " /client # /jamclass/lesson-booking/ #{ response . id } "
url = " /client # /jamclass "
context.location = url
2016-04-06 02:23:15 +00:00
else
2016-04-21 14:23:29 +00:00
context.location = " /client # /jamclass/lesson-payment/lesson-booking_ #{ response . id } "
2016-04-06 02:23:15 +00:00
failedBooking: (jqXHR) ->
@ setState ( { updating: false } )
if jqXHR . status == 422
2016-04-21 14:23:29 +00:00
logger . debug ( " unable to book lesson: " + jqXHR . responseText )
2016-04-06 02:23:15 +00:00
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 ( )
2016-05-20 02:49:54 +00:00
window . history . go ( - 1 ) ;
2016-04-06 02:23:15 +00:00
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 ) } "
2016-07-12 02:38:08 +00:00
results . push ( ` < option key = { value } value = { value } > { display } < / option > ` )
2016-04-06 02:23:15 +00:00
else
for minutes in enabledMinutes
lesson_price = teacher [ " price_per_lesson_ #{ minutes } _cents " ]
2017-07-10 02:21:29 +00:00
if lesson_price
value = " single| #{ minutes } "
display = " #{ minutes } Minute Lesson Each Week - $ #{ ( lesson_price / 100 ) . toFixed ( 2 ) } Per Week "
2017-07-17 00:38:40 +00:00
#results.push(`<option key={value} value={value}>{display}</option>`) # THESE are WEEKLY. CANT DO IT YET DUE TO LACK OF QA OF PAYMENT STYLE WEEKLY
2016-04-06 02:23:15 +00:00
for minutes in enabledMinutes
monthly_price = teacher [ " price_per_month_ #{ minutes } _cents " ]
2017-07-10 02:21:29 +00:00
if monthly_price
value = " monthly| #{ minutes } "
display = " #{ minutes } Minute Lesson Each Week - $ #{ ( monthly_price / 100 ) . toFixed ( 2 ) } Per Month "
results . push ( ` < option value = { value } > { display } < / option > ` )
2016-04-06 02:23:15 +00:00
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: () ->
2016-05-05 02:20:38 +00:00
teacher = @ state . teacher
2016-04-06 02:23:15 +00:00
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 > ` ]
2017-07-10 02:21:29 +00:00
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 } )
2016-04-06 02:23:15 +00:00
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 >
2016-04-21 14:23:29 +00:00
< input className = " date-picker " name = " slot-1-date " type = " text " data - slot = " 1 " > < / input >
2016-04-06 02:23:15 +00:00
< / 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 >
2018-02-26 00:01:19 +00:00
< span className = " tz-notice " > { window . JK . currentTimezone ( ) } < / span >
2016-04-06 02:23:15 +00:00
< / 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 >
2016-04-21 14:23:29 +00:00
< input className = " date-picker " name = " slot-2-date " type = " text " data - slot = " 2 " > < / input >
2016-04-06 02:23:15 +00:00
< / 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 >
2018-02-26 00:01:19 +00:00
< span className = " tz-notice " > { window . JK . currentTimezone ( ) } < / span >
2016-04-06 02:23:15 +00:00
< / div >
2018-02-26 00:01:19 +00:00
2016-04-06 02:23:15 +00:00
{ 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 >
2016-04-25 15:15:23 +00:00
< select name = " day-of-week-1 " className = " day_of_week " data - slot = " 1 " > { days } < / select >
2016-04-06 02:23:15 +00:00
< / 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 >
2016-04-25 15:15:23 +00:00
< select name = " day-of-week-2 " className = " day_of_week " data - slot = " 2 " > { days } < / select >
2016-04-06 02:23:15 +00:00
< / 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 ( )
2016-10-03 02:51:34 +00:00
credits = this . state . user . remaining_test_drives
if this . state . user . jamclass_credits > 0
credits = this . state . user . jamclass_credits
2016-04-06 02:23:15 +00:00
header = ` < h2 > book testdrive lesson < / h2 > `
2016-10-03 02:51:34 +00:00
if credits == 1
2016-04-06 02:23:15 +00:00
testDriveLessons = " 1 TestDrive lesson credit "
else
2016-10-03 02:51:34 +00:00
testDriveLessons = " #{ credits } TestDrive lesson credits "
2016-04-06 02:23:15 +00:00
actions = ` < div className = " actions left " >
< a className = { cancelClasses } onClick = { this . onCancel } > CANCEL < / a >
< a className = { bookLessonClasses } onClick = { this . onBookLesson } > BOOK TESTDRIVE LESSON < / a >
< / div > `
2016-05-05 02:20:38 +00:00
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
2016-10-03 02:51:34 +00:00
if credits > 0
2016-05-05 02:20:38 +00:00
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 > `
2016-04-06 02:23:15 +00:00
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 >
2016-04-21 14:23:29 +00:00
< textarea name = " user-description " className = " user-description " defaultValue = " " / >
2016-04-06 02:23:15 +00:00
{ 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 >
2016-05-05 02:20:38 +00:00
{ testDriveBookingInfo }
2016-04-06 02:23:15 +00:00
< / 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 " >
2016-07-12 02:38:08 +00:00
< input type = " radio " className = " lesson-freq-field " name = " lesson-frequency " value = " single " checked = { ! this . isRecurring ( ) } readOnly = { true } / > < label htmlFor = " lesson-frequency " > A single lesson < / label >
2016-04-06 02:23:15 +00:00
< / div >
< div className = " field lesson-frequency lesson-frequency-recurring " >
2016-07-12 02:38:08 +00:00
< input type = " radio " className = " lesson-freq-field " name = " lesson-frequency " value = " recurring " checked = { this . isRecurring ( ) } readOnly = { true } / > < label htmlFor = " lesson-frequency " > A series of recurring weekly lessons < / label >
2016-04-06 02:23:15 +00:00
< / 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 >
2016-04-21 14:23:29 +00:00
< textarea name = " user-description " className = " user-description " defaultValue = " " / >
2016-04-06 02:23:15 +00:00
{ descriptionErrors }
< / div >
< / div > `
columnRight =
` < div className = " column column-right " >
{ slots }
< br className = " clearall " / >
< div className = { generalClasses } >
{ generalErrors }
< / div >
< / div > `
` < div className = " content-body-scroller " >
2016-04-21 15:51:04 +00:00
< Nav / >
2016-04-06 02:23:15 +00:00
< div className = " lesson-booking " >
{ columnLeft }
{ columnRight }
< br className = " clearall " / >
{ outActions }
< br className = " clearall " / >
< / div >
< / div > `
} )