mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-30 10:56:53 -05:00
Implement teacher accounts
This commit is contained in:
parent
5e2c726a2b
commit
bd3a77da9f
32 changed files with 2149 additions and 411 deletions
|
@ -123,9 +123,15 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'schools': go('NewHomeView')
|
||||
|
||||
'teachers': go('NewHomeView')
|
||||
'teachers/freetrial': go('RequestQuoteView')
|
||||
'teachers/quote': go('RequestQuoteView')
|
||||
'teachers/demo': go('RequestQuoteView')
|
||||
'teachers/demo': go('teachers/RequestQuoteView')
|
||||
'teachers/freetrial': go('teachers/RequestQuoteView')
|
||||
'teachers/quote': go('teachers/RequestQuoteView')
|
||||
'teachers/signup': ->
|
||||
return @routeDirectly('teachers/CreateTeacherAccountView', []) if me.isAnonymous()
|
||||
@navigate('/teachers/convert', {trigger: true, replace: true})
|
||||
'teachers/convert': ->
|
||||
return @navigate('/teachers/signup', {trigger: true, replace: true}) if me.isAnonymous()
|
||||
@routeDirectly('teachers/ConvertToTeacherAccountView', [])
|
||||
|
||||
'test(/*subpath)': go('TestView')
|
||||
|
||||
|
@ -228,3 +234,6 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
navigate: (fragment, options) ->
|
||||
super fragment, options
|
||||
Backbone.Mediator.publish 'router:navigated', route: fragment
|
||||
|
||||
reload: ->
|
||||
document.location.reload()
|
|
@ -295,6 +295,7 @@
|
|||
subject: "Subject"
|
||||
email: "Email"
|
||||
password: "Password"
|
||||
confirm_password: "Confirm Password"
|
||||
message: "Message"
|
||||
code: "Code"
|
||||
ladder: "Ladder"
|
||||
|
@ -313,6 +314,9 @@
|
|||
warrior: "Warrior"
|
||||
ranger: "Ranger"
|
||||
wizard: "Wizard"
|
||||
first_name: "First Name"
|
||||
last_name: "Last Name"
|
||||
username: "Username"
|
||||
|
||||
units:
|
||||
second: "second"
|
||||
|
@ -766,6 +770,7 @@
|
|||
phone_number_help: "Where can we reach you during the workday?"
|
||||
role_label: "Your role"
|
||||
role_help: "Select your primary role."
|
||||
role_default: "Select Role"
|
||||
tech_coordinator: "Technology coordinator"
|
||||
advisor: "Advisor"
|
||||
principal: "Principal"
|
||||
|
@ -776,6 +781,7 @@
|
|||
state: "State"
|
||||
country: "Country"
|
||||
num_students_help: "How many do you anticipate enrolling in CodeCombat?"
|
||||
num_students_default: "Select Range"
|
||||
education_level_label: "Education Level of Students"
|
||||
education_level_help: "Choose as many as apply."
|
||||
elementary_school: "Elementary School"
|
||||
|
@ -784,10 +790,18 @@
|
|||
middle_school: "Middle School"
|
||||
college_plus: "College or higher"
|
||||
anything_else: "Anything else we should know?"
|
||||
thanks_header: "Thanks for requesting a demo!"
|
||||
thanks_p: "We'll be in touch soon. Questions? Email us:"
|
||||
thanks_anon: "Log in or create an account to set up a class, add your students, and monitor their progress as they learn computer science."
|
||||
thanks_logged_in: "Set up a class, add your students, and monitor their progress as they learn computer science."
|
||||
thanks_header: "Request Received!" # {change}
|
||||
thanks_sub_header: "Thanks for expressing interest in CodeCombat for your school."
|
||||
thanks_p: "We'll be in touch soon! If you need to get in contact, you can reach us at:" # {change}
|
||||
finish_signup: "Finish creating your teacher account:"
|
||||
finish_signup_p: "Create an account to set up a class, add your students, and monitor their progress as they learn computer science."
|
||||
signup_with: "Sign up with:"
|
||||
conversion_warning: "WARNING: Your current account is a <em>Student Account</em>. Once you submit this form, your account will be converted into a Teacher Account."
|
||||
learn_more_modal: "Teacher accounts on CodeCombat have the ability to monitor student progress, assign enrollments and manage classrooms. Teacher accounts cannot be a part of a classroom - if you are currently enrolled in a class using this account, you will no longer be able to access it once you convert to a Teacher Account."
|
||||
create_account: "Create a Teacher Account"
|
||||
create_account_subtitle: "Get access to teacher-only tools for using CodeCombat in the classroom. <strong>Set up a class</strong>, add your students, and <strong>monitor their progress</strong>!"
|
||||
convert_account_title: "Convert to Teacher Account"
|
||||
not: "Not"
|
||||
setup_a_class: "Set Up a Class"
|
||||
|
||||
versions:
|
||||
|
|
|
@ -59,8 +59,10 @@ module.exports = class User extends CocoModel
|
|||
|
||||
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
|
||||
|
||||
isStudent: -> @get('role') is 'student'
|
||||
|
||||
isTeacher: ->
|
||||
return @get('role') in ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent']
|
||||
return @get('role') in ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']
|
||||
|
||||
setRole: (role, force=false) ->
|
||||
return if me.isAdmin()
|
||||
|
|
|
@ -215,6 +215,18 @@ $forest: #20572B
|
|||
.btn-lg
|
||||
font-size: 18px
|
||||
|
||||
.btn-gplus
|
||||
color: white
|
||||
background-color: #DD4B39
|
||||
img
|
||||
height: 22px
|
||||
|
||||
.btn-facebook
|
||||
color: white
|
||||
background-color: #3B5998
|
||||
img
|
||||
height: 22px
|
||||
|
||||
// Classes
|
||||
|
||||
.text-navy
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
@import "app/styles/mixins"
|
||||
@import "app/styles/bootstrap/variables"
|
||||
|
||||
#request-quote-view
|
||||
#site-content-area
|
||||
//TODO: Maybe this should go in style-flat
|
||||
margin: 50px 10px 100px
|
||||
margin: 50px 0 100px
|
||||
.row
|
||||
margin: 20px 0
|
||||
|
||||
.section
|
||||
margin-top: 80px
|
||||
margin-bottom: 50px
|
||||
#conversion-warning
|
||||
margin-top: 20px
|
||||
|
||||
.form-group
|
||||
label
|
||||
|
@ -34,5 +37,15 @@
|
|||
#submit-request-btn
|
||||
margin-left: 10px
|
||||
|
||||
#login-btn
|
||||
margin-right: 10px
|
||||
// After submit (anonymous)
|
||||
|
||||
h5
|
||||
margin-top: 50px
|
||||
|
||||
#social-network-signups
|
||||
margin: 20px 0
|
||||
button
|
||||
margin-left: 10px
|
||||
|
||||
.text-h1
|
||||
margin: 40px 0 30px
|
|
@ -58,12 +58,15 @@
|
|||
a(href="/user/#{me.getSlugOrID()}", data-i18n="nav.profile")
|
||||
li
|
||||
a(href="/account/settings", data-i18n="play.settings")
|
||||
li
|
||||
a(href="/account/payments", data-i18n="account.payments")
|
||||
li
|
||||
a(href="/account/subscription", data-i18n="account.subscription")
|
||||
li
|
||||
a(href="/account/prepaid", data-i18n="account.prepaid_codes")
|
||||
unless me.isStudent()
|
||||
li
|
||||
a(href="/account/payments", data-i18n="account.payments")
|
||||
unless me.isTeacher() || me.isStudent()
|
||||
li
|
||||
a(href="/account/subscription", data-i18n="account.subscription")
|
||||
unless me.isStudent()
|
||||
li
|
||||
a(href="/account/prepaid", data-i18n="account.prepaid_codes")
|
||||
li
|
||||
a#logout-button(data-i18n="login.log_out")
|
||||
|
||||
|
|
|
@ -29,15 +29,18 @@ block header
|
|||
div.img-circle(style="background-image: url(#{me.getPhotoURL()})")
|
||||
h3=me.displayName()
|
||||
li
|
||||
a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile")
|
||||
a(href="/user/#{me.getSlugOrID()}", data-i18n="nav.profile")
|
||||
li
|
||||
a(href="/account/settings", data-i18n="play.settings")
|
||||
li
|
||||
a(href="/account/payments", data-i18n="account.payments")
|
||||
li
|
||||
a(href="/account/subscription", data-i18n="account.subscription")
|
||||
li
|
||||
a(href="/account/prepaid", data-i18n="account.prepaid_codes")
|
||||
unless me.isStudent()
|
||||
li
|
||||
a(href="/account/payments", data-i18n="account.payments")
|
||||
unless me.isTeacher() || me.isStudent()
|
||||
li
|
||||
a(href="/account/subscription", data-i18n="account.subscription")
|
||||
unless me.isStudent()
|
||||
li
|
||||
a(href="/account/prepaid", data-i18n="account.prepaid_codes")
|
||||
li
|
||||
a#logout-button(data-i18n="login.log_out")
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ extends /templates/base
|
|||
block content
|
||||
h3.text-right
|
||||
if me.isAnonymous()
|
||||
a(href="/teachers")
|
||||
a(href="/teachers/signup")
|
||||
span(data-i18n="courses.teachers_click")
|
||||
span !
|
||||
else
|
||||
|
|
|
@ -88,4 +88,11 @@ block content
|
|||
strong Invalid number of students
|
||||
|
||||
p.text-center
|
||||
button#purchase-btn.btn.btn-lg.btn-success.uppercase(data-i18n="courses.purchase_now")
|
||||
button#purchase-btn.btn.btn-lg.btn-success.uppercase(data-i18n="courses.purchase_now" disabled=me.isAnonymous())
|
||||
|
||||
if me.isAnonymous()
|
||||
// DNT. Temporary redirect until teacher-dashboard is finished
|
||||
.alert.alert-danger.text-center
|
||||
h2 You must be signed up to purchase enrollments.
|
||||
p
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/signup") Create a teacher account
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
extends /templates/core/modal-base
|
||||
extends /templates/core/modal-base-flat
|
||||
|
||||
block modal-header-content
|
||||
h3= view.title
|
||||
|
||||
block modal-body-content
|
||||
p= view.body
|
||||
p!= view.body
|
||||
|
||||
block modal-footer-content
|
||||
button.btn.btn-secondary#decline-button(type="button", data-dismiss="modal")= view.decline
|
||||
|
|
|
@ -83,7 +83,7 @@ mixin box
|
|||
a.btn.btn-primary.btn-lg.btn-block(href="https://sites.google.com/a/codecombat.com/teacher-guides/course-guides", data-i18n="new_home.educator_wiki")
|
||||
else
|
||||
h6(data-i18n="new_home.want_coco")
|
||||
button.teacher-btn.btn.btn-primary.btn-lg.btn-block(data-i18n="new_home.get_started")
|
||||
a.btn.btn-primary.btn-lg.btn-block(href="/teachers/convert", data-i18n="new_home.get_started")
|
||||
|
||||
else if view.justPlaysCourses()
|
||||
div
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
extends /templates/base-flat
|
||||
|
||||
block content
|
||||
.container
|
||||
form.form(class=view.trialRequest.isNew() ? '' : 'hide')
|
||||
h3.text-center(data-i18n="teachers_quote.title")
|
||||
h4.text-center(data-i18n="[html]teachers_quote.subtitle")
|
||||
|
||||
#form-teacher-info.section
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.name")
|
||||
- var name = me.get('name') || '';
|
||||
input.form-control(name="name" value=name, disabled=!!name)
|
||||
|
||||
.col-sm-4
|
||||
#email-form-group.form-group
|
||||
label.control-label(data-i18n="general.email")
|
||||
- var email = me.get('email') || '';
|
||||
input.form-control(name="email" type="email", value=email, disabled=!!email)
|
||||
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.phone_number")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.phone_number_help")
|
||||
input.form-control(name="phoneNumber")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.role_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.role_help")
|
||||
select.form-control(name="role")
|
||||
option
|
||||
option(data-i18n="courses.teacher", value="Teacher")
|
||||
option(data-i18n="teachers_quote.tech_coordinator", value="Technology coordinator")
|
||||
option(data-i18n="teachers_quote.advisor", value="Advisor")
|
||||
option(data-i18n="teachers_quote.principal", value="Principal")
|
||||
option(data-i18n="teachers_quote.superintendent", value="Superintendent")
|
||||
option(data-i18n="teachers_quote.parent", value="Parent")
|
||||
|
||||
#form-school-info.section
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.organization_label")
|
||||
input.form-control(name="organization")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.city")
|
||||
input.form-control(name="city")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.state")
|
||||
input.form-control(name="state")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
|
||||
input.form-control(name="country")
|
||||
|
||||
#form-students-info.section
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-5
|
||||
.form-group
|
||||
label.control-label(data-i18n="courses.number_students")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.num_students_help")
|
||||
select.form-control(name="numStudents")
|
||||
option
|
||||
option 1-10
|
||||
option 11-50
|
||||
option 51-100
|
||||
option 101-200
|
||||
option 201-500
|
||||
option 501-1000
|
||||
option 1000+
|
||||
|
||||
.form-group
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
label.control-label(data-i18n="teachers_quote.education_level_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.education_level_help")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
span(data-i18n="teachers_quote.college_plus")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-6
|
||||
// Other field uses custom logic, so no name field is included in either input.
|
||||
// That way the forms library ignores it.
|
||||
.form-group
|
||||
label.control-label.checkbox
|
||||
input#other-education-level-checkbox(type="checkbox")
|
||||
span(data-i18n="nav.other")
|
||||
|
|
||||
span(data-i18n="teachers_quote.please_explain")
|
||||
input#other-education-level-input.form-control
|
||||
|
||||
#anything-else-row.section
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-8
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.anything_else")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
|
||||
textarea.form-control(rows=8, name="notes")
|
||||
|
||||
#buttons-row.row.text-center
|
||||
input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.title")
|
||||
|
||||
|
||||
#form-submit-success.text-center(class=view.trialRequest.isNew() ? 'hide' : '')
|
||||
h3.text-center(data-i18n="teachers_quote.thanks_header")
|
||||
p.text-center
|
||||
span.spr(data-i18n="teachers_quote.thanks_p")
|
||||
a.spl(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
|
||||
if me.isAnonymous()
|
||||
p.text-center(data-i18n="teachers_quote.thanks_anon")
|
||||
|
||||
p.text-center
|
||||
button#login-btn.btn.btn-info(data-i18n="login.log_in")
|
||||
button#signup-btn.btn.btn-info(data-i18n="login.sign_up")
|
||||
else
|
||||
p.text-center(data-i18n="teachers_quote.thanks_logged_in")
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/courses/teachers", data-i18n="teachers_quote.setup_a_class")
|
152
app/templates/teachers/convert-to-teacher-account-view.jade
Normal file
152
app/templates/teachers/convert-to-teacher-account-view.jade
Normal file
|
@ -0,0 +1,152 @@
|
|||
extends /templates/base-flat
|
||||
block content
|
||||
|
||||
#learn-more-modal.modal.fade
|
||||
.modal-dialog.modal-sm
|
||||
.modal-content.style-flat
|
||||
.modal-header
|
||||
.button.close(type="button", data-dismiss="modal", aria-hidden="true") ×
|
||||
.modal-body(data-i18n="teachers_quote.learn_more_modal")
|
||||
|
||||
.container
|
||||
form
|
||||
.row.text-center
|
||||
.col-md-offset-2.col-md-8
|
||||
h3.m-t-3(data-i18n="teachers_quote.convert_account_title")
|
||||
h4(data-i18n="[html]teachers_quote.create_account_subtitle")
|
||||
.alert.alert-info.m-y-2
|
||||
div
|
||||
span.spr(data-i18n="teachers_quote.not")
|
||||
strong= me.broadName()
|
||||
| ?
|
||||
a.spl#logout-link(data-i18n="login.log_out")
|
||||
if me.get('role') === 'student'
|
||||
div#conversion-warning.m-t-2
|
||||
span.spr(data-i18n="[html]teachers_quote.conversion_warning")
|
||||
a(data-i18n="new_home.learn_more" data-toggle="modal" data-target="#learn-more-modal")
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.username")
|
||||
input.form-control(disabled=true value=me.get('name'))
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
#email-form-group.form-group
|
||||
label.control-label(data-i18n="general.email")
|
||||
input.form-control(disabled=true value=me.get('email'))
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.first_name")
|
||||
input.form-control(name="firstName" value=me.get('firstName') || '')
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.last_name")
|
||||
input.form-control(name="lastName" value=me.get('lastName') || '')
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.phone_number")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.phone_number_help")
|
||||
input.form-control(name="phoneNumber")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.role_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.role_help")
|
||||
select.form-control(name="role")
|
||||
option(data-i18n="teachers_quote.role_default", , value='')
|
||||
option(data-i18n="courses.teacher", value="Teacher")
|
||||
option(data-i18n="teachers_quote.tech_coordinator", value="Technology coordinator")
|
||||
option(data-i18n="teachers_quote.advisor", value="Advisor")
|
||||
option(data-i18n="teachers_quote.principal", value="Principal")
|
||||
option(data-i18n="teachers_quote.superintendent", value="Superintendent")
|
||||
option(data-i18n="teachers_quote.parent", value="Parent")
|
||||
|
||||
#form-school-info
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.organization_label")
|
||||
input.form-control(name="organization")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.city")
|
||||
input.form-control(name="city")
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.state")
|
||||
input.form-control(name="state")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
|
||||
input.form-control(name="country")
|
||||
|
||||
#form-students-info
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="courses.number_students")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.num_students_help")
|
||||
select.form-control(name="numStudents")
|
||||
option(data-i18n="teachers_quote.num_students_default", value='')
|
||||
option 1-10
|
||||
option 11-50
|
||||
option 51-100
|
||||
option 101-200
|
||||
option 201-500
|
||||
option 501-1000
|
||||
option 1000+
|
||||
|
||||
.form-group
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-10
|
||||
label.control-label(data-i18n="teachers_quote.education_level_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.education_level_help")
|
||||
.col-md-offset-2.col-md-5
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
span(data-i18n="teachers_quote.college_plus")
|
||||
.checkbox
|
||||
label
|
||||
input#other-education-level-checkbox(type="checkbox")
|
||||
span(data-i18n="nav.other").spr
|
||||
span(data-i18n="teachers_quote.please_explain")
|
||||
input#other-education-level-input.form-control
|
||||
|
||||
#anything-else-row.row.m-y-2
|
||||
.col-md-offset-2.col-md-8
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.anything_else")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
textarea.form-control(rows=8, name="notes")
|
||||
#buttons-row.row.m-y-2.text-center
|
||||
input#create-account-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.convert_account_title")
|
||||
|
172
app/templates/teachers/create-teacher-account-view.jade
Normal file
172
app/templates/teachers/create-teacher-account-view.jade
Normal file
|
@ -0,0 +1,172 @@
|
|||
extends /templates/base-flat
|
||||
|
||||
block content
|
||||
.container
|
||||
form
|
||||
.row.text-center
|
||||
.col-md-offset-2.col-md-8
|
||||
h3.m-t-3(data-i18n="teachers_quote.create_account")
|
||||
h4(data-i18n="[html]teachers_quote.create_account_subtitle")
|
||||
.alert.alert-info.m-y-2
|
||||
span.spr(data-i18n="signup.login_switch")
|
||||
a.login-link(data-i18n="login.log_in")
|
||||
#social-network-signups.m-y-2
|
||||
button#facebook-signup-btn.btn.btn-facebook.btn-lg.m-x-1
|
||||
span.spr(data-i18n="teachers_quote.signup_with")
|
||||
| Facebook
|
||||
img.m-l-1(src='/images/pages/community/logo_facebook.png')
|
||||
|
||||
button#gplus-signup-btn.btn.btn-gplus.btn-lg.spr
|
||||
span.spr(data-i18n="teachers_quote.signup_with")
|
||||
| G+
|
||||
img.m-l-1(src='/images/pages/community/logo_g+.png')
|
||||
|
||||
#gplus-logged-in-row.row.text-center.hide
|
||||
.col-md-offset-2.col-md-8
|
||||
h2(data-i18n="signup.connected_gplus_header")
|
||||
p(data-i18n="signup.connected_gplus_p")
|
||||
#facebook-logged-in-row.row.text-center.hide
|
||||
.col-md-offset-2.col-md-8
|
||||
h2(data-i18n="signup.connected_facebook_header")
|
||||
p(data-i18n="signup.connected_facebook_p")
|
||||
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.username")
|
||||
input.form-control(name="name")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
#email-form-group.form-group
|
||||
label.control-label(data-i18n="general.email")
|
||||
input.form-control(name="email")
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.first_name")
|
||||
input.form-control(name="firstName")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.last_name")
|
||||
input.form-control(name="lastName")
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.password")
|
||||
input.form-control(name="password1", type="password")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.confirm_password")
|
||||
input.form-control(name="password2", type="password")
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.phone_number")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.phone_number_help")
|
||||
input.form-control(name="phoneNumber")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.role_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.role_help")
|
||||
select.form-control(name="role")
|
||||
option(data-i18n="teachers_quote.role_default", , value='')
|
||||
option(data-i18n="courses.teacher", value="Teacher")
|
||||
option(data-i18n="teachers_quote.tech_coordinator", value="Technology coordinator")
|
||||
option(data-i18n="teachers_quote.advisor", value="Advisor")
|
||||
option(data-i18n="teachers_quote.principal", value="Principal")
|
||||
option(data-i18n="teachers_quote.superintendent", value="Superintendent")
|
||||
option(data-i18n="teachers_quote.parent", value="Parent")
|
||||
|
||||
#form-school-info
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.organization_label")
|
||||
input.form-control(name="organization")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.city")
|
||||
input.form-control(name="city")
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.state")
|
||||
input.form-control(name="state")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
|
||||
input.form-control(name="country")
|
||||
|
||||
#form-students-info
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="courses.number_students")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.num_students_help")
|
||||
select.form-control(name="numStudents")
|
||||
option(data-i18n="teachers_quote.num_students_default", value='')
|
||||
option 1-10
|
||||
option 11-50
|
||||
option 51-100
|
||||
option 101-200
|
||||
option 201-500
|
||||
option 501-1000
|
||||
option 1000+
|
||||
|
||||
.form-group
|
||||
|
||||
.row.m-y-2
|
||||
.col-md-offset-2.col-md-10
|
||||
label.control-label(data-i18n="teachers_quote.education_level_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.education_level_help")
|
||||
|
||||
.col-md-offset-2.col-md-5
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
span(data-i18n="teachers_quote.college_plus")
|
||||
.checkbox
|
||||
label
|
||||
input#other-education-level-checkbox(type="checkbox")
|
||||
span(data-i18n="nav.other").spr
|
||||
span(data-i18n="teachers_quote.please_explain")
|
||||
input#other-education-level-input.form-control
|
||||
|
||||
#anything-else-row.row.m-y-2
|
||||
.col-md-offset-2.col-md-8
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.anything_else")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
|
||||
textarea.form-control(rows=8, name="notes")
|
||||
|
||||
#buttons-row.row.m-y-2.text-center
|
||||
input#create-account-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.create_account")
|
219
app/templates/teachers/request-quote-view.jade
Normal file
219
app/templates/teachers/request-quote-view.jade
Normal file
|
@ -0,0 +1,219 @@
|
|||
extends /templates/base-flat
|
||||
|
||||
block content
|
||||
- var showDone = !view.trialRequest.isNew() && me.isAnonymous();
|
||||
|
||||
#learn-more-modal.modal.fade
|
||||
.modal-dialog.modal-sm
|
||||
.modal-content.style-flat
|
||||
.modal-header
|
||||
.button.close(type="button", data-dismiss="modal", aria-hidden="true") ×
|
||||
.modal-body(data-i18n="teachers_quote.learn_more_modal")
|
||||
|
||||
.container
|
||||
form#request-form(class=showDone ? 'hide' : '')
|
||||
.row
|
||||
.col-md-offset-2.col-md-8
|
||||
h3.text-center(data-i18n="teachers_quote.title")
|
||||
h4.text-center(data-i18n="[html]teachers_quote.subtitle")
|
||||
|
||||
if !me.isAnonymous()
|
||||
.row
|
||||
.col-md-offset-2.col-md-8
|
||||
.alert.alert-info.text-center
|
||||
div
|
||||
span.spr(data-i18n="teachers_quote.not")
|
||||
strong= me.broadName()
|
||||
| ?
|
||||
a.spl#logout-link(data-i18n="login.log_out")
|
||||
if me.get('role') === 'student'
|
||||
div#conversion-warning
|
||||
span.spr(data-i18n="[html]teachers_quote.conversion_warning")
|
||||
a(data-i18n="new_home.learn_more" data-toggle="modal" data-target="#learn-more-modal")
|
||||
|
||||
#form-teacher-info
|
||||
if !me.isAnonymous()
|
||||
.row
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.username")
|
||||
- var name = me.get('name') || '';
|
||||
input.form-control(name="name" value=name, disabled=!!name)
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.email")
|
||||
- var email = me.get('email') || '';
|
||||
input.form-control(name="email" value=email, disabled=!!email)
|
||||
|
||||
.row
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.first_name")
|
||||
- var firstName = me.get('firstName') || '';
|
||||
input.form-control(name="firstName" value=firstName, disabled=!!firstName)
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.last_name")
|
||||
- var lastName = me.get('lastName') || '';
|
||||
input.form-control(name="lastName" value=lastName, disabled=!!lastName)
|
||||
|
||||
if me.isAnonymous()
|
||||
.row
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
#email-form-group.form-group
|
||||
label.control-label(data-i18n="general.email")
|
||||
- var email = me.get('email') || '';
|
||||
input.form-control(name="email" type="email", value=email, disabled=!!email)
|
||||
|
||||
.row
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.phone_number")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.phone_number_help")
|
||||
input.form-control(name="phoneNumber")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.role_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.role_help")
|
||||
select.form-control(name="role")
|
||||
option(data-i18n="teachers_quote.role_default", , value='')
|
||||
option(data-i18n="courses.teacher", value="Teacher")
|
||||
option(data-i18n="teachers_quote.tech_coordinator", value="Technology coordinator")
|
||||
option(data-i18n="teachers_quote.advisor", value="Advisor")
|
||||
option(data-i18n="teachers_quote.principal", value="Principal")
|
||||
option(data-i18n="teachers_quote.superintendent", value="Superintendent")
|
||||
option(data-i18n="teachers_quote.parent", value="Parent")
|
||||
|
||||
#form-school-info
|
||||
.row
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.organization_label")
|
||||
input.form-control(name="organization")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.city")
|
||||
input.form-control(name="city")
|
||||
|
||||
.row
|
||||
.col-md-offset-2.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.state")
|
||||
input.form-control(name="state")
|
||||
|
||||
.col-md-4.col-sm-6
|
||||
.form-group
|
||||
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
|
||||
input.form-control(name="country")
|
||||
|
||||
#form-students-info
|
||||
.row
|
||||
.col-md-offset-2.col-md-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="courses.number_students")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.num_students_help")
|
||||
select.form-control(name="numStudents")
|
||||
option(data-i18n="teachers_quote.num_students_default", value='')
|
||||
option 1-10
|
||||
option 11-50
|
||||
option 51-100
|
||||
option 101-200
|
||||
option 201-500
|
||||
option 501-1000
|
||||
option 1000+
|
||||
|
||||
.form-group
|
||||
|
||||
.row
|
||||
.col-md-offset-2.col-md-10
|
||||
label.control-label(data-i18n="teachers_quote.education_level_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.education_level_help")
|
||||
|
||||
.col-md-offset-2.col-md-5
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
span(data-i18n="teachers_quote.college_plus")
|
||||
.checkbox
|
||||
label
|
||||
input#other-education-level-checkbox(type="checkbox")
|
||||
span(data-i18n="nav.other").spr
|
||||
span(data-i18n="teachers_quote.please_explain")
|
||||
input#other-education-level-input.form-control
|
||||
|
||||
#anything-else-row.row
|
||||
.col-md-offset-2.col-md-8
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.anything_else")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
|
||||
textarea.form-control(rows=8, name="notes")
|
||||
|
||||
#buttons-row.row.text-center
|
||||
input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.title")
|
||||
|
||||
#form-submit-success.text-center(class=showDone ? '' : 'hide')
|
||||
h3(data-i18n="teachers_quote.thanks_header")
|
||||
h4(data-i18n="teachers_quote.thanks_sub_header")
|
||||
p
|
||||
span.spr(data-i18n="teachers_quote.thanks_p")
|
||||
a.spl(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
|
||||
if me.isAnonymous()
|
||||
h5(data-i18n="teachers_quote.finish_signup")
|
||||
p(data-i18n="teachers_quote.finish_signup_p")
|
||||
|
||||
#social-network-signups
|
||||
span(data-i18n="teachers_quote.signup_with")
|
||||
button#facebook-signup-btn.btn.btn-facebook.btn-lg.m-x-1
|
||||
span.spr(data-i18n="teachers_quote.signup_with")
|
||||
| Facebook
|
||||
img.m-l-1(src='/images/pages/community/logo_facebook.png')
|
||||
button#gplus-signup-btn.btn.btn-gplus.btn-lg.spr
|
||||
span.spr(data-i18n="teachers_quote.signup_with")
|
||||
| G+
|
||||
img.m-l-1(src='/images/pages/community/logo_g+.png')
|
||||
|
||||
.text-h1.text-uppercase(data-i18n="general.or")
|
||||
|
||||
form#signup-form.text-left
|
||||
.row
|
||||
.col-md-offset-2.col-md-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.name")
|
||||
input.form-control(name="name")
|
||||
|
||||
.row
|
||||
.col-md-offset-2.col-md-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.password")
|
||||
input.form-control(name="password1", type="password")
|
||||
.col-md-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.confirm_password")
|
||||
input.form-control(name="password2", type="password")
|
||||
|
||||
.text-center
|
||||
button.btn.btn-lg.btn-navy(data-i18n="login.sign_up")
|
|
@ -1,135 +0,0 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
forms = require 'core/forms'
|
||||
TrialRequest = require 'models/TrialRequest'
|
||||
TrialRequests = require 'collections/TrialRequests'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
storage = require 'core/storage'
|
||||
|
||||
formSchema = {
|
||||
type: 'object'
|
||||
required: ['name', 'email', 'organization', 'role', 'numStudents']
|
||||
properties:
|
||||
name: { type: 'string', minLength: 1 }
|
||||
email: { type: 'string', format: 'email' }
|
||||
phoneNumber: { type: 'string' }
|
||||
role: { type: 'string' }
|
||||
organization: { type: 'string' }
|
||||
city: { type: 'string' }
|
||||
state: { type: 'string' }
|
||||
country: { type: 'string' }
|
||||
numStudents: { type: 'string' }
|
||||
educationLevel: {
|
||||
type: 'array'
|
||||
items: { type: 'string' }
|
||||
}
|
||||
notes: { type: 'string' }
|
||||
}
|
||||
|
||||
module.exports = class RequestQuoteView extends RootView
|
||||
id: 'request-quote-view'
|
||||
template: require 'templates/request-quote-view'
|
||||
|
||||
events:
|
||||
'change form': 'onChangeForm'
|
||||
'submit form': 'onSubmitForm'
|
||||
'click #login-btn': 'onClickLoginButton'
|
||||
'click #signup-btn': 'onClickSignupButton'
|
||||
'click #email-exists-login-link': 'onClickEmailExistsLoginLink'
|
||||
|
||||
initialize: ->
|
||||
@trialRequest = new TrialRequest()
|
||||
@trialRequests = new TrialRequests()
|
||||
@trialRequests.fetchOwn()
|
||||
@supermodel.loadCollection(@trialRequests)
|
||||
|
||||
onLoaded: ->
|
||||
if @trialRequests.size()
|
||||
@trialRequest = @trialRequests.first()
|
||||
if @trialRequest and @trialRequest.get('status') isnt 'submitted' and @trialRequest.get('status') isnt 'approved'
|
||||
window.tracker?.trackEvent 'View Trial Request', category: 'Teachers', label: 'View Trial Request', ['Mixpanel']
|
||||
super()
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
obj = storage.load('request-quote-form')
|
||||
if obj
|
||||
@$('#other-education-level-checkbox').attr('checked', obj.otherChecked)
|
||||
@$('#other-education-level-input').val(obj.otherInput)
|
||||
forms.objectToForm(@$('form'), obj)
|
||||
|
||||
onChangeForm: ->
|
||||
obj = forms.formToObject(@$('form'))
|
||||
obj.otherChecked = @$('#other-education-level-checkbox').is(':checked')
|
||||
obj.otherInput = @$('#other-education-level-input').val()
|
||||
storage.save('request-quote-form', obj, 10)
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
e.preventDefault()
|
||||
form = @$('form')
|
||||
attrs = forms.formToObject(form)
|
||||
|
||||
# custom other input logic (also used in form local storage save/restore)
|
||||
if @$('#other-education-level-checkbox').is(':checked')
|
||||
attrs.educationLevel.push(@$('#other-education-level-input').val())
|
||||
|
||||
forms.clearFormAlerts(form)
|
||||
result = tv4.validateMultiple(attrs, formSchema)
|
||||
error = true
|
||||
if not result.valid
|
||||
forms.applyErrorsToForm(form, result.errors)
|
||||
else if not /^.+@.+\..+$/.test(attrs.email)
|
||||
forms.setErrorToProperty(form, 'email', 'Invalid email.')
|
||||
else if not _.size(attrs.educationLevel)
|
||||
return forms.setErrorToProperty(form, 'educationLevel', 'Check at least one.')
|
||||
else
|
||||
error = false
|
||||
if error
|
||||
forms.scrollToFirstError()
|
||||
return
|
||||
@trialRequest = new TrialRequest({
|
||||
type: 'course'
|
||||
properties: attrs
|
||||
})
|
||||
@trialRequest.notyErrors = false
|
||||
@$('#submit-request-btn').text('Sending').attr('disabled', true)
|
||||
@trialRequest.save()
|
||||
@trialRequest.on 'sync', @onTrialRequestSubmit, @
|
||||
@trialRequest.on 'error', @onTrialRequestError, @
|
||||
me.setRole attrs.role.toLowerCase(), true
|
||||
|
||||
onTrialRequestError: (model, jqxhr) ->
|
||||
if jqxhr.status is 409
|
||||
userExists = $.i18n.t('teachers_quote.email_exists')
|
||||
logIn = $.i18n.t('login.log_in')
|
||||
@$('#email-form-group')
|
||||
.addClass('has-error')
|
||||
.append($("<div class='help-block error-help-block'>#{userExists} <a id='email-exists-login-link'>#{logIn}</a>"))
|
||||
@$('#submit-request-btn').text('Submit').attr('disabled', false)
|
||||
forms.scrollToFirstError()
|
||||
|
||||
onClickEmailExistsLoginLink: ->
|
||||
modal = new AuthModal({ initialValues: { email: @trialRequest.get('properties')?.email } })
|
||||
@openModalView(modal)
|
||||
|
||||
onTrialRequestSubmit: ->
|
||||
@$('form, #form-submit-success').toggleClass('hide')
|
||||
$('#flying-focus').css({top: 0, left: 0}) # Hack copied from Router.coffee#187. Ideally we'd swap out the view and have view-swapping logic handle this
|
||||
window.tracker?.trackEvent 'Submit Trial Request', category: 'Teachers', label: 'Trial Request', ['Mixpanel']
|
||||
|
||||
onClickLoginButton: ->
|
||||
modal = new AuthModal({ initialValues: { email: @trialRequest.get('properties')?.email } })
|
||||
@openModalView(modal)
|
||||
window.nextURL = '/courses/teachers' unless @trialRequest.isNew()
|
||||
|
||||
onClickSignupButton: ->
|
||||
props = @trialRequest.get('properties') or {}
|
||||
me.set('name', props.name)
|
||||
modal = new CreateAccountModal({
|
||||
initialValues: {
|
||||
email: props.email
|
||||
schoolName: props.organization
|
||||
}
|
||||
})
|
||||
@openModalView(modal)
|
||||
window.nextURL = '/courses/teachers' unless @trialRequest.isNew()
|
|
@ -1,5 +1,4 @@
|
|||
app = require 'core/application'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
Classroom = require 'models/Classroom'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
|
@ -40,7 +39,6 @@ module.exports = class PurchaseCoursesView extends RootView
|
|||
|
||||
onLoaded: ->
|
||||
@pricePerStudent = @products.findWhere({name: 'course'}).get('amount')
|
||||
me.setRole 'teacher'
|
||||
super()
|
||||
|
||||
getPriceString: -> '$' + (@getPrice()/100).toFixed(2)
|
||||
|
@ -77,7 +75,7 @@ module.exports = class PurchaseCoursesView extends RootView
|
|||
numberOfStudentsIsValid: -> @numberOfStudents > 0 and @numberOfStudents < 100000
|
||||
|
||||
onClickPurchaseButton: ->
|
||||
return @openModalView new CreateAccountModal() if me.isAnonymous()
|
||||
return application.router.navigate('/teachers/signup', {trigger: true}) if me.isAnonymous()
|
||||
unless @numberOfStudentsIsValid()
|
||||
alert("Please enter the maximum number of students needed for your class.")
|
||||
return
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
ActivateLicensesModal = require 'views/courses/ActivateLicensesModal'
|
||||
app = require 'core/application'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
CocoModel = require 'models/CocoModel'
|
||||
Course = require 'models/Course'
|
||||
|
@ -70,7 +69,7 @@ module.exports = class TeacherCoursesView extends RootView
|
|||
application.tracker?.trackEvent 'Classroom started add students', category: 'Courses', classroomID: classroom.id
|
||||
|
||||
onClickCreateNewClassButton: ->
|
||||
return @openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
return application.router.navigate('/teachers/signup', {trigger: true}) if me.get('anonymous')
|
||||
modal = new ClassroomSettingsModal({})
|
||||
@openModalView(modal)
|
||||
@listenToOnce modal, 'hide', =>
|
||||
|
|
138
app/views/teachers/ConvertToTeacherAccountView.coffee
Normal file
138
app/views/teachers/ConvertToTeacherAccountView.coffee
Normal file
|
@ -0,0 +1,138 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
forms = require 'core/forms'
|
||||
TrialRequest = require 'models/TrialRequest'
|
||||
TrialRequests = require 'collections/TrialRequests'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
storage = require 'core/storage'
|
||||
errors = require 'core/errors'
|
||||
User = require 'models/User'
|
||||
ConfirmModal = require 'views/editor/modal/ConfirmModal'
|
||||
|
||||
FORM_KEY = 'request-quote-form'
|
||||
|
||||
module.exports = class ConvertToTeacherAccountView extends RootView
|
||||
id: 'convert-to-teacher-account-view'
|
||||
template: require 'templates/teachers/convert-to-teacher-account-view'
|
||||
logoutRedirectURL: null
|
||||
|
||||
events:
|
||||
'change form': 'onChangeForm'
|
||||
'submit form': 'onSubmitForm'
|
||||
'click #logout-link': -> me.logout()
|
||||
|
||||
initialize: ->
|
||||
if me.isAnonymous()
|
||||
application.router.navigate('/teachers/signup', {trigger: true, replace: true})
|
||||
return
|
||||
@trialRequest = new TrialRequest()
|
||||
@trialRequests = new TrialRequests()
|
||||
@trialRequests.fetchOwn()
|
||||
@supermodel.trackCollection(@trialRequests)
|
||||
|
||||
onLoaded: ->
|
||||
if @trialRequests.size() and me.isTeacher()
|
||||
return application.router.navigate('/courses/teachers', { trigger: true, replace: true })
|
||||
|
||||
super()
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
|
||||
# apply existing trial request on form
|
||||
properties = @trialRequest.get('properties')
|
||||
if properties
|
||||
forms.objectToForm(@$('form'), properties)
|
||||
commonLevels = _.map @$('[name="educationLevel"]'), (el) -> $(el).val()
|
||||
submittedLevels = properties.educationLevel or []
|
||||
otherLevel = _.first(_.difference(submittedLevels, commonLevels)) or ''
|
||||
@$('#other-education-level-checkbox').attr('checked', !!otherLevel)
|
||||
@$('#other-education-level-input').val(otherLevel)
|
||||
|
||||
# apply changes from local storage
|
||||
obj = storage.load(FORM_KEY)
|
||||
if obj
|
||||
@$('#other-education-level-checkbox').attr('checked', obj.otherChecked)
|
||||
@$('#other-education-level-input').val(obj.otherInput)
|
||||
forms.objectToForm(@$('form'), obj, { overwriteExisting: true })
|
||||
|
||||
onChangeRequestForm: ->
|
||||
# save changes to local storage
|
||||
obj = forms.formToObject(@$('form'))
|
||||
obj.otherChecked = @$('#other-education-level-checkbox').is(':checked')
|
||||
obj.otherInput = @$('#other-education-level-input').val()
|
||||
storage.save(FORM_KEY, obj, 10)
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
form = @$('form')
|
||||
attrs = forms.formToObject(form)
|
||||
|
||||
if @$('#other-education-level-checkbox').is(':checked')
|
||||
val = @$('#other-education-level-input').val()
|
||||
attrs.educationLevel.push(val) if val
|
||||
|
||||
forms.clearFormAlerts(form)
|
||||
|
||||
result = tv4.validateMultiple(attrs, formSchema)
|
||||
error = false
|
||||
if not result.valid
|
||||
forms.applyErrorsToForm(form, result.errors)
|
||||
error = true
|
||||
if not _.size(attrs.educationLevel)
|
||||
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.')
|
||||
error = true
|
||||
if error
|
||||
forms.scrollToFirstError()
|
||||
return
|
||||
@trialRequest = new TrialRequest({
|
||||
type: 'course'
|
||||
properties: attrs
|
||||
})
|
||||
if me.get('role') is 'student' and not me.isAnonymous()
|
||||
modal = new ConfirmModal({
|
||||
title: ''
|
||||
body: "<p>#{$.i18n.t('teachers_quote.conversion_warning')}</p><p>#{$.i18n.t('teachers_quote.learn_more_modal')}</p>"
|
||||
confirm: $.i18n.t('common.continue')
|
||||
decline: $.i18n.t('common.cancel')
|
||||
})
|
||||
@openModalView(modal)
|
||||
modal.once 'confirm', @saveTrialRequest, @
|
||||
else
|
||||
@saveTrialRequest()
|
||||
|
||||
saveTrialRequest: ->
|
||||
@trialRequest.notyErrors = false
|
||||
@$('#create-account-btn').text('Sending').attr('disabled', true)
|
||||
@trialRequest.save()
|
||||
@trialRequest.on 'sync', @onTrialRequestSubmit, @
|
||||
@trialRequest.on 'error', @onTrialRequestError, @
|
||||
|
||||
onTrialRequestError: (model, jqxhr) ->
|
||||
@$('#submit-request-btn').text('Submit').attr('disabled', false)
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
|
||||
onTrialRequestSubmit: ->
|
||||
me.setRole @trialRequest.get('properties').role.toLowerCase(), true
|
||||
storage.remove(FORM_KEY)
|
||||
application.router.navigate('/courses/teachers', {trigger: true})
|
||||
|
||||
formSchema = {
|
||||
type: 'object'
|
||||
required: ['firstName', 'lastName', 'organization', 'role', 'numStudents']
|
||||
properties:
|
||||
firstName: { type: 'string' }
|
||||
lastName: { type: 'string' }
|
||||
phoneNumber: { type: 'string' }
|
||||
role: { type: 'string' }
|
||||
organization: { type: 'string' }
|
||||
city: { type: 'string' }
|
||||
state: { type: 'string' }
|
||||
country: { type: 'string' }
|
||||
numStudents: { type: 'string' }
|
||||
educationLevel: {
|
||||
type: 'array'
|
||||
items: { type: 'string' }
|
||||
}
|
||||
notes: { type: 'string' }
|
||||
}
|
264
app/views/teachers/CreateTeacherAccountView.coffee
Normal file
264
app/views/teachers/CreateTeacherAccountView.coffee
Normal file
|
@ -0,0 +1,264 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
forms = require 'core/forms'
|
||||
TrialRequest = require 'models/TrialRequest'
|
||||
TrialRequests = require 'collections/TrialRequests'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
storage = require 'core/storage'
|
||||
errors = require 'core/errors'
|
||||
User = require 'models/User'
|
||||
|
||||
FORM_KEY = 'request-quote-form'
|
||||
SIGNUP_REDIRECT = '/courses/teachers'
|
||||
|
||||
module.exports = class CreateTeacherAccountView extends RootView
|
||||
id: 'create-teacher-account-view'
|
||||
template: require 'templates/teachers/create-teacher-account-view'
|
||||
|
||||
events:
|
||||
'click .login-link': 'onClickLoginLink'
|
||||
'change form': 'onChangeForm'
|
||||
'submit form': 'onSubmitForm'
|
||||
'click #gplus-signup-btn': 'onClickGPlusSignupButton'
|
||||
'click #facebook-signup-btn': 'onClickFacebookSignupButton'
|
||||
|
||||
initialize: ->
|
||||
@trialRequest = new TrialRequest()
|
||||
@trialRequests = new TrialRequests()
|
||||
@trialRequests.fetchOwn()
|
||||
@supermodel.trackCollection(@trialRequests)
|
||||
|
||||
onLoaded: ->
|
||||
if @trialRequests.size()
|
||||
@trialRequest = @trialRequests.first()
|
||||
if @trialRequest and @trialRequest.get('status') isnt 'submitted' and @trialRequest.get('status') isnt 'approved'
|
||||
window.tracker?.trackEvent 'View Trial Request', category: 'Teachers', label: 'View Trial Request', ['Mixpanel']
|
||||
super()
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
|
||||
# apply existing trial request on form
|
||||
properties = @trialRequest.get('properties')
|
||||
if properties
|
||||
forms.objectToForm(@$('form'), properties)
|
||||
commonLevels = _.map @$('[name="educationLevel"]'), (el) -> $(el).val()
|
||||
submittedLevels = properties.educationLevel or []
|
||||
otherLevel = _.first(_.difference(submittedLevels, commonLevels)) or ''
|
||||
@$('#other-education-level-checkbox').attr('checked', !!otherLevel)
|
||||
@$('#other-education-level-input').val(otherLevel)
|
||||
|
||||
# apply changes from local storage
|
||||
obj = storage.load(FORM_KEY)
|
||||
if obj
|
||||
@$('#other-education-level-checkbox').attr('checked', obj.otherChecked)
|
||||
@$('#other-education-level-input').val(obj.otherInput)
|
||||
forms.objectToForm(@$('form'), obj, { overwriteExisting: true })
|
||||
|
||||
onClickLoginLink: ->
|
||||
modal = new AuthModal({ initialValues: { email: @trialRequest.get('properties')?.email } })
|
||||
@openModalView(modal)
|
||||
|
||||
onChangeRequestForm: ->
|
||||
# Local storage is being used to store the contents of the form whenever it changes,
|
||||
# and filling in the stored values if the page is reloaded or navigated away from and returned to.
|
||||
# save changes to local storage
|
||||
obj = forms.formToObject(@$('form'))
|
||||
obj.otherChecked = @$('#other-education-level-checkbox').is(':checked')
|
||||
obj.otherInput = @$('#other-education-level-input').val()
|
||||
storage.save(FORM_KEY, obj, 10)
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
# Creating Trial Request first, validate user attributes but do not use them
|
||||
form = @$('form')
|
||||
allAttrs = forms.formToObject(form)
|
||||
trialRequestAttrs = _.omit(allAttrs, 'name', 'password1', 'password2')
|
||||
|
||||
if @$('#other-education-level-checkbox').is(':checked')
|
||||
val = @$('#other-education-level-input').val()
|
||||
trialRequestAttrs.educationLevel.push(val) if val
|
||||
|
||||
forms.clearFormAlerts(form)
|
||||
|
||||
result = tv4.validateMultiple(trialRequestAttrs, formSchema)
|
||||
error = false
|
||||
if not result.valid
|
||||
forms.applyErrorsToForm(form, result.errors)
|
||||
error = true
|
||||
if not forms.validateEmail(trialRequestAttrs.email)
|
||||
forms.setErrorToProperty(form, 'email', 'Invalid email.')
|
||||
error = true
|
||||
if not _.size(trialRequestAttrs.educationLevel)
|
||||
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.')
|
||||
error = true
|
||||
unless @gplusAttrs or @facebookAttrs
|
||||
if not allAttrs.password1
|
||||
forms.setErrorToProperty(form, 'password1', 'Required field')
|
||||
error = true
|
||||
else if not allAttrs.password2
|
||||
forms.setErrorToProperty(form, 'password2', 'Required field')
|
||||
error = true
|
||||
else if allAttrs.password1 isnt allAttrs.password2
|
||||
forms.setErrorToProperty(form, 'password1', 'Password fields are not equivalent')
|
||||
error = true
|
||||
if error
|
||||
forms.scrollToFirstError()
|
||||
return
|
||||
@trialRequest = new TrialRequest({
|
||||
type: 'course'
|
||||
properties: trialRequestAttrs
|
||||
})
|
||||
@trialRequest.notyErrors = false
|
||||
@$('#create-account-btn').text('Sending').attr('disabled', true)
|
||||
@trialRequest.save()
|
||||
@trialRequest.on 'sync', @onTrialRequestSubmit, @
|
||||
@trialRequest.on 'error', @onTrialRequestError, @
|
||||
|
||||
onTrialRequestError: (model, jqxhr) ->
|
||||
@$('#create-account-btn').text('Submit').attr('disabled', false)
|
||||
if jqxhr.status is 409
|
||||
userExists = $.i18n.t('teachers_quote.email_exists')
|
||||
logIn = $.i18n.t('login.log_in')
|
||||
@$('#email-form-group')
|
||||
.addClass('has-error')
|
||||
.append($("<div class='help-block error-help-block'>#{userExists} <a class='login-link'>#{logIn}</a>"))
|
||||
forms.scrollToFirstError()
|
||||
else
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
|
||||
onClickEmailExistsLoginLink: ->
|
||||
modal = new AuthModal({ initialValues: { email: @trialRequest.get('properties')?.email } })
|
||||
@openModalView(modal)
|
||||
|
||||
onTrialRequestSubmit: ->
|
||||
storage.remove(FORM_KEY)
|
||||
attrs = _.pick(forms.formToObject(@$('form')), 'name', 'email', 'role')
|
||||
options = {}
|
||||
newUser = new User(attrs)
|
||||
if @gplusAttrs
|
||||
newUser.set('_id', me.id)
|
||||
options.url = "/db/user?gplusID=#{@gplusAttrs.gplusID}&gplusAccessToken=#{application.gplusHandler.accessToken.access_token}"
|
||||
options.type = 'PUT'
|
||||
newUser.set(@gplusAttrs)
|
||||
else if @facebookAttrs
|
||||
newUser.set('_id', me.id)
|
||||
options.url = "/db/user?facebookID=#{@facebookAttrs.facebookID}&facebookAccessToken=#{application.facebookHandler.authResponse.accessToken}"
|
||||
options.type = 'PUT'
|
||||
newUser.set(@facebookAttrs)
|
||||
else
|
||||
newUser.set('password', @$('input[name="password1"]').val())
|
||||
newUser.save(null, options)
|
||||
newUser.once 'sync', ->
|
||||
application.router.navigate(SIGNUP_REDIRECT, { trigger: true })
|
||||
application.router.reload()
|
||||
newUser.once 'error', errors.showNotyNetworkError
|
||||
|
||||
# GPlus signup
|
||||
|
||||
onClickGPlusSignupButton: ->
|
||||
btn = @$('#gplus-signup-btn')
|
||||
btn.attr('disabled', true)
|
||||
application.gplusHandler.loadAPI({
|
||||
success: =>
|
||||
btn.attr('disabled', false)
|
||||
application.gplusHandler.connect({
|
||||
success: =>
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('signup.creating'))
|
||||
btn.attr('disabled', true)
|
||||
application.gplusHandler.loadPerson({
|
||||
success: (@gplusAttrs) =>
|
||||
existingUser = new User()
|
||||
existingUser.fetchGPlusUser(@gplusAttrs.gplusID, {
|
||||
error: (user, jqxhr) =>
|
||||
if jqxhr.status is 404
|
||||
@onGPlusConnected()
|
||||
else
|
||||
errors.showNotyNetworkError(jqxhr)
|
||||
success: =>
|
||||
me.loginGPlusUser(@gplusAttrs.gplusID, {
|
||||
success: ->
|
||||
application.router.navigate('/teachers/convert')
|
||||
error: errors.showNotyNetworkError
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onGPlusConnected: ->
|
||||
forms.objectToForm(@$('form'), @gplusAttrs)
|
||||
for field in ['email', 'firstName', 'lastName']
|
||||
input = @$("input[name='#{field}']")
|
||||
if input.val()
|
||||
input.attr('disabled', true)
|
||||
@$('input[type="password"]').attr('disabled', true)
|
||||
@$('#gplus-logged-in-row, #social-network-signups').toggleClass('hide')
|
||||
|
||||
|
||||
# Facebook signup
|
||||
|
||||
onClickFacebookSignupButton: ->
|
||||
btn = @$('#facebook-signup-btn')
|
||||
btn.attr('disabled', true)
|
||||
application.facebookHandler.loadAPI({
|
||||
success: =>
|
||||
btn.attr('disabled', false)
|
||||
application.facebookHandler.connect({
|
||||
success: =>
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('signup.creating'))
|
||||
btn.attr('disabled', true)
|
||||
application.facebookHandler.loadPerson({
|
||||
success: (@facebookAttrs) =>
|
||||
existingUser = new User()
|
||||
existingUser.fetchFacebookUser(@facebookAttrs.facebookID, {
|
||||
error: (user, jqxhr) =>
|
||||
if jqxhr.status is 404
|
||||
@onFacebookConnected()
|
||||
else
|
||||
errors.showNotyNetworkError(jqxhr)
|
||||
success: =>
|
||||
me.loginFacebookUser(@facebookAttrs.facebookID, {
|
||||
success: ->
|
||||
application.router.navigate('/teachers/convert')
|
||||
error: errors.showNotyNetworkError
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onFacebookConnected: ->
|
||||
forms.objectToForm(@$('form'), @facebookAttrs)
|
||||
for field in ['email', 'firstName', 'lastName']
|
||||
input = @$("input[name='#{field}']")
|
||||
if input.val()
|
||||
input.attr('disabled', true)
|
||||
@$('input[type="password"]').attr('disabled', true)
|
||||
@$('#facebook-logged-in-row, #social-network-signups').toggleClass('hide')
|
||||
|
||||
|
||||
|
||||
formSchema = {
|
||||
type: 'object'
|
||||
required: ['firstName', 'lastName', 'email', 'organization', 'role', 'numStudents']
|
||||
properties:
|
||||
password1: { type: 'string' }
|
||||
password2: { type: 'string' }
|
||||
firstName: { type: 'string' }
|
||||
lastName: { type: 'string' }
|
||||
name: { type: 'string', minLength: 1 }
|
||||
email: { type: 'string', format: 'email' }
|
||||
phoneNumber: { type: 'string' }
|
||||
role: { type: 'string' }
|
||||
organization: { type: 'string' }
|
||||
city: { type: 'string' }
|
||||
state: { type: 'string' }
|
||||
country: { type: 'string' }
|
||||
numStudents: { type: 'string' }
|
||||
educationLevel: {
|
||||
type: 'array'
|
||||
items: { type: 'string' }
|
||||
}
|
||||
notes: { type: 'string' }
|
||||
}
|
260
app/views/teachers/RequestQuoteView.coffee
Normal file
260
app/views/teachers/RequestQuoteView.coffee
Normal file
|
@ -0,0 +1,260 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
forms = require 'core/forms'
|
||||
TrialRequest = require 'models/TrialRequest'
|
||||
TrialRequests = require 'collections/TrialRequests'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
storage = require 'core/storage'
|
||||
errors = require 'core/errors'
|
||||
ConfirmModal = require 'views/editor/modal/ConfirmModal'
|
||||
|
||||
FORM_KEY = 'request-quote-form'
|
||||
SIGNUP_REDIRECT = '/courses/teachers'
|
||||
|
||||
module.exports = class RequestQuoteView extends RootView
|
||||
id: 'request-quote-view'
|
||||
template: require 'templates/teachers/request-quote-view'
|
||||
logoutRedirectURL: null
|
||||
|
||||
events:
|
||||
'change #request-form': 'onChangeRequestForm'
|
||||
'submit #request-form': 'onSubmitRequestForm'
|
||||
'click #email-exists-login-link': 'onClickEmailExistsLoginLink'
|
||||
'submit #signup-form': 'onSubmitSignupForm'
|
||||
'click #logout-link': -> me.logout()
|
||||
'click #gplus-signup-btn': 'onClickGPlusSignupButton'
|
||||
'click #facebook-signup-btn': 'onClickFacebookSignupButton'
|
||||
|
||||
initialize: ->
|
||||
@trialRequest = new TrialRequest()
|
||||
@trialRequests = new TrialRequests()
|
||||
@trialRequests.fetchOwn()
|
||||
@supermodel.trackCollection(@trialRequests)
|
||||
|
||||
onLoaded: ->
|
||||
if @trialRequests.size()
|
||||
@trialRequest = @trialRequests.first()
|
||||
if @trialRequest and @trialRequest.get('status') isnt 'submitted' and @trialRequest.get('status') isnt 'approved'
|
||||
window.tracker?.trackEvent 'View Trial Request', category: 'Teachers', label: 'View Trial Request', ['Mixpanel']
|
||||
super()
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
|
||||
# apply existing trial request on form
|
||||
properties = @trialRequest.get('properties')
|
||||
if properties
|
||||
forms.objectToForm(@$('#request-form'), properties)
|
||||
commonLevels = _.map @$('[name="educationLevel"]'), (el) -> $(el).val()
|
||||
submittedLevels = properties.educationLevel or []
|
||||
otherLevel = _.first(_.difference(submittedLevels, commonLevels)) or ''
|
||||
@$('#other-education-level-checkbox').attr('checked', !!otherLevel)
|
||||
@$('#other-education-level-input').val(otherLevel)
|
||||
|
||||
# apply changes from local storage
|
||||
obj = storage.load(FORM_KEY)
|
||||
if obj
|
||||
@$('#other-education-level-checkbox').attr('checked', obj.otherChecked)
|
||||
@$('#other-education-level-input').val(obj.otherInput)
|
||||
forms.objectToForm(@$('#request-form'), obj, { overwriteExisting: true })
|
||||
|
||||
onChangeRequestForm: ->
|
||||
# save changes to local storage
|
||||
obj = forms.formToObject(@$('form'))
|
||||
obj.otherChecked = @$('#other-education-level-checkbox').is(':checked')
|
||||
obj.otherInput = @$('#other-education-level-input').val()
|
||||
storage.save(FORM_KEY, obj, 10)
|
||||
|
||||
onSubmitRequestForm: (e) ->
|
||||
e.preventDefault()
|
||||
form = @$('#request-form')
|
||||
attrs = forms.formToObject(form)
|
||||
|
||||
# custom other input logic (also used in form local storage save/restore)
|
||||
if @$('#other-education-level-checkbox').is(':checked')
|
||||
val = @$('#other-education-level-input').val()
|
||||
attrs.educationLevel.push(val) if val
|
||||
|
||||
forms.clearFormAlerts(form)
|
||||
requestFormSchema = if me.isAnonymous() then requestFormSchemaAnonymous else requestFormSchemaLoggedIn
|
||||
result = tv4.validateMultiple(attrs, requestFormSchemaAnonymous)
|
||||
error = false
|
||||
if not result.valid
|
||||
forms.applyErrorsToForm(form, result.errors)
|
||||
error = true
|
||||
if not forms.validateEmail(attrs.email)
|
||||
forms.setErrorToProperty(form, 'email', 'Invalid email.')
|
||||
error = true
|
||||
if not _.size(attrs.educationLevel)
|
||||
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.')
|
||||
error = true
|
||||
if error
|
||||
forms.scrollToFirstError()
|
||||
return
|
||||
@trialRequest = new TrialRequest({
|
||||
type: 'course'
|
||||
properties: attrs
|
||||
})
|
||||
if me.get('role') is 'student' and not me.isAnonymous()
|
||||
modal = new ConfirmModal({
|
||||
title: ''
|
||||
body: "<p>#{$.i18n.t('teachers_quote.conversion_warning')}</p><p>#{$.i18n.t('teachers_quote.learn_more_modal')}</p>"
|
||||
confirm: $.i18n.t('common.continue')
|
||||
decline: $.i18n.t('common.cancel')
|
||||
})
|
||||
@openModalView(modal)
|
||||
modal.once 'confirm', @saveTrialRequest, @
|
||||
else
|
||||
@saveTrialRequest()
|
||||
|
||||
saveTrialRequest: ->
|
||||
@trialRequest.notyErrors = false
|
||||
@$('#submit-request-btn').text('Sending').attr('disabled', true)
|
||||
@trialRequest.save()
|
||||
@trialRequest.on 'sync', @onTrialRequestSubmit, @
|
||||
@trialRequest.on 'error', @onTrialRequestError, @
|
||||
|
||||
onTrialRequestError: (model, jqxhr) ->
|
||||
@$('#submit-request-btn').text('Submit').attr('disabled', false)
|
||||
if jqxhr.status is 409
|
||||
userExists = $.i18n.t('teachers_quote.email_exists')
|
||||
logIn = $.i18n.t('login.log_in')
|
||||
@$('#email-form-group')
|
||||
.addClass('has-error')
|
||||
.append($("<div class='help-block error-help-block'>#{userExists} <a id='email-exists-login-link'>#{logIn}</a>"))
|
||||
forms.scrollToFirstError()
|
||||
else
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
|
||||
onClickEmailExistsLoginLink: ->
|
||||
modal = new AuthModal({ initialValues: { email: @trialRequest.get('properties')?.email } })
|
||||
@openModalView(modal)
|
||||
|
||||
onTrialRequestSubmit: ->
|
||||
me.setRole @trialRequest.get('properties').role.toLowerCase(), true
|
||||
storage.remove(FORM_KEY)
|
||||
@$('#request-form, #form-submit-success').toggleClass('hide')
|
||||
@scrollToTop(0)
|
||||
$('#flying-focus').css({top: 0, left: 0}) # Hack copied from Router.coffee#187. Ideally we'd swap out the view and have view-swapping logic handle this
|
||||
window.tracker?.trackEvent 'Submit Trial Request', category: 'Teachers', label: 'Trial Request', ['Mixpanel']
|
||||
|
||||
onClickGPlusSignupButton: ->
|
||||
btn = @$('#gplus-signup-btn')
|
||||
btn.attr('disabled', true)
|
||||
application.gplusHandler.loadAPI({
|
||||
context: @
|
||||
success: ->
|
||||
btn.attr('disabled', false)
|
||||
application.gplusHandler.connect({
|
||||
context: @
|
||||
success: ->
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('signup.creating'))
|
||||
btn.attr('disabled', true)
|
||||
application.gplusHandler.loadPerson({
|
||||
context: @
|
||||
success: (gplusAttrs) ->
|
||||
me.set(gplusAttrs)
|
||||
me.save(null, {
|
||||
url: "/db/user?gplusID=#{gplusAttrs.gplusID}&gplusAccessToken=#{application.gplusHandler.token()}"
|
||||
type: 'PUT'
|
||||
success: ->
|
||||
application.router.navigate(SIGNUP_REDIRECT)
|
||||
window.location.reload()
|
||||
error: errors.showNotyNetworkError
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onClickFacebookSignupButton: ->
|
||||
btn = @$('#facebook-signup-btn')
|
||||
btn.attr('disabled', true)
|
||||
application.facebookHandler.loadAPI({
|
||||
context: @
|
||||
success: ->
|
||||
btn.attr('disabled', false)
|
||||
application.facebookHandler.connect({
|
||||
context: @
|
||||
success: ->
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('signup.creating'))
|
||||
btn.attr('disabled', true)
|
||||
application.facebookHandler.loadPerson({
|
||||
context: @
|
||||
success: (facebookAttrs) ->
|
||||
me.set(facebookAttrs)
|
||||
me.save(null, {
|
||||
url: "/db/user?facebookID=#{facebookAttrs.facebookID}&facebookAccessToken=#{application.facebookHandler.token()}"
|
||||
type: 'PUT'
|
||||
success: ->
|
||||
application.router.navigate(SIGNUP_REDIRECT)
|
||||
window.location.reload()
|
||||
error: errors.showNotyNetworkError
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
onSubmitSignupForm: (e) ->
|
||||
e.preventDefault()
|
||||
form = @$('#signup-form')
|
||||
attrs = forms.formToObject(form)
|
||||
|
||||
forms.clearFormAlerts(form)
|
||||
result = tv4.validateMultiple(attrs, signupFormSchema)
|
||||
error = false
|
||||
if not result.valid
|
||||
forms.applyErrorsToForm(form, result.errors)
|
||||
error = true
|
||||
if attrs.password1 isnt attrs.password2
|
||||
forms.setErrorToProperty(form, 'password1', 'Passwords do not match')
|
||||
error = true
|
||||
return if error
|
||||
|
||||
me.set({
|
||||
password: attrs.password1
|
||||
name: attrs.name
|
||||
email: @trialRequest.get('properties').email
|
||||
})
|
||||
me.save(null, {
|
||||
success: ->
|
||||
application.router.navigate(SIGNUP_REDIRECT)
|
||||
window.location.reload()
|
||||
error: errors.showNotyNetworkError
|
||||
})
|
||||
|
||||
|
||||
|
||||
requestFormSchemaAnonymous = {
|
||||
type: 'object'
|
||||
required: ['firstName', 'lastName', 'email', 'organization', 'role', 'numStudents']
|
||||
properties:
|
||||
firstName: { type: 'string' }
|
||||
lastName: { type: 'string' }
|
||||
name: { type: 'string', minLength: 1 }
|
||||
email: { type: 'string', format: 'email' }
|
||||
phoneNumber: { type: 'string' }
|
||||
role: { type: 'string' }
|
||||
organization: { type: 'string' }
|
||||
city: { type: 'string' }
|
||||
state: { type: 'string' }
|
||||
country: { type: 'string' }
|
||||
numStudents: { type: 'string' }
|
||||
educationLevel: {
|
||||
type: 'array'
|
||||
items: { type: 'string' }
|
||||
}
|
||||
notes: { type: 'string' }
|
||||
}
|
||||
|
||||
# same form, but add username input
|
||||
requestFormSchemaLoggedIn = _.cloneDeep(requestFormSchemaAnonymous)
|
||||
requestFormSchemaLoggedIn.required.push('name')
|
||||
|
||||
signupFormSchema = {
|
||||
type: 'object'
|
||||
required: ['name', 'password1', 'password2']
|
||||
properties:
|
||||
name: { type: 'string' }
|
||||
password1: { type: 'string' }
|
||||
password2: { type: 'string' }
|
||||
}
|
25
scripts/mongodb/migrations/2016-03-18-init-school-roles.js
Normal file
25
scripts/mongodb/migrations/2016-03-18-init-school-roles.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Removes all users with a teacher-like role from classroom membership
|
||||
// Usage: copy and paste into mongo
|
||||
|
||||
var teacherRoles = ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent'];
|
||||
|
||||
db.users.find({'role': {$in: teacherRoles}}, {_id: 1, name: 1, email: 1, role: 1}).forEach(function(user) {
|
||||
print('Updating user', JSON.stringify(user));
|
||||
print(db.classrooms.find({members: user._id}, {name: 1}).toArray().length);
|
||||
print(db.classrooms.update({members: user._id}, {$pull: {members: user._id}}, {multi: true}));
|
||||
});
|
||||
|
||||
|
||||
// Finds all members of classrooms, sets their role to 'student' if they do not already have a role
|
||||
// Usage: copy and paste into mongo
|
||||
|
||||
db.classrooms.find({}, {members: 1}).forEach(function(classroom) {
|
||||
if(!classroom.members) {
|
||||
return;
|
||||
}
|
||||
for (var i in classroom.members) {
|
||||
var memberID = classroom.members[i];
|
||||
print('updating member', memberID);
|
||||
print(db.users.update({_id: memberID, role: {$exists: false}}, {$set: {role: 'student'}}));
|
||||
}
|
||||
});
|
|
@ -15,6 +15,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
hasAccess: (req) ->
|
||||
return false unless req.user
|
||||
return true if req.method is 'GET'
|
||||
# return false if req.method is 'POST' and not req.user?.isTeacher()
|
||||
req.method in @allowedMethods or req.user?.isAdmin()
|
||||
|
||||
hasAccessToDocument: (req, document, method=null) ->
|
||||
|
@ -52,6 +53,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
|
||||
joinClassroomAPI: (req, res, classroomID) ->
|
||||
return @sendBadInputError(res, 'Need an object with a code') unless req.body?.code
|
||||
# return @sendForbiddenError(res, 'Cannot join a classroom as a teacher') if req.user.isTeacher()
|
||||
code = req.body.code.toLowerCase()
|
||||
Classroom.findOne {code: code}, (err, classroom) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
|
@ -64,7 +66,15 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
return @sendDatabaseError(res, err) if err
|
||||
members.push req.user.get('_id')
|
||||
classroom.set('members', members)
|
||||
return @sendSuccess(res, @formatEntity(req, classroom))
|
||||
# TODO: remove teacher check here after forbidden above
|
||||
if req.user.get('role') not in ['student', 'teacher']
|
||||
User.findById(req.user.get('_id')).exec (err, user) =>
|
||||
user.set('role', 'student')
|
||||
user.save (err, user) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendSuccess(res, @formatEntity(req, classroom))
|
||||
else
|
||||
return @sendSuccess(res, @formatEntity(req, classroom))
|
||||
|
||||
removeMember: (req, res, classroomID) ->
|
||||
userID = req.body.userID
|
||||
|
|
|
@ -16,11 +16,15 @@ module.exports =
|
|||
user = yield User.findOne({emailLower: email})
|
||||
throw new errors.Conflict('User with this email already exists.') if user
|
||||
|
||||
trialRequest = database.initDoc(req, TrialRequest)
|
||||
trialRequest.set 'applicant', req.user._id
|
||||
trialRequest.set 'created', new Date()
|
||||
trialRequest = yield TrialRequest.findOne({applicant: req.user._id})
|
||||
if not trialRequest
|
||||
trialRequest = database.initDoc(req, TrialRequest)
|
||||
trialRequest.set 'applicant', req.user._id
|
||||
trialRequest.set 'created', new Date()
|
||||
trialRequest.set 'status', 'submitted'
|
||||
database.assignBody(req, trialRequest)
|
||||
attrs = _.pick req.body, 'properties', 'type'
|
||||
trialRequest.set 'properties', _.extend {}, trialRequest.get('properties'), attrs.properties
|
||||
trialRequest.set 'type', attrs.type
|
||||
database.validateDoc(trialRequest)
|
||||
trialRequest = yield trialRequest.save()
|
||||
res.status(201).send(trialRequest.toObject({req: req}))
|
||||
|
|
|
@ -61,6 +61,11 @@ UserSchema.methods.isArtisan = ->
|
|||
UserSchema.methods.isAnonymous = ->
|
||||
@get 'anonymous'
|
||||
|
||||
UserSchema.statics.teacherRoles = ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']
|
||||
|
||||
UserSchema.methods.isTeacher = ->
|
||||
return @get('role') in User.teacherRoles
|
||||
|
||||
UserSchema.methods.getUserInfo = ->
|
||||
id: @get('_id')
|
||||
email: if @get('anonymous') then 'Unregistered User' else @get('email')
|
||||
|
@ -303,6 +308,10 @@ UserSchema.methods.saveActiveUser = (event, done=null) ->
|
|||
done?()
|
||||
|
||||
UserSchema.pre('save', (next) ->
|
||||
Classroom = require '../classrooms/Classroom'
|
||||
if @isTeacher() and not @wasTeacher
|
||||
Classroom.update({members: @_id}, {$pull: {members: @_id}}, {multi: true}).exec (err, res) ->
|
||||
console.log 'removed self from all classrooms as a member', err, res
|
||||
if email = @get('email')
|
||||
@set('emailLower', email.toLowerCase())
|
||||
if name = @get('name')
|
||||
|
@ -322,6 +331,7 @@ UserSchema.post 'save', (doc) ->
|
|||
UserSchema.statics.updateServiceSettings(doc)
|
||||
|
||||
UserSchema.post 'init', (doc) ->
|
||||
doc.wasTeacher = doc.isTeacher()
|
||||
doc.startingEmails = _.cloneDeep(doc.get('emails'))
|
||||
|
||||
UserSchema.statics.hashPassword = (password) ->
|
||||
|
|
|
@ -42,6 +42,9 @@ UserHandler = class UserHandler extends Handler
|
|||
props.push 'permissions' unless config.isProduction or global.testing
|
||||
props.push 'jobProfileApproved', 'jobProfileNotes','jobProfileApprovedDate' if req.user.isAdmin() # Admins naturally edit these
|
||||
props.push @privateProperties... if req.user.isAdmin() # Admins are mad with power
|
||||
if not req.user.isAdmin()
|
||||
if document.isTeacher() and req.body.role not in User.teacherRoles
|
||||
props = _.without props, 'role'
|
||||
props
|
||||
|
||||
formatEntity: (req, document, publicOnly=false) =>
|
||||
|
|
|
@ -42,14 +42,16 @@ describe 'GET /db/classroom/:id', ->
|
|||
|
||||
it 'returns the classroom for the given id', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
data = { name: 'Classroom 1' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
classroomID = body._id
|
||||
request.get {uri: classroomsURL + '/' + body._id }, (err, res, body) ->
|
||||
user1.set('role', 'teacher')
|
||||
user1.save (err) ->
|
||||
data = { name: 'Classroom 1' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body._id).toBe(classroomID = body._id)
|
||||
done()
|
||||
classroomID = body._id
|
||||
request.get {uri: classroomsURL + '/' + body._id }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body._id).toBe(classroomID = body._id)
|
||||
done()
|
||||
|
||||
describe 'POST /db/classroom', ->
|
||||
|
||||
|
@ -60,13 +62,15 @@ describe 'POST /db/classroom', ->
|
|||
|
||||
it 'creates a new classroom for the given user', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
data = { name: 'Classroom 1' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.name).toBe('Classroom 1')
|
||||
expect(body.members.length).toBe(0)
|
||||
expect(body.ownerID).toBe(user1.id)
|
||||
done()
|
||||
user1.set('role', 'teacher')
|
||||
user1.save (err) ->
|
||||
data = { name: 'Classroom 1' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.name).toBe('Classroom 1')
|
||||
expect(body.members.length).toBe(0)
|
||||
expect(body.ownerID).toBe(user1.id)
|
||||
done()
|
||||
|
||||
it 'does not work for anonymous users', (done) ->
|
||||
logoutUser ->
|
||||
|
@ -75,6 +79,14 @@ describe 'POST /db/classroom', ->
|
|||
expect(res.statusCode).toBe(401)
|
||||
done()
|
||||
|
||||
# TODO: Re-enable when we enforce this again
|
||||
xit 'does not work for non-teacher users', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
data = { name: 'Classroom 1' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
|
||||
describe 'PUT /db/classroom', ->
|
||||
|
||||
|
@ -85,31 +97,35 @@ describe 'PUT /db/classroom', ->
|
|||
|
||||
it 'edits name and description', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
data = { name: 'Classroom 2' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
data = { name: 'Classroom 3', description: 'New Description' }
|
||||
url = classroomsURL + '/' + body._id
|
||||
request.put { uri: url, json: data }, (err, res, body) ->
|
||||
expect(body.name).toBe('Classroom 3')
|
||||
expect(body.description).toBe('New Description')
|
||||
done()
|
||||
user1.set('role', 'teacher')
|
||||
user1.save (err) ->
|
||||
data = { name: 'Classroom 2' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
data = { name: 'Classroom 3', description: 'New Description' }
|
||||
url = classroomsURL + '/' + body._id
|
||||
request.put { uri: url, json: data }, (err, res, body) ->
|
||||
expect(body.name).toBe('Classroom 3')
|
||||
expect(body.description).toBe('New Description')
|
||||
done()
|
||||
|
||||
it 'is not allowed if you are just a member', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
data = { name: 'Classroom 4' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
classroomCode = body.code
|
||||
loginNewUser (user2) ->
|
||||
url = getURL("/db/classroom/~/members")
|
||||
data = { code: classroomCode }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
url = classroomsURL + '/' + body._id
|
||||
request.put { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
user1.set('role', 'teacher')
|
||||
user1.save (err) ->
|
||||
data = { name: 'Classroom 4' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
classroomCode = body.code
|
||||
loginNewUser (user2) ->
|
||||
url = getURL("/db/classroom/~/members")
|
||||
data = { code: classroomCode }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
url = classroomsURL + '/' + body._id
|
||||
request.put { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
describe 'POST /db/classroom/~/members', ->
|
||||
|
||||
|
@ -120,19 +136,45 @@ describe 'POST /db/classroom/~/members', ->
|
|||
|
||||
it 'adds the signed in user to the list of members in the classroom', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
data = { name: 'Classroom 5' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
classroomCode = body.code
|
||||
classroomID = body._id
|
||||
expect(res.statusCode).toBe(200)
|
||||
loginNewUser (user2) ->
|
||||
url = getURL("/db/classroom/~/members")
|
||||
data = { code: classroomCode }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
Classroom.findById classroomID, (err, classroom) ->
|
||||
expect(classroom.get('members').length).toBe(1)
|
||||
done()
|
||||
user1.set('role', 'teacher')
|
||||
user1.save (err) ->
|
||||
data = { name: 'Classroom 5' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
classroomCode = body.code
|
||||
classroomID = body._id
|
||||
expect(res.statusCode).toBe(200)
|
||||
loginNewUser (user2) ->
|
||||
url = getURL("/db/classroom/~/members")
|
||||
data = { code: classroomCode }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
Classroom.findById classroomID, (err, classroom) ->
|
||||
expect(classroom.get('members').length).toBe(1)
|
||||
expect(classroom.get('members')?[0]?.equals(user2.get('_id'))).toBe(true)
|
||||
User.findById user2.get('_id'), (err, user2) ->
|
||||
expect(user2.get('role')).toBe('student')
|
||||
done()
|
||||
|
||||
# TODO: Re-enable when we enforce this again
|
||||
xit 'does not work if the user is a teacher', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('role', 'teacher')
|
||||
user1.save (err) ->
|
||||
data = { name: 'Classroom 5' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
classroomCode = body.code
|
||||
classroomID = body._id
|
||||
expect(res.statusCode).toBe(200)
|
||||
loginNewUser (user2) ->
|
||||
user2.set('role', 'teacher')
|
||||
user2.save (err, user2) ->
|
||||
url = getURL("/db/classroom/~/members")
|
||||
data = { code: classroomCode }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(403)
|
||||
Classroom.findById classroomID, (err, classroom) ->
|
||||
expect(classroom.get('members').length).toBe(0)
|
||||
done()
|
||||
|
||||
|
||||
describe 'DELETE /db/classroom/:id/members', ->
|
||||
|
@ -144,36 +186,40 @@ describe 'DELETE /db/classroom/:id/members', ->
|
|||
|
||||
it 'removes the given user from the list of members in the classroom', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
data = { name: 'Classroom 6' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
classroomCode = body.code
|
||||
classroomID = body._id
|
||||
expect(res.statusCode).toBe(200)
|
||||
loginNewUser (user2) ->
|
||||
url = getURL("/db/classroom/~/members")
|
||||
data = { code: classroomCode }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
Classroom.findById classroomID, (err, classroom) ->
|
||||
expect(classroom.get('members').length).toBe(1)
|
||||
url = getURL("/db/classroom/#{classroom.id}/members")
|
||||
data = { userID: user2.id }
|
||||
request.del { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
Classroom.findById classroomID, (err, classroom) ->
|
||||
expect(classroom.get('members').length).toBe(0)
|
||||
done()
|
||||
user1.set('role', 'teacher')
|
||||
user1.save (err) ->
|
||||
data = { name: 'Classroom 6' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
classroomCode = body.code
|
||||
classroomID = body._id
|
||||
expect(res.statusCode).toBe(200)
|
||||
loginNewUser (user2) ->
|
||||
url = getURL("/db/classroom/~/members")
|
||||
data = { code: classroomCode }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
Classroom.findById classroomID, (err, classroom) ->
|
||||
expect(classroom.get('members').length).toBe(1)
|
||||
url = getURL("/db/classroom/#{classroom.id}/members")
|
||||
data = { userID: user2.id }
|
||||
request.del { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
Classroom.findById classroomID, (err, classroom) ->
|
||||
expect(classroom.get('members').length).toBe(0)
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /db/classroom/:id/invite-members', ->
|
||||
|
||||
it 'takes a list of emails and sends invites', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
data = { name: 'Classroom 6' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
url = classroomsURL + '/' + body._id + '/invite-members'
|
||||
data = { emails: ['test@test.com'] }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
user1.set('role', 'teacher')
|
||||
user1.save (err) ->
|
||||
data = { name: 'Classroom 6' }
|
||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
url = classroomsURL + '/' + body._id + '/invite-members'
|
||||
data = { emails: ['test@test.com'] }
|
||||
request.post { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
|
|
@ -13,8 +13,6 @@ fixture = {
|
|||
}
|
||||
|
||||
describe 'POST /db/trial.request', ->
|
||||
URL = getURL('/db/trial.request')
|
||||
ownURL = getURL('/db/trial.request/-/own')
|
||||
|
||||
it 'sets type and properties given', utils.wrap (done) ->
|
||||
yield utils.clearModels([User, TrialRequest])
|
||||
|
@ -63,6 +61,29 @@ describe 'POST /db/trial.request', ->
|
|||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
||||
it 'updates an existing TrialRequest if there is one', utils.wrap (done) ->
|
||||
yield utils.clearModels([User, TrialRequest])
|
||||
@user = yield utils.initUser()
|
||||
yield utils.loginUser(@user)
|
||||
fixture.properties.email = @user.get('email')
|
||||
[res, body] = yield request.postAsync(getURL('/db/trial.request'), { json: fixture })
|
||||
expect(res.statusCode).toBe(201)
|
||||
expect(body._id).toBeDefined()
|
||||
trialRequest = yield TrialRequest.findById(body._id)
|
||||
|
||||
update = {
|
||||
type: 'course'
|
||||
properties:
|
||||
location: 'Bahamas'
|
||||
}
|
||||
[res, body] = yield request.postAsync(getURL('/db/trial.request'), { json: update })
|
||||
expect(body.type).toBe('course')
|
||||
expect(body.properties.location).toBe('Bahamas')
|
||||
expect(body._id).toBe(trialRequest.id)
|
||||
count = yield TrialRequest.count()
|
||||
expect(count).toBe(1)
|
||||
done()
|
||||
|
||||
describe 'GET /db/trial.request', ->
|
||||
|
||||
beforeEach utils.wrap (done) ->
|
||||
|
|
|
@ -256,6 +256,32 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
|
|||
expect(err).toBeNull()
|
||||
done()
|
||||
|
||||
describe 'when role is changed to teacher or other school administrator', ->
|
||||
it 'removes the user from all classrooms they are in', utils.wrap (done) ->
|
||||
user = yield utils.initUser()
|
||||
classroom = new Classroom({members: [user._id]})
|
||||
yield classroom.save()
|
||||
expect(classroom.get('members').length).toBe(1)
|
||||
yield utils.loginUser(user)
|
||||
[res, body] = yield request.putAsync { uri: getURL('/db/user/'+user.id), json: { role: 'teacher' }}
|
||||
yield new Promise (resolve) -> setTimeout(resolve, 10)
|
||||
classroom = yield Classroom.findById(classroom.id)
|
||||
expect(classroom.get('members').length).toBe(0)
|
||||
done()
|
||||
|
||||
it 'ignores attempts to change away from a teacher role', utils.wrap (done) ->
|
||||
user = yield utils.initUser()
|
||||
yield utils.loginUser(user)
|
||||
url = getURL('/db/user/'+user.id)
|
||||
[res, body] = yield request.putAsync { uri: url, json: { role: 'teacher' }}
|
||||
expect(body.role).toBe('teacher')
|
||||
[res, body] = yield request.putAsync { uri: url, json: { role: 'advisor' }}
|
||||
expect(body.role).toBe('advisor')
|
||||
[res, body] = yield request.putAsync { uri: url, json: { role: 'student' }}
|
||||
expect(body.role).toBe('advisor')
|
||||
done()
|
||||
|
||||
|
||||
describe 'GET /db/user', ->
|
||||
|
||||
it 'logs in as admin', (done) ->
|
||||
|
|
150
test/app/views/teachers/ConvertToTeacherAccountView.spec.coffee
Normal file
150
test/app/views/teachers/ConvertToTeacherAccountView.spec.coffee
Normal file
|
@ -0,0 +1,150 @@
|
|||
ConvertToTeacherAccountView = require 'views/teachers/ConvertToTeacherAccountView'
|
||||
storage = require 'core/storage'
|
||||
forms = require 'core/forms'
|
||||
|
||||
describe '/teachers/convert', ->
|
||||
describe 'when logged out', ->
|
||||
it 'redirects to /teachers/signup', ->
|
||||
spyOn(me, 'isAnonymous').and.returnValue(true)
|
||||
spyOn(application.router, 'navigate')
|
||||
Backbone.history.loadUrl('/teachers/convert')
|
||||
expect(application.router.navigate.calls.count()).toBe(1)
|
||||
args = application.router.navigate.calls.argsFor(0)
|
||||
expect(args[0]).toBe('/teachers/signup')
|
||||
|
||||
describe 'when logged in', ->
|
||||
it 'displays ConvertToTeacherAccountView', ->
|
||||
spyOn(me, 'isAnonymous').and.returnValue(false)
|
||||
spyOn(me, 'isTeacher').and.returnValue(false)
|
||||
spyOn(application.router, 'routeDirectly')
|
||||
Backbone.history.loadUrl('/teachers/convert')
|
||||
expect(application.router.routeDirectly.calls.count()).toBe(1)
|
||||
args = application.router.routeDirectly.calls.argsFor(0)
|
||||
expect(args[0]).toBe('teachers/ConvertToTeacherAccountView')
|
||||
|
||||
|
||||
describe 'ConvertToTeacherAccountView (/teachers/convert)', ->
|
||||
|
||||
view = null
|
||||
|
||||
successForm = {
|
||||
phoneNumber: '555-555-5555'
|
||||
role: 'Teacher'
|
||||
organization: 'School'
|
||||
city: 'Springfield'
|
||||
state: 'AA'
|
||||
country: 'asdf'
|
||||
numStudents: '1-10'
|
||||
educationLevel: ['Middle']
|
||||
firstName: 'Mr'
|
||||
lastName: 'Bean'
|
||||
}
|
||||
|
||||
beforeEach ->
|
||||
me.clear()
|
||||
me.set({
|
||||
_id: '1234'
|
||||
anonymous: false
|
||||
email: 'some@email.com'
|
||||
name: 'Existing User'
|
||||
})
|
||||
me._revertAttributes = {}
|
||||
view = new ConvertToTeacherAccountView()
|
||||
view.render()
|
||||
jasmine.demoEl(view.$el)
|
||||
|
||||
spyOn(storage, 'load').and.returnValue({ lastName: 'Saved Changes' })
|
||||
|
||||
|
||||
describe 'when the user already has a TrialRequest and is a teacher', ->
|
||||
beforeEach (done) ->
|
||||
spyOn(application.router, 'navigate')
|
||||
spyOn(me, 'isTeacher').and.returnValue(true)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 200
|
||||
responseText: JSON.stringify([{
|
||||
_id: '1'
|
||||
properties: {
|
||||
firstName: 'First'
|
||||
lastName: 'Last'
|
||||
}
|
||||
}])
|
||||
})
|
||||
_.defer done # Let SuperModel finish
|
||||
|
||||
it 'redirects to /courses/teachers', ->
|
||||
expect(application.router.navigate).toHaveBeenCalled()
|
||||
args = application.router.navigate.calls.argsFor(0)
|
||||
expect(args[0]).toBe('/courses/teachers')
|
||||
|
||||
|
||||
describe 'when the user has role "student"', ->
|
||||
beforeEach ->
|
||||
me.set('role', 'student')
|
||||
view.render()
|
||||
|
||||
it 'shows a warning that they will convert to a teacher account', ->
|
||||
expect(view.$('#conversion-warning').length).toBe(1)
|
||||
|
||||
# TODO: Figure out how to test this
|
||||
# describe 'the warning', ->
|
||||
# it 'includes a learn more link which opens a modal with more info'
|
||||
|
||||
describe 'submitting the form', ->
|
||||
beforeEach ->
|
||||
form = view.$('form')
|
||||
forms.objectToForm(form, successForm, {overwriteExisting: true})
|
||||
spyOn(view, 'openModalView')
|
||||
form.submit()
|
||||
|
||||
it 'requires confirmation', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).not.toBe('/db/trial.request')
|
||||
expect(request.method).not.toBe('POST')
|
||||
confirmModal = view.openModalView.calls.argsFor(0)[0]
|
||||
confirmModal.trigger 'confirm'
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/trial.request')
|
||||
expect(request.method).toBe('POST')
|
||||
|
||||
describe '"Log out" link', ->
|
||||
|
||||
it 'logs out the user and redirects them to /teachers/signup', ->
|
||||
spyOn(me, 'logout')
|
||||
view.$('#logout-link').click()
|
||||
expect(me.logout).toHaveBeenCalled()
|
||||
|
||||
describe 'submitting the form', ->
|
||||
beforeEach ->
|
||||
form = view.$('form')
|
||||
forms.objectToForm(form, successForm, {overwriteExisting: true})
|
||||
form.submit()
|
||||
|
||||
it 'creates a new TrialRequest with the information', ->
|
||||
request = _.last(jasmine.Ajax.requests.filter((r) -> _.string.startsWith(r.url, '/db/trial.request')))
|
||||
expect(request).toBeTruthy()
|
||||
expect(request.method).toBe('POST')
|
||||
attrs = JSON.parse(request.params)
|
||||
expect(attrs.properties?.firstName).toBe('Mr')
|
||||
|
||||
it 'redirects to /courses/teachers', ->
|
||||
spyOn(application.router, 'navigate')
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 201
|
||||
responseText: JSON.stringify(_.extend({_id:'fraghlarghl'}, JSON.parse(request.params)))
|
||||
})
|
||||
expect(application.router.navigate).toHaveBeenCalled()
|
||||
args = application.router.navigate.calls.argsFor(0)
|
||||
expect(args[0]).toBe('/courses/teachers')
|
||||
|
||||
it 'sets a teacher role', ->
|
||||
spyOn(application.router, 'navigate')
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 201
|
||||
responseText: JSON.stringify(_.extend({_id:'fraghlarghl'}, JSON.parse(request.params)))
|
||||
})
|
||||
expect(me.get('role')).toBe(successForm.role.toLowerCase())
|
||||
|
257
test/app/views/teachers/CreateTeacherAccountView.spec.coffee
Normal file
257
test/app/views/teachers/CreateTeacherAccountView.spec.coffee
Normal file
|
@ -0,0 +1,257 @@
|
|||
CreateTeacherAccountView = require 'views/teachers/CreateTeacherAccountView'
|
||||
storage = require 'core/storage'
|
||||
forms = require 'core/forms'
|
||||
|
||||
describe '/teachers/signup', ->
|
||||
|
||||
describe 'when logged out', ->
|
||||
|
||||
it 'displays CreateTeacherAccountView', ->
|
||||
spyOn(me, 'isAnonymous').and.returnValue(true)
|
||||
spyOn(application.router, 'routeDirectly')
|
||||
Backbone.history.loadUrl('/teachers/signup')
|
||||
expect(application.router.routeDirectly.calls.count()).toBe(1)
|
||||
args = application.router.routeDirectly.calls.argsFor(0)
|
||||
expect(args[0]).toBe('teachers/CreateTeacherAccountView')
|
||||
|
||||
describe 'when logged in', ->
|
||||
|
||||
it 'redirects to /teachers/convert', ->
|
||||
spyOn(me, 'isAnonymous').and.returnValue(false)
|
||||
spyOn(application.router, 'navigate')
|
||||
Backbone.history.loadUrl('/teachers/signup')
|
||||
expect(application.router.navigate.calls.count()).toBe(1)
|
||||
args = application.router.navigate.calls.argsFor(0)
|
||||
expect(args[0]).toBe('/teachers/convert')
|
||||
|
||||
|
||||
describe 'CreateTeacherAccountView', ->
|
||||
|
||||
view = null
|
||||
|
||||
successForm = {
|
||||
name: 'New Name'
|
||||
phoneNumber: '555-555-5555'
|
||||
role: 'Teacher'
|
||||
organization: 'School'
|
||||
city: 'Springfield'
|
||||
state: 'AA'
|
||||
country: 'asdf'
|
||||
numStudents: '1-10'
|
||||
educationLevel: ['Middle']
|
||||
email: 'some@email.com'
|
||||
firstName: 'Mr'
|
||||
lastName: 'Bean'
|
||||
password1: 'letmein'
|
||||
password2: 'letmein'
|
||||
}
|
||||
|
||||
beforeEach (done) ->
|
||||
me.clear()
|
||||
me.set('_id', '1234')
|
||||
me._revertAttributes = {}
|
||||
spyOn(me, 'isAnonymous').and.returnValue(true)
|
||||
view = new CreateTeacherAccountView()
|
||||
view.render()
|
||||
jasmine.demoEl(view.$el)
|
||||
|
||||
spyOn(storage, 'load').and.returnValue({ lastName: 'Saved Changes' })
|
||||
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 200
|
||||
responseText: JSON.stringify([{
|
||||
_id: '1'
|
||||
properties: {
|
||||
firstName: 'First'
|
||||
lastName: 'Last'
|
||||
}
|
||||
}])
|
||||
})
|
||||
_.defer done # Let SuperModel finish
|
||||
|
||||
describe '"Log in" link', ->
|
||||
|
||||
it 'opens the log in modal', ->
|
||||
spyOn(view, 'openModalView')
|
||||
view.$('.alert .login-link').click()
|
||||
expect(view.openModalView.calls.count()).toBe(1)
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
expect(view.openModalView.calls.argsFor(0)[0] instanceof AuthModal).toBe(true)
|
||||
|
||||
describe 'clicking the Facebook button', ->
|
||||
|
||||
beforeEach ->
|
||||
application.facebookHandler.fakeAPI()
|
||||
view.$('#facebook-signup-btn').click()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234')
|
||||
expect(request.method).toBe('GET')
|
||||
|
||||
describe 'when an associated user already exists', ->
|
||||
beforeEach ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 200
|
||||
responseText: JSON.stringify({_id: 'abcd'})
|
||||
})
|
||||
|
||||
it 'logs them in and redirects them to the ConvertToTeacherAccountView', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/auth/login-facebook')
|
||||
|
||||
describe 'when the user\s info is loaded', ->
|
||||
beforeEach ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({ status: 404, responseText: '{}' })
|
||||
|
||||
it 'disables and fills in the email, first name, last name and password fields', ->
|
||||
for field in ['email', 'firstName', 'lastName', 'password1', 'password2']
|
||||
expect(view.$("input[name='#{field}']").attr('disabled')).toBeTruthy()
|
||||
|
||||
it 'hides the social login buttons and shows a success message', ->
|
||||
expect(view.$('#facebook-logged-in-row').hasClass('hide')).toBe(false)
|
||||
expect(view.$('#social-network-signups').hasClass('hide')).toBe(true)
|
||||
|
||||
describe 'and the user finishes filling in the form and submits', ->
|
||||
|
||||
beforeEach ->
|
||||
form = view.$('form')
|
||||
forms.objectToForm(form, successForm)
|
||||
form.submit()
|
||||
|
||||
it 'creates a user associated with the Facebook account', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/trial.request')
|
||||
request.respondWith({
|
||||
status: 201
|
||||
responseText: JSON.stringify(_.extend({_id:'fraghlarghl'}, JSON.parse(request.params)))
|
||||
})
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe("/db/user?facebookID=abcd&facebookAccessToken=1234")
|
||||
body = JSON.parse(request.params)
|
||||
expect(body.name).toBe('New Name')
|
||||
expect(body.email).toBe('some@email.com')
|
||||
expect(body.firstName).toBe('Mr')
|
||||
expect(body.lastName).toBe('Bean')
|
||||
|
||||
describe 'clicking the G+ button', ->
|
||||
|
||||
beforeEach ->
|
||||
application.gplusHandler.fakeAPI()
|
||||
view.$('#gplus-signup-btn').click()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234')
|
||||
expect(request.method).toBe('GET')
|
||||
|
||||
describe 'when an associated user already exists', ->
|
||||
beforeEach ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 200
|
||||
responseText: JSON.stringify({_id: 'abcd'})
|
||||
})
|
||||
|
||||
it 'logs them in and redirects them to the ConvertToTeacherAccountView', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/auth/login-gplus')
|
||||
|
||||
describe 'when the user\s info is loaded', ->
|
||||
beforeEach ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({ status: 404, responseText: '{}' })
|
||||
|
||||
it 'disables and fills in the email, first name, last name and password fields', ->
|
||||
for field in ['email', 'firstName', 'lastName', 'password1', 'password2']
|
||||
expect(view.$("input[name='#{field}']").attr('disabled')).toBeTruthy()
|
||||
|
||||
it 'hides the social login buttons and shows a success message', ->
|
||||
expect(view.$('#gplus-logged-in-row').hasClass('hide')).toBe(false)
|
||||
expect(view.$('#social-network-signups').hasClass('hide')).toBe(true)
|
||||
|
||||
describe 'and the user finishes filling in the form and submits', ->
|
||||
|
||||
beforeEach ->
|
||||
form = view.$('form')
|
||||
forms.objectToForm(form, successForm)
|
||||
form.submit()
|
||||
|
||||
it 'creates a user associated with the GPlus account', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/trial.request')
|
||||
request.respondWith({
|
||||
status: 201
|
||||
responseText: JSON.stringify(_.extend({_id:'fraghlarghl'}, JSON.parse(request.params)))
|
||||
})
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe("/db/user?gplusID=abcd&gplusAccessToken=1234")
|
||||
body = JSON.parse(request.params)
|
||||
expect(body.name).toBe('New Name')
|
||||
expect(body.email).toBe('some@email.com')
|
||||
expect(body.firstName).toBe('Mr')
|
||||
expect(body.lastName).toBe('Bean')
|
||||
|
||||
describe 'submitting the form successfully', ->
|
||||
|
||||
beforeEach ->
|
||||
form = view.$('form')
|
||||
forms.objectToForm(form, successForm)
|
||||
form.submit()
|
||||
|
||||
it 'submits a trial request, which does not include "account" settings', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/trial.request')
|
||||
expect(request.method).toBe('POST')
|
||||
attrs = JSON.parse(request.params)
|
||||
expect(attrs.password1).toBeUndefined()
|
||||
expect(attrs.password2).toBeUndefined()
|
||||
expect(attrs.name).toBeUndefined()
|
||||
|
||||
describe 'after saving the new trial request', ->
|
||||
beforeEach ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 201
|
||||
responseText: JSON.stringify(_.extend({_id:'fraghlarghl'}, JSON.parse(request.params)))
|
||||
})
|
||||
|
||||
it 'creates a new user', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/user')
|
||||
expect(request.method).toBe('POST')
|
||||
attrs = JSON.parse(request.params)
|
||||
for attr in ['password', 'name', 'email', 'role']
|
||||
expect(attrs[attr]).toBeDefined()
|
||||
|
||||
describe 'after saving the new user', ->
|
||||
|
||||
beforeEach ->
|
||||
spyOn(application.router, 'navigate')
|
||||
spyOn(application.router, 'reload')
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 201
|
||||
responseText: JSON.stringify(_.extend({_id:'fraghlarghl'}, JSON.parse(request.params)))
|
||||
})
|
||||
|
||||
it 'redirects to "/courses/teachers"', ->
|
||||
expect(application.router.navigate).toHaveBeenCalled()
|
||||
expect(application.router.reload).toHaveBeenCalled()
|
||||
|
||||
|
||||
describe 'submitting the form with an email for an existing account', ->
|
||||
|
||||
beforeEach ->
|
||||
form = view.$('form')
|
||||
forms.objectToForm(form, successForm)
|
||||
form.submit()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({ status: 409, responseText: '{}' })
|
||||
|
||||
it 'displays an error with a log in link', ->
|
||||
expect(view.$('#email-form-group').hasClass('has-error')).toBe(true)
|
||||
spyOn(view, 'openModalView')
|
||||
view.$('#email-form-group .login-link').click()
|
||||
expect(view.openModalView).toHaveBeenCalled()
|
||||
|
||||
|
210
test/app/views/teachers/RequestQuoteView.spec.coffee
Normal file
210
test/app/views/teachers/RequestQuoteView.spec.coffee
Normal file
|
@ -0,0 +1,210 @@
|
|||
RequestQuoteView = require 'views/teachers/RequestQuoteView'
|
||||
storage = require 'core/storage'
|
||||
forms = require 'core/forms'
|
||||
|
||||
describe 'RequestQuoteView', ->
|
||||
|
||||
view = null
|
||||
|
||||
successFormValues = {
|
||||
firstName: 'A'
|
||||
lastName: 'B'
|
||||
email: 'C@D.com'
|
||||
phoneNumber: '555-555-5555'
|
||||
role: 'Teacher'
|
||||
organization: 'School'
|
||||
city: 'Springfield'
|
||||
state: 'AA'
|
||||
country: 'asdf'
|
||||
numStudents: '1-10'
|
||||
educationLevel: ['Middle']
|
||||
}
|
||||
|
||||
isSubmitRequest = (r) -> _.string.startsWith(r.url, '/db/trial.request') and r.method is 'POST'
|
||||
|
||||
describe 'when user is anonymous and has an existing trial request', ->
|
||||
beforeEach (done) ->
|
||||
me.clear()
|
||||
me.set('_id', '1234')
|
||||
me._revertAttributes = {}
|
||||
spyOn(me, 'isAnonymous').and.returnValue(true)
|
||||
view = new RequestQuoteView()
|
||||
view.render()
|
||||
jasmine.demoEl(view.$el)
|
||||
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 200
|
||||
responseText: JSON.stringify([{
|
||||
_id: '1'
|
||||
properties: {
|
||||
firstName: 'First'
|
||||
lastName: 'Last'
|
||||
}
|
||||
}])
|
||||
})
|
||||
_.defer done # Let SuperModel finish
|
||||
|
||||
it 'shows request received', ->
|
||||
expect(view.$('#request-form').hasClass('hide')).toBe(true)
|
||||
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
|
||||
|
||||
|
||||
describe 'when user is signed in and has an existing trial request', ->
|
||||
beforeEach (done) ->
|
||||
me.clear()
|
||||
me.set('_id', '1234')
|
||||
me._revertAttributes = {}
|
||||
spyOn(me, 'isAnonymous').and.returnValue(false)
|
||||
view = new RequestQuoteView()
|
||||
view.render()
|
||||
jasmine.demoEl(view.$el)
|
||||
|
||||
spyOn(storage, 'load').and.returnValue({ lastName: 'Saved Changes' })
|
||||
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 200
|
||||
responseText: JSON.stringify([{
|
||||
_id: '1'
|
||||
properties: {
|
||||
firstName: 'First'
|
||||
lastName: 'Last'
|
||||
}
|
||||
}])
|
||||
})
|
||||
_.defer done # Let SuperModel finish
|
||||
|
||||
it 'shows form with data from the most recent request', ->
|
||||
expect(view.$('input[name="firstName"]').val()).toBe('First')
|
||||
|
||||
it 'prioritizes showing local, unsaved changes', ->
|
||||
expect(view.$('input[name="lastName"]').val()).toBe('Saved Changes')
|
||||
|
||||
describe 'when the form changes', ->
|
||||
|
||||
it 'stores local, unsaved changes', ->
|
||||
spyOn(storage, 'save')
|
||||
view.$('input[name="firstName"]').val('Just Changed').change()
|
||||
expect(storage.save).toHaveBeenCalled()
|
||||
args = storage.save.calls.argsFor(0)
|
||||
expect(args[1].firstName).toBe('Just Changed')
|
||||
|
||||
describe 'when a user is anonymous and does NOT have an existing trial request', ->
|
||||
beforeEach (done) ->
|
||||
me.clear()
|
||||
me.set('_id', '1234')
|
||||
me._revertAttributes = {}
|
||||
spyOn(me, 'isAnonymous').and.returnValue(true)
|
||||
view = new RequestQuoteView()
|
||||
view.render()
|
||||
jasmine.demoEl(view.$el)
|
||||
|
||||
spyOn(storage, 'load').and.returnValue({ lastName: 'Saved Changes' })
|
||||
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 200
|
||||
responseText: '[]'
|
||||
})
|
||||
_.defer done # Let SuperModel finish
|
||||
|
||||
describe 'on successful form submit', ->
|
||||
beforeEach ->
|
||||
forms.objectToForm(view.$el, successFormValues)
|
||||
view.$('#request-form').submit()
|
||||
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
|
||||
@submitRequest.respondWith({
|
||||
status: 201
|
||||
responseText: JSON.stringify(_.extend({_id: 'a'}, successFormValues))
|
||||
})
|
||||
|
||||
it 'creates a new trial request', ->
|
||||
expect(@submitRequest).toBeTruthy()
|
||||
expect(@submitRequest.method).toBe('POST')
|
||||
|
||||
it 'sets the user\'s role to the one they chose', ->
|
||||
request = _.last(jasmine.Ajax.requests.filter((r) -> _.string.startsWith(r.url, '/db/user')))
|
||||
expect(request).toBeTruthy()
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(JSON.parse(request.params).role).toBe('teacher')
|
||||
|
||||
it 'shows a signup form', ->
|
||||
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
|
||||
expect(view.$('#request-form').hasClass('hide')).toBe(true)
|
||||
|
||||
describe 'signup form', ->
|
||||
beforeEach ->
|
||||
application.facebookHandler.fakeAPI()
|
||||
application.gplusHandler.fakeAPI()
|
||||
|
||||
it 'includes a facebook button which will sign them in immediately', ->
|
||||
view.$('#facebook-signup-btn').click()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234')
|
||||
|
||||
it 'includes a gplus button which will sign them in immediately', ->
|
||||
view.$('#gplus-signup-btn').click()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234')
|
||||
|
||||
it 'can sign them up with username and password', ->
|
||||
form = view.$('#signup-form')
|
||||
forms.objectToForm(form, {
|
||||
password1: 'asdf'
|
||||
password2: 'asdf'
|
||||
name: 'some name'
|
||||
})
|
||||
form.submit()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(request.url).toBe('/db/user/1234')
|
||||
|
||||
describe 'when an anonymous user tries to submit a request with an existing user\'s email', ->
|
||||
|
||||
beforeEach ->
|
||||
forms.objectToForm(view.$el, successFormValues)
|
||||
view.$('#request-form').submit()
|
||||
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
|
||||
@submitRequest.respondWith({
|
||||
status: 409
|
||||
responseText: '{}'
|
||||
})
|
||||
|
||||
it 'shows an error that the email already exists', ->
|
||||
expect(view.$('#email-form-group').hasClass('has-error')).toBe(true)
|
||||
expect(view.$('#email-form-group .error-help-block').length).toBe(1)
|
||||
|
||||
describe 'when user is signed in and has role "student"', ->
|
||||
beforeEach (done) ->
|
||||
me.set('role', 'student')
|
||||
me.set('name', 'Some User')
|
||||
spyOn(me, 'isAnonymous').and.returnValue(false)
|
||||
view = new RequestQuoteView()
|
||||
view.render()
|
||||
jasmine.demoEl(view.$el)
|
||||
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({ status: 200, responseText: '[]'})
|
||||
_.defer done # Let SuperModel finish
|
||||
|
||||
it 'shows a conversion warning', ->
|
||||
expect(view.$('#conversion-warning').length).toBe(1)
|
||||
|
||||
it 'requires confirmation to submit the form', ->
|
||||
form = view.$('#request-form')
|
||||
forms.objectToForm(form, successFormValues)
|
||||
spyOn(view, 'openModalView')
|
||||
form.submit()
|
||||
expect(view.openModalView).toHaveBeenCalled()
|
||||
|
||||
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
|
||||
expect(submitRequest).toBeFalsy()
|
||||
confirmModal = view.openModalView.calls.argsFor(0)[0]
|
||||
confirmModal.trigger 'confirm'
|
||||
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
|
||||
expect(submitRequest).toBeTruthy()
|
||||
|
||||
|
Loading…
Reference in a new issue