Add required district field to teacher trial request forms

School now optional
N/A placeholder on district field and don't save it if it's n/a
Updating required field error UI a bit, and there is some larger
refactoring needed here later.
NCES phone number for district only entries will currently be a child
school, will fix later.

Closes #3818
This commit is contained in:
Matt Lott 2016-08-05 09:08:37 -07:00
parent f4c0e4144e
commit 6f08d5e422
18 changed files with 630 additions and 426 deletions

View file

@ -45,7 +45,7 @@ module.exports.applyErrorsToForm = (el, errors, warning=false) ->
for error in errors for error in errors
if error.code is tv4.errorCodes.OBJECT_REQUIRED if error.code is tv4.errorCodes.OBJECT_REQUIRED
prop = _.last(_.string.words(error.message)) # hack prop = _.last(_.string.words(error.message)) # hack
message = 'Required field' message = $.i18n.t('common.required_field')
else if error.dataPath else if error.dataPath
prop = error.dataPath[1..] prop = error.dataPath[1..]

View file

@ -356,7 +356,7 @@
submit_patch: "Submit Patch" submit_patch: "Submit Patch"
submit_changes: "Submit Changes" submit_changes: "Submit Changes"
save_changes: "Save Changes" save_changes: "Save Changes"
required_field: "Required field" required_field: "required" # {change}
general: general:
and: "and" and: "and"
@ -885,7 +885,9 @@
evaluate_recommend: "Evaluate/Recommend" evaluate_recommend: "Evaluate/Recommend"
approve_funds: "Approve Funds" approve_funds: "Approve Funds"
no_purchaser_role: "No role in purchase decisions" no_purchaser_role: "No role in purchase decisions"
organization_label: "Name of School/District" district_label: "District"
district_na: "Enter N/A if not applicable"
organization_label: "School" # {change}
city: "City" city: "City"
state: "State" state: "State"
country: "Country" country: "Country"

View file

@ -1,96 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
#request-quote-view
#site-content-area
margin: 50px 0 100px
.row
margin: 20px 0
#conversion-warning
margin-top: 20px
.form-group
label
margin-bottom: 0
label.checkbox
font-weight: normal
.help-block
margin: -4px 0 2px
p
margin: 0 0 20px
.checkbox, .checkbox-inline
input
margin-top: 8px
#other-education-level-input
label
display: inline-block
display: inline-block
width: 200px
margin-left: 5px
#submit-request-btn
margin-left: 10px
// After submit (anonymous)
h5
margin-top: 50px
#social-network-signups
margin: 20px 0
button
margin-left: 10px
.text-h1
margin: 40px 0 30px
.algolia-autocomplete
width: 100%;
.aa-input
width: 100%
.aa-hint
color: #999
width: 100%
.aa-dropdown-menu
background-color: #fff
border: 1px solid #999
border-top: none
width: 100%
.aa-suggestion
cursor: pointer
padding: 5px 4px
border-top: 1px solid #ccc
.school
font-family: Open Sans
font-size: 14px
line-height: 20px
font-weight: bold
.district
font-family: Open Sans
font-size: 14px
line-height: 20px
span
white-space: nowrap
.aa-suggestion.aa-cursor
background-color: #B2D7FF
em
font-weight: bold
font-style: normal

View file

@ -0,0 +1,81 @@
#create-teacher-account-view, #convert-to-teacher-account-view, #request-quote-view
.algolia-autocomplete
width: 100%;
.aa-input
width: 100%
.aa-hint
color: #999
width: 100%
.aa-dropdown-menu
background-color: #fff
border: 1px solid #999
border-top: none
width: 100%
.aa-suggestion
cursor: pointer
padding: 5px 4px
border-top: 1px solid #ccc
.school
font-family: Open Sans
font-size: 14px
line-height: 20px
font-weight: bold
.district
font-family: Open Sans
font-size: 14px
line-height: 20px
span
white-space: nowrap
.aa-suggestion.aa-cursor
background-color: #B2D7FF
em
font-weight: bold
font-style: normal
// TODO: update form validation instead of overwriting these styles
.control-label
font-weight: bold
width: 100%
.error-help-block
margin-top: inherit
margin-bottom: 0px
float: right
font-size: 13px
font-style: italic
font-weight: normal
.text-muted
float: right
font-size: 13px
font-style: italic
font-weight: normal
.nullify-form-control
display: inherit
width: inherit
height: inherit
padding: inherit
font-size: inherit
line-height: inherit
color: inherit
vertical-align: inherit
background-color: inherit
background-image: inherit
border: inherit
border-radius: inherit
-webkit-box-shadow: inherit
box-shadow: inherit
-webkit-transition: inherit
transition: inherit

View file

@ -30,8 +30,8 @@ block content
thead thead
tr tr
th Created th Created
th NCES District
th School Name th School Name
th School District
th.number NCES District Schools th.number NCES District Schools
th.number NCES District Students th.number NCES District Students
th.number NCES School Students th.number NCES School Students
@ -44,8 +44,8 @@ block content
- continue; - continue;
tr tr
td.created= trialRequest.get('created').substring(0, 10) td.created= trialRequest.get('created').substring(0, 10)
td= trialRequest.get('properties').nces_district || '' td= trialRequest.get('properties').nces_name || trialRequest.get('properties').organization || ''
td= trialRequest.get('properties').organization || '' td= trialRequest.get('properties').nces_district || trialRequest.get('properties').district || ''
td= trialRequest.get('properties').nces_district_schools || '' td= trialRequest.get('properties').nces_district_schools || ''
td= trialRequest.get('properties').nces_district_students || '' td= trialRequest.get('properties').nces_district_students || ''
td= trialRequest.get('properties').nces_students || '' td= trialRequest.get('properties').nces_students || ''

View file

@ -28,40 +28,36 @@ block content
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.username") span.control-label(data-i18n="general.username")
input.form-control(disabled=true value=me.get('name')) input.form-control(disabled=true value=me.get('name'))
.col-md-4.col-sm-6 .col-md-4.col-sm-6
#email-form-group.form-group #email-form-group.form-group
label.control-label(data-i18n="general.email") span.control-label(data-i18n="general.email")
input.form-control(name='email' disabled=true value=me.get('email')) input.form-control(name='email' disabled=true value=me.get('email'))
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.first_name") span.control-label(data-i18n="general.first_name")
input.form-control(name="firstName" value=me.get('firstName') || '') input.form-control(name="firstName" value=me.get('firstName') || '')
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.last_name") span.control-label(data-i18n="general.last_name")
input.form-control(name="lastName" value=me.get('lastName') || '') input.form-control(name="lastName" value=me.get('lastName') || '')
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="teachers_quote.phone_number") span(data-i18n="teachers_quote.phone_number")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
.help-block.small input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.primary_role_label") span.control-label(data-i18n="teachers_quote.primary_role_label")
.help-block.small
em.text-info(data-i18n="teachers_quote.role_help")
select.form-control(name="role") select.form-control(name="role")
option(data-i18n="teachers_quote.role_default", , value='') option(data-i18n="teachers_quote.role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher") option(data-i18n="courses.teacher", value="Teacher")
@ -75,30 +71,40 @@ block content
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.organization_label") span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization") input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.city") //- TODO: algolia and form errors both change form-control
input.form-control(name="city") //- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.state") span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="state") input.form-control(name="city")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country") span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-labelspan.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country") input.form-control(name="country")
#form-students-info #form-students-info
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="courses.number_programming_students") span.control-label(data-i18n="courses.number_programming_students")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help") em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents") select.form-control(name="numStudents")
@ -113,7 +119,7 @@ block content
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="courses.number_total_students") span(data-i18n="courses.number_total_students")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
select.form-control(name="numStudentsTotal") select.form-control(name="numStudentsTotal")
@ -126,11 +132,10 @@ block content
.form-group .form-group
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-10 .col-md-offset-2.col-md-4
label.control-label(data-i18n="teachers_quote.education_level_label") span.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help") em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.checkbox .checkbox
label label
input(type="checkbox" name="educationLevel" value="Elementary") input(type="checkbox" name="educationLevel" value="Elementary")
@ -156,7 +161,7 @@ block content
#anything-else-row.row.m-y-2 #anything-else-row.row.m-y-2
.col-md-offset-2.col-md-8 .col-md-offset-2.col-md-8
label.control-label span.control-label
span(data-i18n="teachers_quote.anything_else") span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
textarea.form-control(rows=8, name="notes") textarea.form-control(rows=8, name="notes")

View file

@ -34,49 +34,47 @@ block content
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.username") span.control-label(data-i18n="general.username")
input.form-control(name="name") input.form-control(name="name")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
#email-form-group.form-group #email-form-group.form-group
label.control-label(data-i18n="general.email") span.control-label(data-i18n="general.email")
input.form-control(name="email") input.form-control(name="email")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.first_name") span.control-label(data-i18n="general.first_name")
input.form-control(name="firstName") input.form-control(name="firstName")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.last_name") span.control-label(data-i18n="general.last_name")
input.form-control(name="lastName") input.form-control(name="lastName")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.password") span.control-label(data-i18n="general.password")
input.form-control(name="password1", type="password") input.form-control(name="password1", type="password")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.confirm_password") span.control-label(data-i18n="general.confirm_password")
input.form-control(name="password2", type="password") input.form-control(name="password2", type="password")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="teachers_quote.phone_number") span(data-i18n="teachers_quote.phone_number")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
.help-block.small input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.primary_role_label") span.control-label(data-i18n="teachers_quote.primary_role_label")
select.form-control(name="role") select.form-control(name="role")
option(data-i18n="teachers_quote.role_default", , value='') option(data-i18n="teachers_quote.role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher") option(data-i18n="courses.teacher", value="Teacher")
@ -90,30 +88,40 @@ block content
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.organization_label") span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization") input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.city") //- TODO: algolia and form errors both change form-control
input.form-control(name="city") //- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.state") span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="state") input.form-control(name="city")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country") span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country") input.form-control(name="country")
#form-students-info #form-students-info
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="courses.number_programming_students") span.control-label(data-i18n="courses.number_programming_students")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help") em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents") select.form-control(name="numStudents")
@ -128,7 +136,7 @@ block content
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="courses.number_total_students") span(data-i18n="courses.number_total_students")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
select.form-control(name="numStudentsTotal") select.form-control(name="numStudentsTotal")
@ -140,14 +148,11 @@ block content
option 10,000+ option 10,000+
.form-group .form-group
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-10 .col-md-offset-2.col-md-4
label.control-label(data-i18n="teachers_quote.education_level_label") span.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help") em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.checkbox .checkbox
label label
input(type="checkbox" name="educationLevel" value="Elementary") input(type="checkbox" name="educationLevel" value="Elementary")
@ -173,7 +178,7 @@ block content
#anything-else-row.row.m-y-2 #anything-else-row.row.m-y-2
.col-md-offset-2.col-md-8 .col-md-offset-2.col-md-8
label.control-label span.control-label
span(data-i18n="teachers_quote.anything_else") span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")

View file

@ -36,7 +36,7 @@ block content
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="general.username") span(data-i18n="general.username")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
- var name = me.get('name') || ''; - var name = me.get('name') || '';
@ -44,20 +44,20 @@ block content
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="share_progress_modal.form_label") span.control-label(data-i18n="share_progress_modal.form_label")
- var email = me.get('email') || ''; - var email = me.get('email') || '';
input.form-control(name="email" value=email, disabled=!!email) input.form-control(name="email" value=email, disabled=!!email)
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.first_name") span.control-label(data-i18n="general.first_name")
- var firstName = me.get('firstName') || ''; - var firstName = me.get('firstName') || '';
input.form-control(name="firstName" value=firstName, disabled=!!firstName) input.form-control(name="firstName" value=firstName, disabled=!!firstName)
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.last_name") span.control-label(data-i18n="general.last_name")
- var lastName = me.get('lastName') || ''; - var lastName = me.get('lastName') || '';
input.form-control(name="lastName" value=lastName, disabled=!!lastName) input.form-control(name="lastName" value=lastName, disabled=!!lastName)
@ -65,23 +65,20 @@ block content
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label(data-i18n="teachers_quote.phone_number")
span(data-i18n="teachers_quote.phone_number") input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
.help-block.small
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
if me.isAnonymous() if me.isAnonymous()
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
#email-form-group.form-group #email-form-group.form-group
label.control-label(data-i18n="general.email") span.control-label(data-i18n="general.email")
- var email = me.get('email') || ''; - var email = me.get('email') || '';
input.form-control(name="email" type="email", value=email, disabled=!!email) input.form-control(name="email" type="email", value=email, disabled=!!email)
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="teachers_quote.phone_number") span(data-i18n="teachers_quote.phone_number")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.phone_number_help") em.text-info(data-i18n="teachers_quote.phone_number_help")
@ -91,7 +88,7 @@ block content
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.primary_role_label") span.control-label(data-i18n="teachers_quote.primary_role_label")
select.form-control(name="role") select.form-control(name="role")
option(data-i18n="teachers_quote.primary_role_default", , value='') option(data-i18n="teachers_quote.primary_role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher") option(data-i18n="courses.teacher", value="Teacher")
@ -103,7 +100,7 @@ block content
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.purchaser_role_label") span.control-label(data-i18n="teachers_quote.purchaser_role_label")
select.form-control(name="purchaserRole") select.form-control(name="purchaserRole")
option(data-i18n="teachers_quote.purchaser_role_default", , value='') option(data-i18n="teachers_quote.purchaser_role_default", , value='')
option(data-i18n="teachers_quote.influence_advocate", value="Influence/Advocate") option(data-i18n="teachers_quote.influence_advocate", value="Influence/Advocate")
@ -112,33 +109,43 @@ block content
option(data-i18n="teachers_quote.no_purchaser_role", value="No role in purchase decisions") option(data-i18n="teachers_quote.no_purchaser_role", value="No role in purchase decisions")
#form-school-info #form-school-info
.row .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.organization_label") span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization") input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.city") //- TODO: algolia and form errors both change form-control
input.form-control(name="city") //- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.state") span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="state") input.form-control(name="city")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country") span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-labelspan.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country") input.form-control(name="country")
#form-students-info #form-students-info
.row .row
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="courses.number_programming_students") span.control-label(data-i18n="courses.number_programming_students")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help") em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents") select.form-control(name="numStudents")
@ -153,7 +160,7 @@ block content
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="courses.number_total_students") span.control-label(data-i18n="courses.number_total_students")
select.form-control(name="numStudentsTotal") select.form-control(name="numStudentsTotal")
option(data-i18n="teachers_quote.num_students_default", value='') option(data-i18n="teachers_quote.num_students_default", value='')
option 1-500 option 1-500
@ -163,14 +170,11 @@ block content
option 10,000+ option 10,000+
.form-group .form-group
.row.m-y-2
.row .col-md-offset-2.col-md-4
.col-md-offset-2.col-md-10 span.control-label(data-i18n="teachers_quote.education_level_label")
label.control-label(data-i18n="teachers_quote.education_level_label") .help-block.small
.help-block.small em.text-info(data-i18n="teachers_quote.education_level_help")
em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.checkbox .checkbox
label label
input(type="checkbox" name="educationLevel" value="Elementary") input(type="checkbox" name="educationLevel" value="Elementary")
@ -196,7 +200,7 @@ block content
#anything-else-row.row #anything-else-row.row
.col-md-offset-2.col-md-8 .col-md-offset-2.col-md-8
label.control-label span.control-label
span(data-i18n="teachers_quote.anything_else") span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
@ -210,7 +214,7 @@ block content
input(type="hidden" name="nces_students") input(type="hidden" name="nces_students")
input(type="hidden" name="nces_phone") input(type="hidden" name="nces_phone")
#buttons-row.row.text-center #buttons-row.row.m-y-2.text-center
input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.title") 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') #form-submit-success.text-center(class=showDone ? '' : 'hide')
@ -244,17 +248,17 @@ block content
.row .row
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="general.username") span.control-label(data-i18n="general.username")
input.form-control(name="name") input.form-control(name="name")
.row .row
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="general.password") span.control-label(data-i18n="general.password")
input.form-control(name="password1", type="password") input.form-control(name="password1", type="password")
.col-md-4 .col-md-4
.form-group .form-group
label.control-label(data-i18n="general.confirm_password") span.control-label(data-i18n="general.confirm_password")
input.form-control(name="password2", type="password") input.form-control(name="password2", type="password")
.text-center .text-center

View file

@ -7,9 +7,9 @@ errors = require 'core/errors'
User = require 'models/User' User = require 'models/User'
ConfirmModal = require 'views/editor/modal/ConfirmModal' ConfirmModal = require 'views/editor/modal/ConfirmModal'
algolia = require 'core/services/algolia' algolia = require 'core/services/algolia'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone']
FORM_KEY = 'request-quote-form' DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class ConvertToTeacherAccountView extends RootView module.exports = class ConvertToTeacherAccountView extends RootView
id: 'convert-to-teacher-account-view' id: 'convert-to-teacher-account-view'
@ -40,7 +40,7 @@ module.exports = class ConvertToTeacherAccountView extends RootView
return 'Your account has not been updated! If you continue, your changes will be lost.' return 'Your account has not been updated! If you continue, your changes will be lost.'
invalidateNCES: -> invalidateNCES: ->
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val '' @$('input[name="nces_' + key + '"]').val ''
onLoaded: -> onLoaded: ->
@ -75,16 +75,34 @@ module.exports = class ConvertToTeacherAccountView extends RootView
"<div class='school'> #{hr.name.value} </div>" + "<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " + "<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>" "<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) => ]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city @$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state @$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA' @$('input[name="country"]').val 'USA'
for key in SCHOOL_NCES_KEYS
for key in NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key] @$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm() @onChangeForm()
onChangeForm: -> onChangeForm: ->
@ -97,28 +115,35 @@ module.exports = class ConvertToTeacherAccountView extends RootView
form = @$('form') form = @$('form')
attrs = forms.formToObject(form) attrs = forms.formToObject(form)
trialRequestAttrs = _.cloneDeep(attrs)
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
if @$('#other-education-level-checkbox').is(':checked') if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val() val = @$('#other-education-level-input').val()
attrs.educationLevel.push(val) if val trialRequestAttrs.educationLevel.push(val) if val
forms.clearFormAlerts(form) forms.clearFormAlerts(form)
result = tv4.validateMultiple(attrs, formSchema) result = tv4.validateMultiple(trialRequestAttrs, formSchema)
error = false error = false
if not result.valid if not result.valid
forms.applyErrorsToForm(form, result.errors) forms.applyErrorsToForm(form, result.errors)
error = true error = true
if not _.size(attrs.educationLevel) if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless attrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true error = true
if error if error
forms.scrollToFirstError() forms.scrollToFirstError()
return return
attrs['siteOrigin'] = 'convert teacher' trialRequestAttrs['siteOrigin'] = 'convert teacher'
@trialRequest = new TrialRequest({ @trialRequest = new TrialRequest({
type: 'course' type: 'course'
properties: attrs properties: trialRequestAttrs
}) })
if me.get('role') is 'student' and not me.isAnonymous() if me.get('role') is 'student' and not me.isAnonymous()
modal = new ConfirmModal({ modal = new ConfirmModal({
@ -151,15 +176,14 @@ module.exports = class ConvertToTeacherAccountView extends RootView
formSchema = { formSchema = {
type: 'object' type: 'object'
required: [ required: ['firstName', 'lastName', 'role', 'numStudents', 'city', 'state', 'country']
'firstName', 'lastName', 'organization', 'role', 'numStudents', 'city', 'state', 'country'
]
properties: properties:
firstName: { type: 'string' } firstName: { type: 'string' }
lastName: { type: 'string' } lastName: { type: 'string' }
phoneNumber: { type: 'string' } phoneNumber: { type: 'string' }
role: { type: 'string' } role: { type: 'string' }
organization: { type: 'string' } organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' } city: { type: 'string' }
state: { type: 'string' } state: { type: 'string' }
country: { type: 'string' } country: { type: 'string' }
@ -172,5 +196,5 @@ formSchema = {
notes: { type: 'string' } notes: { type: 'string' }
} }
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
formSchema['nces_' + key] = type: 'string' formSchema['nces_' + key] = type: 'string'

View file

@ -7,9 +7,9 @@ errors = require 'core/errors'
User = require 'models/User' User = require 'models/User'
algolia = require 'core/services/algolia' algolia = require 'core/services/algolia'
FORM_KEY = 'request-quote-form'
SIGNUP_REDIRECT = '/teachers/classes' SIGNUP_REDIRECT = '/teachers/classes'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone'] DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class CreateTeacherAccountView extends RootView module.exports = class CreateTeacherAccountView extends RootView
id: 'create-teacher-account-view' id: 'create-teacher-account-view'
@ -43,7 +43,7 @@ module.exports = class CreateTeacherAccountView extends RootView
super() super()
invalidateNCES: -> invalidateNCES: ->
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val '' @$('input[name="nces_' + key + '"]').val ''
afterRender: -> afterRender: ->
@ -72,16 +72,34 @@ module.exports = class CreateTeacherAccountView extends RootView
"<div class='school'> #{hr.name.value} </div>" + "<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " + "<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>" "<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) => ]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city @$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state @$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA' @$('input[name="country"]').val 'USA'
for key in SCHOOL_NCES_KEYS
for key in NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key] @$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm() @onChangeForm()
onClickLoginLink: -> onClickLoginLink: ->
@ -101,6 +119,9 @@ module.exports = class CreateTeacherAccountView extends RootView
allAttrs = forms.formToObject(form) allAttrs = forms.formToObject(form)
trialRequestAttrs = _.omit(allAttrs, 'name', 'password1', 'password2') trialRequestAttrs = _.omit(allAttrs, 'name', 'password1', 'password2')
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
if @$('#other-education-level-checkbox').is(':checked') if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val() val = @$('#other-education-level-input').val()
trialRequestAttrs.educationLevel.push(val) if val trialRequestAttrs.educationLevel.push(val) if val
@ -112,18 +133,21 @@ module.exports = class CreateTeacherAccountView extends RootView
if not result.valid if not result.valid
forms.applyErrorsToForm(form, result.errors) forms.applyErrorsToForm(form, result.errors)
error = true error = true
if not forms.validateEmail(trialRequestAttrs.email) if not error and not forms.validateEmail(trialRequestAttrs.email)
forms.setErrorToProperty(form, 'email', 'Invalid email.') forms.setErrorToProperty(form, 'email', 'invalid email')
error = true error = true
if not _.size(trialRequestAttrs.educationLevel) if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless allAttrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true error = true
unless @gplusAttrs or @facebookAttrs unless @gplusAttrs or @facebookAttrs
if not allAttrs.password1 if not allAttrs.password1
forms.setErrorToProperty(form, 'password1', 'Required field') forms.setErrorToProperty(form, 'password1', $.i18n.t('common.required_field'))
error = true error = true
else if not allAttrs.password2 else if not allAttrs.password2
forms.setErrorToProperty(form, 'password2', 'Required field') forms.setErrorToProperty(form, 'password2', $.i18n.t('common.required_field'))
error = true error = true
else if allAttrs.password1 isnt allAttrs.password2 else if allAttrs.password1 isnt allAttrs.password2
forms.setErrorToProperty(form, 'password1', 'Password fields are not equivalent') forms.setErrorToProperty(form, 'password1', 'Password fields are not equivalent')
@ -225,7 +249,6 @@ module.exports = class CreateTeacherAccountView extends RootView
@$('input[type="password"]').attr('disabled', true) @$('input[type="password"]').attr('disabled', true)
@$('#gplus-logged-in-row, #social-network-signups').toggleClass('hide') @$('#gplus-logged-in-row, #social-network-signups').toggleClass('hide')
# Facebook signup # Facebook signup
onClickFacebookSignupButton: -> onClickFacebookSignupButton: ->
@ -269,13 +292,9 @@ module.exports = class CreateTeacherAccountView extends RootView
@$('#facebook-logged-in-row, #social-network-signups').toggleClass('hide') @$('#facebook-logged-in-row, #social-network-signups').toggleClass('hide')
formSchema = { formSchema = {
type: 'object' type: 'object'
required: [ required: ['firstName', 'lastName', 'email', 'role', 'numStudents', 'city', 'state', 'country']
'firstName', 'lastName', 'email', 'organization', 'role', 'numStudents', 'city'
'state', 'country'
]
properties: properties:
password1: { type: 'string' } password1: { type: 'string' }
password2: { type: 'string' } password2: { type: 'string' }
@ -286,6 +305,7 @@ formSchema = {
phoneNumber: { type: 'string' } phoneNumber: { type: 'string' }
role: { type: 'string' } role: { type: 'string' }
organization: { type: 'string' } organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' } city: { type: 'string' }
state: { type: 'string' } state: { type: 'string' }
country: { type: 'string' } country: { type: 'string' }
@ -298,5 +318,5 @@ formSchema = {
notes: { type: 'string' } notes: { type: 'string' }
} }
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
formSchema['nces_' + key] = type: 'string' formSchema['nces_' + key] = type: 'string'

View file

@ -8,7 +8,8 @@ ConfirmModal = require 'views/editor/modal/ConfirmModal'
algolia = require 'core/services/algolia' algolia = require 'core/services/algolia'
SIGNUP_REDIRECT = '/teachers' SIGNUP_REDIRECT = '/teachers'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone'] DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class RequestQuoteView extends RootView module.exports = class RequestQuoteView extends RootView
id: 'request-quote-view' id: 'request-quote-view'
@ -46,7 +47,7 @@ module.exports = class RequestQuoteView extends RootView
super() super()
invalidateNCES: -> invalidateNCES: ->
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val '' @$('input[name="nces_' + key + '"]').val ''
afterRender: -> afterRender: ->
@ -75,16 +76,34 @@ module.exports = class RequestQuoteView extends RootView
"<div class='school'> #{hr.name.value} </div>" + "<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " + "<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>" "<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) => ]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city @$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state @$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA' @$('input[name="country"]').val 'USA'
for key in SCHOOL_NCES_KEYS
for key in NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key] @$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeRequestForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeRequestForm() @onChangeRequestForm()
onChangeRequestForm: -> onChangeRequestForm: ->
@ -96,32 +115,39 @@ module.exports = class RequestQuoteView extends RootView
e.preventDefault() e.preventDefault()
form = @$('#request-form') form = @$('#request-form')
attrs = forms.formToObject(form) attrs = forms.formToObject(form)
trialRequestAttrs = _.cloneDeep(attrs)
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
# custom other input logic # custom other input logic
if @$('#other-education-level-checkbox').is(':checked') if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val() val = @$('#other-education-level-input').val()
attrs.educationLevel.push(val) if val trialRequestAttrs.educationLevel.push(val) if val
forms.clearFormAlerts(form) forms.clearFormAlerts(form)
requestFormSchema = if me.isAnonymous() then requestFormSchemaAnonymous else requestFormSchemaLoggedIn requestFormSchema = if me.isAnonymous() then requestFormSchemaAnonymous else requestFormSchemaLoggedIn
result = tv4.validateMultiple(attrs, requestFormSchemaAnonymous) result = tv4.validateMultiple(trialRequestAttrs, requestFormSchemaAnonymous)
error = false error = false
if not result.valid if not result.valid
forms.applyErrorsToForm(form, result.errors) forms.applyErrorsToForm(form, result.errors)
error = true error = true
if not forms.validateEmail(attrs.email) if not error and not forms.validateEmail(trialRequestAttrs.email)
forms.setErrorToProperty(form, 'email', 'Invalid email.') forms.setErrorToProperty(form, 'email', 'invalid email')
error = true error = true
if not _.size(attrs.educationLevel) if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless attrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true error = true
if error if error
forms.scrollToFirstError() forms.scrollToFirstError()
return return
attrs['siteOrigin'] = 'demo request' trialRequestAttrs['siteOrigin'] = 'demo request'
@trialRequest = new TrialRequest({ @trialRequest = new TrialRequest({
type: 'course' type: 'course'
properties: attrs properties: trialRequestAttrs
}) })
if me.get('role') is 'student' and not me.isAnonymous() if me.get('role') is 'student' and not me.isAnonymous()
modal = new ConfirmModal({ modal = new ConfirmModal({
@ -262,7 +288,7 @@ module.exports = class RequestQuoteView extends RootView
requestFormSchemaAnonymous = { requestFormSchemaAnonymous = {
type: 'object' type: 'object'
required: [ required: [
'firstName', 'lastName', 'email', 'organization', 'role', 'purchaserRole', 'numStudents', 'firstName', 'lastName', 'email', 'role', 'purchaserRole', 'numStudents',
'numStudentsTotal', 'phoneNumber', 'city', 'state', 'country'] 'numStudentsTotal', 'phoneNumber', 'city', 'state', 'country']
properties: properties:
firstName: { type: 'string' } firstName: { type: 'string' }
@ -273,6 +299,7 @@ requestFormSchemaAnonymous = {
role: { type: 'string' } role: { type: 'string' }
purchaserRole: { type: 'string' } purchaserRole: { type: 'string' }
organization: { type: 'string' } organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' } city: { type: 'string' }
state: { type: 'string' } state: { type: 'string' }
country: { type: 'string' } country: { type: 'string' }
@ -285,7 +312,7 @@ requestFormSchemaAnonymous = {
notes: { type: 'string' }, notes: { type: 'string' },
} }
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
requestFormSchemaAnonymous['nces_' + key] = type: 'string' requestFormSchemaAnonymous['nces_' + key] = type: 'string'
# same form, but add username input # same form, but add username input

View file

@ -34,7 +34,8 @@ module.exports = class TeachersContactModal extends ModalView
message = """ message = """
Hi CodeCombat! I want to learn more about the Classroom experience and get licenses so that my students can access Computer Science 2 and on. Hi CodeCombat! I want to learn more about the Classroom experience and get licenses so that my students can access Computer Science 2 and on.
Name of School/District: #{props.organization or ''} Name of School #{props.nces_name or props.organization or ''}
Name of District: #{props.nces_district or props.district or ''}
Role: #{props.role or ''} Role: #{props.role or ''}
Phone Number: #{props.phoneNumber or ''} Phone Number: #{props.phoneNumber or ''}
""" """

View file

@ -16,7 +16,7 @@ if (process.argv.length !== 10) {
// TODO: Cleanup country/status lookup code // TODO: Cleanup country/status lookup code
// Save as custom fields instead of user-specific lead notes (also saving nces_ props) // Save as custom fields instead of user-specific lead notes (also saving nces_ props)
const commonTrialProperties = ['organization', 'city', 'state', 'country']; const commonTrialProperties = ['organization', 'district', 'city', 'state', 'country'];
// Old properties which are deprecated or moved // Old properties which are deprecated or moved
const customFieldsToRemove = [ const customFieldsToRemove = [
@ -327,7 +327,7 @@ function findCocoLeads(done) {
if (!trialRequest.properties || !trialRequest.properties.email) continue; if (!trialRequest.properties || !trialRequest.properties.email) continue;
const email = trialRequest.properties.email.toLowerCase(); const email = trialRequest.properties.email.toLowerCase();
emails.push(email); emails.push(email);
const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || email; const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || trialRequest.properties.district || trialRequest.properties.nces_district || email;
if (!leads[name]) leads[name] = new CocoLead(name); if (!leads[name]) leads[name] = new CocoLead(name);
leads[name].addTrialRequest(email, trialRequest); leads[name].addTrialRequest(email, trialRequest);
emailLeadMap[email] = leads[name]; emailLeadMap[email] = leads[name];

View file

@ -138,7 +138,7 @@ module.exports =
trialRequests = yield TrialRequest.find({$and: [{type: 'course'}, {applicant: {$in: userIDs}}]}, {applicant: 1, properties: 1}).lean() trialRequests = yield TrialRequest.find({$and: [{type: 'course'}, {applicant: {$in: userIDs}}]}, {applicant: 1, properties: 1}).lean()
schoolPrepaidsMap = {} schoolPrepaidsMap = {}
for trialRequest in trialRequests for trialRequest in trialRequests
school = trialRequest.properties?.organization ? trialRequest.properties?.school school = trialRequest.properties?.nces_name ? trialRequest.properties?.organization ? trialRequest.properties?.school
continue unless school continue unless school
if userPrepaidsMap[trialRequest.applicant.valueOf()]?.length > 0 if userPrepaidsMap[trialRequest.applicant.valueOf()]?.length > 0
schoolPrepaidsMap[school] ?= [] schoolPrepaidsMap[school] ?= []

View file

@ -184,6 +184,7 @@ module.exports = {
email: 'an@email.com' email: 'an@email.com'
phoneNumber: '555-555-5555' phoneNumber: '555-555-5555'
organization: 'Greendale' organization: 'Greendale'
district: 'Green District'
} }
}, attrs) }, attrs)
} }

View file

@ -31,6 +31,7 @@ describe 'ConvertToTeacherAccountView (/teachers/update-account)', ->
phoneNumber: '555-555-5555' phoneNumber: '555-555-5555'
role: 'Teacher' role: 'Teacher'
organization: 'School' organization: 'School'
district: 'District'
city: 'Springfield' city: 'Springfield'
state: 'AA' state: 'AA'
country: 'asdf' country: 'asdf'
@ -168,3 +169,48 @@ describe 'ConvertToTeacherAccountView (/teachers/update-account)', ->
}) })
expect(me.get('role')).toBe(successForm.role.toLowerCase()) expect(me.get('role')).toBe(successForm.role.toLowerCase())
describe 'submitting the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submitting the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submitting the form district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()

View file

@ -33,6 +33,7 @@ describe 'CreateTeacherAccountView', ->
phoneNumber: '555-555-5555' phoneNumber: '555-555-5555'
role: 'Teacher' role: 'Teacher'
organization: 'School' organization: 'School'
district: 'District'
city: 'Springfield' city: 'Springfield'
state: 'AA' state: 'AA'
country: 'asdf' country: 'asdf'
@ -219,6 +220,8 @@ describe 'CreateTeacherAccountView', ->
expect(attrs.password2).toBeUndefined() expect(attrs.password2).toBeUndefined()
expect(attrs.name).toBeUndefined() expect(attrs.name).toBeUndefined()
expect(attrs.properties?.siteOrigin).toBe('create teacher') expect(attrs.properties?.siteOrigin).toBe('create teacher')
expect(attrs.properties?.organization).toEqual('School')
expect(attrs.properties?.district).toEqual('District')
describe 'after saving the new trial request', -> describe 'after saving the new trial request', ->
beforeEach -> beforeEach ->
@ -267,4 +270,48 @@ describe 'CreateTeacherAccountView', ->
view.$('#email-form-group .login-link').click() view.$('#email-form-group .login-link').click()
expect(view.openModalView).toHaveBeenCalled() expect(view.openModalView).toHaveBeenCalled()
describe 'submitting the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submitting the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submitting the form district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()

View file

@ -5,13 +5,14 @@ describe 'RequestQuoteView', ->
view = null view = null
successFormValues = { successForm = {
firstName: 'A' firstName: 'A'
lastName: 'B' lastName: 'B'
email: 'C@D.com' email: 'C@D.com'
phoneNumber: '555-555-5555' phoneNumber: '555-555-5555'
role: 'Teacher' role: 'Teacher'
organization: 'School' organization: 'School'
district: 'District'
city: 'Springfield' city: 'Springfield'
state: 'AA' state: 'AA'
country: 'asdf' country: 'asdf'
@ -23,7 +24,7 @@ describe 'RequestQuoteView', ->
isSubmitRequest = (r) -> _.string.startsWith(r.url, '/db/trial.request') and r.method is 'POST' 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', -> describe 'when an anonymous user', ->
beforeEach (done) -> beforeEach (done) ->
me.clear() me.clear()
me.set('_id', '1234') me.set('_id', '1234')
@ -32,145 +33,112 @@ describe 'RequestQuoteView', ->
view = new RequestQuoteView() view = new RequestQuoteView()
view.render() view.render()
jasmine.demoEl(view.$el) 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 _.defer done # Let SuperModel finish
it 'shows request received', -> describe 'has an existing trial request', ->
expect(view.$('#request-form').hasClass('hide')).toBe(true) beforeEach (done) ->
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false) request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
describe 'when user is signed in and has an existing trial request', -> responseText: JSON.stringify([{
beforeEach (done) -> _id: '1'
me.clear() properties: {
me.set('_id', '1234') firstName: 'First'
me._revertAttributes = {} lastName: 'Last'
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: 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')
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)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: '[]'
})
_.defer done # Let SuperModel finish
describe 'when the form is unchanged', ->
it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
describe 'when the form has changed but is not submitted', ->
beforeEach ->
view.$el.find('#request-form').trigger('change')
it 'prevents navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeTruthy()
describe 'on successful form submit', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
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))
}) })
_.defer done # Let SuperModel finish
it 'does not prevent navigating away', -> it 'shows request received', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
it 'creates a new trial request', ->
expect(@submitRequest).toBeTruthy()
expect(@submitRequest.method).toBe('POST')
attrs = JSON.parse(@submitRequest.params)
expect(attrs.properties?.siteOrigin).toBe('demo request')
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) expect(view.$('#request-form').hasClass('hide')).toBe(true)
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
describe 'signup form', -> describe 'does NOT have an existing trial request', ->
beforeEach (done) ->
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: '[]'
})
_.defer done # Let SuperModel finish
describe 'when the form is unchanged', ->
it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
describe 'when the form has changed but is not submitted', ->
beforeEach -> beforeEach ->
application.facebookHandler.fakeAPI() view.$el.find('#request-form').trigger('change')
application.gplusHandler.fakeAPI()
it 'fills the username field with the given first and last names', -> it 'prevents navigating away', ->
expect(view.$('input[name="name"]').val()).toBe('A B') expect(_.result(view, 'onLeaveMessage')).toBeTruthy()
it 'includes a facebook button which will sign them in immediately', -> describe 'on successful form submit', ->
view.$('#facebook-signup-btn').click() beforeEach ->
request = jasmine.Ajax.requests.mostRecent() view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
expect(request.method).toBe('PUT') forms.objectToForm(view.$el, successForm)
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234') view.$('#request-form').submit()
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
it 'includes a gplus button which will sign them in immediately', -> @submitRequest.respondWith({
view.$('#gplus-signup-btn').click() status: 201
request = jasmine.Ajax.requests.mostRecent() responseText: JSON.stringify(_.extend({_id: 'a'}, successForm))
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() it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
it 'creates a new trial request', ->
expect(@submitRequest).toBeTruthy()
expect(@submitRequest.method).toBe('POST')
attrs = JSON.parse(@submitRequest.params)
expect(attrs.properties?.siteOrigin).toBe('demo request')
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(request.method).toBe('PUT')
expect(request.url).toBe('/db/user/1234') expect(JSON.parse(request.params).role).toBe('teacher')
describe 'when an anonymous user tries to submit a request with an existing user\'s email', -> 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 'fills the username field with the given first and last names', ->
expect(view.$('input[name="name"]').val()).toBe('A B')
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 'tries to submit a request with an existing user\'s email', ->
beforeEach -> beforeEach ->
forms.objectToForm(view.$el, successFormValues) forms.objectToForm(view.$el, successForm)
view.$('#request-form').submit() view.$('#request-form').submit()
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) @submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
@submitRequest.respondWith({ @submitRequest.respondWith({
@ -182,34 +150,103 @@ describe 'RequestQuoteView', ->
expect(view.$('#email-form-group').hasClass('has-error')).toBe(true) expect(view.$('#email-form-group').hasClass('has-error')).toBe(true)
expect(view.$('#email-form-group .error-help-block').length).toBe(1) expect(view.$('#email-form-group .error-help-block').length).toBe(1)
describe 'when user is signed in and has role "student"', -> describe 'submits the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submits the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submits form with district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()
describe 'when a signed in user', ->
beforeEach (done) -> beforeEach (done) ->
me.set('role', 'student') me.clear()
me.set('name', 'Some User') me.set('_id', '1234')
me._revertAttributes = {}
spyOn(me, 'isAnonymous').and.returnValue(false) spyOn(me, 'isAnonymous').and.returnValue(false)
view = new RequestQuoteView() view = new RequestQuoteView()
view.render() view.render()
jasmine.demoEl(view.$el) jasmine.demoEl(view.$el)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({ status: 200, responseText: '[]'})
_.defer done # Let SuperModel finish _.defer done # Let SuperModel finish
it 'shows a conversion warning', -> describe 'has an existing trial request', ->
expect(view.$('#conversion-warning').length).toBe(1) beforeEach (done) ->
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 'requires confirmation to submit the form', -> it 'shows form with data from the most recent request', ->
form = view.$('#request-form') expect(view.$('input[name="firstName"]').val()).toBe('First')
forms.objectToForm(form, successFormValues)
spyOn(view, 'openModalView')
form.submit()
expect(view.openModalView).toHaveBeenCalled()
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) describe 'has role "student"', ->
expect(submitRequest).toBeFalsy() beforeEach (done) ->
confirmModal = view.openModalView.calls.argsFor(0)[0] me.clear()
confirmModal.trigger 'confirm' me.set('role', 'student')
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) me.set('name', 'Some User')
expect(submitRequest).toBeTruthy() 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, successForm)
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()