537 lines
16 KiB
CoffeeScript
537 lines
16 KiB
CoffeeScript
context = window
|
|
rest = context.JK.Rest()
|
|
logger = context.JK.logger
|
|
|
|
AppStore = context.AppStore
|
|
SchoolActions = context.SchoolActions
|
|
SchoolStore = context.SchoolStore
|
|
UserStore = context.UserStore
|
|
|
|
profileUtils = context.JK.ProfileUtils
|
|
|
|
@AccountSchoolScreen = React.createClass({
|
|
|
|
mixins: [
|
|
ICheckMixin,
|
|
Reflux.listenTo(AppStore, "onAppInit"),
|
|
Reflux.listenTo(SchoolStore, "onSchoolChanged")
|
|
Reflux.listenTo(UserStore, "onUserChanged")
|
|
]
|
|
|
|
shownOnce: false
|
|
screenVisible: false
|
|
|
|
TILE_ACCOUNT: 'account'
|
|
TILE_MEMBERS: 'members'
|
|
TILE_EARNINGS: 'earnings'
|
|
TILE_AGREEMENT: 'agreement'
|
|
|
|
TILES: ['account', 'members', 'earnings', 'agreement']
|
|
|
|
onAppInit: (@app) ->
|
|
@app.bindScreen('account/school', {beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide})
|
|
|
|
onSchoolChanged: (schoolState) ->
|
|
@setState(schoolState)
|
|
|
|
onUserChanged: (userState) ->
|
|
@noSchoolCheck(userState?.user)
|
|
@setState({user: userState?.user})
|
|
|
|
componentDidMount: () ->
|
|
@checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}]
|
|
@root = $(@getDOMNode())
|
|
@iCheckify()
|
|
|
|
componentDidUpdate: () ->
|
|
@iCheckify()
|
|
|
|
checkboxChanged: (e) ->
|
|
checked = $(e.target).is(':checked')
|
|
|
|
value = $(e.target).val()
|
|
|
|
@setState({userSchedulingComm: value})
|
|
|
|
|
|
beforeHide: (e) ->
|
|
#ProfileActions.viewTeacherProfileDone()
|
|
@screenVisible = false
|
|
return true
|
|
|
|
beforeShow: (e) ->
|
|
|
|
noSchoolCheck: (user) ->
|
|
if user?.id? && @screenVisible
|
|
|
|
if !user.owned_school_id?
|
|
window.JK.Banner.showAlert("You are not the owner of a school in our systems. If you are, please contact support@jamkazam.com and we'll update your account.")
|
|
return false
|
|
else
|
|
if !@shownOnce
|
|
@shownOnce = true
|
|
SchoolActions.refresh(user.owned_school_id)
|
|
|
|
return true
|
|
|
|
else
|
|
return false
|
|
|
|
afterShow: (e) ->
|
|
@screenVisible = true
|
|
logger.debug("AccountSchoolScreen: afterShow")
|
|
logger.debug("after show", @state.user)
|
|
@noSchoolCheck(@state.user)
|
|
|
|
getInitialState: () ->
|
|
{
|
|
school: null,
|
|
user: null,
|
|
selected: 'account',
|
|
userSchedulingComm: null,
|
|
updateErrors: null,
|
|
schoolName: null,
|
|
studentInvitations: null,
|
|
teacherInvitations: null,
|
|
updating: false,
|
|
distributions: []
|
|
}
|
|
|
|
isSchoolManaged: () ->
|
|
if this.state.userSchedulingComm?
|
|
this.state.userSchedulingComm == 'school'
|
|
else
|
|
this.state.school.scheduling_communication == 'school'
|
|
|
|
nameValue: () ->
|
|
if this.state.schoolName?
|
|
this.state.schoolName
|
|
else
|
|
this.state.school.name
|
|
|
|
nameChanged: (e) ->
|
|
$target = $(e.target)
|
|
val = $target.val()
|
|
@setState({schoolName: val})
|
|
|
|
onCancel: (e) ->
|
|
e.preventDefault()
|
|
context.location.href = '/client#/account'
|
|
|
|
onUpdate: (e) ->
|
|
e.preventDefault()
|
|
|
|
if this.state.updating
|
|
return
|
|
name = @root.find('input[name="name"]').val()
|
|
if @isSchoolManaged()
|
|
scheduling_communication = 'school'
|
|
else
|
|
scheduling_communication = 'teacher'
|
|
correspondence_email = @root.find('input[name="correspondence_email"]').val()
|
|
|
|
@setState(updating: true)
|
|
rest.updateSchool({
|
|
id: this.state.school.id,
|
|
name: name,
|
|
scheduling_communication: scheduling_communication,
|
|
correspondence_email: correspondence_email
|
|
}).done((response) => @onUpdateDone(response)).fail((jqXHR) => @onUpdateFail(jqXHR))
|
|
|
|
onUpdateDone: (response) ->
|
|
@setState({school: response, userSchedulingComm: null, schoolName: null, updateErrors: null, updating: false})
|
|
|
|
@app.layout.notify({title: "update success", text: "Your school information has been successfully updated"})
|
|
|
|
onUpdateFail: (jqXHR) ->
|
|
handled = false
|
|
|
|
@setState({updating: false})
|
|
|
|
if jqXHR.status == 422
|
|
errors = JSON.parse(jqXHR.responseText)
|
|
handled = true
|
|
@setState({updateErrors: errors})
|
|
|
|
|
|
if !handled
|
|
@app.ajaxError(jqXHR, null, null)
|
|
|
|
inviteTeacher: () ->
|
|
@app.layout.showDialog('invite-school-user', {d1: true})
|
|
|
|
inviteStudent: () ->
|
|
@app.layout.showDialog('invite-school-user', {d1: false})
|
|
resendInvitation: (id, e) ->
|
|
e.preventDefault()
|
|
rest.resendSchoolInvitation({
|
|
id: this.state.school.id, invitation_id: id
|
|
}).done((response) => @resendInvitationDone(response)).fail((jqXHR) => @resendInvitationFail(jqXHR))
|
|
|
|
resendInvitationDone: (response) ->
|
|
@app.layout.notify({title: 'invitation resent', text: 'Invitation was resent to ' + response.email})
|
|
|
|
resendInvitationFail: (jqXHR) ->
|
|
@app.ajaxError(jqXHR)
|
|
|
|
deleteInvitation: (id, e) ->
|
|
e.preventDefault()
|
|
rest.deleteSchoolInvitation({
|
|
id: this.state.school.id, invitation_id: id
|
|
}).done((response) => @deleteInvitationDone(id, response)).fail((jqXHR) => @deleteInvitationFail(jqXHR))
|
|
|
|
deleteInvitationDone: (id, response) ->
|
|
context.SchoolActions.deleteInvitation(id)
|
|
|
|
deleteInvitationFail: (jqXHR) ->
|
|
@app.ajaxError(jqXHR)
|
|
|
|
|
|
removeFromSchool: (id, isTeacher, e) ->
|
|
if isTeacher
|
|
rest.deleteSchoolTeacher({
|
|
id: this.state.school.id,
|
|
teacher_id: id
|
|
}).done((response) => @removeFromSchoolDone(response)).fail((jqXHR) => @removeFromSchoolFail(jqXHR))
|
|
else
|
|
rest.deleteSchoolStudent({
|
|
id: this.state.school.id,
|
|
student_id: id
|
|
}).done((response) => @removeFromSchoolDone(response)).fail((jqXHR) => @removeFromSchoolFail(jqXHR))
|
|
|
|
removeFromSchoolDone: (school) ->
|
|
context.JK.Banner.showNotice("User removed", "User was removed from your school.")
|
|
context.SchoolActions.updateSchool(school)
|
|
|
|
removeFromSchoolFail: (jqXHR) ->
|
|
@app.ajaxError(jqXHR)
|
|
|
|
renderUser: (user, isTeacher) ->
|
|
photo_url = user.photo_url
|
|
if !photo_url?
|
|
photo_url = '/assets/shared/avatar_generic.png'
|
|
|
|
mailto = "mailto:#{user.email}"
|
|
|
|
`<div className="school-user">
|
|
<div className="avatar">
|
|
<img src={photo_url}/>
|
|
</div>
|
|
<div className="usersname">
|
|
<span className="just-name">{user.name}</span>
|
|
<span className="just-email"><a href={mailto}>{user.email}</a></span>
|
|
</div>
|
|
<div className="user-actions">
|
|
<a onClick={this.removeFromSchool.bind(this, user.id, isTeacher)}>remove from school</a>
|
|
</div>
|
|
</div>`
|
|
|
|
|
|
renderInvitation: (invitation) ->
|
|
`<div key={invitation.id} className="school-invitation">
|
|
<table>
|
|
<tbody>
|
|
<td className="description">{invitation.first_name} {invitation.last_name}</td>
|
|
<td className="message">
|
|
<div className="detail-block">has not yet accepted invitation<br/>
|
|
<a className="resend" onClick={this.resendInvitation.bind(this, invitation.id)}>resend invitation</a>
|
|
<a className="delete" onClick={this.deleteInvitation.bind(this, invitation.id)}>delete</a>
|
|
</div>
|
|
|
|
</td>
|
|
</tbody>
|
|
</table>
|
|
</div>`
|
|
|
|
renderTeachers: () ->
|
|
teachers = []
|
|
|
|
if this.state.school.teachers? && this.state.school.teachers.length > 0
|
|
for teacher in this.state.school.teachers
|
|
if teacher.user
|
|
teachers.push(@renderUser(teacher.user, true))
|
|
else
|
|
teachers = `<p>No teachers</p>`
|
|
|
|
teachers
|
|
|
|
renderStudents: () ->
|
|
students = []
|
|
|
|
if this.state.school.students? && this.state.school.students.length > 0
|
|
for student in this.state.school.students
|
|
students.push(@renderUser(student, false))
|
|
else
|
|
students = `<p>No students</p>`
|
|
|
|
students
|
|
|
|
renderTeacherInvitations: () ->
|
|
invitations = []
|
|
|
|
if this.state.teacherInvitations? && this.state.teacherInvitations.length > 0
|
|
for invitation in this.state.teacherInvitations
|
|
invitations.push(@renderInvitation(invitation))
|
|
else
|
|
invitations = `<p>No pending invitations</p>`
|
|
invitations
|
|
|
|
renderStudentInvitations: () ->
|
|
invitations = []
|
|
|
|
if this.state.studentInvitations? && this.state.studentInvitations.length > 0
|
|
for invitation in this.state.studentInvitations
|
|
invitations.push(@renderInvitation(invitation))
|
|
else
|
|
invitations = `<p>No pending invitations</p>`
|
|
invitations
|
|
|
|
mainContent: () ->
|
|
if !@state.user? || !@state.school?
|
|
`<div className="loading">Loading...</div>`
|
|
else if @state.selected == @TILE_ACCOUNT
|
|
@account()
|
|
else if @state.selected == @TILE_MEMBERS
|
|
@members()
|
|
else if @state.selected == @TILE_EARNINGS
|
|
@earnings()
|
|
else if @state.selected == @TILE_AGREEMENT
|
|
@agreement()
|
|
else
|
|
@account()
|
|
|
|
account: () ->
|
|
ownerEmail = this.state.school.owner.email
|
|
correspondenceEmail = this.state.school.correspondence_email
|
|
correspondenceDisabled = !@isSchoolManaged()
|
|
|
|
nameErrors = context.JK.reactSingleFieldErrors('name', @state.updateErrors)
|
|
correspondenceEmailErrors = context.JK.reactSingleFieldErrors('correspondence_email', @state.updateErrors)
|
|
nameClasses = classNames({name: true, error: nameErrors?, field: true})
|
|
correspondenceEmailClasses = classNames({
|
|
correspondence_email: true,
|
|
error: correspondenceEmailErrors?,
|
|
field: true
|
|
})
|
|
|
|
cancelClasses = {"button-grey": true, "cancel": true, disabled: this.state.updating}
|
|
updateClasses = {"button-orange": true, "update": true, disabled: this.state.updating}
|
|
|
|
if this.state.school.education
|
|
management = null
|
|
else
|
|
management = `<div>
|
|
<h4>Management Preference</h4>
|
|
|
|
<div className="field scheduling_communication">
|
|
<div className="scheduling_communication school">
|
|
<input type="radio" name="scheduling_communication" readOnly={true} value="school"
|
|
checked={this.isSchoolManaged()}/><label>School owner will manage scheduling of student lessons
|
|
sourced
|
|
by JamKazam</label>
|
|
</div>
|
|
<div className="scheduling_communication teacher">
|
|
<input type="radio" name="scheduling_communication" readOnly={true} value="teacher"
|
|
checked={!this.isSchoolManaged()}/><label>Teacher will manage scheduling of lessons</label>
|
|
</div>
|
|
</div>
|
|
<div className={correspondenceEmailClasses}>
|
|
<label>Correspondence Email:</label>
|
|
<input type="text" name="correspondence_email" placeholder={ownerEmail} defaultValue={correspondenceEmail}
|
|
disabled={correspondenceDisabled}/>
|
|
|
|
<div className="hint">All emails relating to lesson scheduling will go to this email if school owner manages
|
|
scheduling.
|
|
</div>
|
|
{correspondenceEmailErrors}
|
|
</div>
|
|
</div>`
|
|
|
|
|
|
`<div className="account-block info-block">
|
|
<div className={nameClasses}>
|
|
<label>School Name:</label>
|
|
<input type="text" name="name" value={this.nameValue()} onChange={this.nameChanged}/>
|
|
{nameErrors}
|
|
</div>
|
|
<div className="field logo">
|
|
<label>School Logo:</label>
|
|
<AvatarEditLink target={this.state.school} target_type="school"/>
|
|
</div>
|
|
|
|
{management}
|
|
|
|
<h4>Payments</h4>
|
|
|
|
<div className="field stripe-connect">
|
|
<StripeConnect purpose='school' user={this.state.user}/>
|
|
</div>
|
|
|
|
<div className="actions">
|
|
<a className={classNames(cancelClasses)} onClick={this.onCancel}>CANCEL</a>
|
|
<a className={classNames(updateClasses)} onClick={this.onUpdate}>UPDATE</a>
|
|
</div>
|
|
</div>`
|
|
|
|
|
|
members: () ->
|
|
teachers = @renderTeachers()
|
|
teacherInvitations = @renderTeacherInvitations()
|
|
|
|
students = @renderStudents()
|
|
studentInvitations = @renderStudentInvitations()
|
|
|
|
`<div className="members-block info-block">
|
|
<div className="column column-left">
|
|
<div>
|
|
<h3>teachers:</h3>
|
|
<a onClick={this.inviteTeacher} className="button-orange invite-dialog">INVITE TEACHER</a>
|
|
<br className="clearall"/>
|
|
</div>
|
|
<div className="teacher-invites">
|
|
{teacherInvitations}
|
|
</div>
|
|
|
|
<div className="teachers">
|
|
{teachers}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="column column-right">
|
|
<div>
|
|
<h3>students:</h3>
|
|
<a onClick={this.inviteStudent} className="button-orange invite-dialog">INVITE STUDENT</a>
|
|
<br className="clearall"/>
|
|
</div>
|
|
<div className="student-invites">
|
|
{studentInvitations}
|
|
</div>
|
|
<div className="students">
|
|
{students}
|
|
</div>
|
|
</div>
|
|
|
|
</div>`
|
|
|
|
earnings: () ->
|
|
`<div className="earnings-block info-block">
|
|
<p>Coming soon</p>
|
|
</div>`
|
|
|
|
paymentsToYou: () ->
|
|
rows = []
|
|
|
|
for paymentHistory in this.state.distributions
|
|
paymentMethod = 'Stripe'
|
|
|
|
if paymentHistory.distributed
|
|
date = paymentHistory.teacher_payment.teacher_payment_charge.last_billing_attempt_at
|
|
status = 'Paid'
|
|
else
|
|
date = paymentHistory.created_at
|
|
if paymentHistory.not_collectable
|
|
status = 'Uncollectible'
|
|
else if !paymentHistory.teacher?.teacher?.stripe_account_id?
|
|
status = 'No Stripe Acct'
|
|
else
|
|
status = 'Collecting'
|
|
|
|
|
|
date = context.JK.formatDate(date, true)
|
|
description = paymentHistory.description
|
|
|
|
if paymentHistory.teacher_payment?
|
|
amt = paymentHistory.teacher_payment.real_distribution_in_cents
|
|
else
|
|
amt = paymentHistory.real_distribution_in_cents
|
|
|
|
displayAmount = ' $' + (amt / 100).toFixed(2)
|
|
|
|
amountClasses = {status: status}
|
|
|
|
row =
|
|
`<tr>
|
|
<td>{date}</td>
|
|
<td className="capitalize">{paymentMethod}</td>
|
|
<td>{description}</td>
|
|
<td className="capitalize">{status}</td>
|
|
<td className={classNames(amountClasses)}>{displayAmount}</td>
|
|
</tr>`
|
|
rows.push(row)
|
|
|
|
`<div>
|
|
<table className="payment-table">
|
|
<thead>
|
|
<tr>
|
|
<th>DATE</th>
|
|
<th>METHOD</th>
|
|
<th>DESCRIPTION</th>
|
|
<th>STATUS</th>
|
|
<th>AMOUNT</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows}
|
|
</tbody>
|
|
|
|
</table>
|
|
<a className="btn-next-pager" href="/api/sales?page=1">Next</a>
|
|
|
|
<div className="end-of-payments-list end-of-list">No more payment history</div>
|
|
<div className="input-aligner">
|
|
<a className="back button-grey" onClick={this.onBack}>BACK</a>
|
|
</div>
|
|
<br className="clearall"/>
|
|
</div>`
|
|
|
|
|
|
agreement: () ->
|
|
`<div className="agreement-block info-block">
|
|
<p>The agreement between your music school and JamKazam is part of JamKazam's terms of service. You can find the
|
|
complete terms of service <a href="/corp/terms" target="_blank">here</a>. And you can find the section that is
|
|
most specific to the music school terms <a href="/corp/terms" target="_blank">here</a>.</p>
|
|
</div>`
|
|
|
|
selectionMade: (selection, e) ->
|
|
e.preventDefault()
|
|
@setState({selected: selection})
|
|
|
|
createTileLink: (i, tile) ->
|
|
active = this.state.selected == tile
|
|
classes = classNames({last: i == @TILES.length - 1, activeTile: active})
|
|
|
|
return `<div key={i} className="profile-tile"><a className={classes}
|
|
onClick={this.selectionMade.bind(this, tile)}>{tile}</a></div>`
|
|
|
|
onCustomBack: (customBack, e) ->
|
|
e.preventDefault()
|
|
context.location = customBack
|
|
|
|
render: () ->
|
|
mainContent = @mainContent()
|
|
|
|
profileSelections = []
|
|
for tile, i in @TILES
|
|
profileSelections.push(@createTileLink(i, tile, profileSelections))
|
|
|
|
profileNav = `<div className="profile-nav">
|
|
{profileSelections}
|
|
</div>`
|
|
|
|
`<div className="content-body-scroller">
|
|
<div className="profile-header profile-head">
|
|
<div className="store-header">school:</div>
|
|
{profileNav}
|
|
<div className="clearall"></div>
|
|
</div>
|
|
|
|
<div className="profile-body">
|
|
<div className="profile-wrapper">
|
|
<div className="main-content">
|
|
{mainContent}
|
|
<br />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`
|
|
}) |