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')
|
'schools': go('NewHomeView')
|
||||||
|
|
||||||
'teachers': go('NewHomeView')
|
'teachers': go('NewHomeView')
|
||||||
'teachers/freetrial': go('RequestQuoteView')
|
'teachers/demo': go('teachers/RequestQuoteView')
|
||||||
'teachers/quote': go('RequestQuoteView')
|
'teachers/freetrial': go('teachers/RequestQuoteView')
|
||||||
'teachers/demo': go('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')
|
'test(/*subpath)': go('TestView')
|
||||||
|
|
||||||
|
@ -228,3 +234,6 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
navigate: (fragment, options) ->
|
navigate: (fragment, options) ->
|
||||||
super fragment, options
|
super fragment, options
|
||||||
Backbone.Mediator.publish 'router:navigated', route: fragment
|
Backbone.Mediator.publish 'router:navigated', route: fragment
|
||||||
|
|
||||||
|
reload: ->
|
||||||
|
document.location.reload()
|
|
@ -295,6 +295,7 @@
|
||||||
subject: "Subject"
|
subject: "Subject"
|
||||||
email: "Email"
|
email: "Email"
|
||||||
password: "Password"
|
password: "Password"
|
||||||
|
confirm_password: "Confirm Password"
|
||||||
message: "Message"
|
message: "Message"
|
||||||
code: "Code"
|
code: "Code"
|
||||||
ladder: "Ladder"
|
ladder: "Ladder"
|
||||||
|
@ -313,6 +314,9 @@
|
||||||
warrior: "Warrior"
|
warrior: "Warrior"
|
||||||
ranger: "Ranger"
|
ranger: "Ranger"
|
||||||
wizard: "Wizard"
|
wizard: "Wizard"
|
||||||
|
first_name: "First Name"
|
||||||
|
last_name: "Last Name"
|
||||||
|
username: "Username"
|
||||||
|
|
||||||
units:
|
units:
|
||||||
second: "second"
|
second: "second"
|
||||||
|
@ -766,6 +770,7 @@
|
||||||
phone_number_help: "Where can we reach you during the workday?"
|
phone_number_help: "Where can we reach you during the workday?"
|
||||||
role_label: "Your role"
|
role_label: "Your role"
|
||||||
role_help: "Select your primary role."
|
role_help: "Select your primary role."
|
||||||
|
role_default: "Select Role"
|
||||||
tech_coordinator: "Technology coordinator"
|
tech_coordinator: "Technology coordinator"
|
||||||
advisor: "Advisor"
|
advisor: "Advisor"
|
||||||
principal: "Principal"
|
principal: "Principal"
|
||||||
|
@ -776,6 +781,7 @@
|
||||||
state: "State"
|
state: "State"
|
||||||
country: "Country"
|
country: "Country"
|
||||||
num_students_help: "How many do you anticipate enrolling in CodeCombat?"
|
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_label: "Education Level of Students"
|
||||||
education_level_help: "Choose as many as apply."
|
education_level_help: "Choose as many as apply."
|
||||||
elementary_school: "Elementary School"
|
elementary_school: "Elementary School"
|
||||||
|
@ -784,10 +790,18 @@
|
||||||
middle_school: "Middle School"
|
middle_school: "Middle School"
|
||||||
college_plus: "College or higher"
|
college_plus: "College or higher"
|
||||||
anything_else: "Anything else we should know?"
|
anything_else: "Anything else we should know?"
|
||||||
thanks_header: "Thanks for requesting a demo!"
|
thanks_header: "Request Received!" # {change}
|
||||||
thanks_p: "We'll be in touch soon. Questions? Email us:"
|
thanks_sub_header: "Thanks for expressing interest in CodeCombat for your school."
|
||||||
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_p: "We'll be in touch soon! If you need to get in contact, you can reach us at:" # {change}
|
||||||
thanks_logged_in: "Set up a class, add your students, and monitor their progress as they learn computer science."
|
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"
|
setup_a_class: "Set Up a Class"
|
||||||
|
|
||||||
versions:
|
versions:
|
||||||
|
@ -1674,4 +1688,4 @@
|
||||||
one_month_coupon: "coupon: choose either Rails or HTML"
|
one_month_coupon: "coupon: choose either Rails or HTML"
|
||||||
one_month_discount: "discount, 30% off: choose either Rails or HTML"
|
one_month_discount: "discount, 30% off: choose either Rails or HTML"
|
||||||
license: "license"
|
license: "license"
|
||||||
oreilly: "ebook of your choice"
|
oreilly: "ebook of your choice"
|
|
@ -59,8 +59,10 @@ module.exports = class User extends CocoModel
|
||||||
|
|
||||||
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
|
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
|
||||||
|
|
||||||
|
isStudent: -> @get('role') is 'student'
|
||||||
|
|
||||||
isTeacher: ->
|
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) ->
|
setRole: (role, force=false) ->
|
||||||
return if me.isAdmin()
|
return if me.isAdmin()
|
||||||
|
|
|
@ -215,6 +215,18 @@ $forest: #20572B
|
||||||
.btn-lg
|
.btn-lg
|
||||||
font-size: 18px
|
font-size: 18px
|
||||||
|
|
||||||
|
.btn-gplus
|
||||||
|
color: white
|
||||||
|
background-color: #DD4B39
|
||||||
|
img
|
||||||
|
height: 22px
|
||||||
|
|
||||||
|
.btn-facebook
|
||||||
|
color: white
|
||||||
|
background-color: #3B5998
|
||||||
|
img
|
||||||
|
height: 22px
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
|
|
||||||
.text-navy
|
.text-navy
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
|
@import "app/styles/mixins"
|
||||||
|
@import "app/styles/bootstrap/variables"
|
||||||
|
|
||||||
#request-quote-view
|
#request-quote-view
|
||||||
#site-content-area
|
#site-content-area
|
||||||
//TODO: Maybe this should go in style-flat
|
margin: 50px 0 100px
|
||||||
margin: 50px 10px 100px
|
.row
|
||||||
|
margin: 20px 0
|
||||||
|
|
||||||
|
#conversion-warning
|
||||||
|
margin-top: 20px
|
||||||
|
|
||||||
.section
|
|
||||||
margin-top: 80px
|
|
||||||
margin-bottom: 50px
|
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label
|
label
|
||||||
margin-bottom: 0
|
margin-bottom: 0
|
||||||
|
@ -34,5 +37,15 @@
|
||||||
#submit-request-btn
|
#submit-request-btn
|
||||||
margin-left: 10px
|
margin-left: 10px
|
||||||
|
|
||||||
#login-btn
|
// After submit (anonymous)
|
||||||
margin-right: 10px
|
|
||||||
|
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")
|
a(href="/user/#{me.getSlugOrID()}", data-i18n="nav.profile")
|
||||||
li
|
li
|
||||||
a(href="/account/settings", data-i18n="play.settings")
|
a(href="/account/settings", data-i18n="play.settings")
|
||||||
li
|
unless me.isStudent()
|
||||||
a(href="/account/payments", data-i18n="account.payments")
|
li
|
||||||
li
|
a(href="/account/payments", data-i18n="account.payments")
|
||||||
a(href="/account/subscription", data-i18n="account.subscription")
|
unless me.isTeacher() || me.isStudent()
|
||||||
li
|
li
|
||||||
a(href="/account/prepaid", data-i18n="account.prepaid_codes")
|
a(href="/account/subscription", data-i18n="account.subscription")
|
||||||
|
unless me.isStudent()
|
||||||
|
li
|
||||||
|
a(href="/account/prepaid", data-i18n="account.prepaid_codes")
|
||||||
li
|
li
|
||||||
a#logout-button(data-i18n="login.log_out")
|
a#logout-button(data-i18n="login.log_out")
|
||||||
|
|
||||||
|
|
|
@ -29,15 +29,18 @@ block header
|
||||||
div.img-circle(style="background-image: url(#{me.getPhotoURL()})")
|
div.img-circle(style="background-image: url(#{me.getPhotoURL()})")
|
||||||
h3=me.displayName()
|
h3=me.displayName()
|
||||||
li
|
li
|
||||||
a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile")
|
a(href="/user/#{me.getSlugOrID()}", data-i18n="nav.profile")
|
||||||
li
|
li
|
||||||
a(href="/account/settings", data-i18n="play.settings")
|
a(href="/account/settings", data-i18n="play.settings")
|
||||||
li
|
unless me.isStudent()
|
||||||
a(href="/account/payments", data-i18n="account.payments")
|
li
|
||||||
li
|
a(href="/account/payments", data-i18n="account.payments")
|
||||||
a(href="/account/subscription", data-i18n="account.subscription")
|
unless me.isTeacher() || me.isStudent()
|
||||||
li
|
li
|
||||||
a(href="/account/prepaid", data-i18n="account.prepaid_codes")
|
a(href="/account/subscription", data-i18n="account.subscription")
|
||||||
|
unless me.isStudent()
|
||||||
|
li
|
||||||
|
a(href="/account/prepaid", data-i18n="account.prepaid_codes")
|
||||||
li
|
li
|
||||||
a#logout-button(data-i18n="login.log_out")
|
a#logout-button(data-i18n="login.log_out")
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ extends /templates/base
|
||||||
block content
|
block content
|
||||||
h3.text-right
|
h3.text-right
|
||||||
if me.isAnonymous()
|
if me.isAnonymous()
|
||||||
a(href="/teachers")
|
a(href="/teachers/signup")
|
||||||
span(data-i18n="courses.teachers_click")
|
span(data-i18n="courses.teachers_click")
|
||||||
span !
|
span !
|
||||||
else
|
else
|
||||||
|
|
|
@ -88,4 +88,11 @@ block content
|
||||||
strong Invalid number of students
|
strong Invalid number of students
|
||||||
|
|
||||||
p.text-center
|
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
|
block modal-header-content
|
||||||
h3= view.title
|
h3= view.title
|
||||||
|
|
||||||
block modal-body-content
|
block modal-body-content
|
||||||
p= view.body
|
p!= view.body
|
||||||
|
|
||||||
block modal-footer-content
|
block modal-footer-content
|
||||||
button.btn.btn-secondary#decline-button(type="button", data-dismiss="modal")= view.decline
|
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")
|
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
|
else
|
||||||
h6(data-i18n="new_home.want_coco")
|
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()
|
else if view.justPlaysCourses()
|
||||||
div
|
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'
|
app = require 'core/application'
|
||||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
|
||||||
Classroom = require 'models/Classroom'
|
Classroom = require 'models/Classroom'
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
Course = require 'models/Course'
|
Course = require 'models/Course'
|
||||||
|
@ -40,7 +39,6 @@ module.exports = class PurchaseCoursesView extends RootView
|
||||||
|
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
@pricePerStudent = @products.findWhere({name: 'course'}).get('amount')
|
@pricePerStudent = @products.findWhere({name: 'course'}).get('amount')
|
||||||
me.setRole 'teacher'
|
|
||||||
super()
|
super()
|
||||||
|
|
||||||
getPriceString: -> '$' + (@getPrice()/100).toFixed(2)
|
getPriceString: -> '$' + (@getPrice()/100).toFixed(2)
|
||||||
|
@ -77,7 +75,7 @@ module.exports = class PurchaseCoursesView extends RootView
|
||||||
numberOfStudentsIsValid: -> @numberOfStudents > 0 and @numberOfStudents < 100000
|
numberOfStudentsIsValid: -> @numberOfStudents > 0 and @numberOfStudents < 100000
|
||||||
|
|
||||||
onClickPurchaseButton: ->
|
onClickPurchaseButton: ->
|
||||||
return @openModalView new CreateAccountModal() if me.isAnonymous()
|
return application.router.navigate('/teachers/signup', {trigger: true}) if me.isAnonymous()
|
||||||
unless @numberOfStudentsIsValid()
|
unless @numberOfStudentsIsValid()
|
||||||
alert("Please enter the maximum number of students needed for your class.")
|
alert("Please enter the maximum number of students needed for your class.")
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
ActivateLicensesModal = require 'views/courses/ActivateLicensesModal'
|
ActivateLicensesModal = require 'views/courses/ActivateLicensesModal'
|
||||||
app = require 'core/application'
|
app = require 'core/application'
|
||||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
CocoModel = require 'models/CocoModel'
|
CocoModel = require 'models/CocoModel'
|
||||||
Course = require 'models/Course'
|
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
|
application.tracker?.trackEvent 'Classroom started add students', category: 'Courses', classroomID: classroom.id
|
||||||
|
|
||||||
onClickCreateNewClassButton: ->
|
onClickCreateNewClassButton: ->
|
||||||
return @openModalView new CreateAccountModal() if me.get('anonymous')
|
return application.router.navigate('/teachers/signup', {trigger: true}) if me.get('anonymous')
|
||||||
modal = new ClassroomSettingsModal({})
|
modal = new ClassroomSettingsModal({})
|
||||||
@openModalView(modal)
|
@openModalView(modal)
|
||||||
@listenToOnce modal, 'hide', =>
|
@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) ->
|
hasAccess: (req) ->
|
||||||
return false unless req.user
|
return false unless req.user
|
||||||
return true if req.method is 'GET'
|
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()
|
req.method in @allowedMethods or req.user?.isAdmin()
|
||||||
|
|
||||||
hasAccessToDocument: (req, document, method=null) ->
|
hasAccessToDocument: (req, document, method=null) ->
|
||||||
|
@ -52,6 +53,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
||||||
|
|
||||||
joinClassroomAPI: (req, res, classroomID) ->
|
joinClassroomAPI: (req, res, classroomID) ->
|
||||||
return @sendBadInputError(res, 'Need an object with a code') unless req.body?.code
|
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()
|
code = req.body.code.toLowerCase()
|
||||||
Classroom.findOne {code: code}, (err, classroom) =>
|
Classroom.findOne {code: code}, (err, classroom) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@ -64,7 +66,15 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
members.push req.user.get('_id')
|
members.push req.user.get('_id')
|
||||||
classroom.set('members', members)
|
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) ->
|
removeMember: (req, res, classroomID) ->
|
||||||
userID = req.body.userID
|
userID = req.body.userID
|
||||||
|
|
|
@ -16,11 +16,15 @@ module.exports =
|
||||||
user = yield User.findOne({emailLower: email})
|
user = yield User.findOne({emailLower: email})
|
||||||
throw new errors.Conflict('User with this email already exists.') if user
|
throw new errors.Conflict('User with this email already exists.') if user
|
||||||
|
|
||||||
trialRequest = database.initDoc(req, TrialRequest)
|
trialRequest = yield TrialRequest.findOne({applicant: req.user._id})
|
||||||
trialRequest.set 'applicant', req.user._id
|
if not trialRequest
|
||||||
trialRequest.set 'created', new Date()
|
trialRequest = database.initDoc(req, TrialRequest)
|
||||||
|
trialRequest.set 'applicant', req.user._id
|
||||||
|
trialRequest.set 'created', new Date()
|
||||||
trialRequest.set 'status', 'submitted'
|
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)
|
database.validateDoc(trialRequest)
|
||||||
trialRequest = yield trialRequest.save()
|
trialRequest = yield trialRequest.save()
|
||||||
res.status(201).send(trialRequest.toObject({req: req}))
|
res.status(201).send(trialRequest.toObject({req: req}))
|
||||||
|
|
|
@ -61,6 +61,11 @@ UserSchema.methods.isArtisan = ->
|
||||||
UserSchema.methods.isAnonymous = ->
|
UserSchema.methods.isAnonymous = ->
|
||||||
@get 'anonymous'
|
@get 'anonymous'
|
||||||
|
|
||||||
|
UserSchema.statics.teacherRoles = ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']
|
||||||
|
|
||||||
|
UserSchema.methods.isTeacher = ->
|
||||||
|
return @get('role') in User.teacherRoles
|
||||||
|
|
||||||
UserSchema.methods.getUserInfo = ->
|
UserSchema.methods.getUserInfo = ->
|
||||||
id: @get('_id')
|
id: @get('_id')
|
||||||
email: if @get('anonymous') then 'Unregistered User' else @get('email')
|
email: if @get('anonymous') then 'Unregistered User' else @get('email')
|
||||||
|
@ -303,6 +308,10 @@ UserSchema.methods.saveActiveUser = (event, done=null) ->
|
||||||
done?()
|
done?()
|
||||||
|
|
||||||
UserSchema.pre('save', (next) ->
|
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')
|
if email = @get('email')
|
||||||
@set('emailLower', email.toLowerCase())
|
@set('emailLower', email.toLowerCase())
|
||||||
if name = @get('name')
|
if name = @get('name')
|
||||||
|
@ -322,6 +331,7 @@ UserSchema.post 'save', (doc) ->
|
||||||
UserSchema.statics.updateServiceSettings(doc)
|
UserSchema.statics.updateServiceSettings(doc)
|
||||||
|
|
||||||
UserSchema.post 'init', (doc) ->
|
UserSchema.post 'init', (doc) ->
|
||||||
|
doc.wasTeacher = doc.isTeacher()
|
||||||
doc.startingEmails = _.cloneDeep(doc.get('emails'))
|
doc.startingEmails = _.cloneDeep(doc.get('emails'))
|
||||||
|
|
||||||
UserSchema.statics.hashPassword = (password) ->
|
UserSchema.statics.hashPassword = (password) ->
|
||||||
|
|
|
@ -42,6 +42,9 @@ UserHandler = class UserHandler extends Handler
|
||||||
props.push 'permissions' unless config.isProduction or global.testing
|
props.push 'permissions' unless config.isProduction or global.testing
|
||||||
props.push 'jobProfileApproved', 'jobProfileNotes','jobProfileApprovedDate' if req.user.isAdmin() # Admins naturally edit these
|
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
|
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
|
props
|
||||||
|
|
||||||
formatEntity: (req, document, publicOnly=false) =>
|
formatEntity: (req, document, publicOnly=false) =>
|
||||||
|
|
|
@ -42,14 +42,16 @@ describe 'GET /db/classroom/:id', ->
|
||||||
|
|
||||||
it 'returns the classroom for the given id', (done) ->
|
it 'returns the classroom for the given id', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
data = { name: 'Classroom 1' }
|
user1.set('role', 'teacher')
|
||||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
user1.save (err) ->
|
||||||
expect(res.statusCode).toBe(200)
|
data = { name: 'Classroom 1' }
|
||||||
classroomID = body._id
|
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||||
request.get {uri: classroomsURL + '/' + body._id }, (err, res, body) ->
|
|
||||||
expect(res.statusCode).toBe(200)
|
expect(res.statusCode).toBe(200)
|
||||||
expect(body._id).toBe(classroomID = body._id)
|
classroomID = body._id
|
||||||
done()
|
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', ->
|
describe 'POST /db/classroom', ->
|
||||||
|
|
||||||
|
@ -60,13 +62,15 @@ describe 'POST /db/classroom', ->
|
||||||
|
|
||||||
it 'creates a new classroom for the given user', (done) ->
|
it 'creates a new classroom for the given user', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
data = { name: 'Classroom 1' }
|
user1.set('role', 'teacher')
|
||||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
user1.save (err) ->
|
||||||
expect(res.statusCode).toBe(200)
|
data = { name: 'Classroom 1' }
|
||||||
expect(body.name).toBe('Classroom 1')
|
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||||
expect(body.members.length).toBe(0)
|
expect(res.statusCode).toBe(200)
|
||||||
expect(body.ownerID).toBe(user1.id)
|
expect(body.name).toBe('Classroom 1')
|
||||||
done()
|
expect(body.members.length).toBe(0)
|
||||||
|
expect(body.ownerID).toBe(user1.id)
|
||||||
|
done()
|
||||||
|
|
||||||
it 'does not work for anonymous users', (done) ->
|
it 'does not work for anonymous users', (done) ->
|
||||||
logoutUser ->
|
logoutUser ->
|
||||||
|
@ -74,6 +78,14 @@ describe 'POST /db/classroom', ->
|
||||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||||
expect(res.statusCode).toBe(401)
|
expect(res.statusCode).toBe(401)
|
||||||
done()
|
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', ->
|
describe 'PUT /db/classroom', ->
|
||||||
|
@ -85,31 +97,35 @@ describe 'PUT /db/classroom', ->
|
||||||
|
|
||||||
it 'edits name and description', (done) ->
|
it 'edits name and description', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
data = { name: 'Classroom 2' }
|
user1.set('role', 'teacher')
|
||||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
user1.save (err) ->
|
||||||
expect(res.statusCode).toBe(200)
|
data = { name: 'Classroom 2' }
|
||||||
data = { name: 'Classroom 3', description: 'New Description' }
|
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||||
url = classroomsURL + '/' + body._id
|
expect(res.statusCode).toBe(200)
|
||||||
request.put { uri: url, json: data }, (err, res, body) ->
|
data = { name: 'Classroom 3', description: 'New Description' }
|
||||||
expect(body.name).toBe('Classroom 3')
|
url = classroomsURL + '/' + body._id
|
||||||
expect(body.description).toBe('New Description')
|
request.put { uri: url, json: data }, (err, res, body) ->
|
||||||
done()
|
expect(body.name).toBe('Classroom 3')
|
||||||
|
expect(body.description).toBe('New Description')
|
||||||
|
done()
|
||||||
|
|
||||||
it 'is not allowed if you are just a member', (done) ->
|
it 'is not allowed if you are just a member', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
data = { name: 'Classroom 4' }
|
user1.set('role', 'teacher')
|
||||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
user1.save (err) ->
|
||||||
expect(res.statusCode).toBe(200)
|
data = { name: 'Classroom 4' }
|
||||||
classroomCode = body.code
|
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||||
loginNewUser (user2) ->
|
expect(res.statusCode).toBe(200)
|
||||||
url = getURL("/db/classroom/~/members")
|
classroomCode = body.code
|
||||||
data = { code: classroomCode }
|
loginNewUser (user2) ->
|
||||||
request.post { uri: url, json: data }, (err, res, body) ->
|
url = getURL("/db/classroom/~/members")
|
||||||
expect(res.statusCode).toBe(200)
|
data = { code: classroomCode }
|
||||||
url = classroomsURL + '/' + body._id
|
request.post { uri: url, json: data }, (err, res, body) ->
|
||||||
request.put { uri: url, json: data }, (err, res, body) ->
|
expect(res.statusCode).toBe(200)
|
||||||
expect(res.statusCode).toBe(403)
|
url = classroomsURL + '/' + body._id
|
||||||
done()
|
request.put { uri: url, json: data }, (err, res, body) ->
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
|
||||||
describe 'POST /db/classroom/~/members', ->
|
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) ->
|
it 'adds the signed in user to the list of members in the classroom', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
data = { name: 'Classroom 5' }
|
user1.set('role', 'teacher')
|
||||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
user1.save (err) ->
|
||||||
classroomCode = body.code
|
data = { name: 'Classroom 5' }
|
||||||
classroomID = body._id
|
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||||
expect(res.statusCode).toBe(200)
|
classroomCode = body.code
|
||||||
loginNewUser (user2) ->
|
classroomID = body._id
|
||||||
url = getURL("/db/classroom/~/members")
|
expect(res.statusCode).toBe(200)
|
||||||
data = { code: classroomCode }
|
loginNewUser (user2) ->
|
||||||
request.post { uri: url, json: data }, (err, res, body) ->
|
url = getURL("/db/classroom/~/members")
|
||||||
expect(res.statusCode).toBe(200)
|
data = { code: classroomCode }
|
||||||
Classroom.findById classroomID, (err, classroom) ->
|
request.post { uri: url, json: data }, (err, res, body) ->
|
||||||
expect(classroom.get('members').length).toBe(1)
|
expect(res.statusCode).toBe(200)
|
||||||
done()
|
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', ->
|
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) ->
|
it 'removes the given user from the list of members in the classroom', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
data = { name: 'Classroom 6' }
|
user1.set('role', 'teacher')
|
||||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
user1.save (err) ->
|
||||||
classroomCode = body.code
|
data = { name: 'Classroom 6' }
|
||||||
classroomID = body._id
|
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||||
expect(res.statusCode).toBe(200)
|
classroomCode = body.code
|
||||||
loginNewUser (user2) ->
|
classroomID = body._id
|
||||||
url = getURL("/db/classroom/~/members")
|
expect(res.statusCode).toBe(200)
|
||||||
data = { code: classroomCode }
|
loginNewUser (user2) ->
|
||||||
request.post { uri: url, json: data }, (err, res, body) ->
|
url = getURL("/db/classroom/~/members")
|
||||||
expect(res.statusCode).toBe(200)
|
data = { code: classroomCode }
|
||||||
Classroom.findById classroomID, (err, classroom) ->
|
request.post { uri: url, json: data }, (err, res, body) ->
|
||||||
expect(classroom.get('members').length).toBe(1)
|
expect(res.statusCode).toBe(200)
|
||||||
url = getURL("/db/classroom/#{classroom.id}/members")
|
Classroom.findById classroomID, (err, classroom) ->
|
||||||
data = { userID: user2.id }
|
expect(classroom.get('members').length).toBe(1)
|
||||||
request.del { uri: url, json: data }, (err, res, body) ->
|
url = getURL("/db/classroom/#{classroom.id}/members")
|
||||||
expect(res.statusCode).toBe(200)
|
data = { userID: user2.id }
|
||||||
Classroom.findById classroomID, (err, classroom) ->
|
request.del { uri: url, json: data }, (err, res, body) ->
|
||||||
expect(classroom.get('members').length).toBe(0)
|
expect(res.statusCode).toBe(200)
|
||||||
done()
|
Classroom.findById classroomID, (err, classroom) ->
|
||||||
|
expect(classroom.get('members').length).toBe(0)
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
||||||
describe 'POST /db/classroom/:id/invite-members', ->
|
describe 'POST /db/classroom/:id/invite-members', ->
|
||||||
|
|
||||||
it 'takes a list of emails and sends invites', (done) ->
|
it 'takes a list of emails and sends invites', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
data = { name: 'Classroom 6' }
|
user1.set('role', 'teacher')
|
||||||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
user1.save (err) ->
|
||||||
expect(res.statusCode).toBe(200)
|
data = { name: 'Classroom 6' }
|
||||||
url = classroomsURL + '/' + body._id + '/invite-members'
|
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||||
data = { emails: ['test@test.com'] }
|
|
||||||
request.post { uri: url, json: data }, (err, res, body) ->
|
|
||||||
expect(res.statusCode).toBe(200)
|
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', ->
|
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) ->
|
it 'sets type and properties given', utils.wrap (done) ->
|
||||||
yield utils.clearModels([User, TrialRequest])
|
yield utils.clearModels([User, TrialRequest])
|
||||||
|
@ -62,6 +60,29 @@ describe 'POST /db/trial.request', ->
|
||||||
[res, body] = yield request.postAsync(getURL('/db/trial.request'), { json: true })
|
[res, body] = yield request.postAsync(getURL('/db/trial.request'), { json: true })
|
||||||
expect(res.statusCode).toBe(422)
|
expect(res.statusCode).toBe(422)
|
||||||
done()
|
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', ->
|
describe 'GET /db/trial.request', ->
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,32 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
|
||||||
request.put {uri:getURL(urlUser + '/' + sam.id), json: sam.toObject()}, (err, response) ->
|
request.put {uri:getURL(urlUser + '/' + sam.id), json: sam.toObject()}, (err, response) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
done()
|
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', ->
|
describe 'GET /db/user', ->
|
||||||
|
|
||||||
|
|
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