context = window rest = context.JK.Rest() logger = context.JK.logger SessionActions = context.SessionActions UserStore = context.UserStore LessonTimerStore = context.LessonTimerStore LessonTimerActions = context.LessonTimerActions @JamClassScreen = React.createClass({ mixins: [ @ICheckMixin, @PostProcessorMixin, Reflux.listenTo(AppStore, "onAppInit"), Reflux.listenTo(UserStore, "onUserChanged"), Reflux.listenTo(LessonTimerStore, "onLessonTimersChanged") ] lookup: { name_specified: {name: 'Profile', profile: ''}, experiences_teaching: {name: 'Experience', teacher_profile: "experience"}, experiences_education: {name: 'Experience', teacher_profile: "experience"}, experiences_award: {name: 'Experience', teacher_profile: "experience"}, has_stripe_account: {name: 'Stripe', text: "Press the 'Stripe Connect' button in the bottom-left of the page"}, has_teacher_bio: {name: 'Introduction', teacher_profile: "introduction"}, intro_video: {name: 'Introduction', teacher_profile: "introduction"}, years_teaching: {name: 'Introduction', teacher_profile: "introduction"}, years_playing: {name: 'Introduction', teacher_profile: "introduction"}, instruments_or_subject: {name: 'Basics', teacher_profile: "basics"}, genres: {name: 'Basics', teacher_profile: "basics"}, languages: {name: 'Basics', teacher_profile: "basics"}, teaches_ages_specified: {name: 'Basics', teacher_profile: "basics"}, teaching_level_specified: {name: 'Basics', teacher_profile: "basics"}, has_pricing_specified: {name: 'Pricing', teacher_profile: "pricing"} } needsFetching: false fetchLessons: () -> if @needsFetching if @state.user?.id? @needsFetching = false rest.getLessonSessions({as_teacher: @viewerTeacher()}).done((response) => @jamClassLoaded(response)).fail((jqXHR) => @failedJamClassLoad(jqXHR)) onAppInit: (@app) -> @app.bindScreen('jamclass', {beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide}) onUserChanged: (userState) -> @setState({user: userState?.user}) onLessonTimersChanged: (lessons) -> @setState({lessonTimes: lessons}) componentDidMount: () -> @root = $(@getDOMNode()) componentWillUpdate: (nextProps, nextState) -> # associate time info from LessonTimerStore with our lesson info if nextState.lessonTimes? && nextState.lesson_sessions?.entries? for lesson in nextState.lesson_sessions.entries lessonWithTime = nextState.lessonTimes[lesson.id] if lessonWithTime? lesson.times = lessonWithTime.times componentDidUpdate: () -> @fetchLessons() items = @root.find('.jamtable tbody td.actionsColumn .lesson-session-actions-btn') $.each(items, (i, node) => ( $node = $(node) lesson = @findLesson($node.attr('data-lesson-id')) $node.lessonSessionActions(lesson).off(context.JK.EVENTS.LESSON_SESSION_ACTION).on(context.JK.EVENTS.LESSON_SESSION_ACTION, @lessonSessionActionSelected) )) context.JK.popExternalLinks(@root) lessonSessionActionSelected: (e, data) -> lessonId = data.options.id lesson = @findLesson(lessonId) if data.lessonAction == 'status' # show the status of the lesson window.location.href = '/client#/jamclass/lesson-booking/' + lessonId else if data.lessonAction == 'messages' @app.layout.showDialog('chat-dialog', {d1: 'lesson_' + lessonId}) else if data.lessonAction == 'cancel' @cancelLesson(lesson) # @@app.layout.showDialog('cancel-lesson-dialog', {d1: lessonId}) else if data.lessonAction == 'join' SessionActions.enterSession(lesson.music_session.id) else if data.lessonAction == 'reschedule' @rescheduleLesson(lesson) else if data.lessonAction == 'attach-recording' window.AttachmentActions.startAttachRecording.trigger(lesson.id) else if data.lessonAction == 'attach-notation' window.AttachmentActions.startAttachNotation.trigger(lesson.id) else if data.lessonAction == 'attach-audio' window.AttachmentActions.startAttachAudio.trigger(lesson.id) else if data.lessonAction == 'start-5-min' rest.lessonStartTime({id: lessonId, minutes: 5}).done((response) => (@app.layout.notify({ title: 'Start Time Set', text: "Start time for session set to 5 mins from now" }))) else if data.lessonAction == 'start-65-ago' rest.lessonStartTime({ id: lessonId, minutes: -65 }).done((response) => (@app.layout.notify({ title: 'Start Time Set', text: "Start time for session set to 65 mins ago" }))) else if data.lessonAction == 'enter-payment' if lesson.lesson_booking.test_drive_package_choice_id? window.location.href = "/client#/jamclass/lesson-payment/package_#{lesson.lesson_booking.test_drive_package_choice_id}" else window.location.href = "/client#/jamclass/lesson-payment/lesson-booking_#{lessonId}" else context.JK.showAlert('unknown lesson action', 'The option in the menu is unknown') findLesson: (lessonId) -> for lesson in @lessons() if lessonId == lesson.id return lesson return null rescheduleSelected: (lesson, recurring) -> rest.checkLessonReschedule({id: lesson.id, update_all: recurring}) .done((response) => ( if recurring window.location.href = '/client#/jamclass/lesson-booking/' + lesson.lesson_booking_id + "_rescheduling" else window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id + "_rescheduling" )) .fail((jqXHR) => ( if jqXHR.status == 422 if recurring if @viewerStudent() context.JK.Banner.showAlert("Policy Issue", "

We’re sorry, but you cannot reschedule this recurring lesson right now because it is less than 24 hours before the lesson start time.

This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost. You may reschedule this recurring lesson anytime starting from the end of this next scheduled lesson, so please plan to do it then.

") else context.JK.Banner.showAlert("Policy Issue", "

We’re sorry, but you cannot reschedule this recurring lesson right now because it is less than 24 hours before the lesson start time.

This is not allowed in the terms of service. You may reschedule this recurring lesson anytime starting from the end of this next scheduled lesson, so please plan to do it then.

") else if @viewerStudent() context.JK.Banner.showAlert("Policy Issue", "

We’re sorry, but you cannot reschedule a lesson less than 24 hours before the lesson start time.

This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.

") else context.JK.Banner.showAlert("Policy Issue", "

We’re sorry, but you cannot reschedule a lesson less than 24 hours before the lesson start time.

This is not allowed in the terms of service.

") else @app.ajaxError(jqXHR) )) refreshLesson: (lessonId) -> rest.getLesson({id: lessonId}).done((response) => @refreshLessonDone(response)).fail((jqXHR) => @refreshLessonFail(jqXHR)) refreshLessonDone: (lesson_session) -> @updateLessonState(lesson_session) refreshLessonFail: (jqXHR) -> @app.ajaxError(jqXHR) issueCancelLesson: (lesson, update_all) -> request = {} request.message = '' request.id = lesson.lesson_booking_id request.lesson_session_id = lesson.id request.update_all = update_all rest.cancelLessonBooking(request).done((response) => @cancelLessonBookingDone(response, lesson)).fail((response) => @cancelLessonBookingFail(response)) cancelLessonBookingDone: (booking, lesson) -> if booking.focused_lesson.teacher_short_canceled if @teacherViewing() context.JK.Banner.showAlert('late cancellation warning', 'Cancelling a lesson less than 24 hours before it’s scheduled to start should be avoided, as it’s an inconvenience to the student. Repeated violations of this policy will negatively affect your teacher score.') lessonsFromBooking = [] for check in @lessons() if check.lesson_booking_id == lesson.lesson_booking_id lessonsFromBooking.push(check) for check in lessonsFromBooking @refreshLesson(check.id) cancelLessonBookingFail: (jqXHR) -> @app.ajaxError(jqXHR) cancelSelected: (lesson, update_all) -> rest.checkLessonCancel({id: lesson.id, update_all: update_all}) .done((response) => @issueCancelLesson(lesson, update_all)) .fail((jqXHR) => @cancelSelectedFail(jqXHR, lesson, update_all)) cancelSelectedFail: (jqXHR, lesson, update_all) -> if jqXHR.status == 422 if lesson.recurring && update_all if @viewerStudent() buttons = [] buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'}) buttons.push({ name: 'CANCEL RECURRING LESSONS', buttonStyle: 'button-orange', click: (() => (@issueCancelLesson(lesson, true))) }) context.JK.Banner.show({ title: "Policy Issue", html: "You may cancel this recurring series of lessons, but it is too late to cancel the next scheduled lesson because it is less than 24 hours before the lesson start time.

This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.", buttons: buttons }) else # path should not be taken context.JK.Banner.showAlert("Policy Issue", "

We’re sorry, but you cannot cancel a lesson less than 24 hours before the lesson start time.

This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.

") else if @viewerStudent() context.JK.Banner.showAlert("Policy Issue", "

We’re sorry, but you cannot cancel a lesson less than 24 hours before the lesson start time.

This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.

") else # path should not be taken context.JK.Banner.showAlert("Policy Issue", "

We’re sorry, but you cannot cancel a lesson less than 24 hours before the lesson start time.

This is not allowed in the terms of service.

") else @app.ajaxError(jqXHR) rescheduleLesson: (lesson) -> if lesson.recurring buttons = [] buttons.push({ name: 'CANCEL', buttonStyle: 'button-grey', click: (() => (logger.debug("cancelling out of reschedule dialog"))) }) buttons.push({ name: 'THIS LESSON', buttonStyle: 'button-orange', click: (() => (@rescheduleSelected(lesson, false))) }) buttons.push({ name: 'ALL LESSONS', buttonStyle: 'button-orange', click: (() => (@rescheduleSelected(lesson, true))) }) context.JK.Banner.show({ title: 'Rescheduling Selection', html: 'Do you wish to all reschedule all lessons or just the one selected?', buttons: buttons }) else @rescheduleSelected(lesson, false) cancelLesson: (lesson) -> if lesson.isRequested && @viewerTeacher() confirmTitle = 'Confirm Decline' verbLower = 'decline' else confirmTitle = 'Confirm Cancelation' verbLower = 'cancel' if !lesson.isRequested && lesson.recurring buttons = [] buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'}) buttons.push({ name: 'CANCEL THIS LESSON', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, false))) }) buttons.push({name: 'CANCEL ALL LESSONS', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, true)))}) context.JK.Banner.show({ title: 'Select One', html: "Do you wish to all #{verbLower} all lessons or just the one selected?", buttons: buttons }) else context.JK.Banner.showYesNo({ title: confirmTitle, html: "Are you sure you want to #{verbLower} this lesson?", yes: () => (@cancelSelected(lesson, lesson.recurring)) }) getInitialState: () -> { user: null, } beforeHide: (e) -> beforeShow: (e) -> afterShow: (e) -> @checkStripeSuccessReturn() @setState({updating: true}) @needsFetching = true @fetchLessons() checkStripeSuccessReturn: () -> if $.QueryString['stripe-success']? if $.QueryString['stripe-success'] == 'true' context.JK.Banner.showNotice('stripe connected', 'Congratulations, you have successfully connected your Stripe account, so payments for student lessons can now be processed.') if window.history.replaceState #ie9 proofing window.history.replaceState({}, "", "/client#/jamclass") resetState: () -> @setState({updating: false, lesson: null}) jamClassLoaded: (response) -> @setState({updating: false}) @postProcess(response) LessonTimerActions.loadLessons(response) @setState({lesson_sessions: response}) failedJamClassLoad: (jqXHR) -> @setState({updating: false}) if jqXHR.status == 404 @app.layout.notify({title: "Unable to load JamClass info", text: "Try refreshing the web page"}) lessons: () -> if @state.lesson_sessions? @state.lesson_sessions.entries else [] postProcess: (data) -> for lesson in data.entries # calculate: # .other (user object), # .me (user object), # other/me.musician_profile (link to musician profile) # other/me.resolved_photo_url # .hasUnreadMessages # .isRequested (doesn't matter if countered or not). but can't be done # .isScheduled (doesn't matter if countered or not). @postProcessLesson(lesson) onReadMessage: (lesson_session, e) -> e.preventDefault() data = {id: lesson_session.id} data["#{this.myRole()}_unread_messages"] = false rest.updateLessonSessionUnreadMessages(data).done((response) => @updatedLessonSessionReadDone(response)).fail((jqXHR) => @updatedLessonSessionReadFail(jqXHR)) updateLessonState: (lesson_session) -> @postProcessLesson(lesson_session) for lesson in @lessons() if lesson.id == lesson_session.id $.extend(lesson, lesson_session) @setState({lesson_sessions: this.state.lesson_sessions}) updatedLessonSessionReadDone: (lesson_session) -> # update lesson session data in local state @updateLessonState(lesson_session) # if this is a not-confirmed lesson, send them to the view statu screen if lesson_session.isRequested window.location.href = '/client#/jamclass/lesson-booking/' + lesson_session.id else @app.layout.showDialog('chat-dialog', {d1: 'lesson_' + lesson_session.id}) updatedLessonSessionReadFail: (jqXHR) -> @app.ajaxError(jqXHR) openMenu: (lesson, e) -> $this = $(e.target) if !$this.is('.lesson-session-actions-btn') $this = $this.closest('.lesson-session-actions-btn') $this.btOn() constructMissingLinks: (user) -> links = [] matches = {} if user.teacher? for problem, isPresent of user.teacher.profile_pct_summary data = @lookup[problem] if data? && !isPresent matches[data.name] = data for name, data of matches if !data.text? links.push(`{data.name}`) for name, data of matches if data.text? links.push(`{data.name}`) `` else null onProfileHelp: (data, e) -> e.preventDefault() @app.layout.notify({title: data.name, text: data.text}) missingProfileClick: (data, e) -> if data.profile? window.ProfileActions.startProfileEdit(data.profile, true) else if data.teacher_profile? window.ProfileActions.startTeacherEdit(data.teacher_profile, true) else throw "unknown type in data " + JSON.stringify(data) viewTeacherProfile: (e) -> e.preventDefault() window.ProfileActions.viewTeacherProfile(this.state.user, '/client#/jamclass', 'BACK TO JAMCLASS HOME') joinLessonNow: (lesson, e) -> e.preventDefault(); SessionActions.enterSession(lesson.music_session.id) render: () -> disabled = @state.updating if !@state.user?.id return `
Loading
` if @viewerStudent() searchTeachers = `

search teachers

JamClass instructors are each individually screened to ensure that they are highly qualified music teachers, and are equipped with the right gear to teach effectively online.

SEARCH TEACHERS
` if @state.user?['can_buy_test_drive?'] rightContent = `

JamClass is the best way to take online music lessons.

TestDrive lets you try:

To get started, click the Search Teachers button to the left. When you find a teacher you like, click the Book TestDrive Lesson button, and follow the on-screen instructions to choose the 4-, 2-, or 1-teacher offer.

` else rightContent = `

Read our JamClass user guide for students to learn how to get the most out of your online lessons!

If you have any problems, please email us at support@jamkazam.com. We're here to help make your lessons a great experience.

` else searchTeachers = `

stripe status

` if this.state.user? teacherProfileUri = "/client#/profile/teacher/#{this.state.user.id}" pct = Math.round(this.state.user.teacher?.profile_pct) if !pct? pct = 0 if pct == 100 pctCompleteMsg = `

Your teacher profile is {pct}% complete.

` else pctCompleteMsg = `

Your teacher profile is {pct}% complete. The following sections of your profile are missing information. Click any of these links to view and add missing information:

` missingLinks = @constructMissingLinks(this.state.user) rightContent = `
{pctCompleteMsg} {missingLinks} VIEW TEACHER PROFILE

Read our JamClass user guide for teachers to learn how to get the most out of the JamClass technology and marketplace.

If you have any problems, please email us support@jamkazam.com. We're here to help you build your lesson business.

` classes = [] if @state.updating classes = [` Loading... `] else for lessonData in @lessons() if lessonData.hasUnreadMessages unreadMessages = `` else unreadMessages = null if lessonData.lesson_booking.no_slots timeStmt = 'No time has been scheduled yet' else if lessonData.status == 'countered' timeStmt = lessonData.counter_slot.pretty_scheduled_start_with_timezone else timeStmt = lessonData.music_session.pretty_scheduled_start_with_timezone if lessonData.times? && lessonData.displayStatus == 'Scheduled' if lessonData.times.startingSoon if lessonData.times.until.minutes == 0 timeDesc = "Starts in less than a minute" else timeDesc = "Starts in #{lessonData.times.until.minutes} minutes." timeStmt = `{timeDesc}
join lesson now
` else if lessonData.times.inThePast minutes = -lessonData.times.until.minutes if minutes == 1 || minutes == 0 timeStmt = `This lesson just started. join lesson now` else timeStmt = `Started {minutes} minutes ago. join lesson now` lesson = `
{lessonData.other.first_name}
{lessonData.other.last_name}
{timeStmt} {lessonData.displayStatus} {unreadMessages} menu
` classes.push(lesson) if classes.length == 0 classes = [` No Lessons Yet `] `

my lessons

{classes}
{this.otherRole()} DATE/TIME STATUS ACTIONS
{searchTeachers}
{rightContent}

` viewerStudent: () -> !@viewerTeacher() viewerTeacher: () -> this.state.user?.is_a_teacher myRole: () -> if @viewerStudent() 'student' else 'teacher' otherRole: () -> if @viewerStudent() 'teacher' else 'student' })