mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-04 21:01:06 -05:00
274 lines
9.3 KiB
CoffeeScript
274 lines
9.3 KiB
CoffeeScript
CocoView = require 'views/core/CocoView'
|
|
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 CocoView
|
|
id: 'basic-info-view'
|
|
template: template
|
|
|
|
events:
|
|
'change input[name="email"]': 'onChangeEmail'
|
|
'change input[name="name"]': 'onChangeName'
|
|
'change input[name="password"]': 'onChangePassword'
|
|
'click .back-button': 'onClickBackButton'
|
|
'submit form': 'onSubmitForm'
|
|
'click .use-suggested-name-link': 'onClickUseSuggestedNameLink'
|
|
'click #facebook-signup-btn': 'onClickSsoSignupButton'
|
|
'click #gplus-signup-btn': 'onClickSsoSignupButton'
|
|
|
|
initialize: ({ @signupState } = {}) ->
|
|
@state = new State {
|
|
suggestedNameText: '...'
|
|
checkEmailState: 'standby' # 'checking', 'exists', 'available'
|
|
checkEmailValue: null
|
|
checkEmailPromise: null
|
|
checkNameState: 'standby' # same
|
|
checkNameValue: null
|
|
checkNamePromise: null
|
|
error: ''
|
|
}
|
|
@listenTo @state, 'change:checkEmailState', -> @renderSelectors('.email-check')
|
|
@listenTo @state, 'change:checkNameState', -> @renderSelectors('.name-check')
|
|
@listenTo @state, 'change:error', -> @renderSelectors('.error-area')
|
|
@listenTo @signupState, 'change:facebookEnabled', -> @renderSelectors('.auth-network-logins')
|
|
@listenTo @signupState, 'change:gplusEnabled', -> @renderSelectors('.auth-network-logins')
|
|
|
|
# These values are passed along to AuthModal if the user clicks "Sign In" (handled by CreateAccountModal)
|
|
updateAuthModalInitialValues: (values) ->
|
|
@signupState.set {
|
|
authModalInitialValues: _.merge @signupState.get('authModalInitialValues'), values
|
|
}, { silent: true }
|
|
|
|
onChangeEmail: (e) ->
|
|
@updateAuthModalInitialValues { email: @$(e.currentTarget).val() }
|
|
@checkEmail()
|
|
|
|
checkEmail: ->
|
|
email = @$('[name="email"]').val()
|
|
|
|
if @signupState.get('path') isnt 'student' and (not _.isEmpty(email) and email is @state.get('checkEmailValue'))
|
|
return @state.get('checkEmailPromise')
|
|
|
|
if not (email and forms.validateEmail(email))
|
|
@state.set({
|
|
checkEmailState: 'standby'
|
|
checkEmailValue: email
|
|
checkEmailPromise: null
|
|
})
|
|
return Promise.resolve()
|
|
|
|
@state.set({
|
|
checkEmailState: 'checking'
|
|
checkEmailValue: email
|
|
|
|
checkEmailPromise: (User.checkEmailExists(email)
|
|
.then ({exists}) =>
|
|
return unless email is @$('[name="email"]').val()
|
|
if exists
|
|
@state.set('checkEmailState', 'exists')
|
|
else
|
|
@state.set('checkEmailState', 'available')
|
|
.catch (e) =>
|
|
@state.set('checkEmailState', 'standby')
|
|
throw e
|
|
)
|
|
})
|
|
return @state.get('checkEmailPromise')
|
|
|
|
onChangeName: (e) ->
|
|
@updateAuthModalInitialValues { name: @$(e.currentTarget).val() }
|
|
@checkName()
|
|
|
|
checkName: ->
|
|
name = @$('input[name="name"]').val()
|
|
|
|
if name is @state.get('checkNameValue')
|
|
return @state.get('checkNamePromise')
|
|
|
|
if not name
|
|
@state.set({
|
|
checkNameState: 'standby'
|
|
checkNameValue: name
|
|
checkNamePromise: null
|
|
})
|
|
return Promise.resolve()
|
|
|
|
@state.set({
|
|
checkNameState: 'checking'
|
|
checkNameValue: name
|
|
|
|
checkNamePromise: (User.checkNameConflicts(name)
|
|
.then ({ suggestedName, conflicts }) =>
|
|
return unless name is @$('input[name="name"]').val()
|
|
if conflicts
|
|
suggestedNameText = $.i18n.t('signup.name_taken').replace('{{suggestedName}}', suggestedName)
|
|
@state.set({ checkNameState: 'exists', suggestedNameText })
|
|
else
|
|
@state.set { checkNameState: 'available' }
|
|
.catch (error) =>
|
|
@state.set('checkNameState', 'standby')
|
|
throw error
|
|
)
|
|
})
|
|
|
|
return @state.get('checkNamePromise')
|
|
|
|
onChangePassword: (e) ->
|
|
@updateAuthModalInitialValues { password: @$(e.currentTarget).val() }
|
|
|
|
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)
|
|
|
|
if data.name and forms.validateEmail(data.name)
|
|
forms.setErrorToProperty(@$el, 'name', $.i18n.t('signup.name_is_email'))
|
|
return false
|
|
|
|
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: ['name', 'password'].concat (if @signupState.get('path') is 'student' then ['firstName', 'lastName'] else ['email'])
|
|
|
|
onClickBackButton: -> @trigger 'nav-back'
|
|
|
|
onClickUseSuggestedNameLink: (e) ->
|
|
@$('input[name="name"]').val(@state.get('suggestedName'))
|
|
forms.clearFormAlerts(@$el.find('input[name="name"]').closest('.form-group').parent())
|
|
|
|
onSubmitForm: (e) ->
|
|
@state.unset('error')
|
|
e.preventDefault()
|
|
data = forms.formToObject(e.currentTarget)
|
|
valid = @checkBasicInfo(data)
|
|
return unless valid
|
|
|
|
@displayFormSubmitting()
|
|
AbortError = new Error()
|
|
|
|
@checkEmail()
|
|
.then @checkName()
|
|
.then =>
|
|
if not (@state.get('checkEmailState') in ['available', 'standby'] and @state.get('checkNameState') is 'available')
|
|
throw AbortError
|
|
|
|
# update User
|
|
emails = _.assign({}, me.get('emails'))
|
|
emails.generalNews ?= {}
|
|
emails.generalNews.enabled = @$('#subscribe-input').is(':checked') and not _.isEmpty(@state.get('checkEmailValue'))
|
|
me.set('emails', emails)
|
|
me.set(_.pick(data, 'firstName', 'lastName'))
|
|
|
|
unless _.isNaN(@signupState.get('birthday').getTime())
|
|
me.set('birthday', @signupState.get('birthday').toISOString())
|
|
|
|
me.set(_.omit(@signupState.get('ssoAttrs') or {}, 'email', 'facebookID', 'gplusID'))
|
|
|
|
jqxhr = me.save()
|
|
if not jqxhr
|
|
console.error(me.validationError)
|
|
throw new Error('Could not save user')
|
|
|
|
return new Promise(jqxhr.then)
|
|
|
|
.then =>
|
|
# Use signup method
|
|
window.tracker?.identify()
|
|
switch @signupState.get('ssoUsed')
|
|
when 'gplus'
|
|
{ email, gplusID } = @signupState.get('ssoAttrs')
|
|
{ name } = forms.formToObject(@$el)
|
|
jqxhr = me.signupWithGPlus(name, email, gplusID)
|
|
when 'facebook'
|
|
{ email, facebookID } = @signupState.get('ssoAttrs')
|
|
{ name } = forms.formToObject(@$el)
|
|
jqxhr = me.signupWithFacebook(name, email, facebookID)
|
|
else
|
|
{ name, email, password } = forms.formToObject(@$el)
|
|
jqxhr = me.signupWithPassword(name, email, password)
|
|
|
|
return new Promise(jqxhr.then)
|
|
|
|
.then =>
|
|
{ classCode, classroom } = @signupState.attributes
|
|
if classCode and classroom
|
|
return new Promise(classroom.joinWithCode(classCode).then)
|
|
|
|
.then =>
|
|
@finishSignup()
|
|
|
|
.catch (e) =>
|
|
@displayFormStandingBy()
|
|
if e is AbortError
|
|
return
|
|
else
|
|
console.error 'BasicInfoView form submission Promise error:', e
|
|
@state.set('error', e.responseJSON?.message or 'Unknown Error')
|
|
|
|
finishSignup: ->
|
|
@trigger 'signup'
|
|
|
|
displayFormSubmitting: ->
|
|
@$('#create-account-btn').text($.i18n.t('signup.creating')).attr('disabled', true)
|
|
@$('input').attr('disabled', true)
|
|
|
|
displayFormStandingBy: ->
|
|
@$('#create-account-btn').text($.i18n.t('signup.create_account')).attr('disabled', false)
|
|
@$('input').attr('disabled', false)
|
|
|
|
onClickSsoSignupButton: (e) ->
|
|
e.preventDefault()
|
|
ssoUsed = $(e.currentTarget).data('sso-used')
|
|
handler = if ssoUsed is 'facebook' then application.facebookHandler else application.gplusHandler
|
|
handler.connect({
|
|
context: @
|
|
success: ->
|
|
handler.loadPerson({
|
|
context: @
|
|
success: (ssoAttrs) ->
|
|
@signupState.set { ssoAttrs }
|
|
{ email } = ssoAttrs
|
|
User.checkEmailExists(email).then ({exists}) =>
|
|
@signupState.set {
|
|
ssoUsed
|
|
email: ssoAttrs.email
|
|
}
|
|
if exists
|
|
@trigger 'sso-connect:already-in-use'
|
|
else
|
|
@trigger 'sso-connect:new-user'
|
|
})
|
|
})
|