mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Allow username login, tear out a bunch of related stuff in change
* Switch from auth.loginUser to User.loginPasswordUser with Promise * Remove a cascade of unused views that were using auth.loginUser: StudentLogInModal, StudentSignupModal, HourOfCodeView * Also remove auth.createUser
This commit is contained in:
parent
fe8760b122
commit
69f3ee3a5b
21 changed files with 90 additions and 549 deletions
|
@ -14,40 +14,6 @@ init = ->
|
|||
|
||||
Backbone.listenTo me, 'sync', -> Backbone.Mediator.publish('auth:me-synced', me: me)
|
||||
|
||||
module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null) ->
|
||||
user = new User(userObject)
|
||||
user.notyErrors = false
|
||||
user.save({}, {
|
||||
error: (model, jqxhr, options) ->
|
||||
error = parseServerError(jqxhr.responseText)
|
||||
property = error.property if error.property
|
||||
if jqxhr.status is 409 and property is 'name'
|
||||
anonUserObject = _.omit(userObject, 'name')
|
||||
module.exports.createUser anonUserObject, failure, nextURL
|
||||
else
|
||||
genericFailure(jqxhr)
|
||||
success: -> if nextURL then window.location.href = nextURL else window.location.reload()
|
||||
})
|
||||
|
||||
module.exports.createUserWithoutReload = (userObject, failure=backboneFailure) ->
|
||||
user = new User(userObject)
|
||||
user.save({}, {
|
||||
error: failure
|
||||
success: ->
|
||||
Backbone.Mediator.publish('created-user-without-reload')
|
||||
})
|
||||
|
||||
module.exports.loginUser = (userObject, failure=genericFailure, nextURL=null) ->
|
||||
console.log 'logging in as', userObject.email
|
||||
jqxhr = $.post('/auth/login',
|
||||
{
|
||||
username: userObject.email,
|
||||
password: userObject.password
|
||||
},
|
||||
(model) -> if nextURL then window.location.href = nextURL else window.location.reload()
|
||||
)
|
||||
jqxhr.fail(failure)
|
||||
|
||||
module.exports.logoutUser = ->
|
||||
# TODO: Refactor to use User.logout
|
||||
FB?.logout?()
|
||||
|
|
|
@ -243,6 +243,7 @@
|
|||
|
||||
login:
|
||||
sign_up: "Create Account"
|
||||
email_or_username: "Email or username"
|
||||
log_in: "Log In"
|
||||
logging_in: "Logging In"
|
||||
log_out: "Log Out"
|
||||
|
|
|
@ -275,6 +275,13 @@ module.exports = class User extends CocoModel
|
|||
options.data.facebookAccessToken = application.facebookHandler.token()
|
||||
@fetch(options)
|
||||
|
||||
loginPasswordUser: (usernameOrEmail, password, options={}) ->
|
||||
options.url = '/auth/login'
|
||||
options.type = 'POST'
|
||||
options.data ?= {}
|
||||
_.extend(options.data, { username: usernameOrEmail, password })
|
||||
@fetch(options)
|
||||
|
||||
makeCoursePrepaid: ->
|
||||
coursePrepaid = @get('coursePrepaid')
|
||||
return null unless coursePrepaid
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
#hour-of-code-view
|
||||
|
||||
hr
|
||||
border-top: 1px solid grey
|
||||
margin: 30px 20px
|
||||
|
||||
#site-content-area
|
||||
padding: 20px 300px
|
||||
|
||||
h1
|
||||
margin-bottom: 40px
|
||||
p
|
||||
margin: 20px
|
||||
|
||||
h3
|
||||
margin-top: 50px
|
||||
|
||||
ul
|
||||
margin-bottom: 50px
|
|
@ -1,7 +0,0 @@
|
|||
#student-log-in-modal
|
||||
#log-in-btn
|
||||
min-width: 30%
|
||||
margin-bottom: 10px
|
||||
|
||||
.form
|
||||
margin: 0 25%
|
|
@ -1,10 +0,0 @@
|
|||
#student-sign-up-modal
|
||||
#sign-up-btn
|
||||
min-width: 30%
|
||||
margin-bottom: 10px
|
||||
|
||||
.form
|
||||
margin: 0 25%
|
||||
|
||||
.modal-dialog
|
||||
margin-top: 0
|
|
@ -143,7 +143,7 @@
|
|||
|
||||
//- Primary auth button
|
||||
|
||||
#login-button
|
||||
#login-btn
|
||||
position: absolute
|
||||
top: 186px
|
||||
height: 70px
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
img(src="/images/pages/modal/auth/login-background.png", draggable="false").auth-modal-background
|
||||
h1(data-i18n="login.log_in")
|
||||
|
||||
div#close-modal
|
||||
#close-modal
|
||||
span.glyphicon.glyphicon-remove
|
||||
|
||||
.auth-form-content
|
||||
|
@ -12,23 +12,39 @@
|
|||
.alert.alert-success
|
||||
span(data-i18n="signup.required")
|
||||
|
||||
#unknown-error-alert.alert.alert-danger.hide(data-i18n="loading_error.unknown")
|
||||
|
||||
form.form
|
||||
.form-group
|
||||
label.control-label(for="email")
|
||||
span(data-i18n="general.email")
|
||||
label.control-label(for="username-or-email-input")
|
||||
span(data-i18n="login.email_or_username")
|
||||
| :
|
||||
.input-border
|
||||
input#email.input-large.form-control(name="email", type="email", value=formValues.email)
|
||||
input#username-or-email-input.input-large.form-control(
|
||||
name="emailOrUsername"
|
||||
value=view.previousFormInputs.email
|
||||
)
|
||||
.form-group
|
||||
div#recover-account-wrapper
|
||||
a(data-toggle="coco-modal", data-target="core/RecoverModal", data-i18n="login.forgot_password")#link-to-recover
|
||||
#recover-account-wrapper
|
||||
a#link-to-recover(
|
||||
data-toggle="coco-modal"
|
||||
data-target="core/RecoverModal"
|
||||
data-i18n="login.forgot_password"
|
||||
)
|
||||
label.control-label(for="password")
|
||||
span(data-i18n="general.password")
|
||||
| :
|
||||
.input-border
|
||||
input#password.input-large.form-control(name="password", type="password", value=formValues.password)
|
||||
input#password-input.input-large.form-control(
|
||||
name="password"
|
||||
type="password"
|
||||
value=view.previousFormInputs.password
|
||||
)
|
||||
|
||||
input.btn.btn-lg.btn-illustrated.btn-block.btn-success#login-button(value=translate("login.log_in"), type="submit")
|
||||
input#login-btn.btn.btn-lg.btn-illustrated.btn-block.btn-success(
|
||||
value=translate("login.log_in")
|
||||
type="submit"
|
||||
)
|
||||
|
||||
.wait.secret
|
||||
h3(data-i18n="login.logging_in")
|
|
@ -1,74 +0,0 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
.pull-right
|
||||
if me.isAnonymous()
|
||||
a(href="/teachers")
|
||||
span(data-i18n="courses.teachers_click")
|
||||
span !
|
||||
else
|
||||
a(href="/teachers/classes")
|
||||
span(data-i18n="courses.teachers_click")
|
||||
span !
|
||||
br
|
||||
|
||||
h1.text-center(data-i18n="courses.welcome_to_hoc")
|
||||
|
||||
#main-content
|
||||
.well.text-center
|
||||
if !me.isAnonymous()
|
||||
p
|
||||
strong.spr(data-i18n="courses.logged_in_as")
|
||||
strong= me.get('name') || me.get('email')
|
||||
|
||||
p
|
||||
span.spr(data-i18n="courses.not_you")
|
||||
a#log-out-link(data-i18n="login.logout")
|
||||
|
||||
hr
|
||||
|
||||
if !view.lastLevel
|
||||
p
|
||||
strong(data-i18n="courses.ready_to_play")
|
||||
|
||||
p
|
||||
button#start-new-game-btn.btn.btn-success.btn-lg(data-i18n="courses.start_new_game")
|
||||
else
|
||||
|
||||
p
|
||||
strong(data-i18n="courses.welcome_back")
|
||||
p
|
||||
button#continue-playing-btn.btn.btn-success.btn-lg(data-i18n="courses.continue_playing")
|
||||
p
|
||||
em.spr
|
||||
span(data-i18n="clans.last_played")
|
||||
span.spr :
|
||||
span= view.lastLevel.get('name').replace('Course :', '')
|
||||
|
||||
if me.isAnonymous()
|
||||
p
|
||||
strong(data-i18n="courses.more_options")
|
||||
p
|
||||
button#start-new-game-btn.btn.btn-default.btn-lg(data-i18n="courses.start_new_game")
|
||||
|
||||
if me.isAnonymous()
|
||||
p
|
||||
span.spr -
|
||||
span.text-uppercase(data-i18n="general.or")
|
||||
span.spl -
|
||||
|
||||
p
|
||||
button#log-in-btn.btn.btn-default.btn-lg(data-i18n="login.log_in")
|
||||
|
||||
#begin-hoc-area.hide
|
||||
h2.text-center(data-i18n="common.loading")
|
||||
.progress.progress-striped.active
|
||||
.progress-bar(style="width: 100%")
|
||||
|
||||
|
||||
h3.text-center.text-uppercase(data-i18n="courses.play_now_learn_header")
|
||||
ul
|
||||
li(data-i18n="courses.play_now_learn_1")
|
||||
li(data-i18n="courses.play_now_learn_2")
|
||||
li(data-i18n="courses.play_now_learn_3")
|
||||
li(data-i18n="courses.play_now_learn_4")
|
|
@ -1,30 +0,0 @@
|
|||
extends /templates/core/modal-base
|
||||
|
||||
block modal-header-content
|
||||
.clearfix
|
||||
|
||||
|
||||
block modal-body-content
|
||||
.text-center
|
||||
h2.modal-title(data-i18n="login.log_in")
|
||||
|
||||
form.form
|
||||
.form-group
|
||||
label.control-label(for="email")
|
||||
span(data-i18n="general.email")
|
||||
input#email.input-large.form-control(name="email", type="email")
|
||||
.form-group
|
||||
label.control-label(for="password")
|
||||
span(data-i18n="general.password")
|
||||
input#password.input-large.form-control(name="password", type="password")
|
||||
|
||||
#errors-alert.alert.alert-danger.hide
|
||||
|
||||
.text-center
|
||||
input#log-in-btn.btn.btn-default(data-i18n="[value]login.log_in", type="submit")
|
||||
p
|
||||
a#create-new-account-link(data-i18n="login.signup_switch")
|
||||
p
|
||||
a(data-toggle="coco-modal", data-target="core/RecoverModal", data-i18n="login.forgot_password")
|
||||
|
||||
block modal-footer-content
|
|
@ -1,46 +0,0 @@
|
|||
extends /templates/core/modal-base
|
||||
|
||||
block modal-header-content
|
||||
.clearfix
|
||||
|
||||
|
||||
block modal-body-content
|
||||
.text-center
|
||||
h2.modal-title(data-i18n="signup.sign_up")
|
||||
|
||||
form.form
|
||||
.form-group
|
||||
label.control-label(for="email")
|
||||
span(data-i18n="general.email")
|
||||
input#email.input-large.form-control(name="email", type="email")
|
||||
.help-block(data-i18n="courses.use_school_email")
|
||||
.form-group
|
||||
label.control-label(for="name")
|
||||
span(data-i18n="general.name")
|
||||
if me.get('name')
|
||||
input#name.input-large.form-control(name="name", type="text", value="#{me.get('name')}")
|
||||
else
|
||||
input#name.input-large.form-control(name="name", type="text", value="", placeholder="e.g. Alex W the Skater")
|
||||
.help-block(data-i18n="courses.unique_name")
|
||||
.form-group
|
||||
label.control-label(for="password")
|
||||
span(data-i18n="general.password")
|
||||
input#password.input-large.form-control(name="password", type="password")
|
||||
.help-block(data-i18n="courses.pick_something")
|
||||
.form-group
|
||||
label.control-label(for="class-code-input")
|
||||
span(data-i18n="courses.class_code")
|
||||
input#class-code-input.input-large.form-control(name="classCode", value=view.classCode)
|
||||
.help-block(data-i18n="courses.optional_ask")
|
||||
.form-group
|
||||
label.control-label(for="school-input")
|
||||
span(data-i18n="signup.school_name")
|
||||
input#school-input.input-large.form-control(name="schoolName", data-i18n="[placeholder]signup.school_name_placeholder")
|
||||
.help-block(data-i18n="courses.optional_school")
|
||||
|
||||
#errors-alert.alert.alert-danger.hide
|
||||
|
||||
.text-center
|
||||
input#sign-up-btn.btn.btn-default(data-i18n="[value]signup.sign_up", type="submit")
|
||||
|
||||
block modal-footer-content
|
|
@ -1,6 +1,5 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/core/auth'
|
||||
{loginUser, createUser, me} = require 'core/auth'
|
||||
template = require 'templates/core/auth-modal'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
application = require 'core/application'
|
||||
|
@ -12,16 +11,12 @@ module.exports = class AuthModal extends ModalView
|
|||
|
||||
events:
|
||||
'click #switch-to-signup-btn': 'onSignupInstead'
|
||||
'click #github-login-button': 'onGitHubLoginClicked'
|
||||
'submit form': 'onSubmitForm'
|
||||
'keyup #name': 'onNameChange'
|
||||
'click #gplus-login-btn': 'onClickGPlusLoginButton'
|
||||
'click #facebook-login-btn': 'onClickFacebookLoginButton'
|
||||
'click #close-modal': 'hide'
|
||||
|
||||
subscriptions:
|
||||
'errors:server-error': 'onServerError'
|
||||
|
||||
|
||||
# Initialization
|
||||
|
||||
|
@ -32,15 +27,6 @@ module.exports = class AuthModal extends ModalView
|
|||
application.gplusHandler.loadAPI({ success: => _.defer => @$('#gplus-login-btn').attr('disabled', false) })
|
||||
application.facebookHandler.loadAPI({ success: => _.defer => @$('#facebook-login-btn').attr('disabled', false) })
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.showRequiredError = @options.showRequiredError
|
||||
c.showSignupRationale = @options.showSignupRationale
|
||||
c.mode = @mode
|
||||
c.formValues = @previousFormInputs or {}
|
||||
c.me = me
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@playSound 'game-menu-open'
|
||||
|
@ -58,14 +44,28 @@ module.exports = class AuthModal extends ModalView
|
|||
@playSound 'menu-button-click'
|
||||
e.preventDefault()
|
||||
forms.clearFormAlerts(@$el)
|
||||
@$('#unknown-error-alert').addClass('hide')
|
||||
userObject = forms.formToObject @$el
|
||||
res = tv4.validateMultiple userObject, formSchema
|
||||
return forms.applyErrorsToForm(@$el, res.errors) unless res.valid
|
||||
@enableModalInProgress(@$el) # TODO: part of forms
|
||||
loginUser userObject, null, window.nextURL
|
||||
new Promise(me.loginPasswordUser(userObject.emailOrUsername, userObject.password).then)
|
||||
.then(->
|
||||
if window.nextURL then window.location.href = window.nextURL else window.location.reload()
|
||||
)
|
||||
.catch((jqxhr) =>
|
||||
showingError = false
|
||||
if jqxhr.status is 401
|
||||
errorID = jqxhr.responseJSON.errorID
|
||||
if errorID is 'not-found'
|
||||
forms.setErrorToProperty(@$el, 'emailOrUsername', $.i18n.t('loading_error.not_found'))
|
||||
showingError = true
|
||||
if errorID is 'wrong-password'
|
||||
forms.setErrorToProperty(@$el, 'password', $.i18n.t('account_settings.wrong_password'))
|
||||
showingError = true
|
||||
|
||||
onServerError: (e) -> # TODO: work error handling into a separate forms system
|
||||
@disableModalInProgress(@$el)
|
||||
if not showingError
|
||||
@$('#unknown-error-alert').removeClass('hide')
|
||||
)
|
||||
|
||||
|
||||
# Google Plus
|
||||
|
@ -136,6 +136,14 @@ module.exports = class AuthModal extends ModalView
|
|||
|
||||
formSchema = {
|
||||
type: 'object'
|
||||
properties: _.pick(User.schema.properties, 'email', 'password')
|
||||
required: ['email', 'password']
|
||||
properties: {
|
||||
emailOrUsername: {
|
||||
$or: [
|
||||
User.schema.properties.name
|
||||
User.schema.properties.email
|
||||
]
|
||||
}
|
||||
password: User.schema.properties.password
|
||||
}
|
||||
required: ['emailOrUsername', 'password']
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/core/create-account-modal'
|
||||
{loginUser, createUser, me} = require 'core/auth'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
application = require 'core/application'
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
app = require 'core/application'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
CourseInstance = require 'models/CourseInstance'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/courses/hour-of-code-view'
|
||||
utils = require 'core/utils'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
Level = require 'models/Level'
|
||||
ChooseLanguageModal = require 'views/courses/ChooseLanguageModal'
|
||||
StudentLogInModal = require 'views/courses/StudentLogInModal'
|
||||
StudentSignUpModal = require 'views/courses/StudentSignUpModal'
|
||||
auth = require 'core/auth'
|
||||
|
||||
module.exports = class HourOfCodeView extends RootView
|
||||
id: 'hour-of-code-view'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click #continue-playing-btn': 'onClickContinuePlayingButton'
|
||||
'click #start-new-game-btn': 'onClickStartNewGameButton'
|
||||
'click #log-in-btn': 'onClickLogInButton'
|
||||
'click #log-out-link': 'onClickLogOutLink'
|
||||
|
||||
initialize: ->
|
||||
@setUpHourOfCode()
|
||||
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
||||
@listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
|
||||
@courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID')
|
||||
@supermodel.loadCollection(@courseInstances, 'course_instances', { cache: false })
|
||||
|
||||
onCourseInstancesLoaded: ->
|
||||
@hourOfCodeCourseInstance = @courseInstances.findWhere({hourOfCode: true})
|
||||
if @hourOfCodeCourseInstance
|
||||
@sessions = new CocoCollection([], {
|
||||
url: "/db/course_instance/#{@hourOfCodeCourseInstance.id}/level_sessions"
|
||||
model: LevelSession
|
||||
})
|
||||
@sessions.comparator = 'created'
|
||||
@listenTo @sessions, 'sync', @onSessionsLoaded
|
||||
@supermodel.loadCollection(@sessions, 'sessions', { cache: false })
|
||||
|
||||
onSessionsLoaded: ->
|
||||
@lastSession = @sessions.last()
|
||||
if @lastSession
|
||||
@lastLevel = new Level()
|
||||
levelData = @lastSession.get('level')
|
||||
@supermodel.loadModel(@lastLevel, {
|
||||
url: "/db/level/#{levelData.original}/version/#{levelData.majorVersion}"
|
||||
data: {
|
||||
project: 'name,slug'
|
||||
}
|
||||
})
|
||||
|
||||
setUpHourOfCode: ->
|
||||
# If we haven't tracked this player as an hourOfCode player yet, and it's a new account, we do that now.
|
||||
elapsed = new Date() - new Date(me.get('dateCreated'))
|
||||
if not me.get('hourOfCode') and (elapsed < 5 * 60 * 1000 or me.get('anonymous'))
|
||||
me.set('hourOfCode', true)
|
||||
me.patch()
|
||||
$('body').append($('<img src="https://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">'))
|
||||
window.tracker?.trackEvent 'Hour of Code Begin'
|
||||
|
||||
onClickContinuePlayingButton: ->
|
||||
url = @continuePlayingLink()
|
||||
window.tracker?.trackEvent 'HoC continue playing ', category: 'HoC', label: url
|
||||
app.router.navigate(url, { trigger: true })
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@onClickStartNewGameButton() if @getQueryVariable('go') and not @lastLevel
|
||||
|
||||
onClickStartNewGameButton: ->
|
||||
# User without hour of code course instance, creates one, starts playing
|
||||
modal = new ChooseLanguageModal({
|
||||
logoutFirst: @hourOfCodeCourseInstance?
|
||||
})
|
||||
@openModalView(modal)
|
||||
@listenToOnce modal, 'set-language', @startHourOfCodePlay
|
||||
window.tracker?.trackEvent 'Start New Game', category: 'HoC', label: 'HoC Start New Game'
|
||||
|
||||
continuePlayingLink: ->
|
||||
ci = @hourOfCodeCourseInstance
|
||||
"/play/level/#{@lastLevel.get('slug')}?course=#{ci.get('courseID')}&course-instance=#{ci.id}"
|
||||
|
||||
startHourOfCodePlay: ->
|
||||
@$('#main-content').hide()
|
||||
@$('#begin-hoc-area').removeClass('hide')
|
||||
hocCourseInstance = new CourseInstance()
|
||||
hocCourseInstance.upsertForHOC({cache: false})
|
||||
@listenToOnce hocCourseInstance, 'sync', ->
|
||||
url = hocCourseInstance.firstLevelURL()
|
||||
document.location.href = url
|
||||
|
||||
onClickLogInButton: ->
|
||||
modal = new StudentLogInModal()
|
||||
@openModalView(modal)
|
||||
modal.on 'want-to-create-account', @onWantToCreateAccount, @
|
||||
window.tracker?.trackEvent 'Started Login', category: 'HoC', label: 'HoC Login'
|
||||
|
||||
onWantToCreateAccount: ->
|
||||
modal = new StudentSignUpModal()
|
||||
@openModalView(modal)
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'HoC', label: 'HoC Sign Up'
|
||||
|
||||
onClickLogOutLink: ->
|
||||
window.tracker?.trackEvent 'Log Out', category: 'HoC', label: 'HoC Log Out'
|
||||
auth.logoutUser()
|
|
@ -1,43 +0,0 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/courses/student-log-in-modal'
|
||||
auth = require 'core/auth'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
|
||||
module.exports = class StudentLogInModal extends ModalView
|
||||
id: 'student-log-in-modal'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click #log-in-btn': 'onClickLogInButton'
|
||||
'submit form': 'onSubmitForm'
|
||||
'click #create-new-account-link': 'onClickCreateNewAccountLink'
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
e.preventDefault()
|
||||
@login()
|
||||
|
||||
onClickLogInButton: ->
|
||||
@login()
|
||||
|
||||
login: ->
|
||||
# TODO: doesn't track failed login
|
||||
window.tracker?.trackEvent 'Finished Login', category: 'Courses', label: 'Courses Student Login'
|
||||
data = forms.formToObject @$el
|
||||
@enableModalInProgress(@$el)
|
||||
auth.loginUser data, (jqxhr) =>
|
||||
message = jqxhr.responseText
|
||||
if jqxhr.status is 401
|
||||
message = 'Wrong username or password. Try again!'
|
||||
# TODO: Make the server return better error message
|
||||
message = _.string.capitalize(message)
|
||||
@disableModalInProgress(@$el)
|
||||
@$('#errors-alert').text(message).removeClass('hide')
|
||||
|
||||
onClickCreateNewAccountLink: ->
|
||||
@trigger 'want-to-create-account'
|
||||
@hide?()
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
_.delay (=> @$('input:visible:first').focus()), 500
|
|
@ -1,101 +0,0 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/courses/student-sign-up-modal'
|
||||
auth = require 'core/auth'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
Classroom = require 'models/Classroom'
|
||||
utils = require 'core/utils'
|
||||
|
||||
module.exports = class StudentSignUpModal extends ModalView
|
||||
id: 'student-sign-up-modal'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'submit form': 'onSubmitForm'
|
||||
'click #skip-link': 'onClickSkipLink'
|
||||
|
||||
initialize: (options) ->
|
||||
options ?= {}
|
||||
@willPlay = options.willPlay
|
||||
@classCode = utils.getQueryVariable('_cc') or ''
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
_.delay (=> @$('input:visible:first').focus()), 500
|
||||
|
||||
onClickSkipLink: ->
|
||||
@trigger 'click-skip-link' # defer to view that opened this modal
|
||||
@hide?()
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
e.preventDefault()
|
||||
@signupClassroomPrecheck()
|
||||
|
||||
emailCheck: ->
|
||||
email = @$('#email').val()
|
||||
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
|
||||
unless filter.test(email)
|
||||
@$('#errors-alert').text($.i18n.t('share_progress_modal.email_invalid')).removeClass('hide')
|
||||
return false
|
||||
return true
|
||||
|
||||
signupClassroomPrecheck: ->
|
||||
if not _.all([@$('#email').val(), @$('#password').val(), @$('#name').val()])
|
||||
@$('#errors-alert').text('Enter email, username and password').removeClass('hide')
|
||||
return
|
||||
classCode = @$('#class-code-input').val()
|
||||
if not classCode
|
||||
return @signup()
|
||||
classroom = new Classroom()
|
||||
classroom.fetch({ url: '/db/classroom?code='+classCode })
|
||||
classroom.once 'sync', @signup, @
|
||||
classroom.once 'error', @onClassroomFetchError, @
|
||||
@enableModalInProgress(@$el)
|
||||
|
||||
onClassroomFetchError: ->
|
||||
@disableModalInProgress(@$el)
|
||||
@$('#errors-alert').text('Classroom code could not be found').removeClass('hide')
|
||||
|
||||
signup: ->
|
||||
return unless @emailCheck()
|
||||
# TODO: consolidate with AuthModal logic, or make user creation process less magical, more RESTful
|
||||
data = forms.formToObject @$el
|
||||
delete data.classCode
|
||||
for key, val of me.attributes when key in ['preferredLanguage', 'testGroupNumber', 'dateCreated', 'wizardColor1', 'name', 'music', 'volume', 'emails', 'schoolName']
|
||||
data[key] ?= val
|
||||
Backbone.Mediator.publish "auth:signed-up", {}
|
||||
data.emails ?= {}
|
||||
data.emails.generalNews ?= {}
|
||||
data.emails.generalNews.enabled = false
|
||||
# TODO: Doesn't handle failed user creation. Double posts when placed in onCreateUserSuccess.
|
||||
window.tracker?.trackEvent 'Finished Student Signup', category: 'Courses', label: 'Courses Student Signup'
|
||||
@enableModalInProgress(@$el)
|
||||
user = new User(data)
|
||||
user.notyErrors = false
|
||||
user.save({}, {
|
||||
validate: false # make server deal with everything
|
||||
error: @onCreateUserError
|
||||
success: @onCreateUserSuccess
|
||||
})
|
||||
|
||||
onCreateUserError: (model, jqxhr) =>
|
||||
# really need to make our server errors uniform
|
||||
if jqxhr.responseJSON
|
||||
error = jqxhr.responseJSON
|
||||
error = error[0] if _.isArray(error)
|
||||
message = _.filter([error.property, error.message]).join(' ')
|
||||
else
|
||||
message = jqxhr.responseText
|
||||
@disableModalInProgress(@$el)
|
||||
@$('#errors-alert').text(message).removeClass('hide')
|
||||
|
||||
onCreateUserSuccess: =>
|
||||
classCode = @$('#class-code-input').val()
|
||||
if classCode
|
||||
url = "/courses?_cc="+classCode
|
||||
document.location.href = url
|
||||
# This was a terrible hack to make navigating trigger when just adding query params
|
||||
# application.router.navigate('/thisisahack')
|
||||
# application.router.navigate(url, { trigger: true })
|
||||
else
|
||||
window.location.reload()
|
|
@ -17,16 +17,17 @@ module.exports.setup = ->
|
|||
authentication.use(new LocalStrategy(
|
||||
(username, password, done) ->
|
||||
|
||||
# kind of a hacky way to make it possible for iPads to 'log in' with their unique device id
|
||||
if username.length is 36 and '@' not in username # must be an identifier for vendor
|
||||
q = { iosIdentifierForVendor: username }
|
||||
else
|
||||
q = { emailLower: username.toLowerCase() }
|
||||
# TODO: Add special iPad login endpoint. There was some logic here for the old, hacky method,
|
||||
# but was removed for username login
|
||||
q = { $or: [
|
||||
{ emailLower: username.toLowerCase() }
|
||||
{ slug: _.str.slugify(username) }
|
||||
]}
|
||||
|
||||
User.findOne(q).exec((err, user) ->
|
||||
return done(err) if err
|
||||
if not user
|
||||
return done(new errors.Unauthorized('not found', { property: 'email' }))
|
||||
return done(new errors.Unauthorized('not found', { errorID: 'not-found' }))
|
||||
passwordReset = (user.get('passwordReset') or '').toLowerCase()
|
||||
if passwordReset and password.toLowerCase() is passwordReset
|
||||
User.update {_id: user.get('_id')}, {$unset: {passwordReset: ''}}, {}, ->
|
||||
|
@ -34,7 +35,7 @@ module.exports.setup = ->
|
|||
|
||||
hash = User.hashPassword(password)
|
||||
unless user.get('passwordHash') is hash
|
||||
return done(new errors.Unauthorized('is wrong', { property: 'password' }))
|
||||
return done(new errors.Unauthorized('is wrong', { errorID: 'wrong-password' }))
|
||||
return done(null, user)
|
||||
)
|
||||
))
|
||||
|
|
|
@ -92,6 +92,10 @@ errorResponseSchema = {
|
|||
type: 'string'
|
||||
description: 'Provided for /auth/name.' # TODO: refactor out
|
||||
}
|
||||
errorID: {
|
||||
type: 'string'
|
||||
description: 'Error id to be used by the client to handle specific errors'
|
||||
}
|
||||
}
|
||||
}
|
||||
errorProps = _.keys(errorResponseSchema.properties)
|
||||
|
|
|
@ -24,18 +24,6 @@ describe 'POST /auth/login', ->
|
|||
yield utils.becomeAnonymous()
|
||||
done()
|
||||
|
||||
it 'allows logging in by iosIdentifierForVendor', utils.wrap (done) ->
|
||||
yield utils.initUser({
|
||||
'iosIdentifierForVendor': '012345678901234567890123456789012345'
|
||||
'password': '12345'
|
||||
})
|
||||
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
||||
username: '012345678901234567890123456789012345'
|
||||
password: '12345'
|
||||
}})
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
it 'returns 401 when the user does not exist', utils.wrap (done) ->
|
||||
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
||||
username: 'some@email.com'
|
||||
|
@ -56,6 +44,19 @@ describe 'POST /auth/login', ->
|
|||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
it 'allows login by username', utils.wrap (done) ->
|
||||
yield utils.initUser({
|
||||
name: 'Some name that will be lowercased...'
|
||||
'email': 'some@email.com'
|
||||
'password': '12345'
|
||||
})
|
||||
[res, body] = yield request.postAsync({uri: urlLogin, json: {
|
||||
username: 'Some name that will be lowercased...'
|
||||
password: '12345'
|
||||
}})
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
it 'rejects wrong passwords', utils.wrap (done) ->
|
||||
yield utils.initUser({
|
||||
'email': 'some@email.com'
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
HeroSelectModal = require 'views/courses/HeroSelectModal'
|
||||
auth = require 'core/auth'
|
||||
factories = require 'test/app/factories'
|
||||
|
||||
describe 'HeroSelectModal', ->
|
||||
|
@ -14,7 +13,6 @@ describe 'HeroSelectModal', ->
|
|||
|
||||
beforeEach (done) ->
|
||||
window.me = user = factories.makeUser({ heroConfig: { thangType: hero1.get('original') } })
|
||||
auth.loginUser(user.attributes)
|
||||
modal = new HeroSelectModal({ currentHeroID: hero1.id })
|
||||
modal.heroes.fakeRequests[0].respondWith({ status: 200, responseText: heroesResponse })
|
||||
jasmine.demoModal(modal)
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
StudentLoginModal = require 'views/courses/StudentLogInModal'
|
||||
RecoverModal = require 'views/core/RecoverModal'
|
||||
auth = require 'core/auth'
|
||||
|
||||
describe 'StudentLoginModal', ->
|
||||
|
||||
modal = null
|
||||
|
||||
beforeEach ->
|
||||
modal = new StudentLoginModal()
|
||||
modal.render()
|
||||
|
||||
afterEach ->
|
||||
modal.stopListening()
|
||||
|
||||
it 'displays an error when you submit an empty login form', ->
|
||||
spyOn(auth, 'loginUser').and.callFake (data, callback) ->
|
||||
callback { status: 401, responseText: "Unauthorized" }
|
||||
modal.$el.find('#log-in-btn').click()
|
||||
expect(modal.$el.html()).toContain('Wrong username or password. Try again!')
|
||||
|
||||
jasmine.demoModal(modal)
|
Loading…
Reference in a new issue