mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Start new CreateAccountModal
This commit is contained in:
parent
5e6c9709f9
commit
e9b7543242
41 changed files with 1198 additions and 663 deletions
BIN
app/assets/images/pages/modal/auth/facebook_sso_button.png
Normal file
BIN
app/assets/images/pages/modal/auth/facebook_sso_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
app/assets/images/pages/modal/auth/google_plus_sso_button.png
Normal file
BIN
app/assets/images/pages/modal/auth/google_plus_sso_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
|
@ -57,6 +57,14 @@ module.exports.applyErrorsToForm = (el, errors, warning=false) ->
|
|||
message = error.message if error.formatted
|
||||
prop = error.property
|
||||
|
||||
if error.code is tv4.errorCodes.FORMAT_CUSTOM
|
||||
originalMessage = /Format validation failed \(([^\(\)]+)\)/.exec(message)[1]
|
||||
unless _.isEmpty(originalMessage)
|
||||
message = originalMessage
|
||||
|
||||
if error.code is 409 and error.property is 'email'
|
||||
message += ' <a class="login-link">Log in?</a>'
|
||||
|
||||
missingErrors.push error unless setErrorToProperty el, prop, message, warning
|
||||
missingErrors
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
|
|||
login: (cb, options) ->
|
||||
cb({status: 'connected', authResponse: { accessToken: '1234' }})
|
||||
api: (url, options, cb) ->
|
||||
cb({
|
||||
cb({
|
||||
first_name: 'Mr'
|
||||
last_name: 'Bean'
|
||||
id: 'abcd'
|
||||
|
@ -103,4 +103,4 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
|
|||
options.success.bind(options.context)(attrs)
|
||||
|
||||
renderButtons: ->
|
||||
setTimeout(FB.XFBML.parse, 10) if FB?.XFBML?.parse # Handles FB login and Like
|
||||
setTimeout(FB.XFBML.parse, 10) if FB?.XFBML?.parse # Handles FB login and Like
|
||||
|
|
|
@ -265,7 +265,7 @@
|
|||
school_name: "School Name and City"
|
||||
optional: "optional"
|
||||
school_name_placeholder: "Example High School, Springfield, IL"
|
||||
or_sign_up_with: "or sign up with"
|
||||
connect_with: "Connect with:"
|
||||
connected_gplus_header: "You've successfully connected with Google+!"
|
||||
connected_gplus_p: "Finish signing up so you can log in with your Google+ account."
|
||||
gplus_exists: "You already have an account associated with Google+!"
|
||||
|
@ -274,6 +274,8 @@
|
|||
facebook_exists: "You already have an account associated with Facebook!"
|
||||
hey_students: "Students, enter the class code from your teacher."
|
||||
birthday: "Birthday"
|
||||
|
||||
parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions."
|
||||
|
||||
recover:
|
||||
recover_account_title: "Recover Account"
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
@import "app/styles/mixins"
|
||||
@import "app/styles/bootstrap/variables"
|
||||
|
||||
#create-account-modal
|
||||
|
||||
//- Clear modal defaults
|
||||
|
||||
.modal-dialog
|
||||
padding: 0
|
||||
width: 666px
|
||||
height: 694px
|
||||
|
||||
|
||||
//- Background
|
||||
|
||||
.auth-modal-background
|
||||
position: absolute
|
||||
top: -90px
|
||||
left: -40px
|
||||
|
||||
|
||||
//- Header
|
||||
|
||||
h1
|
||||
position: absolute
|
||||
left: 183px
|
||||
top: 0px
|
||||
margin: 0
|
||||
width: 255px
|
||||
text-align: center
|
||||
color: rgb(254,188,68)
|
||||
font-size: 32px
|
||||
text-shadow: black 2px 2px 0, black -2px -2px 0, black 2px -2px 0, black -2px 2px 0, black 2px 0px 0, black 0px -2px 0, black -2px 0px 0, black 0px 2px 0
|
||||
|
||||
&.long-title
|
||||
top: -14px
|
||||
|
||||
|
||||
//- Close modal button
|
||||
|
||||
#close-modal
|
||||
position: absolute
|
||||
left: 442px
|
||||
top: -15px
|
||||
width: 60px
|
||||
height: 60px
|
||||
color: white
|
||||
text-align: center
|
||||
font-size: 30px
|
||||
padding-top: 15px
|
||||
cursor: pointer
|
||||
@include rotate(-3deg)
|
||||
|
||||
&:hover
|
||||
color: yellow
|
||||
|
||||
|
||||
//- Modal body content
|
||||
|
||||
#gplus-account-exists-row, #facebook-account-exists-row
|
||||
margin-bottom: 20px
|
||||
|
||||
#facebook-signup-btn
|
||||
margin-bottom: 5px
|
||||
|
||||
.auth-form-content
|
||||
position: absolute
|
||||
top: 100px
|
||||
left: 40px
|
||||
width: 588px
|
||||
|
||||
.help-block
|
||||
margin: 0
|
||||
|
||||
.alert
|
||||
margin-top: -25px
|
||||
margin-bottom: 0
|
||||
padding: 10px 15px
|
||||
|
||||
.form-group
|
||||
color: rgb(51,51,51)
|
||||
padding: 0
|
||||
margin: 0
|
||||
|
||||
.input-border
|
||||
border: 2px solid rgb(233, 221, 194)
|
||||
border-radius: 4px
|
||||
margin-bottom: 7px
|
||||
|
||||
input, select
|
||||
background-color: rgb(239, 232, 216)
|
||||
border: 2px solid rgb(26, 21, 18)
|
||||
border-radius: 4px
|
||||
|
||||
select
|
||||
margin-right: 2px
|
||||
|
||||
label
|
||||
font-size: 20px
|
||||
text-transform: uppercase
|
||||
font-family: $headings-font-family
|
||||
margin-bottom: 0
|
||||
|
||||
.optional-note
|
||||
font-size: 14px
|
||||
|
||||
.well
|
||||
font-size: 18px
|
||||
position: absolute
|
||||
right: 0
|
||||
bottom: 0
|
||||
width: 278px
|
||||
margin-bottom: 0
|
||||
|
||||
//- Check boxes
|
||||
|
||||
.form-group.checkbox
|
||||
margin: 10px 0
|
||||
|
||||
label
|
||||
position: relative
|
||||
line-height: 34px
|
||||
|
||||
span:not(.custom-checkbox)
|
||||
margin-left: 40px
|
||||
|
||||
input
|
||||
display: none
|
||||
|
||||
& + .custom-checkbox
|
||||
.glyphicon
|
||||
display: none
|
||||
|
||||
&:checked + .custom-checkbox .glyphicon
|
||||
display: inline
|
||||
color: rgb(248,169,67)
|
||||
text-align: center
|
||||
text-shadow: 0 0 3px black, 0 0 3px black, 0 0 3px black
|
||||
font-size: 20px
|
||||
position: relative
|
||||
top: -2px
|
||||
|
||||
.input-border
|
||||
border-radius: 4px
|
||||
height: 34px
|
||||
width: 34px
|
||||
position: absolute
|
||||
|
||||
.custom-checkbox
|
||||
border-radius: 4px
|
||||
position: absolute
|
||||
height: 30px
|
||||
width: 30px
|
||||
border: 2px solid rgb(26,21,18)
|
||||
background: rgb(228,217,196)
|
||||
text-align: center
|
||||
|
||||
//- Primary auth button
|
||||
|
||||
#signup-button
|
||||
position: absolute
|
||||
top: 405px
|
||||
height: 70px
|
||||
font-size: 32px
|
||||
line-height: 42px
|
||||
|
||||
|
||||
//- Footer area
|
||||
|
||||
.auth-network-logins
|
||||
|
||||
.btn.btn-lg.network-login
|
||||
width: 251px
|
||||
height: 60px
|
||||
text-align: center
|
||||
position: relative
|
||||
|
||||
.network-logo
|
||||
height: 30px
|
||||
position: absolute
|
||||
left: -10px
|
||||
top: 2px
|
||||
|
||||
.sign-in-blurb
|
||||
line-height: 34px
|
||||
margin-left: 12px
|
||||
|
||||
.fb-login-button
|
||||
$scaleX: 251 / 64
|
||||
$scaleY: 60 / 23
|
||||
transform: scale($scaleX, $scaleY)
|
||||
position: absolute
|
||||
top: 4px
|
||||
left: 74px
|
||||
@include opacity(0.01)
|
||||
|
||||
.gplus-login-wrapper
|
||||
position: absolute
|
||||
left: 65px
|
||||
top: -6px
|
||||
$scaleX: 251 / 84
|
||||
$scaleY: 60 / 24
|
||||
transform: scale($scaleX, $scaleY)
|
||||
@include opacity(0.01)
|
||||
|
||||
#github-login-button
|
||||
position: relative
|
||||
top: -1px
|
||||
border-radius: 5px
|
||||
img
|
||||
width: 16px
|
||||
margin: 0 5px 0 -5px
|
||||
|
||||
#gplus-login-button
|
||||
position: relative
|
||||
top: 8px
|
||||
|
||||
|
||||
//- Extra bottom pane area
|
||||
.extra-pane
|
||||
background-image: url(/images/pages/modal/auth/extra-pane.png)
|
||||
width: 633px
|
||||
height: 139px
|
||||
padding: 23px 23px 23px 168px
|
||||
position: absolute
|
||||
top: 580px
|
||||
|
||||
.switch-explanation
|
||||
margin: 25px 10px 0 0
|
||||
width: 200px
|
||||
color: rgb(254,188,68)
|
||||
font-size: 20px
|
||||
font-family: $headings-font-family
|
||||
font-weight: bold
|
||||
text-transform: uppercase
|
||||
text-shadow: black 1px 1px 0, black -1px -1px 0, black 1px -1px 0, black -1px 1px 0, black 1px 0px 0, black 0px -1px 0, black -1px 0px 0, black 0px 1px 0
|
||||
float: left
|
||||
|
||||
.btn
|
||||
float: right
|
||||
margin-top: 20px
|
||||
width: 230px
|
||||
height: 70px
|
||||
line-height: 40px
|
||||
|
||||
|
||||
.ie10 #create-account-modal .auth-network-logins .btn.btn-lg.network-login .network-logo, .lt-ie10 #create-account-modal .auth-network-logins .btn.btn-lg.network-login .network-logo
|
||||
left: 15px
|
||||
top: 15px
|
22
app/styles/modal/create-account-modal/basic-info-view.sass
Normal file
22
app/styles/modal/create-account-modal/basic-info-view.sass
Normal file
|
@ -0,0 +1,22 @@
|
|||
#basic-info-view
|
||||
.network-login
|
||||
width: 175px
|
||||
height: 40px
|
||||
border: solid 0.5px darkgray
|
||||
|
||||
span
|
||||
visibility: hidden
|
||||
|
||||
// Forms
|
||||
|
||||
.basic-info
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.form-group
|
||||
width: 255px
|
||||
text-align: left
|
||||
|
||||
.btn-illustrated img
|
||||
// Undo previous opacity-toggling hover behavior
|
||||
opacity: 1
|
|
@ -0,0 +1,68 @@
|
|||
@import "app/styles/style-flat-variables"
|
||||
|
||||
#choose-account-type-view
|
||||
.path-cards
|
||||
margin-top: 15px
|
||||
display: flex
|
||||
|
||||
.path-card ~ .path-card
|
||||
margin-left: 6.5px
|
||||
|
||||
.path-card
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: space-between
|
||||
width: 235px
|
||||
min-height: 340px
|
||||
border-style: solid
|
||||
border-width: thin
|
||||
border-radius: 5px
|
||||
|
||||
&.navy
|
||||
border-color: $navy
|
||||
.card-title
|
||||
background-color: $navy
|
||||
|
||||
&.forest
|
||||
border-color: $forest
|
||||
.card-title
|
||||
background-color: $forest
|
||||
|
||||
|
||||
.card-title
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: center
|
||||
height: 50px
|
||||
color: white
|
||||
font-weight: bold
|
||||
text-align: center
|
||||
|
||||
.card-content
|
||||
flex-grow: 1
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin: 50px 20px 0
|
||||
|
||||
ul
|
||||
align-self: center
|
||||
text-align: left
|
||||
padding-left: 20px
|
||||
li
|
||||
span
|
||||
position: relative
|
||||
left: -5px
|
||||
|
||||
.card-footer
|
||||
margin: 20px
|
||||
|
||||
.individual-section
|
||||
margin-top: 50px
|
||||
max-width: 425px
|
||||
|
||||
.individual-title
|
||||
font-weight: bold
|
||||
|
||||
.text-h6
|
||||
color: white
|
17
app/styles/modal/create-account-modal/coppa-deny-view.sass
Normal file
17
app/styles/modal/create-account-modal/coppa-deny-view.sass
Normal file
|
@ -0,0 +1,17 @@
|
|||
#coppa-deny-view
|
||||
.parent-email-blurb
|
||||
width: 500px
|
||||
|
||||
.parent-email-input-group
|
||||
display: flex
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
text-align: center
|
||||
|
||||
.glyphicon
|
||||
width: 0
|
||||
line-height: 40px
|
||||
font-size: 30px
|
||||
|
||||
.error
|
||||
color: red
|
152
app/styles/modal/create-account-modal/create-account-modal.sass
Normal file
152
app/styles/modal/create-account-modal/create-account-modal.sass
Normal file
|
@ -0,0 +1,152 @@
|
|||
//
|
||||
Layout for CreateAccountModal is done largely with flexboxes.
|
||||
Rules in this file should be ones that apply to all screens/subviews, but this
|
||||
separation may not be perfect.
|
||||
|
||||
Currently it uses .modal-dialog, .modal-content, etc, for some parts of the modal.
|
||||
Unfortunately those preexesting classes don't line up perfectly with the needs of this modal,
|
||||
so many of the other styles for those classes don't apply or are overridden.
|
||||
|
||||
@import "app/styles/style-flat-variables"
|
||||
|
||||
#create-account-modal
|
||||
.modal-dialog
|
||||
width: 850px
|
||||
|
||||
.modal-content
|
||||
display: flex
|
||||
flex-direction: column
|
||||
height: 850px
|
||||
width: 850px
|
||||
text-align: center
|
||||
padding: 0
|
||||
border: none
|
||||
|
||||
// General modal stuff
|
||||
|
||||
.modal-header, .modal-footer
|
||||
&.teacher
|
||||
background-color: $burgandy
|
||||
|
||||
&.student
|
||||
background-color: $forest
|
||||
|
||||
.modal-header
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: flex-end
|
||||
height: 100px
|
||||
padding: 0
|
||||
|
||||
background-color: $navy
|
||||
|
||||
h3
|
||||
color: white
|
||||
|
||||
.modal-footer
|
||||
padding: 0
|
||||
margin: 0
|
||||
height: 50px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
background-color: $navy
|
||||
|
||||
span
|
||||
color: white
|
||||
|
||||
#choose-account-type-view, #segment-check-view, #basic-info-view, #coppa-deny-view, #single-sign-on-already-exists-view, #single-sign-on-confirm-view,
|
||||
display: flex
|
||||
flex-direction: column
|
||||
flex-grow: 1
|
||||
|
||||
.modal-body
|
||||
display: flex
|
||||
flex-direction: column
|
||||
flex-grow: 1
|
||||
|
||||
.modal-body-content
|
||||
flex-grow: 2
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
// Back/forward buttons
|
||||
|
||||
.history-nav-buttons
|
||||
width: 100%
|
||||
display: flex
|
||||
flex-direction: row-reverse
|
||||
flex-grow: 1
|
||||
align-items: flex-end
|
||||
justify-content: space-between
|
||||
|
||||
.btn
|
||||
// Undo .style-flat's .btn ~ .btn margin
|
||||
margin: 0
|
||||
|
||||
&.just-one
|
||||
flex-direction: row
|
||||
|
||||
// Forms
|
||||
|
||||
.full-name
|
||||
display: flex
|
||||
flex-direction: row
|
||||
|
||||
.form-group
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-content: flex-start
|
||||
|
||||
&.text-center
|
||||
text-align: center
|
||||
|
||||
input
|
||||
height: 40px
|
||||
|
||||
&.row
|
||||
display: block
|
||||
|
||||
&.last-initial
|
||||
margin-left: 30px
|
||||
width: auto
|
||||
|
||||
input
|
||||
width: 70px
|
||||
|
||||
&.subscribe
|
||||
width: 100%
|
||||
|
||||
// Fancy text inside horizontal rules
|
||||
|
||||
.hr-text
|
||||
position: relative
|
||||
hr
|
||||
width: 430px
|
||||
padding: 0
|
||||
border: none
|
||||
border-top: thin solid #444
|
||||
color: #444
|
||||
span
|
||||
position: absolute
|
||||
left: 50%
|
||||
top: 0.45em
|
||||
transform: translateX(-50%)
|
||||
padding: 0 0.75em
|
||||
font-weight: bold
|
||||
font-size: 10pt
|
||||
background: white
|
||||
|
||||
|
||||
// Glyphicon colors
|
||||
|
||||
.glyphicon-ok-circle
|
||||
color: green
|
||||
|
||||
.glyphicon-remove-circle
|
||||
color: red
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#segment-check-view
|
||||
.class-code-input-group
|
||||
display: flex
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
.class-code-input
|
||||
text-align: center
|
||||
|
||||
.glyphicon
|
||||
width: 0
|
||||
line-height: 40px
|
||||
font-size: 30px
|
||||
|
||||
.classroom-name
|
||||
font-size: 26pt
|
||||
|
||||
.teacher-name
|
||||
font-size: 14pt
|
|
@ -0,0 +1,5 @@
|
|||
#single-sign-on-already-exists-view
|
||||
.modal-body
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
|
@ -0,0 +1,5 @@
|
|||
#single-sign-on-confirm-view
|
||||
.modal-body
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
|
@ -1,11 +0,0 @@
|
|||
extends /templates/core/modal-base-flat
|
||||
|
||||
block modal-header-content
|
||||
|
||||
block modal-body-content
|
||||
p
|
||||
h2(data-i18n="coppa_deny.text1")
|
||||
div(data-i18n="coppa_deny.text2")
|
||||
|
||||
block modal-footer-content
|
||||
a.btn.btn-primary(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="coppa_deny.close").btn
|
|
@ -1,118 +0,0 @@
|
|||
.modal-dialog
|
||||
.modal-content
|
||||
img(src="/images/pages/modal/auth/signup-background.png", draggable="false").auth-modal-background
|
||||
h1(data-i18n="login.sign_up")
|
||||
|
||||
div#close-modal
|
||||
span.glyphicon.glyphicon-remove
|
||||
|
||||
form.auth-form-content
|
||||
if view.options.showRequiredError
|
||||
#required-error-alert.alert.alert-success
|
||||
span(data-i18n="signup.required")
|
||||
if view.options.showSignupRationale
|
||||
#signup-rationale-alert.alert.alert-info
|
||||
span(data-i18n="play_level.victory_sign_up_poke")
|
||||
|
||||
#email-password-row.row
|
||||
.col-md-6
|
||||
.form-group
|
||||
label.control-label(for="email")
|
||||
span(data-i18n="general.email")
|
||||
| :
|
||||
.input-border
|
||||
input#email.input-large.form-control(name="email", type="email", value=view.previousFormInputs.email, tabindex=1)
|
||||
|
||||
.form-group
|
||||
label.control-label(for="password")
|
||||
span(data-i18n="general.password")
|
||||
| :
|
||||
.input-border
|
||||
input#password.input-large.form-control(name="password", type="password", value=view.previousFormInputs.password, tabindex=2)
|
||||
|
||||
.col-md-6
|
||||
.auth-network-logins.text-center
|
||||
strong(data-i18n="signup.or_sign_up_with")
|
||||
button#facebook-signup-btn.btn.btn-primary.btn-lg.btn-illustrated.network-login(type="button", disabled=true)
|
||||
img.network-logo(src="/images/pages/community/logo_facebook.png", draggable="false")
|
||||
span.sign-in-blurb(data-i18n="login.sign_in_with_facebook")
|
||||
button#gplus-signup-btn.btn.btn-danger.btn-lg.btn-illustrated.network-login(type="button", disabled=true)
|
||||
img.network-logo(src="/images/pages/community/logo_g+.png", draggable="false")
|
||||
span.sign-in-blurb(data-i18n="login.sign_in_with_gplus")
|
||||
.gplus-login-wrapper
|
||||
.gplus-login-button
|
||||
|
||||
#gplus-logged-in-row.row.text-center.hide
|
||||
h2(data-i18n="signup.connected_gplus_header")
|
||||
p(data-i18n="signup.connected_gplus_p")
|
||||
|
||||
#gplus-account-exists-row.row.text-center.hide
|
||||
h2(data-i18n="signup.gplus_exists")
|
||||
a.btn.btn-primary#gplus-login-btn(data-i18n="login.log_in")
|
||||
|
||||
#facebook-logged-in-row.row.text-center.hide
|
||||
h2(data-i18n="signup.connected_facebook_header")
|
||||
p(data-i18n="signup.connected_facebook_p")
|
||||
|
||||
#facebook-account-exists-row.row.text-center.hide
|
||||
h2(data-i18n="signup.facebook_exists")
|
||||
a.btn.btn-primary#facebook-login-btn(data-i18n="login.log_in")
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
.form-group
|
||||
label.control-label(for="name")
|
||||
span(data-i18n="general.username")
|
||||
| :
|
||||
.input-border
|
||||
if me.get('name')
|
||||
input#name.input-large.form-control(name="name", type="text", value=me.get('name'), tabindex=3)
|
||||
else
|
||||
input#name.input-large.form-control(name="name", type="text", value="", placeholder="e.g. Alex W the Skater", tabindex=3)
|
||||
|
||||
.form-group
|
||||
label.control-label(for="birthday-input")
|
||||
span(data-i18n="signup.birthday")
|
||||
| :
|
||||
.input-border
|
||||
select#birthday-month-input.input-large.form-control(name="birthdayMonth", value=view.previousFormInputs.birthdayMonth || '', tabindex=4, style="width: 106px; float: left")
|
||||
option(value='',data-i18n="calendar.month")
|
||||
for name, val in ['january','february','march','april','may','june','july','august','september','october','november','december']
|
||||
option(data-i18n="calendar.#{name}" value=val+1)
|
||||
select#birthday-day-input.input-large.form-control(name="birthdayDay", value=view.previousFormInputs.birthdayDay || '', tabindex=5, style="width: 75px; float: left")
|
||||
option(value='',data-i18n="calendar.day")
|
||||
for dummy, val in new Array(31)
|
||||
option #{val+1}
|
||||
select#birthday-year-input.input-large.form-control(name="birthdayYear", value=view.previousFormInputs.birthdayMonth || '', tabindex=6, style="width: 90px;")
|
||||
option(value='',data-i18n="calendar.year")
|
||||
- var startYear = new Date().getFullYear() - 1
|
||||
for dummy, val in new Array(100)
|
||||
option #{startYear-val}
|
||||
|
||||
.form-group
|
||||
label.control-label(for="school-input")
|
||||
span.spr(data-i18n="courses.class_code")
|
||||
em.optional-note
|
||||
| (
|
||||
span(data-i18n="signup.optional")
|
||||
| ):
|
||||
.input-border
|
||||
input#class-code-input.input-large.form-control(name="classCode", value=view.previousFormInputs.classCode || '', tabindex=7)
|
||||
|
||||
.col-md-6
|
||||
.form-group.checkbox
|
||||
label.control-label(for="subscribe")
|
||||
.input-border
|
||||
input#subscribe(type="checkbox", checked='checked')
|
||||
span.custom-checkbox
|
||||
.glyphicon.glyphicon-ok
|
||||
span(data-i18n="signup.email_announcements")
|
||||
|
||||
.well(data-i18n="signup.hey_students")
|
||||
|
||||
|
||||
input#signup-button.btn.btn-lg.btn-illustrated.btn-block.btn-success(value=translate("signup.sign_up"), type="submit")
|
||||
|
||||
.extra-pane
|
||||
.switch-explanation(data-i18n="signup.login_switch")
|
||||
.btn.btn-lg.btn-illustrated.btn-warning#switch-to-login-btn(data-i18n="login.log_in")
|
67
app/templates/core/create-account-modal/basic-info-view.jade
Normal file
67
app/templates/core/create-account-modal/basic-info-view.jade
Normal file
|
@ -0,0 +1,67 @@
|
|||
form#basic-info-form.modal-body.basic-info
|
||||
.modal-body-content
|
||||
.auth-network-logins.text-center
|
||||
h4
|
||||
span(data-i18n="signup.connect_with")
|
||||
a#facebook-signup-btn.btn.btn-primary.btn-lg.btn-illustrated.network-login(disabled=!view.sharedState.get('facebookEnabled'), data-sso-used="facebook")
|
||||
img.network-logo(src="/images/pages/modal/auth/facebook_sso_button.png", draggable="false", width="175", height="40")
|
||||
span.sign-in-blurb(data-i18n="login.sign_in_with_facebook")
|
||||
a#gplus-signup-btn.btn.btn-danger.btn-lg.btn-illustrated.network-login(disabled=!view.sharedState.get('gplusEnabled'), data-sso-used="gplus")
|
||||
img.network-logo(src="/images/pages/modal/auth/google_plus_sso_button.png", draggable="false", width="175", height="40")
|
||||
span.sign-in-blurb(data-i18n="login.sign_in_with_gplus")
|
||||
.gplus-login-wrapper
|
||||
.gplus-login-button
|
||||
|
||||
.hr-text
|
||||
hr
|
||||
span(data-i18n="TODO")
|
||||
| or
|
||||
|
||||
div
|
||||
if ['student', 'teacher'].indexOf(view.sharedState.get('path')) !== -1
|
||||
.row.full-name
|
||||
.form-group
|
||||
label.control-label(for="first-name-input")
|
||||
span(data-i18n="TODO")
|
||||
| First name:
|
||||
input#first-name-input(name="firstName")
|
||||
.last-initial.form-group
|
||||
label.control-label(for="last-name-input")
|
||||
span(data-i18n="TODO")
|
||||
| Last initial:
|
||||
input#last-name-input(name="lastName" maxlength="1")
|
||||
.row
|
||||
.form-group
|
||||
label.control-label(for="email-input")
|
||||
span(data-i18n="TODO")
|
||||
| Email address:
|
||||
input#email-input(name="email" type="email")
|
||||
.row
|
||||
//- This form group needs a parent so its errors can be cleared individually
|
||||
.form-group
|
||||
label.control-label(for="username-input")
|
||||
span(data-i18n="TODO")
|
||||
| Username:
|
||||
input#username-input(name="name")
|
||||
.row
|
||||
.form-group
|
||||
label.control-label(for="password-input")
|
||||
span(data-i18n="TODO")
|
||||
| Password:
|
||||
input#password-input(name="password" type="password")
|
||||
.row
|
||||
.form-group.checkbox.subscribe
|
||||
label.control-label(for="subscribe-input")
|
||||
input#subscribe-input(type="checkbox" checked="checked" name="subscribe")
|
||||
span.small(data-i18n="TODO")
|
||||
| Receive announcements about CodeCombat
|
||||
|
||||
// In reverse order for tabbing purposes
|
||||
.history-nav-buttons
|
||||
button.next-button.btn.btn-lg.btn-navy(type='submit')
|
||||
span(data-i18n="TODO")
|
||||
| Create Account
|
||||
|
||||
button.back-button.btn.btn-lg.btn-navy-alt(type='button')
|
||||
span(data-i18n="TODO")
|
||||
| Back
|
|
@ -0,0 +1,67 @@
|
|||
.modal-body-content
|
||||
h4
|
||||
span(data-i18n="TODO")
|
||||
| Choose your account type:
|
||||
.path-cards
|
||||
.path-card.navy
|
||||
.card-title
|
||||
span(data-i18n="TODO")
|
||||
| Teacher
|
||||
.card-content
|
||||
h6.card-description
|
||||
span(data-i18n="TODO")
|
||||
| Teach programming using CodeCombat!
|
||||
ul.small.m-t-1
|
||||
li
|
||||
span(data-i18n="TODO")
|
||||
| Create/manage classes
|
||||
li
|
||||
span(data-i18n="TODO")
|
||||
| Access course guides
|
||||
li
|
||||
span(data-i18n="TODO")
|
||||
| View student progress
|
||||
.card-footer
|
||||
button.btn.btn-lg.btn-navy.teacher-path-button
|
||||
.text-h6
|
||||
span(data-i18n="TODO")
|
||||
| Sign up as a Teacher
|
||||
|
||||
.path-card.forest
|
||||
.card-title
|
||||
span(data-i18n="TODO")
|
||||
| Student
|
||||
.card-content
|
||||
h6.card-description
|
||||
span(data-i18n="TODO")
|
||||
| Learn to program while playing an engaging game!
|
||||
ul.small.m-t-1
|
||||
li
|
||||
span(data-i18n="TODO")
|
||||
| Join a classroom
|
||||
li
|
||||
span(data-i18n="TODO")
|
||||
| Play assigned Courses
|
||||
li
|
||||
span(data-i18n="TODO")
|
||||
| Compete in arenas
|
||||
.card-footer
|
||||
i.small
|
||||
span(data-i18n="TODO")
|
||||
| Have your class code ready!
|
||||
button.btn.btn-lg.btn-forest.student-path-button
|
||||
.text-h6
|
||||
span(data-i18n="TODO")
|
||||
| Sign up as a Student
|
||||
|
||||
.individual-section
|
||||
.individual-title
|
||||
span(data-i18n="TODO")
|
||||
| Individual
|
||||
p.individual-description.small
|
||||
span(data-i18n="TODO")
|
||||
| Learn to program at your own pace! For players who don't have a class code and want to save their progress. Also parents!
|
||||
button.btn.btn-lg.btn-navy.individual-path-button
|
||||
.text-h6
|
||||
span(data-i18n="TODO")
|
||||
| I'm an Individual
|
31
app/templates/core/create-account-modal/coppa-deny-view.jade
Normal file
31
app/templates/core/create-account-modal/coppa-deny-view.jade
Normal file
|
@ -0,0 +1,31 @@
|
|||
form.modal-body.coppa-deny
|
||||
.modal-body-content
|
||||
.parent-email-input-group.form-group
|
||||
label.control-label.text-h4(for="parent-email-input")
|
||||
span(data-i18n="TODO")
|
||||
| Enter your parent's email address:
|
||||
input#parent-email-input(type="email" name="parentEmail" value=state.get('parentEmail'))
|
||||
|
||||
.render
|
||||
if state.get('error')
|
||||
p.small.error
|
||||
span(data-i18n="TODO")
|
||||
| Something went wrong when trying to send the email. Check the email address and try again.
|
||||
|
||||
p.small.parent-email-blurb.render
|
||||
span
|
||||
!= translate('signup.parent_email_blurb').replace('{{email_link}}', '<a href="mailto:team@codecombat.com">team@codecombat.com</a>')
|
||||
|
||||
// In reverse order for tabbing purposes
|
||||
.history-nav-buttons.render
|
||||
button.send-parent-email-button.btn.btn-lg.btn-navy(type='submit', disabled=state.get('parentEmailSent') || state.get('parentEmailSending'))
|
||||
if state.get('parentEmailSent')
|
||||
span(data-i18n="TODO")
|
||||
| Sent
|
||||
else
|
||||
span(data-i18n="TODO")
|
||||
| Send
|
||||
|
||||
button.back-to-segment-check.btn.btn-lg.btn-navy-alt(type='button')
|
||||
span(data-i18n="TODO")
|
||||
| Back
|
|
@ -0,0 +1,63 @@
|
|||
extends /templates/core/modal-base-flat
|
||||
|
||||
block modal-header
|
||||
//-
|
||||
This allows for the header color to switch without the subview templates
|
||||
needing to contain the header
|
||||
.modal-header(class=state.get('path'))
|
||||
+modal-header-content
|
||||
|
||||
mixin modal-header-content
|
||||
h3
|
||||
case state.get('path')
|
||||
when 'student'
|
||||
span(data-i18n="TODO")
|
||||
| Create Student Account
|
||||
when 'teacher'
|
||||
span(data-i18n="TODO")
|
||||
| Create Teacher Account
|
||||
when 'individual'
|
||||
span(data-i18n="TODO")
|
||||
| Create Individual Account
|
||||
default
|
||||
span(data-i18n="TODO")
|
||||
| Create Account
|
||||
|
||||
//-
|
||||
This is where the subviews (screens) are hooked up.
|
||||
Most subview templates have a .modal-body at their root, but this is inconsistent and needs organization.
|
||||
block modal-body
|
||||
case state.get('screen')
|
||||
when 'choose-account-type'
|
||||
#choose-account-type-view
|
||||
when 'segment-check'
|
||||
#segment-check-view
|
||||
when 'basic-info'
|
||||
#basic-info-view
|
||||
when 'coppa-deny'
|
||||
#coppa-deny-view
|
||||
when 'sso-already-exists'
|
||||
#single-sign-on-already-exists-view
|
||||
when 'sso-confirm'
|
||||
#single-sign-on-confirm-view
|
||||
//- These are not yet implemented
|
||||
//- when 'extras'
|
||||
//- #extras-view
|
||||
//- when 'confirmation'
|
||||
//- #confirmation-view
|
||||
|
||||
block modal-footer
|
||||
//-
|
||||
This allows for the footer color to switch without the subview templates
|
||||
needing to contain the footer
|
||||
.modal-footer(class=state.get('path'))
|
||||
+modal-footer-content
|
||||
|
||||
mixin modal-footer-content
|
||||
.modal-footer-content
|
||||
.small-details
|
||||
span.spr(data-i18n="TODO")
|
||||
| Already have an account?
|
||||
a.login-link
|
||||
span(data-i18n="TODO")
|
||||
| Sign in
|
|
@ -0,0 +1,76 @@
|
|||
form.modal-body.segment-check
|
||||
.modal-body-content
|
||||
case view.sharedState.get('path')
|
||||
when 'student'
|
||||
span(data-i18n="TODO")
|
||||
| Enter your class code:
|
||||
.class-code-input-group.form-group
|
||||
input.class-code-input(name="classCode" value=view.sharedState.get('classCode'))
|
||||
.render
|
||||
unless _.isEmpty(view.sharedState.get('classCode'))
|
||||
if state.get('classCodeValid')
|
||||
span.glyphicon.glyphicon-ok-circle.class-code-valid-icon
|
||||
else
|
||||
span.glyphicon.glyphicon-remove-circle.class-code-valid-icon
|
||||
|
||||
p.render
|
||||
if _.isEmpty(view.sharedState.get('classCode'))
|
||||
span(data-i18n="TODO")
|
||||
| Ask your teacher for your class code.
|
||||
else if state.get('classCodeValid')
|
||||
span.small(data-i18n="TODO")
|
||||
| You're about to join:
|
||||
br
|
||||
span.classroom-name= view.classroom.get('name')
|
||||
br
|
||||
span.teacher-name= view.classroom.owner.get('name')
|
||||
else
|
||||
span(data-i18n="TODO")
|
||||
| This class code doesn't exist! Check your spelling or ask your teacher for help.
|
||||
if _.isEmpty(view.sharedState.get('classCode')) || !state.get('classCodeValid')
|
||||
br
|
||||
span.spr(data-i18n="TODO")
|
||||
| Don't have a class code? Create an
|
||||
a.individual-path-button
|
||||
span(data-i18n="TODO")
|
||||
| Individual Account
|
||||
span.spl(data-i18n="TODO")
|
||||
| instead.
|
||||
|
||||
when 'teacher'
|
||||
// TODO
|
||||
when 'individual'
|
||||
.birthday-form-group.form-group
|
||||
span(data-i18n="TODO")
|
||||
| Enter your birthdate:
|
||||
.input-border
|
||||
select#birthday-month-input.input-large.form-control(name="birthdayMonth", style="width: 106px; float: left")
|
||||
option(value='',data-i18n="calendar.month")
|
||||
for name, index in ['january','february','march','april','may','june','july','august','september','october','november','december']
|
||||
- var month = index + 1
|
||||
option(data-i18n="calendar.#{name}" value=month, selected=(month == view.sharedState.get('birthdayMonth')))
|
||||
select#birthday-day-input.input-large.form-control(name="birthdayDay", style="width: 75px; float: left")
|
||||
option(value='',data-i18n="calendar.day")
|
||||
for day in _.range(1,32)
|
||||
option(selected=(day == view.sharedState.get('birthdayDay'))) #{day}
|
||||
select#birthday-year-input.input-large.form-control(name="birthdayYear", style="width: 90px;")
|
||||
option(value='',data-i18n="calendar.year")
|
||||
- var thisYear = new Date().getFullYear()
|
||||
for year in _.range(thisYear, thisYear - 100, -1)
|
||||
option(selected=(year == view.sharedState.get('birthdayYear'))) #{year}
|
||||
|
||||
default
|
||||
span
|
||||
| Something went wrong :(
|
||||
|
||||
// In reverse order for tabbing purposes
|
||||
.history-nav-buttons
|
||||
//- disabled=!view.sharedState.get('segmentCheckValid')
|
||||
button.next-button.btn.btn-lg.btn-navy(type='submit')
|
||||
span(data-i18n="TODO")
|
||||
| Next
|
||||
|
||||
button.back-to-account-type.btn.btn-lg.btn-navy-alt(type='button')
|
||||
span(data-i18n="TODO")
|
||||
| Back
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
.modal-body
|
||||
.modal-body-content
|
||||
if view.sharedState.get('ssoUsed')
|
||||
h4
|
||||
span(data-i18n="TODO")
|
||||
| This email is already in use:
|
||||
div.small
|
||||
b
|
||||
span= view.sharedState.get('email')
|
||||
|
||||
.hr-text
|
||||
hr
|
||||
span(data-i18n="TODO")
|
||||
| continue
|
||||
|
||||
button.sso-login-btn.btn.btn-lg.btn-navy
|
||||
span(data-i18n="login.log_in")
|
||||
|
||||
.history-nav-buttons.just-one
|
||||
button.back-button.btn.btn-lg.btn-navy-alt(type='button')
|
||||
span(data-i18n="TODO")
|
||||
| Back
|
|
@ -0,0 +1,40 @@
|
|||
form#basic-info-form.modal-body
|
||||
.modal-body-content
|
||||
h4
|
||||
span(data-i18n="TODO")
|
||||
| Connect with:
|
||||
div.small
|
||||
b
|
||||
span= view.sharedState.get('email')
|
||||
span.glyphicon.glyphicon-ok-circle.class-code-valid-icon
|
||||
|
||||
.hr-text
|
||||
hr
|
||||
span(data-i18n="TODO")
|
||||
| continue
|
||||
|
||||
div
|
||||
input.hidden(name="email" value=view.sharedState.get('email'))
|
||||
div
|
||||
//- This form group needs a parent so its errors can be cleared individually
|
||||
.form-group
|
||||
h4
|
||||
span(data-i18n="TODO")
|
||||
| Pick a username:
|
||||
input(name="name" value=view.sharedState.get('name'))
|
||||
|
||||
.form-group.checkbox.subscribe
|
||||
label.control-label(for="subscribe-input")
|
||||
input#subscribe-input(type="checkbox" checked="checked" name="subscribe")
|
||||
span.small(data-i18n="TODO")
|
||||
| Receive announcements about CodeCombat
|
||||
|
||||
// In reverse order for tabbing purposes
|
||||
.history-nav-buttons
|
||||
button.next-button.btn.btn-lg.btn-navy(type='submit')
|
||||
span(data-i18n="TODO")
|
||||
| Create Account
|
||||
|
||||
button.back-button.btn.btn-lg.btn-navy-alt(type='button')
|
||||
span(data-i18n="TODO")
|
||||
| Back
|
|
@ -110,6 +110,7 @@ module.exports = class NewHomeView extends RootView
|
|||
$(window).on 'resize', @fitToPage
|
||||
@fitToPage()
|
||||
setTimeout(@fitToPage, 0)
|
||||
@$('#create-account-link').click()
|
||||
super()
|
||||
|
||||
destroy: ->
|
||||
|
|
|
@ -22,6 +22,7 @@ module.exports = class AuthModal extends ModalView
|
|||
|
||||
initialize: (options={}) ->
|
||||
@previousFormInputs = options.initialValues or {}
|
||||
@previousFormInputs.emailOrUsername ?= @previousFormInputs.email or @previousFormInputs.username
|
||||
|
||||
# TODO: Switch to promises and state, rather than using defer to hackily enable buttons after render
|
||||
application.gplusHandler.loadAPI({ success: => _.defer => @$('#gplus-login-btn').attr('disabled', false) })
|
||||
|
@ -96,7 +97,7 @@ module.exports = class AuthModal extends ModalView
|
|||
btn = @$('#gplus-login-btn')
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('login.sign_in_with_gplus'))
|
||||
btn.attr('disabled', false)
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
|
||||
|
||||
# Facebook
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/core/coppa-deny'
|
||||
|
||||
|
||||
module.exports = class COPPADenyModal extends ModalView
|
||||
id: 'coppa-deny-modal'
|
||||
template: template
|
||||
closeButton: true
|
||||
|
||||
constructor: ->
|
||||
super()
|
||||
window.tracker?.trackEvent 'COPPA Message Shown', category: 'Homepage'
|
|
@ -100,8 +100,10 @@ module.exports = class CocoView extends Backbone.View
|
|||
|
||||
renderSelectors: (selectors...) ->
|
||||
newTemplate = $(@template(@getRenderData()))
|
||||
console.log newTemplate.find('p.render')
|
||||
for selector, i in selectors
|
||||
for elPair in _.zip(@$el.find(selector), newTemplate.find(selector))
|
||||
console.log elPair
|
||||
$(elPair[0]).replaceWith($(elPair[1]))
|
||||
@delegateEvents()
|
||||
@$el.i18n()
|
||||
|
|
|
@ -1,259 +0,0 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/core/create-account-modal'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
application = require 'core/application'
|
||||
Classroom = require 'models/Classroom'
|
||||
errors = require 'core/errors'
|
||||
COPPADenyModal = require 'views/core/COPPADenyModal'
|
||||
utils = require 'core/utils'
|
||||
|
||||
|
||||
module.exports = class CreateAccountModal extends ModalView
|
||||
id: 'create-account-modal'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'submit form': 'onSubmitForm'
|
||||
'keyup #name': 'onNameChange'
|
||||
'click #gplus-signup-btn': 'onClickGPlusSignupButton'
|
||||
'click #gplus-login-btn': 'onClickGPlusLoginButton'
|
||||
'click #facebook-signup-btn': 'onClickFacebookSignupButton'
|
||||
'click #facebook-login-btn': 'onClickFacebookLoginButton'
|
||||
'click #close-modal': 'hide'
|
||||
'click #switch-to-login-btn': 'onClickSwitchToLoginButton'
|
||||
|
||||
|
||||
# Initialization
|
||||
|
||||
initialize: (options={}) ->
|
||||
@onNameChange = _.debounce(_.bind(@checkNameExists, @), 500)
|
||||
options.initialValues ?= {}
|
||||
options.initialValues?.classCode ?= utils.getQueryVariable('_cc', "")
|
||||
@previousFormInputs = options.initialValues or {}
|
||||
|
||||
# TODO: Switch to promises and state, rather than using defer to hackily enable buttons after render
|
||||
application.gplusHandler.loadAPI({ success: => _.defer => @$('#gplus-signup-btn').attr('disabled', false) })
|
||||
application.facebookHandler.loadAPI({ success: => _.defer => @$('#facebook-signup-btn').attr('disabled', false) })
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@playSound 'game-menu-open'
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
_.delay (=> $('input:visible:first', @$el).focus()), 500
|
||||
|
||||
|
||||
# User creation
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
e.preventDefault()
|
||||
@playSound 'menu-button-click'
|
||||
|
||||
forms.clearFormAlerts(@$el)
|
||||
attrs = forms.formToObject @$el
|
||||
attrs.name = @suggestedName if @suggestedName
|
||||
_.defaults attrs, me.pick([
|
||||
'preferredLanguage', 'testGroupNumber', 'dateCreated', 'wizardColor1',
|
||||
'name', 'music', 'volume', 'emails', 'schoolName'
|
||||
])
|
||||
attrs.emails ?= {}
|
||||
attrs.emails.generalNews ?= {}
|
||||
attrs.emails.generalNews.enabled = @$el.find('#subscribe').prop('checked')
|
||||
@classCode = attrs.classCode
|
||||
delete attrs.classCode
|
||||
|
||||
error = false
|
||||
birthday = new Date Date.UTC attrs.birthdayYear, attrs.birthdayMonth - 1, attrs.birthdayDay
|
||||
if @classCode
|
||||
attrs.role = 'student'
|
||||
else if isNaN(birthday.getTime())
|
||||
forms.setErrorToProperty @$el, 'birthdayDay', 'Required'
|
||||
error = true
|
||||
else
|
||||
age = (new Date().getTime() - birthday.getTime()) / 365.4 / 24 / 60 / 60 / 1000
|
||||
attrs.birthday = birthday.toISOString()
|
||||
|
||||
delete attrs.birthdayYear
|
||||
delete attrs.birthdayMonth
|
||||
delete attrs.birthdayDay
|
||||
|
||||
_.assign attrs, @gplusAttrs if @gplusAttrs
|
||||
_.assign attrs, @facebookAttrs if @facebookAttrs
|
||||
res = tv4.validateMultiple attrs, User.schema
|
||||
|
||||
if not res.valid
|
||||
forms.applyErrorsToForm(@$el, res.errors)
|
||||
error = true
|
||||
if not _.any([attrs.password, @gplusAttrs, @facebookAttrs])
|
||||
forms.setErrorToProperty @$el, 'password', 'Required'
|
||||
error = true
|
||||
if not forms.validateEmail(attrs.email)
|
||||
forms.setErrorToProperty @$el, 'email', 'Please enter a valid email address'
|
||||
error = true
|
||||
return if error
|
||||
|
||||
@$('#signup-button').text($.i18n.t('signup.creating')).attr('disabled', true)
|
||||
@newUser = new User(attrs)
|
||||
if @classCode
|
||||
@signupClassroomPrecheck()
|
||||
else
|
||||
if age < 13
|
||||
@openModalView new COPPADenyModal
|
||||
else
|
||||
@createUser()
|
||||
|
||||
signupClassroomPrecheck: ->
|
||||
classroom = new Classroom()
|
||||
classroom.fetch({ data: { code: @classCode } })
|
||||
classroom.once 'sync', @createUser, @
|
||||
classroom.once 'error', @onClassroomFetchError, @
|
||||
|
||||
onClassroomFetchError: ->
|
||||
@$('#signup-button').text($.i18n.t('signup.sign_up')).attr('disabled', false)
|
||||
forms.setErrorToProperty(@$el, 'classCode', "#{@classCode} is not a valid code. Please verify the code is typed correctly.")
|
||||
@$('#class-code-input').val('')
|
||||
|
||||
createUser: ->
|
||||
options = {}
|
||||
window.tracker?.identify()
|
||||
if @gplusAttrs
|
||||
@newUser.set('_id', me.id)
|
||||
options.url = "/db/user?gplusID=#{@gplusAttrs.gplusID}&gplusAccessToken=#{application.gplusHandler.accessToken.access_token}"
|
||||
options.type = 'PUT'
|
||||
if @facebookAttrs
|
||||
@newUser.set('_id', me.id)
|
||||
options.url = "/db/user?facebookID=#{@facebookAttrs.facebookID}&facebookAccessToken=#{application.facebookHandler.authResponse.accessToken}"
|
||||
options.type = 'PUT'
|
||||
@newUser.save(null, options)
|
||||
@newUser.once 'sync', @onUserCreated, @
|
||||
@newUser.once 'error', @onUserSaveError, @
|
||||
|
||||
onUserSaveError: (user, jqxhr) ->
|
||||
@$('#signup-button').text($.i18n.t('signup.sign_up')).attr('disabled', false)
|
||||
if _.isObject(jqxhr.responseJSON) and jqxhr.responseJSON.property
|
||||
error = jqxhr.responseJSON
|
||||
if jqxhr.status is 409 and error.property is 'name'
|
||||
@newUser.unset 'name'
|
||||
return @createUser()
|
||||
return forms.applyErrorsToForm(@$el, [jqxhr.responseJSON])
|
||||
errors.showNotyNetworkError(jqxhr)
|
||||
|
||||
onUserCreated: ->
|
||||
Backbone.Mediator.publish "auth:signed-up", {}
|
||||
if @gplusAttrs
|
||||
window.tracker?.trackEvent 'Google Login', category: "Signup", label: 'GPlus'
|
||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'GPlus'
|
||||
else if @facebookAttrs
|
||||
window.tracker?.trackEvent 'Facebook Login', category: "Signup", label: 'Facebook'
|
||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'Facebook'
|
||||
else
|
||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'CodeCombat'
|
||||
if @classCode
|
||||
url = "/courses?_cc="+@classCode
|
||||
location.href = url
|
||||
else
|
||||
window.location.reload()
|
||||
|
||||
|
||||
# Google Plus
|
||||
|
||||
onClickGPlusSignupButton: ->
|
||||
btn = @$('#gplus-signup-btn')
|
||||
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) ->
|
||||
existingUser = new User()
|
||||
existingUser.fetchGPlusUser(@gplusAttrs.gplusID, {
|
||||
context: @
|
||||
complete: ->
|
||||
@$('#email-password-row').remove()
|
||||
success: =>
|
||||
@$('#gplus-account-exists-row').removeClass('hide')
|
||||
error: (user, jqxhr) =>
|
||||
if jqxhr.status is 404
|
||||
@$('#gplus-logged-in-row').toggleClass('hide')
|
||||
else
|
||||
errors.showNotyNetworkError(jqxhr)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onClickGPlusLoginButton: ->
|
||||
me.loginGPlusUser(@gplusAttrs.gplusID, {
|
||||
context: @
|
||||
success: -> window.location.reload()
|
||||
error: ->
|
||||
@$('#gplus-login-btn').text($.i18n.t('login.log_in')).attr('disabled', false)
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
})
|
||||
@$('#gplus-login-btn').text($.i18n.t('login.logging_in')).attr('disabled', true)
|
||||
|
||||
|
||||
|
||||
# Facebook
|
||||
|
||||
onClickFacebookSignupButton: ->
|
||||
btn = @$('#facebook-signup-btn')
|
||||
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) ->
|
||||
existingUser = new User()
|
||||
existingUser.fetchFacebookUser(@facebookAttrs.facebookID, {
|
||||
context: @
|
||||
complete: ->
|
||||
@$('#email-password-row').remove()
|
||||
success: =>
|
||||
@$('#facebook-account-exists-row').removeClass('hide')
|
||||
error: (user, jqxhr) =>
|
||||
if jqxhr.status is 404
|
||||
@$('#facebook-logged-in-row').toggleClass('hide')
|
||||
else
|
||||
errors.showNotyNetworkError(jqxhr)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onClickFacebookLoginButton: ->
|
||||
me.loginFacebookUser(@facebookAttrs.facebookID, {
|
||||
context: @
|
||||
success: -> window.location.reload()
|
||||
error: =>
|
||||
@$('#facebook-login-btn').text($.i18n.t('login.log_in')).attr('disabled', false)
|
||||
errors.showNotyNetworkError(jqxhr)
|
||||
})
|
||||
@$('#facebook-login-btn').text($.i18n.t('login.logging_in')).attr('disabled', true)
|
||||
|
||||
|
||||
# Misc
|
||||
|
||||
onHidden: ->
|
||||
super()
|
||||
@playSound 'game-menu-close'
|
||||
|
||||
checkNameExists: ->
|
||||
name = $('#name', @$el).val()
|
||||
return forms.clearFormAlerts(@$el) if name is ''
|
||||
User.getUnconflictedName name, (newName) =>
|
||||
forms.clearFormAlerts(@$el)
|
||||
if name is newName
|
||||
@suggestedName = undefined
|
||||
else
|
||||
@suggestedName = newName
|
||||
forms.setErrorToProperty @$el, 'name', "That name is taken! How about #{newName}?"
|
||||
|
||||
onClickSwitchToLoginButton: ->
|
||||
AuthModal = require('./AuthModal')
|
||||
modal = new AuthModal({initialValues: forms.formToObject @$el})
|
||||
currentView.openModalView(modal)
|
206
app/views/core/CreateAccountModal/BasicInfoView.coffee
Normal file
206
app/views/core/CreateAccountModal/BasicInfoView.coffee
Normal file
|
@ -0,0 +1,206 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
template = require 'templates/core/create-account-modal/basic-info-view'
|
||||
forms = require 'core/forms'
|
||||
errors = require 'core/errors'
|
||||
User = require 'models/User'
|
||||
State = require 'models/State'
|
||||
|
||||
###
|
||||
This view handles the primary form for user details — name, email, password, etc,
|
||||
and the AJAX that actually creates the user.
|
||||
|
||||
It also handles facebook/g+ login, which if used, open one of two other screens:
|
||||
sso-already-exists: If the facebook/g+ connection is already associated with a user, they're given a log in button
|
||||
sso-confirm: If this is a new facebook/g+ connection, ask for a username, then allow creation of a user
|
||||
|
||||
The sso-confirm view *inherits from this view* in order to share its account-creation logic and events.
|
||||
This means the selectors used in these events must work in both templates.
|
||||
|
||||
This view currently uses the old form API instead of stateful render.
|
||||
It needs some work to make error UX and rendering better, but is functional.
|
||||
###
|
||||
|
||||
module.exports = class BasicInfoView extends ModalView
|
||||
id: 'basic-info-view'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'input input[name="name"]': 'onInputName'
|
||||
'click .back-button': 'onClickBackButton'
|
||||
'submit form': 'onSubmitForm'
|
||||
'click .use-suggested-name-link': 'onClickUseSuggestedNameLink'
|
||||
'click #facebook-signup-btn': 'onClickSsoSignupButton'
|
||||
'click #gplus-signup-btn': 'onClickSsoSignupButton'
|
||||
|
||||
initialize: ({ @sharedState } = {}) ->
|
||||
@state = new State {
|
||||
suggestedName: null
|
||||
}
|
||||
@onNameChange = _.debounce(_.bind(@checkNameUnique, @), 500)
|
||||
@listenTo @sharedState, 'change:facebookEnabled', -> @renderSelectors('.auth-network-logins')
|
||||
@listenTo @sharedState, 'change:gplusEnabled', -> @renderSelectors('.auth-network-logins')
|
||||
|
||||
checkNameUnique: ->
|
||||
name = $('input[name="name"]', @$el).val()
|
||||
return forms.clearFormAlerts(@$('input[name="name"]').closest('.form-group').parent()) if name is ''
|
||||
@nameUniquePromise = new Promise((resolve, reject) => User.getUnconflictedName name, (newName) =>
|
||||
if name is newName
|
||||
@state.set { suggestedName: null }
|
||||
@clearNameError()
|
||||
resolve true
|
||||
else
|
||||
@state.set { suggestedName: newName }
|
||||
@setNameError(newName)
|
||||
resolve false
|
||||
)
|
||||
|
||||
clearNameError: ->
|
||||
forms.clearFormAlerts(@$('input[name="name"]').closest('.form-group').parent())
|
||||
|
||||
setNameError: (newName) ->
|
||||
@clearNameError()
|
||||
forms.setErrorToProperty @$el, 'name', "Username already taken!<br>Try <a class='use-suggested-name-link'>#{newName}</a>?" # TODO: Translate!
|
||||
|
||||
checkBasicInfo: (data) ->
|
||||
# TODO: Move this to somewhere appropriate
|
||||
tv4.addFormat({
|
||||
'email': (email) ->
|
||||
if forms.validateEmail(email)
|
||||
return null
|
||||
else
|
||||
return {code: tv4.errorCodes.FORMAT_CUSTOM, message: "Please enter a valid email address."}
|
||||
})
|
||||
|
||||
forms.clearFormAlerts(@$el)
|
||||
res = tv4.validateMultiple data, @formSchema()
|
||||
forms.applyErrorsToForm(@$('form'), res.errors) unless res.valid
|
||||
return res.valid
|
||||
|
||||
formSchema: ->
|
||||
type: 'object'
|
||||
properties:
|
||||
email: User.schema.properties.email
|
||||
name: User.schema.properties.name
|
||||
password: User.schema.properties.password
|
||||
required: ['email', 'name', 'password'].concat (if @sharedState.get('path') is 'student' then ['firstName', 'lastName'] else [])
|
||||
|
||||
onClickBackButton: -> @trigger 'nav-back'
|
||||
|
||||
onInputName: ->
|
||||
@nameUniquePromise = null
|
||||
@onNameChange()
|
||||
|
||||
onClickUseSuggestedNameLink: (e) ->
|
||||
@$('input[name="name"]').val(@state.get('suggestedName'))
|
||||
forms.clearFormAlerts(@$el.find('input[name="name"]').closest('.form-group').parent())
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
e.preventDefault()
|
||||
data = forms.formToObject(e.currentTarget)
|
||||
valid = @checkBasicInfo(data)
|
||||
# TODO: This promise logic is super weird and confusing. Rewrite.
|
||||
@checkNameUnique() unless @nameUniquePromise
|
||||
@nameUniquePromise.then ->
|
||||
@nameUniquePromise = null
|
||||
return unless valid
|
||||
|
||||
attrs = forms.formToObject @$el
|
||||
_.defaults attrs, me.pick([
|
||||
'preferredLanguage', 'testGroupNumber', 'dateCreated', 'wizardColor1',
|
||||
'name', 'music', 'volume', 'emails', 'schoolName'
|
||||
])
|
||||
attrs.emails ?= {}
|
||||
attrs.emails.generalNews ?= {}
|
||||
attrs.emails.generalNews.enabled = (attrs.subscribe[0] is 'on')
|
||||
delete attrs.subscribe
|
||||
|
||||
error = false
|
||||
|
||||
if @sharedState.get('birthday')
|
||||
attrs.birthday = @sharedState.get('birthday').toISOString()
|
||||
|
||||
_.assign attrs, @sharedState.get('ssoAttrs') if @sharedState.get('ssoAttrs')
|
||||
res = tv4.validateMultiple attrs, User.schema
|
||||
|
||||
@$('#signup-button').text($.i18n.t('signup.creating')).attr('disabled', true)
|
||||
@newUser = new User(attrs)
|
||||
@createUser()
|
||||
|
||||
createUser: ->
|
||||
options = {}
|
||||
window.tracker?.identify()
|
||||
if @sharedState.get('ssoUsed') is 'gplus'
|
||||
@newUser.set('_id', me.id)
|
||||
options.url = "/db/user?gplusID=#{@sharedState.get('ssoAttrs').gplusID}&gplusAccessToken=#{application.gplusHandler.accessToken.access_token}"
|
||||
options.type = 'PUT'
|
||||
if @sharedState.get('ssoUsed') is 'facebook'
|
||||
@newUser.set('_id', me.id)
|
||||
options.url = "/db/user?facebookID=#{@sharedState.get('ssoAttrs').facebookID}&facebookAccessToken=#{application.facebookHandler.authResponse.accessToken}"
|
||||
options.type = 'PUT'
|
||||
@newUser.save(null, options)
|
||||
@newUser.once 'sync', @onUserCreated, @
|
||||
@newUser.once 'error', @onUserSaveError, @
|
||||
|
||||
onUserSaveError: (user, jqxhr) ->
|
||||
# TODO: Do we need to enable/disable the submit button to prevent multiple users being created?
|
||||
# Seems to work okay without that, but mongo had 2 copies of the user... temporarily. Very strange.
|
||||
if _.isObject(jqxhr.responseJSON) and jqxhr.responseJSON.property
|
||||
forms.applyErrorsToForm(@$el, [jqxhr.responseJSON])
|
||||
@setNameError(@state.get('suggestedName'))
|
||||
else
|
||||
console.log "Error:", jqxhr.responseText
|
||||
errors.showNotyNetworkError(jqxhr)
|
||||
|
||||
onUserCreated: ->
|
||||
Backbone.Mediator.publish "auth:signed-up", {}
|
||||
if @sharedState.get('gplusAttrs')
|
||||
window.tracker?.trackEvent 'Google Login', category: "Signup", label: 'GPlus'
|
||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'GPlus'
|
||||
else if @sharedState.get('facebookAttrs')
|
||||
window.tracker?.trackEvent 'Facebook Login', category: "Signup", label: 'Facebook'
|
||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'Facebook'
|
||||
else
|
||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'CodeCombat'
|
||||
if @sharedState.get('classCode')
|
||||
url = "/courses?_cc="+@sharedState.get('classCode')
|
||||
location.href = url
|
||||
else
|
||||
window.location.reload()
|
||||
|
||||
onClickSsoSignupButton: (e) ->
|
||||
e.preventDefault()
|
||||
ssoUsed = $(e.currentTarget).data('sso-used')
|
||||
if ssoUsed is 'facebook'
|
||||
handler = application.facebookHandler
|
||||
fetchSsoUser = 'fetchFacebookUser'
|
||||
idName = 'facebookID'
|
||||
else
|
||||
handler = application.gplusHandler
|
||||
fetchSsoUser = 'fetchGPlusUser'
|
||||
idName = 'gplusID'
|
||||
handler.connect({
|
||||
context: @
|
||||
success: ->
|
||||
handler.loadPerson({
|
||||
context: @
|
||||
success: (ssoAttrs) ->
|
||||
@sharedState.set { ssoAttrs }
|
||||
existingUser = new User()
|
||||
existingUser[fetchSsoUser](@sharedState.get('ssoAttrs')[idName], {
|
||||
context: @
|
||||
success: =>
|
||||
@sharedState.set {
|
||||
ssoUsed
|
||||
email: ssoAttrs.email
|
||||
}
|
||||
@trigger 'sso-connect:already-in-use'
|
||||
error: (user, jqxhr) =>
|
||||
@sharedState.set {
|
||||
ssoUsed
|
||||
email: ssoAttrs.email
|
||||
}
|
||||
@trigger 'sso-connect:new-user'
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,11 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/core/create-account-modal/choose-account-type-view'
|
||||
|
||||
module.exports = class ChooseAccountTypeView extends ModalView
|
||||
id: 'choose-account-type-view'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click .teacher-path-button': -> @trigger 'choose-path', 'teacher'
|
||||
'click .student-path-button': -> @trigger 'choose-path', 'student'
|
||||
'click .individual-path-button': -> @trigger 'choose-path', 'individual'
|
32
app/views/core/CreateAccountModal/CoppaDenyView.coffee
Normal file
32
app/views/core/CreateAccountModal/CoppaDenyView.coffee
Normal file
|
@ -0,0 +1,32 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
State = require 'models/State'
|
||||
template = require 'templates/core/create-account-modal/coppa-deny-view'
|
||||
forms = require 'core/forms'
|
||||
|
||||
module.exports = class SegmentCheckView extends ModalView
|
||||
id: 'coppa-deny-view'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click .send-parent-email-button': 'onClickSendParentEmailButton'
|
||||
'input input[name="parentEmail"]': 'onInputParentEmail'
|
||||
|
||||
initialize: ({ @sharedState } = {}) ->
|
||||
@state = new State({ parentEmail: '' })
|
||||
@listenTo @state, 'all', -> @renderSelectors('.render')
|
||||
|
||||
onInputParentEmail: (e) ->
|
||||
@state.set { parentEmail: $(e.currentTarget).val() }, { silent: true }
|
||||
|
||||
onClickSendParentEmailButton: (e) ->
|
||||
e.preventDefault()
|
||||
@state.set({ parentEmailSending: true })
|
||||
$.ajax('/send-parent-signup-instructions', {
|
||||
method: 'POST'
|
||||
data:
|
||||
parentEmail: @state.get('parentEmail')
|
||||
success: =>
|
||||
@state.set({ error: false, parentEmailSent: true, parentEmailSending: false })
|
||||
error: =>
|
||||
@state.set({ error: true, parentEmailSent: false, parentEmailSending: false })
|
||||
})
|
115
app/views/core/CreateAccountModal/CreateAccountModal.coffee
Normal file
115
app/views/core/CreateAccountModal/CreateAccountModal.coffee
Normal file
|
@ -0,0 +1,115 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
ChooseAccountTypeView = require 'views/core/CreateAccountModal/ChooseAccountTypeView'
|
||||
SegmentCheckView = require 'views/core/CreateAccountModal/SegmentCheckView'
|
||||
CoppaDenyView = require 'views/core/CreateAccountModal/CoppaDenyView'
|
||||
BasicInfoView = require 'views/core/CreateAccountModal/BasicInfoView'
|
||||
SingleSignOnAlreadyExistsView = require 'views/core/CreateAccountModal/SingleSignOnAlreadyExistsView'
|
||||
SingleSignOnConfirmView = require 'views/core/CreateAccountModal/SingleSignOnConfirmView'
|
||||
State = require 'models/State'
|
||||
template = require 'templates/core/create-account-modal/create-account-modal'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
application = require 'core/application'
|
||||
errors = require 'core/errors'
|
||||
utils = require 'core/utils'
|
||||
|
||||
###
|
||||
CreateAccountModal is a wizard-style modal with several subviews, one for each
|
||||
`screen` that the user navigates forward and back through.
|
||||
|
||||
There are three `path`s, one for each account type (individual, student).
|
||||
Teacher account path will be added later; for now it defers to /teachers/signup)
|
||||
Each subview handles only one `screen`, but all three `path` variants because
|
||||
their logic is largely the same.
|
||||
|
||||
They `screen`s are:
|
||||
choose-account-type: Sets the `path`.
|
||||
segment-check: Checks required info for the path (age, )
|
||||
coppa-deny: Seen if the indidual segment-check age is < 13 years old
|
||||
basic-info: This is the form for username/password/email/etc.
|
||||
It asks for whatever is needed for this type of user.
|
||||
It also handles the actual user creation.
|
||||
A user may create their account here, or connect with facebook/g+
|
||||
sso-confirm: Alternate version of basic-info for new facebook/g+ users
|
||||
sso-already-exists: When facebook/g+ user already exists, this prompts them to sign in.
|
||||
extras: Not yet implemented
|
||||
confirmation: Not yet implemented
|
||||
|
||||
NOTE: BasicInfoView's two children (SingleSignOn...View) inherit from it.
|
||||
This allows them to have the same form-handling logic, but different templates.
|
||||
###
|
||||
|
||||
module.exports = class CreateAccountModal extends ModalView
|
||||
id: 'create-account-modal'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click .login-link': 'onClickLoginLink'
|
||||
'click .back-to-segment-check': -> @state.set { screen: 'segment-check' }
|
||||
|
||||
initialize: (options={}) ->
|
||||
classCode = utils.getQueryVariable('_cc', undefined)
|
||||
@state = new State {
|
||||
path: if classCode then 'student' else null
|
||||
screen: if classCode then 'segment-check' else 'choose-account-type'
|
||||
facebookEnabled: application.facebookHandler.apiLoaded
|
||||
gplusEnabled: application.gplusHandler.apiLoaded
|
||||
classCode
|
||||
birthday: new Date('') # so that birthday.getTime() is NaN
|
||||
}
|
||||
|
||||
@listenTo @state, 'all', @render #TODO: debounce
|
||||
|
||||
@customSubviews = {
|
||||
choose_account_type: new ChooseAccountTypeView()
|
||||
segment_check: new SegmentCheckView({ sharedState: @state })
|
||||
coppa_deny_view: new CoppaDenyView({ sharedState: @state })
|
||||
basic_info_view: new BasicInfoView({ sharedState: @state })
|
||||
sso_already_exists: new SingleSignOnAlreadyExistsView({ sharedState: @state })
|
||||
sso_confirm: new SingleSignOnConfirmView({ sharedState: @state })
|
||||
}
|
||||
|
||||
@listenTo @customSubviews.choose_account_type, 'choose-path', (path) ->
|
||||
if path is 'teacher'
|
||||
application.router.navigate('/teachers/signup', trigger: true)
|
||||
else
|
||||
@state.set { path, screen: 'segment-check' }
|
||||
@listenTo @customSubviews.segment_check, 'choose-path', (path) ->
|
||||
@state.set { path, screen: 'segment-check' }
|
||||
@listenTo @customSubviews.segment_check, 'nav-back', ->
|
||||
@state.set { path: null, screen: 'choose-account-type' }
|
||||
@listenTo @customSubviews.segment_check, 'nav-forward', (screen) ->
|
||||
@state.set { screen: screen or 'basic-info' }
|
||||
|
||||
@listenTo @customSubviews.basic_info_view, 'sso-connect:already-in-use', ->
|
||||
@state.set { screen: 'sso-already-exists' }
|
||||
@listenTo @customSubviews.basic_info_view, 'sso-connect:new-user', ->
|
||||
@state.set { screen: 'sso-confirm' }
|
||||
@listenTo @customSubviews.basic_info_view, 'nav-back', ->
|
||||
@state.set { screen: 'segment-check' }
|
||||
|
||||
@listenTo @customSubviews.sso_confirm, 'nav-back', ->
|
||||
@state.set { screen: 'basic-info' }
|
||||
|
||||
@listenTo @customSubviews.sso_already_exists, 'nav-back', ->
|
||||
@state.set { screen: 'basic-info' }
|
||||
|
||||
# options.initialValues ?= {}
|
||||
# options.initialValues?.classCode ?= utils.getQueryVariable('_cc', "")
|
||||
# @previousFormInputs = options.initialValues or {}
|
||||
|
||||
# TODO: Switch to promises and state, rather than using defer to hackily enable buttons after render
|
||||
|
||||
application.facebookHandler.loadAPI({ success: => @state.set { facebookEnabled: true } unless @destroyed })
|
||||
application.gplusHandler.loadAPI({ success: => @state.set { gplusEnabled: true } unless @destroyed })
|
||||
|
||||
afterRender: ->
|
||||
# @$el.html(@template(@getRenderData()))
|
||||
for key, subview of @customSubviews
|
||||
subview.setElement(@$('#' + subview.id))
|
||||
subview.render()
|
||||
|
||||
onClickLoginLink: ->
|
||||
# TODO: Make sure the right information makes its way into the state.
|
||||
@openModalView(new AuthModal({ initialValues: @state.pick(['email', 'name', 'password']) }))
|
63
app/views/core/CreateAccountModal/SegmentCheckView.coffee
Normal file
63
app/views/core/CreateAccountModal/SegmentCheckView.coffee
Normal file
|
@ -0,0 +1,63 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
template = require 'templates/core/create-account-modal/segment-check-view'
|
||||
forms = require 'core/forms'
|
||||
Classroom = require 'models/Classroom'
|
||||
State = require 'models/State'
|
||||
|
||||
module.exports = class SegmentCheckView extends CocoView
|
||||
id: 'segment-check-view'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click .back-to-account-type': -> @trigger 'nav-back'
|
||||
'input .class-code-input': 'onInputClassCode'
|
||||
'input .birthday-form-group': 'onInputBirthday'
|
||||
'submit form.segment-check': 'onSubmitSegmentCheck'
|
||||
'click .individual-path-button': ->
|
||||
@trigger 'choose-path', 'individual'
|
||||
|
||||
onInputClassCode: (e) ->
|
||||
classCode = $(e.currentTarget).val()
|
||||
@checkClassCodeDebounced(classCode)
|
||||
@sharedState.set { classCode }, { silent: true }
|
||||
|
||||
onInputBirthday: ->
|
||||
{ birthdayYear, birthdayMonth, birthdayDay } = forms.formToObject(@$('form'))
|
||||
birthday = new Date Date.UTC(birthdayYear, birthdayMonth - 1, birthdayDay)
|
||||
@sharedState.set { birthdayYear, birthdayMonth, birthdayDay, birthday }, { silent: true }
|
||||
unless isNaN(birthday.getTime())
|
||||
forms.clearFormAlerts(@$el)
|
||||
|
||||
onSubmitSegmentCheck: (e) ->
|
||||
e.preventDefault()
|
||||
if @sharedState.get('path') is 'student'
|
||||
@trigger 'nav-forward' if @state.get('segmentCheckValid')
|
||||
else if @sharedState.get('path') is 'individual'
|
||||
if isNaN(@sharedState.get('birthday').getTime())
|
||||
forms.clearFormAlerts(@$el)
|
||||
forms.setErrorToProperty @$el, 'birthdayDay', 'Required'
|
||||
else
|
||||
age = (new Date().getTime() - @sharedState.get('birthday').getTime()) / 365.4 / 24 / 60 / 60 / 1000
|
||||
if age > 13
|
||||
@trigger 'nav-forward'
|
||||
else
|
||||
@trigger 'nav-forward', 'coppa-deny'
|
||||
|
||||
initialize: ({ @sharedState } = {}) ->
|
||||
@checkClassCodeDebounced = _.debounce @checkClassCode, 1000
|
||||
@state = new State()
|
||||
@classroom = new Classroom()
|
||||
if @sharedState.get('classCode')
|
||||
@checkClassCode(@sharedState.get('classCode'))
|
||||
@listenTo @state, 'all', -> @renderSelectors('.render')
|
||||
|
||||
checkClassCode: (classCode) ->
|
||||
@classroom.clear()
|
||||
return forms.clearFormAlerts(@$el) if classCode is ''
|
||||
|
||||
new Promise(@classroom.fetchByCode(classCode).then)
|
||||
.then =>
|
||||
@state.set { classCodeValid: true, segmentCheckValid: true }
|
||||
.catch =>
|
||||
@state.set { classCodeValid: false, segmentCheckValid: false }
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/core/create-account-modal/single-sign-on-already-exists-view'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
|
||||
module.exports = class SingleSignOnAlreadyExistsView extends ModalView
|
||||
id: 'single-sign-on-already-exists-view'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click .back-button': 'onClickBackButton'
|
||||
'click .sso-login-btn': 'onClickSsoLoginButton'
|
||||
|
||||
initialize: ({ @sharedState } = {}) ->
|
||||
|
||||
onClickBackButton: ->
|
||||
@state.set {
|
||||
ssoUsed: undefined
|
||||
ssoAttrs: undefined
|
||||
}
|
||||
-> @trigger 'nav-back'
|
||||
|
||||
onClickSsoLoginButton: ->
|
||||
options = {
|
||||
context: @
|
||||
success: -> window.location.reload()
|
||||
error: ->
|
||||
@$('#gplus-login-btn').text($.i18n.t('login.log_in')).attr('disabled', false)
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
}
|
||||
|
||||
if @sharedState.get('ssoUsed') is 'gplus'
|
||||
me.loginGPlusUser(@sharedState.get('ssoAttrs').gplusID, options)
|
||||
@$('#gplus-login-btn').text($.i18n.t('login.logging_in')).attr('disabled', true)
|
||||
else if @sharedState.get('ssoUsed') is 'facebook'
|
||||
me.loginFacebookUser(@sharedState.get('ssoAttrs').facebookID, options)
|
||||
@$('#facebook-login-btn').text($.i18n.t('login.log_in')).attr('disabled', false)
|
||||
else
|
||||
console.log "Uh oh, we didn't record which SSO they used"
|
|
@ -0,0 +1,31 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
BasicInfoView = require 'views/core/CreateAccountModal/BasicInfoView'
|
||||
template = require 'templates/core/create-account-modal/single-sign-on-confirm-view'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
|
||||
module.exports = class SingleSignOnConfirmView extends BasicInfoView
|
||||
id: 'single-sign-on-confirm-view'
|
||||
template: template
|
||||
|
||||
events: _.extend {}, BasicInfoView.prototype.events, {
|
||||
'click .back-button': 'onClickBackButton'
|
||||
}
|
||||
|
||||
initialize: ({ @sharedState } = {}) ->
|
||||
super(arguments...)
|
||||
|
||||
onClickBackButton: ->
|
||||
@sharedState.set {
|
||||
ssoUsed: undefined
|
||||
ssoAttrs: undefined
|
||||
}
|
||||
console.log @sharedState.attributes
|
||||
@trigger 'nav-back'
|
||||
|
||||
|
||||
formSchema: ->
|
||||
type: 'object'
|
||||
properties:
|
||||
name: User.schema.properties.name
|
||||
required: ['name']
|
1
app/views/core/CreateAccountModal/index.coffee
Normal file
1
app/views/core/CreateAccountModal/index.coffee
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = require 'views/core/CreateAccountModal/CreateAccountModal'
|
|
@ -103,7 +103,7 @@ UserHandler = class UserHandler extends Handler
|
|||
return callback(res: 'Facebook user login error.', code: 500) if err
|
||||
return callback(null, req, otherUser)
|
||||
)
|
||||
r = {message: 'is already used by another account', property: 'email'}
|
||||
r = {message: 'is already used by another account', property: 'email', code: 409}
|
||||
return callback({res: r, code: 409}) if otherUser
|
||||
user.set('email', req.body.email)
|
||||
callback(null, req, user)
|
||||
|
@ -118,7 +118,7 @@ UserHandler = class UserHandler extends Handler
|
|||
User.findOne({nameLower: nameLower, anonymous: false}).exec (err, otherUser) ->
|
||||
log.error "Database error setting user name: #{err}" if err
|
||||
return callback(res: 'Database error.', code: 500) if err
|
||||
r = {message: 'is already used by another account', property: 'name'}
|
||||
r = {message: 'is already used by another account', property: 'name', code: 409}
|
||||
log.info 'Another user exists' if otherUser
|
||||
return callback({res: r, code: 409}) if otherUser
|
||||
user.set('name', req.body.name)
|
||||
|
|
20
server/middleware/coppa-deny.coffee
Normal file
20
server/middleware/coppa-deny.coffee
Normal file
|
@ -0,0 +1,20 @@
|
|||
sendwithus = require '../sendwithus'
|
||||
utils = require '../lib/utils'
|
||||
errors = require '../commons/errors'
|
||||
wrap = require 'co-express'
|
||||
database = require '../commons/database'
|
||||
parse = require '../commons/parse'
|
||||
|
||||
module.exports =
|
||||
sendParentSignupInstructions: wrap (req, res, next) ->
|
||||
context =
|
||||
email_id: sendwithus.templates.coppa_deny_parent_signup
|
||||
recipient:
|
||||
address: req.body.parentEmail
|
||||
sendwithus.api.send context, (err, result) ->
|
||||
console.log err
|
||||
console.log result
|
||||
if err
|
||||
return next(new errors.InternalServerError("Error sending email. Check that it's valid and try again."))
|
||||
else
|
||||
res.status(200).send()
|
|
@ -4,6 +4,7 @@ module.exports =
|
|||
classrooms: require './classrooms'
|
||||
campaigns: require './campaigns'
|
||||
codelogs: require './codelogs'
|
||||
coppaDeny: require './coppa-deny'
|
||||
courseInstances: require './course-instances'
|
||||
courses: require './courses'
|
||||
files: require './files'
|
||||
|
|
|
@ -264,12 +264,7 @@ UserSchema.statics.unconflictName = unconflictName = (name, done) ->
|
|||
|
||||
UserSchema.methods.register = (done) ->
|
||||
@set('anonymous', false)
|
||||
if (name = @get 'name')? and name isnt ''
|
||||
unconflictName name, (err, uniqueName) =>
|
||||
return done err if err
|
||||
@set 'name', uniqueName
|
||||
done()
|
||||
else done()
|
||||
done()
|
||||
{ welcome_email_student, welcome_email_user } = sendwithus.templates
|
||||
timestamp = (new Date).getTime()
|
||||
data =
|
||||
|
|
|
@ -111,3 +111,5 @@ module.exports.setup = (app) ->
|
|||
app.get('/db/trial.request/-/users', mw.auth.checkHasPermission(['admin']), mw.trialRequests.getUsers)
|
||||
|
||||
app.get('/healthcheck', mw.healthcheck)
|
||||
|
||||
app.post('/send-parent-signup-instructions', mw.coppaDeny.sendParentSignupInstructions)
|
||||
|
|
|
@ -11,12 +11,13 @@ module.exports.api =
|
|||
send: (context, cb) ->
|
||||
log.debug('Tried to send email with context: ', JSON.stringify(context, null, ' '))
|
||||
setTimeout(cb, 10)
|
||||
|
||||
|
||||
if swuAPIKey
|
||||
module.exports.api = new sendwithusAPI swuAPIKey, debug
|
||||
|
||||
|
||||
module.exports.templates =
|
||||
parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
|
||||
coppa_deny_parent_signup: 'tem_d5fCpXS8V7jgff2sYKCinX'
|
||||
share_progress_email: 'tem_VHE3ihhGmVa3727qds9zY8'
|
||||
welcome_email_user: 'tem_z7Xvj3mtWYk6ec6aW7RwFk'
|
||||
welcome_email_student: 'tem_4WYPZNLzs5wawMF9qUJXUH'
|
||||
|
|
Loading…
Reference in a new issue