275 lines
9.1 KiB
CoffeeScript
275 lines
9.1 KiB
CoffeeScript
context = window
|
|
rest = context.JK.Rest()
|
|
logger = context.JK.logger
|
|
|
|
UserStore = context.UserStore
|
|
|
|
@BookLessonFree = React.createClass({
|
|
|
|
mixins: [
|
|
Reflux.listenTo(AppStore, "onAppInit"),
|
|
Reflux.listenTo(UserStore, "onUserChanged")
|
|
]
|
|
|
|
onAppInit: (@app) ->
|
|
@app.bindScreen('jamclass/book-lesson-free',
|
|
{beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide})
|
|
|
|
onUserChanged: (userState) ->
|
|
@setState({user: userState?.user})
|
|
|
|
componentDidMount: () ->
|
|
@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))
|
|
})
|
|
|
|
toggleDate: (e) ->
|
|
|
|
beforeHide: (e) ->
|
|
logger.debug("LessonBookingFree: beforeHide")
|
|
@resetErrors()
|
|
|
|
beforeShow: (e) ->
|
|
logger.debug("LessonBookingFree: beforeShow", e.id)
|
|
|
|
afterShow: (e) ->
|
|
logger.debug("LessonBookingFree: afterShow", e.id)
|
|
|
|
@setState({teacherId: e.id})
|
|
@resetErrors()
|
|
rest.getUserDetail({
|
|
id: e.id,
|
|
show_teacher: true
|
|
}).done((response) => @userDetailDone(response)).fail(@app.ajaxError)
|
|
|
|
userDetailDone: (response) ->
|
|
if response.id == @state.teacherId
|
|
@setState({teacher: response, isSelf: response.id == context.JK.currentUserId})
|
|
else
|
|
logger.debug("BookLessonFree: ignoring teacher details", response.id, @state.teacherId)
|
|
|
|
getInitialState: () ->
|
|
{
|
|
user: null,
|
|
teacher: null,
|
|
teacherId: null,
|
|
generalErrors: null,
|
|
descriptionErrors: null,
|
|
slot1Errors: null,
|
|
slot2Errors: null
|
|
updating: false
|
|
}
|
|
|
|
jamclassPolicies: (e) ->
|
|
e.preventDefault()
|
|
context.JK.popExternalLink($(e.target).attr('href'))
|
|
|
|
getSlotData: (position) ->
|
|
$slot = @root.find('.slot-' + (position + 1))
|
|
picker = $slot.find('.date-picker')
|
|
|
|
date = picker.datepicker("getDate")
|
|
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
|
|
hour -= 1
|
|
else
|
|
hour = null
|
|
|
|
if minute? and minute != ''
|
|
minute = new Number(minute)
|
|
else
|
|
minute = null
|
|
|
|
day = null
|
|
if date?
|
|
day = context.JK.formatDateYYYYMMDD(date)
|
|
|
|
{hour: hour, minute: minute, day: day}
|
|
|
|
resetErrors: () ->
|
|
@setState({generalErrors: null, slot1Errors: null, slot2Errors: null, descriptionErrors: null})
|
|
|
|
onBookFreeLesson: (e) ->
|
|
e.preventDefault()
|
|
|
|
if $(e.target).is('.disabled')
|
|
return
|
|
|
|
options = {}
|
|
options.teacher = this.state.teacher.id
|
|
options.payment_style = 'elsewhere'
|
|
options.lesson_type = 'single-free'
|
|
options.slots = [@getSlotData(0), @getSlotData(1)]
|
|
options.timezone = Ajstz.determine().name()
|
|
description = @root.find('textarea.user-description').val()
|
|
if description == ''
|
|
description == null
|
|
options.description = description
|
|
|
|
@resetErrors()
|
|
@setState({updating: true})
|
|
rest.bookLesson(options).done((response) => @booked(response)).fail((jqXHR) => @failedBooking(jqXHR))
|
|
|
|
booked: (response) ->
|
|
@setState({updating: false})
|
|
if response.user['has_stored_credit_card?']
|
|
context.location = '/client#/home'
|
|
else
|
|
context.location = '/client#/jamclass/payment'
|
|
|
|
failedBooking: (jqXHR) ->
|
|
@setState({updating: false})
|
|
if jqXHR.status == 422
|
|
body = JSON.parse(jqXHR.responseText)
|
|
|
|
generalErrors = {errors: {}}
|
|
for errorType, errors of body.errors
|
|
if errorType == 'description'
|
|
@setState({descriptionErrors: errors})
|
|
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()
|
|
|
|
render: () ->
|
|
photo_url = teacher?.photo_url
|
|
if !photo_url?
|
|
photo_url = '/assets/shared/avatar_generic.png'
|
|
|
|
teacher = @state.teacher
|
|
|
|
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']
|
|
hours.push(`<option key={hour} value={hour}>{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, disabled: !this.state.teacher? && !@state.updating})
|
|
cancelClasses = classNames({"button-grey": true, disabled: !this.state.teacher? && !@state.updating})
|
|
|
|
descriptionErrors = context.JK.reactSingleFieldErrors('description', @state.descriptionErrors)
|
|
slot1Errors = context.JK.reactErrors(@state.slot1Errors, {preferred_day: 'Date'})
|
|
slot2Errors = context.JK.reactErrors(@state.slot2Errors, {preferred_day: 'Date'})
|
|
generalErrors = context.JK.reactErrors(@state.generalErrors, {user: 'You'})
|
|
|
|
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?})
|
|
|
|
`<div className="content-body-scroller">
|
|
<div className="lesson-booking">
|
|
<div className="column column-left">
|
|
<h2>book free lesson</h2>
|
|
|
|
<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" 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" 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>
|
|
<div className={descriptionClasses}>
|
|
<div className="description-prompt">Tell {teacher_first_name} a little about yourself as a student.</div>
|
|
<textarea className="user-description" defaultValue=""/>
|
|
{descriptionErrors}
|
|
</div>
|
|
</div>
|
|
<div className="column column-right">
|
|
<div className="teacher-header">
|
|
<div className="avatar">
|
|
<img src={photo_url}/>
|
|
</div>
|
|
{name}
|
|
</div>
|
|
<div className="booking-info">
|
|
<p>You are purchasing a single free 30-minute JamClass session.</p>
|
|
|
|
<p>To book this lesson, you will need to enter your credit card information.
|
|
You will absolutely not be charged for this free lesson, and you have no further
|
|
commitment to purchase anything. We have to collect a credit card to prevent abuse
|
|
by some users who would otherwise set up multiple free accounts to get multiple free lessons.<br/>
|
|
|
|
<div className="jamclass-policies"><a href="/corp/terms" rel="external" onClick={this.jamclassPolicies}>jamclass
|
|
policies</a></div>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<br className="clearall"/>
|
|
|
|
<div className={generalClasses}>
|
|
{generalErrors}
|
|
</div>
|
|
|
|
<div className="actions">
|
|
|
|
<a className={bookLessonClasses} onClick={this.onBookFreeLesson}>BOOK FREE LESSON</a>
|
|
<a className={cancelClasses} onClick={this.onCancel}>CANCEL</a>
|
|
</div>
|
|
</div>
|
|
</div>`
|
|
}) |