mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Refactor CreateAccountModal out of AuthModal, add class code to signup
This commit is contained in:
parent
910953d383
commit
ebc98f988f
56 changed files with 1292 additions and 451 deletions
Binary file not shown.
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 93 KiB |
|
@ -1,6 +1,3 @@
|
|||
gplusClientID = '800329290710-j9sivplv2gpcdgkrsis9rff3o417mlfa.apps.googleusercontent.com'
|
||||
# TODO: Move to GPlusHandler
|
||||
|
||||
go = (path) -> -> @routeDirectly path, arguments
|
||||
|
||||
module.exports = class CocoRouter extends Backbone.Router
|
||||
|
@ -196,7 +193,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
initializeSocialMediaServices: ->
|
||||
return if application.testing or application.demoing
|
||||
require('core/services/facebook')()
|
||||
require('core/services/google')()
|
||||
application.gplusHandler.loadAPI()
|
||||
require('core/services/twitter')()
|
||||
|
||||
renderLoginButtons: =>
|
||||
|
@ -204,22 +201,8 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
$('.share-buttons, .partner-badges').addClass('fade-in').delay(10000).removeClass('fade-in', 5000)
|
||||
setTimeout(FB.XFBML.parse, 10) if FB?.XFBML?.parse # Handles FB login and Like
|
||||
twttr?.widgets?.load?()
|
||||
|
||||
return unless gapi?.plusone?
|
||||
gapi.plusone.go?() # Handles +1 button
|
||||
for gplusButton in $('.gplus-login-button')
|
||||
params = {
|
||||
callback: 'signinCallback',
|
||||
clientid: gplusClientID,
|
||||
cookiepolicy: 'single_host_origin',
|
||||
scope: 'https://www.googleapis.com/auth/plus.login email',
|
||||
height: 'short',
|
||||
}
|
||||
if gapi.signin?.render
|
||||
gapi.signin.render(gplusButton, params)
|
||||
else
|
||||
console.warn 'Didn\'t have gapi.signin to render G+ login button. (DoNotTrackMe extension?)'
|
||||
|
||||
application.gplusHandler.renderLoginButtons()
|
||||
|
||||
activateTab: ->
|
||||
base = _.string.words(document.location.pathname[1..], '/')[0]
|
||||
$("ul.nav li.#{base}").addClass('active')
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
module.exports = initializeGoogle = ->
|
||||
window.onGPlusLoaded = ->
|
||||
Backbone.Mediator.publish 'auth:gplus-api-loaded', {}
|
||||
return
|
||||
window.signinCallback = (authResult) ->
|
||||
Backbone.Mediator.publish 'auth:logged-in-with-gplus', authResult if authResult.access_token
|
||||
return
|
||||
(->
|
||||
po = document.createElement('script')
|
||||
po.type = 'text/javascript'
|
||||
po.async = true
|
||||
po.src = 'https://apis.google.com/js/client:platform.js?onload=onGPlusLoaded'
|
||||
s = document.getElementsByTagName('script')[0]
|
||||
s.parentNode.insertBefore po, s
|
||||
return
|
||||
)()
|
|
@ -17,6 +17,14 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
|
|||
'auth:logged-in-with-facebook': 'onFacebookLoggedIn'
|
||||
|
||||
loggedIn: false
|
||||
|
||||
token: -> @authResponse?.accessToken
|
||||
|
||||
fakeFacebookLogin: ->
|
||||
@onFacebookLoggedIn({
|
||||
response:
|
||||
authResponse: { accessToken: '1234' }
|
||||
})
|
||||
|
||||
onFacebookLoggedIn: (e) ->
|
||||
# user is logged in also when the page first loads, so check to see
|
||||
|
@ -28,46 +36,21 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
|
|||
@loggedIn = true
|
||||
break
|
||||
|
||||
if @waitingForLogin and @loggedIn
|
||||
@fetchMeForLogin()
|
||||
@trigger 'logged-into-facebook'
|
||||
|
||||
loginThroughFacebook: ->
|
||||
if @loggedIn
|
||||
@fetchMeForLogin()
|
||||
return true
|
||||
else
|
||||
FB.login ((response) ->
|
||||
console.log 'Received FB login response:', response
|
||||
), scope: 'email'
|
||||
@waitingForLogin = true
|
||||
|
||||
fetchMeForLogin: ->
|
||||
FB.api('/me', {fields: 'email,last_name,first_name,gender'}, @onReceiveMeInfo)
|
||||
|
||||
onReceiveMeInfo: (r) =>
|
||||
console.log "Got Facebook user info:", r
|
||||
unless r.email
|
||||
console.error('could not get data, since no email provided')
|
||||
return
|
||||
|
||||
oldEmail = me.get('email')
|
||||
me.set('firstName', r.first_name) if r.first_name
|
||||
me.set('lastName', r.last_name) if r.last_name
|
||||
me.set('gender', r.gender) if r.gender
|
||||
me.set('email', r.email) if r.email
|
||||
me.set('facebookID', r.id) if r.id
|
||||
|
||||
Backbone.Mediator.publish 'auth:logging-in-with-facebook', {}
|
||||
window.tracker?.identify()
|
||||
beforeID = me.id
|
||||
me.patch({
|
||||
error: backboneFailure,
|
||||
url: "/db/user/#{me.id}?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
|
||||
success: (model) ->
|
||||
window.tracker?.trackEvent 'Facebook Login', category: "Signup", label: 'Facebook'
|
||||
if model.id is beforeID
|
||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'Facebook'
|
||||
window.location.reload() if model.get('email') isnt oldEmail
|
||||
})
|
||||
|
||||
destroy: ->
|
||||
super()
|
||||
loadPerson: ->
|
||||
FB.api '/me', {fields: 'email,last_name,first_name,gender'}, (person) =>
|
||||
attrs = {}
|
||||
for fbProp, userProp of userPropsToSave
|
||||
value = person[fbProp]
|
||||
if value
|
||||
attrs[userProp] = value
|
||||
@trigger 'person-loaded', attrs
|
||||
|
|
|
@ -20,13 +20,27 @@ scope = 'https://www.googleapis.com/auth/plus.login email'
|
|||
module.exports = GPlusHandler = class GPlusHandler extends CocoClass
|
||||
constructor: ->
|
||||
@accessToken = storage.load GPLUS_TOKEN_KEY, false
|
||||
window.onGPlusLogin = _.bind(@onGPlusLogin, @)
|
||||
super()
|
||||
|
||||
subscriptions:
|
||||
'auth:logged-in-with-gplus':'onGPlusLogin'
|
||||
'auth:gplus-api-loaded':'onGPlusLoaded'
|
||||
token: -> @accessToken?.access_token
|
||||
|
||||
onGPlusLoaded: ->
|
||||
loadAPI: ->
|
||||
return if @loadedAPI
|
||||
@loadedAPI = true
|
||||
(=>
|
||||
po = document.createElement('script')
|
||||
po.type = 'text/javascript'
|
||||
po.async = true
|
||||
po.src = 'https://apis.google.com/js/client:platform.js?onload=onGPlusLoaded'
|
||||
s = document.getElementsByTagName('script')[0]
|
||||
s.parentNode.insertBefore po, s
|
||||
window.onGPlusLoaded = _.bind(@onLoadAPI, @)
|
||||
return
|
||||
)()
|
||||
|
||||
onLoadAPI: ->
|
||||
Backbone.Mediator.publish 'auth:gplus-api-loaded', {}
|
||||
session_state = null
|
||||
if @accessToken and me.get('gplusID')
|
||||
# We need to check the current state, given our access token
|
||||
|
@ -39,6 +53,26 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
|
|||
func = => @trigger 'checked-state'
|
||||
setTimeout func, 1
|
||||
|
||||
renderLoginButtons: ->
|
||||
return false unless gapi?.plusone?
|
||||
gapi.plusone.go?() # Handles +1 button
|
||||
if not gapi.signin?.render
|
||||
console.warn 'Didn\'t have gapi.signin to render G+ login button. (DoNotTrackMe extension?)'
|
||||
return
|
||||
|
||||
for gplusButton in $('.gplus-login-button')
|
||||
params = {
|
||||
callback: 'onGPlusLogin',
|
||||
clientid: clientID,
|
||||
cookiepolicy: 'single_host_origin',
|
||||
scope: 'https://www.googleapis.com/auth/plus.login email',
|
||||
height: 'short',
|
||||
}
|
||||
if gapi.signin?.render
|
||||
gapi.signin.render(gplusButton, params)
|
||||
|
||||
@trigger 'render-login-buttons'
|
||||
|
||||
onCheckedSessionState: (@loggedIn) =>
|
||||
@trigger 'checked-state'
|
||||
|
||||
|
@ -47,9 +81,16 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
|
|||
'client_id' : clientID
|
||||
'scope' : scope
|
||||
gapi.auth.authorize params, @onGPlusLogin
|
||||
|
||||
fakeGPlusLogin: ->
|
||||
@onGPlusLogin({
|
||||
access_token: '1234'
|
||||
})
|
||||
|
||||
onGPlusLogin: (e) =>
|
||||
onGPlusLogin: (e) ->
|
||||
return unless e.access_token
|
||||
@loggedIn = true
|
||||
Backbone.Mediator.publish 'auth:logged-in-with-gplus', e
|
||||
try
|
||||
# Without removing this, we sometimes get a cross-domain error
|
||||
d = _.omit(e, 'g-oauth-window')
|
||||
|
@ -58,55 +99,29 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
|
|||
console.error 'Unable to save G+ token key', e
|
||||
@accessToken = e
|
||||
@trigger 'logged-in'
|
||||
@trigger 'logged-into-google'
|
||||
|
||||
loginCodeCombat: ->
|
||||
loadPerson: (options={}) ->
|
||||
@reloadOnLogin = options.reloadOnLogin
|
||||
# email and profile data loaded separately
|
||||
gapi.client.load('plus', 'v1', =>
|
||||
gapi.client.plus.people.get({userId: 'me'}).execute(@onPersonReceived))
|
||||
|
||||
onPersonReceived: (r) =>
|
||||
attrs = {}
|
||||
for gpProp, userProp of userPropsToSave
|
||||
keys = gpProp.split('.')
|
||||
value = r
|
||||
for key in keys
|
||||
value = value[key]
|
||||
if value and not me.get(userProp)
|
||||
me.set(userProp, value)
|
||||
if value
|
||||
attrs[userProp] = value
|
||||
|
||||
newEmail = r.emails?.length and r.emails[0] isnt me.get('email')
|
||||
return unless newEmail or me.get('anonymous', true)
|
||||
me.set('email', r.emails[0].value)
|
||||
@trigger 'person-loaded'
|
||||
@save()
|
||||
|
||||
save: =>
|
||||
console.debug 'Email, gplusID:', me.get('email'), me.get('gplusID')
|
||||
return unless me.get('email') and me.get('gplusID')
|
||||
|
||||
Backbone.Mediator.publish 'auth:logging-in-with-gplus', {}
|
||||
gplusID = me.get('gplusID')
|
||||
window.tracker?.identify()
|
||||
patch = {}
|
||||
patch[key] = me.get(key) for gplusKey, key of userPropsToSave
|
||||
patch._id = beforeID = me.id
|
||||
patch.email = me.get('email')
|
||||
wasAnonymous = me.get('anonymous')
|
||||
@trigger 'logging-into-codecombat'
|
||||
console.debug('Logging into GPlus.')
|
||||
me.save(patch, {
|
||||
patch: true
|
||||
type: 'PUT'
|
||||
error: ->
|
||||
console.warn('Logging into GPlus fail.', arguments)
|
||||
backboneFailure(arguments...)
|
||||
url: "/db/user?gplusID=#{gplusID}&gplusAccessToken=#{@accessToken.access_token}"
|
||||
success: (model) ->
|
||||
console.info('GPLus login success!')
|
||||
window.tracker?.trackEvent 'Google Login', category: "Signup"
|
||||
if model.id is beforeID
|
||||
window.tracker?.trackEvent 'Finished Signup', label: 'GPlus'
|
||||
window.location.reload() if wasAnonymous and not model.get('anonymous')
|
||||
})
|
||||
if r.emails?.length
|
||||
attrs.email = r.emails[0].value
|
||||
@trigger 'person-loaded', attrs
|
||||
|
||||
loadFriends: (friendsCallback) ->
|
||||
return friendsCallback() unless @loggedIn
|
||||
|
|
|
@ -189,6 +189,14 @@
|
|||
school_name: "School Name and City"
|
||||
optional: "optional"
|
||||
school_name_placeholder: "Example High School, Springfield, IL"
|
||||
or_sign_up_with: "or sign up 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+!"
|
||||
connected_facebook_header: "You've successfully connected with Facebook!"
|
||||
connected_facebook_p: "Finish signing up so you can log in with your Facebook account."
|
||||
facebook_exists: "You already have an account associated with Facebook!"
|
||||
hey_students: "Students, enter the class code from your teacher."
|
||||
|
||||
recover:
|
||||
recover_account_title: "Recover Account"
|
||||
|
|
|
@ -197,6 +197,34 @@ module.exports = class User extends CocoModel
|
|||
options.type = 'POST'
|
||||
@fetch(options)
|
||||
|
||||
fetchGPlusUser: (gplusID, options={}) ->
|
||||
options.data ?= {}
|
||||
options.data.gplusID = gplusID
|
||||
options.data.gplusAccessToken = application.gplusHandler.token()
|
||||
@fetch(options)
|
||||
|
||||
loginGPlusUser: (gplusID, options={}) ->
|
||||
options.url = '/auth/login-gplus'
|
||||
options.type = 'POST'
|
||||
options.data ?= {}
|
||||
options.data.gplusID = gplusID
|
||||
options.data.gplusAccessToken = application.gplusHandler.token()
|
||||
@fetch(options)
|
||||
|
||||
fetchFacebookUser: (facebookID, options={}) ->
|
||||
options.data ?= {}
|
||||
options.data.facebookID = facebookID
|
||||
options.data.facebookAccessToken = application.facebookHandler.token()
|
||||
@fetch(options)
|
||||
|
||||
loginFacebookUser: (facebookID, options={}) ->
|
||||
options.url = '/auth/login-facebook'
|
||||
options.type = 'POST'
|
||||
options.data ?= {}
|
||||
options.data.facebookID = facebookID
|
||||
options.data.facebookAccessToken = application.facebookHandler.token()
|
||||
@fetch(options)
|
||||
|
||||
tiersByLevel = [-1, 0, 0.05, 0.14, 0.18, 0.32, 0.41, 0.5, 0.64, 0.82, 0.91, 1.04, 1.22, 1.35, 1.48, 1.65, 1.78, 1.96, 2.1, 2.24, 2.38, 2.55, 2.69, 2.86, 3.03, 3.16, 3.29, 3.42, 3.58, 3.74, 3.89, 4.04, 4.19, 4.32, 4.47, 4.64, 4.79, 4.96,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15
|
||||
]
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
.modal-dialog
|
||||
padding: 0
|
||||
width: 666px
|
||||
height: 694px
|
||||
height: 582px
|
||||
|
||||
|
||||
//- Background
|
||||
|
@ -143,9 +143,9 @@
|
|||
|
||||
//- Primary auth button
|
||||
|
||||
#login-button, #signup-button
|
||||
#login-button
|
||||
position: absolute
|
||||
top: 298px
|
||||
top: 186px
|
||||
height: 70px
|
||||
font-size: 32px
|
||||
line-height: 42px
|
||||
|
@ -155,7 +155,7 @@
|
|||
|
||||
.auth-network-logins
|
||||
position: absolute
|
||||
top: 470px
|
||||
top: 358px
|
||||
width: 580px
|
||||
left: 48px
|
||||
padding-left: 29px
|
||||
|
@ -216,7 +216,7 @@
|
|||
height: 139px
|
||||
padding: 23px 23px 23px 168px
|
||||
position: absolute
|
||||
top: 520px
|
||||
top: 408px
|
||||
|
||||
.switch-explanation
|
||||
margin: 25px 10px 0 0
|
||||
|
@ -236,20 +236,6 @@
|
|||
height: 70px
|
||||
line-height: 40px
|
||||
|
||||
//- Login-specific styling
|
||||
|
||||
&.login
|
||||
.modal-dialog
|
||||
height: 582px
|
||||
|
||||
#login-button, #signup-button
|
||||
top: 186px
|
||||
|
||||
.auth-network-logins
|
||||
top: 358px
|
||||
|
||||
.extra-pane
|
||||
top: 408px
|
||||
|
||||
.ie10 #auth-modal .auth-network-logins .btn.btn-lg.network-login .network-logo, .lt-ie10 #auth-modal .auth-network-logins .btn.btn-lg.network-login .network-logo
|
||||
left: 15px
|
||||
|
|
246
app/styles/modal/create-account-modal.sass
Normal file
246
app/styles/modal/create-account-modal.sass
Normal file
|
@ -0,0 +1,246 @@
|
|||
@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
|
||||
background-color: rgb(239, 232, 216)
|
||||
border: 2px solid rgb(26, 21, 18)
|
||||
border-radius: 4px
|
||||
|
||||
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
|
|
@ -11,7 +11,7 @@ block content
|
|||
|
||||
if me.isAdmin() || !newModelsAdminOnly
|
||||
if me.get('anonymous')
|
||||
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="core/AuthModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Something
|
||||
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="core/CreateAccountModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Something
|
||||
else
|
||||
a.btn.btn-primary.open-modal-button#new-model-button(data-i18n="#{currentNew}") Create a New Something
|
||||
input#search(data-i18n="[placeholder]#{currentSearch}")
|
||||
|
|
|
@ -1,87 +1,38 @@
|
|||
.modal-dialog
|
||||
.modal-content
|
||||
|
||||
if mode === 'login'
|
||||
img(src="/images/pages/modal/auth/login-background.png", draggable="false").auth-modal-background
|
||||
else
|
||||
img(src="/images/pages/modal/auth/signup-background.png", draggable="false").auth-modal-background
|
||||
|
||||
if mode === 'login'
|
||||
h1(data-i18n="login.log_in") Log In
|
||||
if mode === 'signup'
|
||||
h1(data-i18n="login.sign_up") Create Account
|
||||
img(src="/images/pages/modal/auth/login-background.png", draggable="false").auth-modal-background
|
||||
h1(data-i18n="login.log_in")
|
||||
|
||||
div#close-modal
|
||||
span.glyphicon.glyphicon-remove
|
||||
|
||||
|
||||
.auth-form-content
|
||||
|
||||
if showRequiredError
|
||||
.alert.alert-success
|
||||
span(data-i18n="signup.required") You need to log in before you can that way.
|
||||
else if mode === 'signup' && showSignupRationale
|
||||
.alert.alert-info
|
||||
span(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!
|
||||
span(data-i18n="signup.required")
|
||||
|
||||
form.form
|
||||
.form-group
|
||||
label.control-label(for="email")
|
||||
span(data-i18n="general.email") Email
|
||||
span(data-i18n="general.email")
|
||||
| :
|
||||
.input-border
|
||||
input#email.input-large.form-control(name="email", type="email", value=formValues.email)
|
||||
.form-group
|
||||
if mode === 'login'
|
||||
div#recover-account-wrapper
|
||||
a(data-toggle="coco-modal", data-target="core/RecoverModal", data-i18n="login.forgot_password")#link-to-recover Forgot your password?
|
||||
div#recover-account-wrapper
|
||||
a(data-toggle="coco-modal", data-target="core/RecoverModal", data-i18n="login.forgot_password")#link-to-recover
|
||||
label.control-label(for="password")
|
||||
span(data-i18n="general.password") Password
|
||||
span(data-i18n="general.password")
|
||||
| :
|
||||
.input-border
|
||||
input#password.input-large.form-control(name="password", type="password", value=formValues.password)
|
||||
|
||||
if mode === 'signup'
|
||||
.row
|
||||
.col-md-6
|
||||
.form-group
|
||||
label.control-label(for="name")
|
||||
span(data-i18n="general.name") Name
|
||||
| :
|
||||
.input-border
|
||||
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")
|
||||
.col-md-6
|
||||
.form-group
|
||||
label.control-label(for="school-input")
|
||||
span.spr(data-i18n="signup.school_name") School Name and City
|
||||
em.optional-note
|
||||
| (
|
||||
span(data-i18n="signup.optional") optional
|
||||
| ):
|
||||
.input-border
|
||||
input#school-input.input-large.form-control(name="schoolName", data-i18n="[placeholder]signup.school_name_placeholder", value=formValues.schoolName || '')
|
||||
.form-group.checkbox
|
||||
label.control-label(for="subscribe")
|
||||
.input-border
|
||||
input#subscribe(name="subscribe", type="checkbox", checked='checked')
|
||||
span.custom-checkbox
|
||||
.glyphicon.glyphicon-ok
|
||||
span(data-i18n="signup.email_announcements") Receive announcements by email
|
||||
|
||||
if mode === 'login'
|
||||
input.btn.btn-lg.btn-illustrated.btn-block.btn-success#login-button(value=translate("login.log_in"), type="submit")
|
||||
else if mode === 'signup'
|
||||
input.btn.btn-lg.btn-illustrated.btn-block.btn-success#signup-button(value=translate("signup.sign_up"), type="submit")
|
||||
input.btn.btn-lg.btn-illustrated.btn-block.btn-success#login-button(value=translate("login.log_in"), type="submit")
|
||||
|
||||
.wait.secret
|
||||
if mode === 'login'
|
||||
h3(data-i18n="login.logging_in") Logging In
|
||||
if mode === 'signup'
|
||||
h3(data-i18n="signup.creating") Creating Account...
|
||||
|
||||
h3(data-i18n="login.logging_in")
|
||||
|
||||
.auth-network-logins
|
||||
// GitHub login complete, but the button does not fit in with the design yet. Hidden for now
|
||||
//div.network-login
|
||||
|
@ -90,17 +41,13 @@
|
|||
// | GitHub
|
||||
#facebook-login-btn.btn.btn-primary.btn-lg.btn-illustrated.network-login
|
||||
img.network-logo(src="/images/pages/community/logo_facebook.png", draggable="false")
|
||||
span.sign-in-blurb(data-i18n="login.sign_in_with_facebook") Sign in with Facebook
|
||||
.btn.btn-danger.btn-lg.btn-illustrated.network-login
|
||||
span.sign-in-blurb(data-i18n="login.sign_in_with_facebook")
|
||||
#gplus-login-btn.btn.btn-danger.btn-lg.btn-illustrated.network-login(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") Sign in with G+
|
||||
span.sign-in-blurb(data-i18n="login.sign_in_with_gplus")
|
||||
.gplus-login-wrapper
|
||||
.gplus-login-button#gplus-login-button
|
||||
.gplus-login-button
|
||||
|
||||
.extra-pane
|
||||
if mode === 'login'
|
||||
.switch-explanation(data-i18n="login.signup_switch") Want to create an account?
|
||||
.btn.btn-lg.btn-illustrated.btn-warning#switch-to-signup-button(data-i18n="login.sign_up") Create Account
|
||||
else if mode === 'signup'
|
||||
.switch-explanation(data-i18n="signup.login_switch") Already have an account?
|
||||
.btn.btn-lg.btn-illustrated.btn-warning#switch-to-login-button(data-i18n="login.log_in")
|
||||
.switch-explanation(data-i18n="login.signup_switch")
|
||||
.btn.btn-lg.btn-illustrated.btn-warning#switch-to-signup-btn(data-i18n="login.sign_up")
|
||||
|
|
108
app/templates/core/create-account-modal.jade
Normal file
108
app/templates/core/create-account-modal.jade
Normal file
|
@ -0,0 +1,108 @@
|
|||
.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
|
||||
|
||||
.auth-form-content
|
||||
if view.options.showSignupRationale
|
||||
.alert.alert-info
|
||||
span(data-i18n="play_level.victory_sign_up_poke")
|
||||
|
||||
#email-password-row.row
|
||||
.col-md-6
|
||||
form
|
||||
.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")
|
||||
#facebook-signup-btn.btn.btn-primary.btn-lg.btn-illustrated.network-login
|
||||
img.network-logo(src="/images/pages/community/logo_facebook.png", draggable="false")
|
||||
span.sign-in-blurb(data-i18n="login.sign_in_with_facebook")
|
||||
#gplus-signup-btn.btn.btn-danger.btn-lg.btn-illustrated.network-login(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")
|
||||
|
||||
form
|
||||
.row
|
||||
.col-md-6
|
||||
.form-group
|
||||
label.control-label(for="name")
|
||||
span(data-i18n="general.name") Name
|
||||
| :
|
||||
.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="school-input")
|
||||
span.spr(data-i18n="signup.school_name")
|
||||
em.optional-note
|
||||
| (
|
||||
span(data-i18n="signup.optional")
|
||||
| ):
|
||||
.input-border
|
||||
input#school-input.input-large.form-control(name="schoolName", data-i18n="[placeholder]signup.school_name_placeholder", value=view.previousFormInputs.schoolName || '', tabindex=4)
|
||||
|
||||
.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#school-input.input-large.form-control(name="classCode", value=view.previousFormInputs.classCode || '', tabindex=5)
|
||||
|
||||
.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")
|
|
@ -119,7 +119,7 @@ else
|
|||
//if me.isAdmin()
|
||||
// button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
|
||||
if me.get('anonymous', true)
|
||||
button.btn.settings(data-toggle='coco-modal', data-target='core/AuthModal', data-i18n="[title]play.settings")
|
||||
button.btn.settings(data-toggle='coco-modal', data-target='core/CreateAccountModal', data-i18n="[title]play.settings")
|
||||
|
||||
.user-status.header-font.picoctf-hide
|
||||
.user-status-line
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
h4.friends-header(data-i18n="ladder.friends_playing") Friends Playing
|
||||
if me.get('anonymous')
|
||||
div.alert.alert-info
|
||||
a(data-toggle="coco-modal", data-target="core/AuthModal", data-i18n="ladder.log_in_for_friends") Log in to play with your friends!
|
||||
a(data-toggle="coco-modal", data-target="core/CreateAccountModal", data-i18n="ladder.log_in_for_friends") Log in to play with your friends!
|
||||
|
||||
else
|
||||
if !onFacebook || !onGPlus
|
||||
|
|
|
@ -43,4 +43,4 @@ else
|
|||
button.btn.btn-xs.btn-primary.banner#level-done-button(data-i18n="play_level.done") Done
|
||||
|
||||
if me.get('anonymous')
|
||||
button.btn.btn-xs.btn-primary.banner#control-bar-sign-up-button(data-toggle='coco-modal', data-target='core/AuthModal', data-i18n="signup.sign_up")
|
||||
button.btn.btn-xs.btn-primary.banner#control-bar-sign-up-button(data-toggle='coco-modal', data-target='core/CreateAccountModal', data-i18n="signup.sign_up")
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
- var i18nKey = 'game_menu.' + submenu.replace('-', '_');
|
||||
span(data-i18n='[title]' + i18nKey + '_caption;' + i18nKey + '_tab')
|
||||
if me.get('anonymous')
|
||||
li.auth-tab(data-toggle='coco-modal', data-target="core/AuthModal")
|
||||
a(data-toggle='coco-modal', data-target="core/AuthModal")
|
||||
li.auth-tab(data-toggle='coco-modal', data-target="core/CreateAccountModal")
|
||||
a(data-toggle='coco-modal', data-target="core/CreateAccountModal")
|
||||
span.glyphicon.glyphicon-pencil
|
||||
span(data-i18n='[title]game_menu.auth_caption;game_menu.auth_tab')
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/home-view'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
|
||||
module.exports = class HomeView extends RootView
|
||||
id: 'home-view'
|
||||
|
@ -40,6 +41,8 @@ module.exports = class HomeView extends RootView
|
|||
|
||||
afterInsert: ->
|
||||
super(arguments...)
|
||||
modal = new CreateAccountModal()
|
||||
@openModalView(modal)
|
||||
|
||||
justPlaysCourses: ->
|
||||
# This heuristic could be better, but currently we don't add to me.get('courseInstances') for single-player anonymous intro courses, so they have to beat a level without choosing a hero.
|
||||
|
|
|
@ -3,6 +3,7 @@ forms = require 'core/forms'
|
|||
TrialRequest = require 'models/TrialRequest'
|
||||
TrialRequests = require 'collections/TrialRequests'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
storage = require 'core/storage'
|
||||
|
||||
formSchema = {
|
||||
|
@ -108,10 +109,7 @@ module.exports = class RequestQuoteView extends RootView
|
|||
forms.scrollToFirstError()
|
||||
|
||||
onClickEmailExistsLoginLink: ->
|
||||
modal = new AuthModal({
|
||||
mode: 'login'
|
||||
initialValues: { email: @trialRequest.get('properties')?.email }
|
||||
})
|
||||
modal = new AuthModal({ initialValues: { email: @trialRequest.get('properties')?.email } })
|
||||
@openModalView(modal)
|
||||
|
||||
onTrialRequestSubmit: ->
|
||||
|
@ -119,18 +117,14 @@ module.exports = class RequestQuoteView extends RootView
|
|||
window.tracker?.trackEvent 'Submit Trial Request', category: 'Teachers', label: 'Trial Request', ['Mixpanel']
|
||||
|
||||
onClickLoginButton: ->
|
||||
modal = new AuthModal({
|
||||
mode: 'login'
|
||||
initialValues: { email: @trialRequest.get('properties')?.email }
|
||||
})
|
||||
modal = new AuthModal({ initialValues: { email: @trialRequest.get('properties')?.email } })
|
||||
@openModalView(modal)
|
||||
window.nextURL = '/courses/teachers' unless @trialRequest.isNew()
|
||||
|
||||
onClickSignupButton: ->
|
||||
props = @trialRequest.get('properties') or {}
|
||||
me.set('name', props.name)
|
||||
modal = new AuthModal({
|
||||
mode: 'signup'
|
||||
modal = new CreateAccountModal({
|
||||
initialValues: {
|
||||
email: props.email
|
||||
schoolName: props.organization
|
||||
|
|
|
@ -2,6 +2,7 @@ app = require 'core/application'
|
|||
AuthModal = require 'views/core/AuthModal'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/sales-view'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
|
||||
module.exports = class SalesView extends RootView
|
||||
id: 'sales-view'
|
||||
|
@ -20,11 +21,11 @@ module.exports = class SalesView extends RootView
|
|||
app.router.navigate '/teachers/quote', trigger: true
|
||||
|
||||
onClickLogin: (e) ->
|
||||
@openModalView new AuthModal(mode: 'login') if me.get('anonymous')
|
||||
@openModalView new AuthModal() if me.get('anonymous')
|
||||
window.tracker?.trackEvent 'Started Login', category: 'Sales', label: 'Sales Login', ['Mixpanel']
|
||||
|
||||
onClickSignup: (e) ->
|
||||
@openModalView new AuthModal() if me.get('anonymous')
|
||||
@openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'Sales', label: 'Sales Create', ['Mixpanel']
|
||||
|
||||
logoutRedirectURL: false
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
AuthModal = require 'views/core/AuthModal'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/teachers'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
|
||||
module.exports = class TeachersView extends RootView
|
||||
id: 'teachers-view'
|
||||
|
@ -22,11 +23,11 @@ module.exports = class TeachersView extends RootView
|
|||
application.router.navigate "/courses/teachers", trigger: true
|
||||
|
||||
onClickLogin: (e) ->
|
||||
@openModalView new AuthModal(mode: 'login') if me.get('anonymous')
|
||||
@openModalView new AuthModal() if me.get('anonymous')
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'Teachers', label: 'Teachers Login'
|
||||
|
||||
onClickSignup: (e) ->
|
||||
@openModalView new AuthModal() if me.get('anonymous')
|
||||
@openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'Teachers', label: 'Teachers Create'
|
||||
|
||||
logoutRedirectURL: false
|
||||
|
|
|
@ -3,7 +3,7 @@ template = require 'templates/account/account-settings-view'
|
|||
{me} = require 'core/auth'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
ConfirmModal = require 'views/editor/modal/ConfirmModal'
|
||||
{logoutUser, me} = require('core/auth')
|
||||
|
||||
|
@ -27,7 +27,7 @@ module.exports = class AccountSettingsView extends CocoView
|
|||
|
||||
afterInsert: ->
|
||||
super()
|
||||
@openModalView new AuthModal() if me.get('anonymous')
|
||||
@openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
|
||||
getEmailSubsDict: ->
|
||||
subs = {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/clans/clan-details'
|
||||
app = require 'core/application'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Campaign = require 'models/Campaign'
|
||||
Clan = require 'models/Clan'
|
||||
|
@ -281,7 +281,7 @@ module.exports = class ClanDetailsView extends RootView
|
|||
window.open url, '_blank'
|
||||
|
||||
onDeleteClan: (e) ->
|
||||
return @openModalView(new AuthModal()) if me.isAnonymous()
|
||||
return @openModalView(new CreateAccountModal()) if me.isAnonymous()
|
||||
return unless window.confirm("Delete Clan?")
|
||||
options =
|
||||
url: "/db/clan/#{@clanID}"
|
||||
|
@ -312,7 +312,7 @@ module.exports = class ClanDetailsView extends RootView
|
|||
$('.expand-progress-checkbox').attr('checked', @showExpandedProgress)
|
||||
|
||||
onJoinClan: (e) ->
|
||||
return @openModalView(new AuthModal()) if me.isAnonymous()
|
||||
return @openModalView(new CreateAccountModal()) if me.isAnonymous()
|
||||
return unless @clan.loaded
|
||||
if @clan.get('type') is 'private' and not me.isPremium()
|
||||
@openModalView new SubscribeModal()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
app = require 'core/application'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/clans/clans'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
|
@ -93,7 +93,7 @@ module.exports = class ClansView extends RootView
|
|||
)
|
||||
|
||||
onClickCreateClan: (e) ->
|
||||
return @openModalView new AuthModal() if me.isAnonymous()
|
||||
return @openModalView new CreateAccountModal() if me.isAnonymous()
|
||||
clanType = if $('.private-clan-checkbox').prop('checked') then 'private' else 'public'
|
||||
if clanType is 'private' and not me.isPremium()
|
||||
@openModalView new SubscribeModal()
|
||||
|
@ -114,7 +114,7 @@ module.exports = class ClansView extends RootView
|
|||
console.log 'Invalid name'
|
||||
|
||||
onJoinClan: (e) ->
|
||||
return @openModalView(new AuthModal()) if me.isAnonymous()
|
||||
return @openModalView(new CreateAccountModal()) if me.isAnonymous()
|
||||
if clanID = $(e.target).data('id')
|
||||
options =
|
||||
url: "/db/clan/#{clanID}/join"
|
||||
|
@ -144,7 +144,7 @@ module.exports = class ClansView extends RootView
|
|||
console.error "No clan ID attached to leave button."
|
||||
|
||||
onClickPrivateCheckbox: (e) ->
|
||||
return @openModalView new AuthModal() if me.isAnonymous()
|
||||
return @openModalView new CreateAccountModal() if me.isAnonymous()
|
||||
if $('.private-clan-checkbox').prop('checked') and not me.isPremium()
|
||||
$('.private-clan-checkbox').attr('checked', false)
|
||||
@openModalView new SubscribeModal()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
RootView = require 'views/core/RootView'
|
||||
{me} = require 'core/auth'
|
||||
contributorSignupAnonymousTemplate = require 'templates/contribute/contributor_signup_anonymous'
|
||||
|
@ -37,7 +37,7 @@ module.exports = class ContributeClassView extends RootView
|
|||
|
||||
me.setEmailSubscription subscription+'News', checked
|
||||
me.patch()
|
||||
@openModalView new AuthModal() if me.get 'anonymous'
|
||||
@openModalView new CreateAccountModal() if me.get 'anonymous'
|
||||
el.parent().find('.saved-notification').finish().show('fast').delay(3000).fadeOut(2000)
|
||||
|
||||
contributors: []
|
||||
|
|
|
@ -4,33 +4,36 @@ template = require 'templates/core/auth'
|
|||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
application = require 'core/application'
|
||||
errors = require 'core/errors'
|
||||
|
||||
module.exports = class AuthModal extends ModalView
|
||||
id: 'auth-modal'
|
||||
template: template
|
||||
mode: 'signup' # or 'login'
|
||||
|
||||
events:
|
||||
# login buttons
|
||||
'click #switch-to-signup-button': 'onSignupInstead'
|
||||
'click #switch-to-login-button': 'onLoginInstead'
|
||||
'click #switch-to-signup-btn': 'onSignupInstead'
|
||||
'click #github-login-button': 'onGitHubLoginClicked'
|
||||
'submit': 'onSubmitForm' # handles both submit buttons
|
||||
'submit form': 'onSubmitForm' # handles both submit buttons
|
||||
'keyup #name': 'onNameChange'
|
||||
'click #gplus-login-button': 'onClickGPlusLogin'
|
||||
'click #close-modal': 'hide'
|
||||
'click #gplus-login-btn': 'onClickGPlusLogin'
|
||||
'click #facebook-login-btn': 'onClickFacebookLoginButton'
|
||||
'click #close-modal': 'hide'
|
||||
|
||||
subscriptions:
|
||||
'errors:server-error': 'onServerError'
|
||||
'auth:logging-in-with-facebook': 'onLoggingInWithFacebook'
|
||||
'auth:facebook-api-loaded': 'onFacebookAPILoaded'
|
||||
|
||||
constructor: (options) ->
|
||||
options ?= {}
|
||||
@onNameChange = _.debounce @checkNameExists, 500
|
||||
super options
|
||||
@mode = options.mode if options.mode
|
||||
|
||||
# Initialization
|
||||
|
||||
initialize: (options={}) ->
|
||||
@previousFormInputs = options.initialValues or {}
|
||||
@listenTo application.gplusHandler, 'logged-into-google', @onGPlusHandlerLoggedIntoGoogle
|
||||
@listenTo application.gplusHandler, 'person-loaded', @onGPlusPersonLoaded
|
||||
@listenTo application.gplusHandler, 'render-login-buttons', @onGPlusRenderLoginButtons
|
||||
@listenTo application.facebookHandler, 'logged-into-facebook', @onFacebookHandlerLoggedIntoFacebook
|
||||
@listenTo application.facebookHandler, 'person-loaded', @onFacebookPersonLoaded
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
|
@ -43,35 +46,28 @@ module.exports = class AuthModal extends ModalView
|
|||
|
||||
afterRender: ->
|
||||
super()
|
||||
@$el.toggleClass('signup', @mode is 'signup').toggleClass('login', @mode is 'login')
|
||||
@playSound 'game-menu-open'
|
||||
@$('#facebook-login-btn').attr('disabled', true) if not window.FB?
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
_.delay (=> application.router.renderLoginButtons()), 500
|
||||
_.delay (=> $('input:visible:first', @$el).focus()), 500
|
||||
|
||||
onSignupInstead: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
@mode = 'signup'
|
||||
@previousFormInputs = forms.formToObject @$el
|
||||
@render()
|
||||
_.delay application.router.renderLoginButtons, 500
|
||||
onGPlusRenderLoginButtons: ->
|
||||
@$('#gplus-login-btn').attr('disabled', false)
|
||||
|
||||
onLoginInstead: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
@mode = 'login'
|
||||
@previousFormInputs = forms.formToObject @$el
|
||||
@render()
|
||||
_.delay application.router.renderLoginButtons, 500
|
||||
onFacebookAPILoaded: ->
|
||||
@$('#facebook-login-btn').attr('disabled', false)
|
||||
|
||||
onSignupInstead: (e) ->
|
||||
CreateAccountModal = require('./CreateAccountModal')
|
||||
modal = new CreateAccountModal({initialValues: forms.formToObject @$el})
|
||||
currentView.openModalView(modal)
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
e.preventDefault()
|
||||
if @mode is 'login' then @loginAccount() else @createAccount()
|
||||
false
|
||||
|
||||
loginAccount: ->
|
||||
forms.clearFormAlerts(@$el)
|
||||
userObject = forms.formToObject @$el
|
||||
res = tv4.validateMultiple userObject, User.schema
|
||||
|
@ -79,89 +75,73 @@ module.exports = class AuthModal extends ModalView
|
|||
@enableModalInProgress(@$el) # TODO: part of forms
|
||||
loginUser userObject, null, window.nextURL
|
||||
|
||||
emailCheck: ->
|
||||
email = $('#email', @$el).val()
|
||||
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
|
||||
unless filter.test(email)
|
||||
forms.setErrorToProperty @$el, 'email', 'Please enter a valid email address', true
|
||||
return false
|
||||
return true
|
||||
|
||||
createAccount: ->
|
||||
forms.clearFormAlerts(@$el)
|
||||
return unless @emailCheck()
|
||||
userObject = forms.formToObject @$el
|
||||
delete userObject.subscribe
|
||||
delete userObject.name if userObject.name is ''
|
||||
delete userObject.schoolName if userObject.schoolName is ''
|
||||
userObject.name = @suggestedName if @suggestedName
|
||||
for key, val of me.attributes when key in ['preferredLanguage', 'testGroupNumber', 'dateCreated', 'wizardColor1', 'name', 'music', 'volume', 'emails', 'schoolName']
|
||||
userObject[key] ?= val
|
||||
subscribe = @$el.find('#subscribe').prop('checked')
|
||||
userObject.emails ?= {}
|
||||
userObject.emails.generalNews ?= {}
|
||||
userObject.emails.generalNews.enabled = subscribe
|
||||
res = tv4.validateMultiple userObject, User.schema
|
||||
return forms.applyErrorsToForm(@$el, res.errors) unless res.valid
|
||||
Backbone.Mediator.publish "auth:signed-up", {}
|
||||
window.tracker?.trackEvent 'Finished Signup', label: 'CodeCombat'
|
||||
@enableModalInProgress(@$el)
|
||||
createUser userObject, null, window.nextURL
|
||||
|
||||
onLoggingInWithFacebook: (e) ->
|
||||
modal = $('.modal:visible', @$el)
|
||||
@enableModalInProgress(modal) # TODO: part of forms
|
||||
|
||||
onServerError: (e) -> # TODO: work error handling into a separate forms system
|
||||
@disableModalInProgress(@$el)
|
||||
|
||||
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}?", true
|
||||
|
||||
onGitHubLoginClicked: ->
|
||||
@playSound 'menu-button-click'
|
||||
Backbone.Mediator.publish 'auth:log-in-with-github', {}
|
||||
|
||||
gplusAuthSteps: [
|
||||
{ i18n: 'login.authenticate_gplus', done: false }
|
||||
{ i18n: 'login.load_profile', done: false }
|
||||
{ i18n: 'login.finishing', done: false }
|
||||
]
|
||||
|
||||
|
||||
# Google Plus
|
||||
|
||||
onClickGPlusLogin: ->
|
||||
@playSound 'menu-button-click'
|
||||
step.done = false for step in @gplusAuthSteps
|
||||
handler = application.gplusHandler
|
||||
@clickedGPlusLogin = true
|
||||
|
||||
@listenToOnce handler, 'logged-in', ->
|
||||
@gplusAuthSteps[0].done = true
|
||||
@renderGPlusAuthChecklist()
|
||||
handler.loginCodeCombat()
|
||||
@listenToOnce handler, 'person-loaded', ->
|
||||
@gplusAuthSteps[1].done = true
|
||||
@renderGPlusAuthChecklist()
|
||||
onGPlusHandlerLoggedIntoGoogle: ->
|
||||
return unless @clickedGPlusLogin
|
||||
application.gplusHandler.loadPerson()
|
||||
btn = @$('#gplus-login-btn')
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('login.logging_in'))
|
||||
btn.attr('disabled', true)
|
||||
|
||||
@listenToOnce handler, 'logging-into-codecombat', ->
|
||||
@gplusAuthSteps[2].done = true
|
||||
@renderGPlusAuthChecklist()
|
||||
|
||||
renderGPlusAuthChecklist: ->
|
||||
template = require 'templates/core/auth-modal-gplus-checklist'
|
||||
el = $(template({steps: @gplusAuthSteps}))
|
||||
el.i18n()
|
||||
@$el.find('.modal-body:visible').empty().append(el)
|
||||
@$el.find('.modal-footer').remove()
|
||||
onGPlusPersonLoaded: (gplusAttrs) ->
|
||||
existingUser = new User()
|
||||
existingUser.fetchGPlusUser(gplusAttrs.gplusID, {
|
||||
success: =>
|
||||
me.loginGPlusUser(gplusAttrs.gplusID, {
|
||||
success: -> window.location.reload()
|
||||
error: @onGPlusLoginError
|
||||
})
|
||||
error: @onGPlusLoginError
|
||||
})
|
||||
|
||||
onGPlusLoginError: =>
|
||||
btn = @$('#gplus-login-btn')
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('login.sign_in_with_gplus'))
|
||||
btn.attr('disabled', false)
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
|
||||
|
||||
# Facebook
|
||||
|
||||
onClickFacebookLoginButton: ->
|
||||
application.facebookHandler.loginThroughFacebook()
|
||||
@clickedFacebookLogin = true
|
||||
if application.facebookHandler.loggedIn
|
||||
@onFacebookHandlerLoggedIntoFacebook()
|
||||
else
|
||||
application.facebookHandler.loginThroughFacebook()
|
||||
|
||||
onFacebookHandlerLoggedIntoFacebook: ->
|
||||
return unless @clickedFacebookLogin
|
||||
application.facebookHandler.loadPerson()
|
||||
btn = @$('#facebook-login-btn')
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('login.logging_in'))
|
||||
btn.attr('disabled', true)
|
||||
|
||||
onFacebookPersonLoaded: (facebookAttrs) ->
|
||||
existingUser = new User()
|
||||
existingUser.fetchFacebookUser(facebookAttrs.facebookID, {
|
||||
success: =>
|
||||
me.loginFacebookUser(facebookAttrs.facebookID, {
|
||||
success: -> window.location.reload()
|
||||
error: @onFacebookLoginError
|
||||
})
|
||||
error: @onFacebookLoginError
|
||||
})
|
||||
|
||||
onFacebookLoginError: =>
|
||||
btn = @$('#facebook-login-btn')
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('login.sign_in_with_facebook'))
|
||||
btn.attr('disabled', false)
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
|
||||
|
||||
onHidden: ->
|
||||
super()
|
||||
|
|
|
@ -170,12 +170,12 @@ module.exports = class CocoView extends Backbone.View
|
|||
onClickLoadingErrorLoginButton: (e) ->
|
||||
e.stopPropagation() # Backbone subviews and superviews will handle this call repeatedly otherwise
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
@openModalView(new AuthModal({mode: 'login'}))
|
||||
@openModalView(new AuthModal())
|
||||
|
||||
onClickLoadingErrorCreateAccountButton: (e) ->
|
||||
e.stopPropagation()
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
@openModalView(new AuthModal({mode: 'signup'}))
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
@openModalView(new CreateAccountModal({mode: 'signup'}))
|
||||
|
||||
onClickLoadingErrorLogoutButton: (e) ->
|
||||
e.stopPropagation()
|
||||
|
|
250
app/views/core/CreateAccountModal.coffee
Normal file
250
app/views/core/CreateAccountModal.coffee
Normal file
|
@ -0,0 +1,250 @@
|
|||
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'
|
||||
Classroom = require 'models/Classroom'
|
||||
errors = require 'core/errors'
|
||||
|
||||
# TODO: Avoid using G+ render buttons to login, login directly instead.
|
||||
# Form object is split in two in template to avoid having rendered buttons triggered on form submit.
|
||||
|
||||
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'
|
||||
|
||||
subscriptions:
|
||||
'auth:facebook-api-loaded': 'onFacebookAPILoaded'
|
||||
|
||||
|
||||
# Initialization
|
||||
|
||||
initialize: (options={}) ->
|
||||
@onNameChange = _.debounce(_.bind(@checkNameExists, @), 500)
|
||||
@previousFormInputs = options.initialValues or {}
|
||||
@listenTo application.gplusHandler, 'logged-into-google', @onGPlusHandlerLoggedIntoGoogle
|
||||
@listenTo application.gplusHandler, 'person-loaded', @onGPlusPersonLoaded
|
||||
@listenTo application.gplusHandler, 'render-login-buttons', @onGPlusRenderLoginButtons
|
||||
@listenTo application.facebookHandler, 'logged-into-facebook', @onFacebookHandlerLoggedIntoFacebook
|
||||
@listenTo application.facebookHandler, 'person-loaded', @onFacebookPersonLoaded
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@playSound 'game-menu-open'
|
||||
@$('#facebook-signup-btn').attr('disabled', true) if not window.FB?
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
_.delay (-> application.router.renderLoginButtons()), 500
|
||||
_.delay (=> $('input:visible:first', @$el).focus()), 500
|
||||
|
||||
onGPlusRenderLoginButtons: ->
|
||||
@$('#gplus-signup-btn').attr('disabled', false)
|
||||
|
||||
onFacebookAPILoaded: ->
|
||||
@$('#facebook-signup-btn').attr('disabled', false)
|
||||
|
||||
|
||||
# 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
|
||||
_.assign attrs, @gplusAttrs if @gplusAttrs
|
||||
_.assign attrs, @facebookAttrs if @facebookAttrs
|
||||
res = tv4.validateMultiple attrs, User.schema
|
||||
error = false
|
||||
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
|
||||
@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', 'Classroom code not found. Please check with your instructor.')
|
||||
|
||||
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
|
||||
application.router.navigate(url)
|
||||
window.location.reload()
|
||||
|
||||
|
||||
# Google Plus
|
||||
|
||||
onClickGPlusSignupButton: ->
|
||||
@clickedGPlusLogin = true
|
||||
|
||||
onGPlusHandlerLoggedIntoGoogle: ->
|
||||
return unless @clickedGPlusLogin
|
||||
application.gplusHandler.loadPerson()
|
||||
btn = @$('#gplus-signup-btn')
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('signup.creating'))
|
||||
btn.attr('disabled', true)
|
||||
|
||||
onGPlusPersonLoaded: (@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: ->
|
||||
@clickedFacebookLogin = true
|
||||
if application.facebookHandler.loggedIn
|
||||
@onFacebookHandlerLoggedIntoFacebook()
|
||||
else
|
||||
application.facebookHandler.loginThroughFacebook()
|
||||
|
||||
onFacebookHandlerLoggedIntoFacebook: ->
|
||||
return unless @clickedFacebookLogin
|
||||
application.facebookHandler.loadPerson()
|
||||
btn = @$('#facebook-signup-btn')
|
||||
btn.find('.sign-in-blurb').text($.i18n.t('signup.creating'))
|
||||
btn.attr('disabled', true)
|
||||
|
||||
onFacebookPersonLoaded: (@facebookAttrs) ->
|
||||
existingUser = new User()
|
||||
existingUser.fetchFacebookUser(@facebookAttrs.facebookID, {
|
||||
success: =>
|
||||
@$('#email-password-row').remove()
|
||||
@$('#facebook-account-exists-row').removeClass('hide')
|
||||
error: (model, jqxhr) =>
|
||||
@$('#email-password-row').remove()
|
||||
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)
|
|
@ -60,7 +60,7 @@ module.exports = class RootView extends CocoView
|
|||
logoutUser($('#login-email').val())
|
||||
|
||||
onClickSignupButton: ->
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
switch @id
|
||||
when 'home-view'
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'Homepage', label: 'Homepage'
|
||||
|
@ -69,12 +69,12 @@ module.exports = class RootView extends CocoView
|
|||
window.tracker?.trackEvent 'Started Signup', category: 'World Map', label: 'World Map'
|
||||
else
|
||||
window.tracker?.trackEvent 'Started Signup', label: @id
|
||||
@openModalView new AuthModal {mode: 'signup'}
|
||||
@openModalView new CreateAccountModal()
|
||||
|
||||
onClickLoginButton: ->
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
window.tracker?.trackEvent 'Login', category: 'Homepage', ['Google Analytics'] if @id is 'home-view'
|
||||
@openModalView new AuthModal {mode: 'login'}
|
||||
@openModalView new AuthModal()
|
||||
|
||||
onClickAnchor: (e) ->
|
||||
return if @destroyed
|
||||
|
|
|
@ -2,7 +2,7 @@ ModalView = require 'views/core/ModalView'
|
|||
template = require 'templates/core/subscribe-modal'
|
||||
stripeHandler = require 'core/services/stripe'
|
||||
utils = require 'core/utils'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
Products = require 'collections/Products'
|
||||
|
||||
module.exports = class SubscribeModal extends ModalView
|
||||
|
@ -134,7 +134,7 @@ module.exports = class SubscribeModal extends ModalView
|
|||
onClickPurchaseButton: (e) ->
|
||||
return unless @basicProduct
|
||||
@playSound 'menu-button-click'
|
||||
return @openModalView new AuthModal() if me.get('anonymous')
|
||||
return @openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
application.tracker?.trackEvent 'Started subscription purchase'
|
||||
options = {
|
||||
description: $.i18n.t('subscribe.stripe_description')
|
||||
|
@ -148,7 +148,7 @@ module.exports = class SubscribeModal extends ModalView
|
|||
|
||||
onClickSaleButton: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
return @openModalView new AuthModal() if me.get('anonymous')
|
||||
return @openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
application.tracker?.trackEvent 'Started 1 year subscription purchase'
|
||||
discount = @basicProduct.get('amount') * 12 - @yearProduct.get('amount')
|
||||
discountString = (discount/100).toFixed(2)
|
||||
|
|
|
@ -49,9 +49,9 @@ module.exports = class CourseDetailsView extends RootView
|
|||
afterRender: ->
|
||||
super()
|
||||
if @supermodel.finished() and @courseComplete and me.isAnonymous() and @options.justBeatLevel
|
||||
# TODO: Make an intermediate modal that tells them they've finished HoC and has some snazzy stuff for convincing players to sign up instead of just throwing up the bare AuthModal
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
@openModalView new AuthModal showSignupRationale: true
|
||||
# TODO: Make an intermediate modal that tells them they've finished HoC and has some snazzy stuff for convincing players to sign up instead of just throwing up the bare CreateAccountModal
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
@openModalView new CreateAccountModal showSignupRationale: true
|
||||
|
||||
onCourseSync: ->
|
||||
return if @destroyed
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
app = require 'core/application'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
RootView = require 'views/core/RootView'
|
||||
|
@ -46,7 +46,7 @@ module.exports = class CourseEnrollView extends RootView
|
|||
@renderNewPrice()
|
||||
|
||||
onClickBuy: (e) ->
|
||||
return @openModalView new AuthModal() if me.isAnonymous()
|
||||
return @openModalView new CreateAccountModal() if me.isAnonymous()
|
||||
|
||||
if @price is 0
|
||||
@seats = 9999
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
app = require 'core/application'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/courses/courses-view'
|
||||
StudentLogInModal = require 'views/courses/StudentLogInModal'
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
app = require 'core/application'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
CourseInstance = require 'models/CourseInstance'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
app = require 'core/application'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
Classroom = require 'models/Classroom'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
|
@ -77,7 +77,7 @@ module.exports = class PurchaseCoursesView extends RootView
|
|||
numberOfStudentsIsValid: -> @numberOfStudents > 0 and @numberOfStudents < 100000
|
||||
|
||||
onClickPurchaseButton: ->
|
||||
return @openModalView new AuthModal() if me.isAnonymous()
|
||||
return @openModalView new CreateAccountModal() if me.isAnonymous()
|
||||
unless @numberOfStudentsIsValid()
|
||||
alert("Please enter the maximum number of students needed for your class.")
|
||||
return
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
app = require 'core/application'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
Classroom = require 'models/Classroom'
|
||||
|
@ -35,7 +35,7 @@ module.exports = class StudentCoursesView extends RootView
|
|||
super()
|
||||
|
||||
onClickJoinClassButton: (e) ->
|
||||
return @openModalView new AuthModal() if me.isAnonymous()
|
||||
return @openModalView new CreateAccountModal() if me.isAnonymous()
|
||||
@classCode = @$('#classroom-code-input').val()
|
||||
@joinClass()
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
ActivateLicensesModal = require 'views/courses/ActivateLicensesModal'
|
||||
app = require 'core/application'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
CocoModel = require 'models/CocoModel'
|
||||
Course = require 'models/Course'
|
||||
|
@ -70,7 +70,7 @@ module.exports = class TeacherCoursesView extends RootView
|
|||
application.tracker?.trackEvent 'Classroom started add students', category: 'Courses', classroomID: classroom.id
|
||||
|
||||
onClickCreateNewClassButton: ->
|
||||
return @openModalView new AuthModal() if me.get('anonymous')
|
||||
return @openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
modal = new ClassroomSettingsModal({})
|
||||
@openModalView(modal)
|
||||
@listenToOnce modal, 'hide', =>
|
||||
|
|
|
@ -9,7 +9,7 @@ LevelSetupManager = require 'lib/LevelSetupManager'
|
|||
ThangType = require 'models/ThangType'
|
||||
MusicPlayer = require 'lib/surface/MusicPlayer'
|
||||
storage = require 'core/storage'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
SubscribeModal = require 'views/core/SubscribeModal'
|
||||
LeaderboardModal = require 'views/play/modal/LeaderboardModal'
|
||||
Level = require 'models/Level'
|
||||
|
@ -159,7 +159,7 @@ module.exports = class CampaignView extends RootView
|
|||
@preloadTopHeroes() unless me.get('heroConfig')?.thangType
|
||||
@$el.find('#campaign-status').delay(4000).animate({top: "-=58"}, 1000) unless @terrain is 'dungeon'
|
||||
if @terrain and me.get('anonymous') and me.get('lastLevel') is 'shadow-guard' and me.level() < 4
|
||||
@openModalView new AuthModal supermodel: @supermodel, showSignupRationale: true, mode: 'signup'
|
||||
@openModalView new CreateAccountModal supermodel: @supermodel, showSignupRationale: true
|
||||
else if @terrain and me.get('name') and me.get('lastLevel') in ['forgetful-gemsmith', 'signs-and-portents'] and me.level() < 5 and not (me.get('ageRange') in ['18-24', '25-34', '35-44', '45-100']) and not storage.load('sent-parent-email') and not me.isPremium()
|
||||
@openModalView new ShareProgressModal()
|
||||
|
||||
|
@ -262,7 +262,7 @@ module.exports = class CampaignView extends RootView
|
|||
return unless @getQueryVariable 'signup'
|
||||
return if me.get('email')
|
||||
@endHighlight()
|
||||
authModal = new AuthModal supermodel: @supermodel
|
||||
authModal = new CreateAccountModal supermodel: @supermodel
|
||||
authModal.mode = 'signup'
|
||||
@openModalView authModal
|
||||
|
||||
|
|
|
@ -49,8 +49,8 @@ module.exports = class LadderSubmissionView extends CocoView
|
|||
@$el.find('.last-submitted').toggle(showLastSubmitted)
|
||||
|
||||
showApologeticSignupModal: ->
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
@openModalView(new AuthModal({showRequiredError: true}))
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
@openModalView(new CreateAccountModal({showRequiredError: true}))
|
||||
|
||||
rankSession: (e) ->
|
||||
return unless @session.readyToRank()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
template = require 'templates/play/level/modal/hero-victory-modal'
|
||||
Achievement = require 'models/Achievement'
|
||||
EarnedAchievement = require 'models/EarnedAchievement'
|
||||
|
@ -481,7 +481,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
onClickSignupButton: (e) ->
|
||||
e.preventDefault()
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'Play Level', label: 'Hero Victory Modal', level: @level.get('slug')
|
||||
@openModalView new AuthModal {mode: 'signup'}
|
||||
@openModalView new CreateAccountModal()
|
||||
|
||||
showOffer: (@navigationEventUponCompletion) ->
|
||||
@$el.find('.modal-footer > *').hide()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
template = require 'templates/play/level/modal/victory'
|
||||
{me} = require 'core/auth'
|
||||
LadderSubmissionView = require 'views/play/common/LadderSubmissionView'
|
||||
|
@ -59,7 +59,7 @@ module.exports = class VictoryModal extends ModalView
|
|||
onClickSignupButton: (e) ->
|
||||
e.preventDefault()
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'Play Level', label: 'Victory Modal', level: @level.get('slug')
|
||||
@openModalView new AuthModal {mode: 'signup'}
|
||||
@openModalView new CreateAccountModal()
|
||||
|
||||
onGameSubmitted: (e) ->
|
||||
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
template = require 'templates/play/menu/game-menu-modal'
|
||||
submenuViews = [
|
||||
require 'views/play/menu/SaveLoadView'
|
||||
|
@ -80,4 +80,4 @@ module.exports = class GameMenuModal extends ModalView
|
|||
window.tracker?.trackEvent 'Started Signup', category: 'Play Level', label: 'Game Menu', level: @options.levelID
|
||||
# TODO: Default already seems to be prevented. Need to be explicit?
|
||||
e.preventDefault()
|
||||
@openModalView new AuthModal {mode: 'signup'}
|
||||
@openModalView new CreateAccountModal()
|
||||
|
|
|
@ -9,7 +9,7 @@ SpriteBuilder = require 'lib/sprites/SpriteBuilder'
|
|||
ItemDetailsView = require 'views/play/modal/ItemDetailsView'
|
||||
Purchase = require 'models/Purchase'
|
||||
BuyGemsModal = require 'views/play/modal/BuyGemsModal'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
|
||||
hasGoneFullScreenOnce = false
|
||||
|
||||
|
@ -574,9 +574,8 @@ module.exports = class InventoryModal extends ModalView
|
|||
button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0]
|
||||
|
||||
askToSignUp: ->
|
||||
authModal = new AuthModal supermodel: @supermodel
|
||||
authModal.mode = 'signup'
|
||||
return @openModalView authModal
|
||||
createAccountModal = new CreateAccountModal supermodel: @supermodel
|
||||
return @openModalView createAccountModal
|
||||
|
||||
askToBuyGems: (unlockButton) ->
|
||||
@$el.find('.unlock-button').popover 'destroy'
|
||||
|
|
|
@ -7,7 +7,7 @@ SpriteBuilder = require 'lib/sprites/SpriteBuilder'
|
|||
AudioPlayer = require 'lib/AudioPlayer'
|
||||
utils = require 'core/utils'
|
||||
BuyGemsModal = require 'views/play/modal/BuyGemsModal'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
Purchase = require 'models/Purchase'
|
||||
LayerAdapter = require 'lib/surface/LayerAdapter'
|
||||
Lank = require 'lib/surface/Lank'
|
||||
|
@ -265,9 +265,8 @@ module.exports = class PlayHeroesModal extends ModalView
|
|||
button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0]
|
||||
|
||||
askToSignUp: ->
|
||||
authModal = new AuthModal supermodel: @supermodel
|
||||
authModal.mode = 'signup'
|
||||
return @openModalView authModal
|
||||
createAccountModal = new CreateAccountModal supermodel: @supermodel
|
||||
return @openModalView createAccountModal
|
||||
|
||||
askToBuyGems: (unlockButton) ->
|
||||
@$el.find('.unlock-button').popover 'destroy'
|
||||
|
|
|
@ -3,7 +3,7 @@ template = require 'templates/play/modal/play-items-modal'
|
|||
buyGemsPromptTemplate = require 'templates/play/modal/buy-gems-prompt'
|
||||
ItemDetailsView = require './ItemDetailsView'
|
||||
BuyGemsModal = require 'views/play/modal/BuyGemsModal'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
ThangType = require 'models/ThangType'
|
||||
|
@ -213,9 +213,8 @@ module.exports = class PlayItemsModal extends ModalView
|
|||
button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0]
|
||||
|
||||
askToSignUp: ->
|
||||
authModal = new AuthModal supermodel: @supermodel
|
||||
authModal.mode = 'signup'
|
||||
return @openModalView authModal
|
||||
createAccountModal = new CreateAccountModal supermodel: @supermodel
|
||||
return @openModalView createAccountModal
|
||||
|
||||
askToBuyGems: (unlockButton) ->
|
||||
@$el.find('.unlock-button').popover 'destroy'
|
||||
|
|
0
server/commons/users.coffee
Normal file
0
server/commons/users.coffee
Normal file
|
@ -1,13 +1,15 @@
|
|||
# Middleware for both authentication and authorization
|
||||
|
||||
errors = require '../commons/errors'
|
||||
utils = require '../lib/utils'
|
||||
wrap = require 'co-express'
|
||||
Promise = require 'bluebird'
|
||||
parse = require '../commons/parse'
|
||||
request = require 'request'
|
||||
User = require '../users/User'
|
||||
utils = require '../lib/utils'
|
||||
mongoose = require 'mongoose'
|
||||
User = require '../models/User'
|
||||
|
||||
module.exports = {
|
||||
module.exports =
|
||||
checkDocumentPermissions: (req, res, next) ->
|
||||
return next() if req.user?.isAdmin()
|
||||
if not req.doc.hasPermissionsForMethod(req.user, req.method)
|
||||
|
@ -33,6 +35,36 @@ module.exports = {
|
|||
return next new errors.Forbidden('You do not have permissions necessary.')
|
||||
next()
|
||||
|
||||
loginByGPlus: wrap (req, res) ->
|
||||
gpID = req.body.gplusID
|
||||
gpAT = req.body.gplusAccessToken
|
||||
throw new errors.UnprocessableEntity('gplusID and gplusAccessToken required.') unless gpID and gpAT
|
||||
|
||||
url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=#{gpAT}"
|
||||
[googleRes, body] = yield request.getAsync(url, {json: true})
|
||||
idsMatch = gpID is body.id
|
||||
throw new errors.UnprocessableEntity('Invalid G+ Access Token.') unless idsMatch
|
||||
user = yield User.findOne({gplusID: gpID})
|
||||
throw new errors.NotFound('No user with that G+ ID') unless user
|
||||
req.logInAsync = Promise.promisify(req.logIn)
|
||||
yield req.logInAsync(user)
|
||||
res.status(200).send(user.formatEntity(req))
|
||||
|
||||
loginByFacebook: wrap (req, res) ->
|
||||
fbID = req.body.facebookID
|
||||
fbAT = req.body.facebookAccessToken
|
||||
throw new errors.UnprocessableEntity('facebookID and facebookAccessToken required.') unless fbID and fbAT
|
||||
|
||||
url = "https://graph.facebook.com/me?access_token=#{fbAT}"
|
||||
[facebookRes, body] = yield request.getAsync(url, {json: true})
|
||||
idsMatch = fbID is body.id
|
||||
throw new errors.UnprocessableEntity('Invalid Facebook Access Token.') unless idsMatch
|
||||
user = yield User.findOne({facebookID: fbID})
|
||||
throw new errors.NotFound('No user with that Facebook ID') unless user
|
||||
req.logInAsync = Promise.promisify(req.logIn)
|
||||
yield req.logInAsync(user)
|
||||
res.status(200).send(user.formatEntity(req))
|
||||
|
||||
spy: wrap (req, res) ->
|
||||
throw new errors.Unauthorized('You must be logged in to enter espionage mode') unless req.user
|
||||
throw new errors.Forbidden('You must be an admin to enter espionage mode') unless req.user.isAdmin()
|
||||
|
@ -62,5 +94,3 @@ module.exports = {
|
|||
req.loginAsync = Promise.promisify(req.login)
|
||||
yield req.loginAsync user
|
||||
res.status(200).send(user.toObject({req: req}))
|
||||
|
||||
}
|
|
@ -4,4 +4,5 @@ module.exports =
|
|||
named: require './named'
|
||||
patchable: require './patchable'
|
||||
rest: require './rest'
|
||||
users: require './users'
|
||||
versions: require './versions'
|
40
server/middleware/users.coffee
Normal file
40
server/middleware/users.coffee
Normal file
|
@ -0,0 +1,40 @@
|
|||
errors = require '../commons/errors'
|
||||
wrap = require 'co-express'
|
||||
Promise = require 'bluebird'
|
||||
parse = require '../commons/parse'
|
||||
request = require 'request'
|
||||
User = require '../users/User'
|
||||
|
||||
|
||||
module.exports =
|
||||
fetchByGPlusID: wrap (req, res, next) ->
|
||||
gpID = req.query.gplusID
|
||||
gpAT = req.query.gplusAccessToken
|
||||
next() unless gpID and gpAT
|
||||
|
||||
dbq = User.find()
|
||||
dbq.select(parse.getProjectFromReq(req))
|
||||
url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=#{gpAT}"
|
||||
[googleRes, body] = yield request.getAsync(url, {json: true})
|
||||
idsMatch = gpID is body.id
|
||||
throw new errors.UnprocessableEntity('Invalid G+ Access Token.') unless idsMatch
|
||||
user = yield User.findOne({gplusID: gpID})
|
||||
throw new errors.NotFound('No user with that G+ ID') unless user
|
||||
res.status(200).send(user.formatEntity(req))
|
||||
|
||||
fetchByFacebookID: wrap (req, res, next) ->
|
||||
fbID = req.query.facebookID
|
||||
fbAT = req.query.facebookAccessToken
|
||||
next() unless fbID and fbAT
|
||||
|
||||
dbq = User.find()
|
||||
dbq.select(parse.getProjectFromReq(req))
|
||||
url = "https://graph.facebook.com/me?access_token=#{fbAT}"
|
||||
[facebookRes, body] = yield request.getAsync(url, {json: true})
|
||||
console.log '...', body, facebookRes.statusCode
|
||||
idsMatch = fbID is body.id
|
||||
throw new errors.UnprocessableEntity('Invalid Facebook Access Token.') unless idsMatch
|
||||
user = yield User.findOne({facebookID: fbID})
|
||||
throw new errors.NotFound('No user with that Facebook ID') unless user
|
||||
console.log 'okay done'
|
||||
res.status(200).send(user.formatEntity(req))
|
|
@ -1,6 +1,9 @@
|
|||
mw = require '../middleware'
|
||||
|
||||
module.exports.setup = (app) ->
|
||||
|
||||
app.post('/auth/login-facebook', mw.auth.loginByFacebook)
|
||||
app.post('/auth/login-gplus', mw.auth.loginByGPlus)
|
||||
app.post('/auth/spy', mw.auth.spy)
|
||||
app.post('/auth/stop-spying', mw.auth.stopSpying)
|
||||
|
||||
|
@ -19,6 +22,8 @@ module.exports.setup = (app) ->
|
|||
app.get('/db/article/:handle/patches', mw.patchable.patches(Article))
|
||||
app.post('/db/article/:handle/watchers', mw.patchable.joinWatchers(Article))
|
||||
app.delete('/db/article/:handle/watchers', mw.patchable.leaveWatchers(Article))
|
||||
|
||||
app.get('/db/user', mw.users.fetchByGPlusID, mw.users.fetchByFacebookID)
|
||||
|
||||
app.get '/db/products', require('./db/product').get
|
||||
|
||||
|
|
|
@ -248,6 +248,16 @@ UserSchema.methods.isPremium = ->
|
|||
return true if @hasSubscription()
|
||||
return false
|
||||
|
||||
UserSchema.methods.formatEntity = (req, publicOnly=false) ->
|
||||
obj = @toObject()
|
||||
serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset', 'lastIP']
|
||||
delete obj[prop] for prop in serverProperties
|
||||
candidateProperties = ['jobProfile', 'jobProfileApproved', 'jobProfileNotes']
|
||||
delete obj[prop] for prop in candidateProperties
|
||||
includePrivates = not publicOnly and (req.user and (req.user.isAdmin() or req.user._id.equals(@_id)))
|
||||
delete obj[prop] for prop in User.privateProperties unless includePrivates
|
||||
return obj
|
||||
|
||||
UserSchema.methods.isOnPremiumServer = ->
|
||||
@get('country') in ['china', 'brazil']
|
||||
|
||||
|
|
|
@ -21,6 +21,9 @@ global.tv4 = require 'tv4' # required for TreemaUtils to work
|
|||
global.jsondiffpatch = require 'jsondiffpatch'
|
||||
global.stripe = require('stripe')(config.stripe.secretKey)
|
||||
errors = require './server/commons/errors'
|
||||
request = require 'request'
|
||||
Promise = require 'bluebird'
|
||||
Promise.promisifyAll(request, {multiArgs: true})
|
||||
|
||||
|
||||
productionLogging = (tokens, req, res) ->
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
require '../common'
|
||||
User = require '../../../server/users/User'
|
||||
utils = require '../utils'
|
||||
_ = require 'lodash'
|
||||
Promise = require 'bluebird'
|
||||
nock = require 'nock'
|
||||
|
||||
urlLogin = getURL('/auth/login')
|
||||
urlReset = getURL('/auth/reset')
|
||||
|
@ -202,7 +202,77 @@ describe '/auth/name', ->
|
|||
expect(response.body.name).not.toBe 'joe'
|
||||
expect(response.body.name.length).toBe 4 # 'joe' and a random number
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /auth/login-facebook', ->
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([User])
|
||||
done()
|
||||
|
||||
afterEach ->
|
||||
nock.cleanAll()
|
||||
|
||||
url = getURL('/auth/login-facebook')
|
||||
it 'takes facebookID and facebookAccessToken and logs the user in', utils.wrap (done) ->
|
||||
nock('https://graph.facebook.com').get('/me').query({access_token: 'abcd'}).reply(200, { id: '1234' })
|
||||
yield new User({name: 'someone', facebookID: '1234'}).save()
|
||||
[res, body] = yield request.postAsync url, { json: { facebookID: '1234', facebookAccessToken: 'abcd' }}
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
it 'returns 422 if no token or id is provided', utils.wrap (done) ->
|
||||
[res, body] = yield request.postAsync url
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
||||
it 'returns 422 if the token is invalid', utils.wrap (done) ->
|
||||
nock('https://graph.facebook.com').get('/me').query({access_token: 'abcd'}).reply(400, {})
|
||||
yield new User({name: 'someone', facebookID: '1234'}).save()
|
||||
[res, body] = yield request.postAsync url, { json: { facebookID: '1234', facebookAccessToken: 'abcd' }}
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
||||
it 'returns 404 if the user does not already exist', utils.wrap (done) ->
|
||||
nock('https://graph.facebook.com').get('/me').query({access_token: 'abcd'}).reply(200, { id: '1234' })
|
||||
[res, body] = yield request.postAsync url, { json: { facebookID: '1234', facebookAccessToken: 'abcd' }}
|
||||
expect(res.statusCode).toBe(404)
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /auth/login-gplus', ->
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([User])
|
||||
done()
|
||||
|
||||
afterEach ->
|
||||
nock.cleanAll()
|
||||
|
||||
url = getURL('/auth/login-gplus')
|
||||
it 'takes gplusID and gplusAccessToken and logs the user in', utils.wrap (done) ->
|
||||
nock('https://www.googleapis.com').get('/oauth2/v2/userinfo').query({access_token: 'abcd'}).reply(200, { id: '1234' })
|
||||
yield new User({name: 'someone', gplusID: '1234'}).save()
|
||||
[res, body] = yield request.postAsync url, { json: { gplusID: '1234', gplusAccessToken: 'abcd' }}
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
it 'returns 422 if no token or id is provided', utils.wrap (done) ->
|
||||
[res, body] = yield request.postAsync url
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
||||
it 'returns 422 if the token is invalid', utils.wrap (done) ->
|
||||
nock('https://www.googleapis.com').get('/oauth2/v2/userinfo').query({access_token: 'abcd'}).reply(400, {})
|
||||
yield new User({name: 'someone', gplusID: '1234'}).save()
|
||||
[res, body] = yield request.postAsync url, { json: { gplusID: '1234', gplusAccessToken: 'abcd' }}
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
||||
it 'returns 404 if the user does not already exist', utils.wrap (done) ->
|
||||
nock('https://www.googleapis.com').get('/oauth2/v2/userinfo').query({access_token: 'abcd'}).reply(200, { id: '1234' })
|
||||
[res, body] = yield request.postAsync url, { json: { gplusID: '1234', gplusAccessToken: 'abcd' }}
|
||||
expect(res.statusCode).toBe(404)
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /auth/spy', ->
|
||||
beforeEach utils.wrap (done) ->
|
||||
|
|
|
@ -9,46 +9,6 @@ mockAuthEvent =
|
|||
signedRequest: 'akjsdhfjkhea.3423nkfkdsejnfkd'
|
||||
status: 'connected'
|
||||
|
||||
# Whatev, it's all public info anyway
|
||||
mockMe =
|
||||
id: '4301938'
|
||||
email: 'scott@codecombat.com'
|
||||
first_name: 'Scott'
|
||||
gender: 'male'
|
||||
last_name: 'Erickson'
|
||||
link: 'https://www.facebook.com/scott.erickson.779'
|
||||
locale: 'en_US'
|
||||
name: 'Scott Erickson'
|
||||
timezone: -7
|
||||
updated_time: '2014-05-21T04:58:06+0000'
|
||||
username: 'scott.erickson.779'
|
||||
verified: true
|
||||
work: [
|
||||
{
|
||||
employer:
|
||||
id: '167559910060759'
|
||||
name: 'CodeCombat'
|
||||
|
||||
location:
|
||||
id: '114952118516947'
|
||||
name: 'San Francisco, California'
|
||||
|
||||
start_date: '2013-02-28'
|
||||
}
|
||||
{
|
||||
end_date: '2013-01-31'
|
||||
employer:
|
||||
id: '39198748555'
|
||||
name: 'Skritter'
|
||||
|
||||
location:
|
||||
id: '106109576086811'
|
||||
name: 'Oberlin, Ohio'
|
||||
|
||||
start_date: '2008-06-01'
|
||||
}
|
||||
]
|
||||
|
||||
window.FB ?= {
|
||||
api: ->
|
||||
login: ->
|
||||
|
@ -60,24 +20,7 @@ describe 'lib/FacebookHandler.coffee', ->
|
|||
me.markToRevert()
|
||||
me.set({_id: '12345'})
|
||||
|
||||
spyOn FB, 'api'
|
||||
|
||||
facebookHandler = new FacebookHandler()
|
||||
facebookHandler.loginThroughFacebook()
|
||||
Backbone.Mediator.publish 'auth:logged-in-with-facebook', mockAuthEvent
|
||||
|
||||
expect(FB.api).toHaveBeenCalled()
|
||||
apiArgs = FB.api.calls.argsFor(0)
|
||||
expect(apiArgs[0]).toBe('/me')
|
||||
apiArgs[2](mockMe) # sending the 'response'
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request).toBeDefined()
|
||||
params = JSON.parse request.params
|
||||
expect(params.firstName).toBe(mockMe.first_name)
|
||||
expect(params.lastName).toBe(mockMe.last_name)
|
||||
expect(params.gender).toBe(mockMe.gender)
|
||||
expect(params.email).toBe(mockMe.email)
|
||||
expect(params.facebookID).toBe(mockMe.id)
|
||||
#expect(request.method).toBe('PATCH') # PATCHes are PUTs until more proxy servers recognize PATCH
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(_.string.startsWith(request.url, '/db/user/12345')).toBeTruthy()
|
||||
|
|
|
@ -2,11 +2,22 @@ AuthModal = require 'views/core/AuthModal'
|
|||
RecoverModal = require 'views/core/RecoverModal'
|
||||
|
||||
describe 'AuthModal', ->
|
||||
|
||||
modal = null
|
||||
|
||||
beforeEach ->
|
||||
modal = new AuthModal()
|
||||
modal.render()
|
||||
|
||||
afterEach ->
|
||||
modal.stopListening()
|
||||
|
||||
it 'opens the recover modal when you click the recover link', ->
|
||||
m = new AuthModal({mode: 'login'})
|
||||
m.render()
|
||||
spyOn(m, 'openModalView')
|
||||
m.$el.find('#link-to-recover').click()
|
||||
expect(m.openModalView.calls.count()).toEqual(1)
|
||||
args = m.openModalView.calls.argsFor(0)
|
||||
spyOn(modal, 'openModalView')
|
||||
modal.$el.find('#link-to-recover').click()
|
||||
expect(modal.openModalView.calls.count()).toEqual(1)
|
||||
args = modal.openModalView.calls.argsFor(0)
|
||||
expect(args[0] instanceof RecoverModal).toBeTruthy()
|
||||
|
||||
it '(demo)', ->
|
||||
jasmine.demoModal(modal)
|
|
@ -1,5 +1,7 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
User = require 'models/User'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
|
||||
BlandView = class BlandView extends CocoView
|
||||
template: ->
|
||||
|
@ -56,14 +58,18 @@ describe 'CocoView', ->
|
|||
it 'shows a login button which opens the AuthModal', ->
|
||||
button = view.$el.find('.login-btn')
|
||||
expect(button.length).toBe(3) # including the two in the links section
|
||||
spyOn(view, 'openModalView').and.callFake (modal) -> expect(modal.mode).toBe('login')
|
||||
spyOn(view, 'openModalView').and.callFake (modal) ->
|
||||
expect(modal instanceof AuthModal).toBe(true)
|
||||
modal.stopListening()
|
||||
button.click()
|
||||
expect(view.openModalView).toHaveBeenCalled()
|
||||
|
||||
it 'shows a create account button which opens the AuthModal', ->
|
||||
it 'shows a create account button which opens the CreateAccountModal', ->
|
||||
button = view.$el.find('#create-account-btn')
|
||||
expect(button.length).toBe(1)
|
||||
spyOn(view, 'openModalView').and.callFake (modal) -> expect(modal.mode).toBe('signup')
|
||||
spyOn(view, 'openModalView').and.callFake (modal) ->
|
||||
expect(modal instanceof CreateAccountModal).toBe(true)
|
||||
modal.stopListening()
|
||||
button.click()
|
||||
expect(view.openModalView).toHaveBeenCalled()
|
||||
|
||||
|
|
210
test/app/views/core/CreateAccountModal.spec.coffee
Normal file
210
test/app/views/core/CreateAccountModal.spec.coffee
Normal file
|
@ -0,0 +1,210 @@
|
|||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
forms = require 'core/forms'
|
||||
|
||||
describe 'CreateAccountModal', ->
|
||||
|
||||
modal = null
|
||||
|
||||
beforeEach ->
|
||||
modal = new CreateAccountModal()
|
||||
modal.render()
|
||||
modal.render = _.noop
|
||||
jasmine.demoModal(modal)
|
||||
window.gapi =
|
||||
client:
|
||||
load: _.noop
|
||||
window.FB =
|
||||
login: _.noop
|
||||
api: _.noop
|
||||
|
||||
afterEach ->
|
||||
modal.stopListening()
|
||||
|
||||
describe 'clicking the save button', ->
|
||||
|
||||
it 'fails if nothing is in the form, showing errors for email and password', ->
|
||||
modal.$('form').each (i, el) -> el.reset()
|
||||
modal.$('form:first').submit()
|
||||
expect(jasmine.Ajax.requests.all().length).toBe(0)
|
||||
expect(modal.$('.has-error').length).toBe(2)
|
||||
|
||||
it 'fails if email is missing', ->
|
||||
modal.$('form').each (i, el) -> el.reset()
|
||||
forms.objectToForm(modal.$el, { name: 'Name', password: 'xyzzy' })
|
||||
modal.$('form:first').submit()
|
||||
expect(jasmine.Ajax.requests.all().length).toBe(0)
|
||||
expect(modal.$('.has-error').length).toBeTruthy()
|
||||
|
||||
it 'signs up if only email and password is provided', ->
|
||||
modal.$('form').each (i, el) -> el.reset()
|
||||
forms.objectToForm(modal.$el, { email: 'some@email.com', password: 'xyzzy' })
|
||||
modal.$('form:first').submit()
|
||||
requests = jasmine.Ajax.requests.all()
|
||||
expect(requests.length).toBe(1)
|
||||
expect(modal.$el.has('.has-warning').length).toBeFalsy()
|
||||
expect(modal.$('#signup-button').is(':disabled')).toBe(true)
|
||||
|
||||
describe 'and a class code is entered', ->
|
||||
|
||||
beforeEach ->
|
||||
modal.$('form').each (i, el) -> el.reset()
|
||||
forms.objectToForm(modal.$el, { email: 'some@email.com', password: 'xyzzy', classCode: 'qwerty' })
|
||||
modal.$('form:first').submit()
|
||||
expect(jasmine.Ajax.requests.all().length).toBe(1)
|
||||
|
||||
it 'checks for Classroom existence if a class code was entered', ->
|
||||
jasmine.demoModal(modal)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/classroom?code=qwerty')
|
||||
|
||||
it 'has not hidden the close-modal button', ->
|
||||
expect(modal.$('#close-modal').css('display')).toBe('block')
|
||||
|
||||
it 'continues with signup if the Classroom exists', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({status: 200, responseText: JSON.stringify({})})
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/db/user')
|
||||
expect(request.method).toBe('POST')
|
||||
|
||||
it 'fails and shows an error if the Classroom does not exist', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({status: 404, responseText: JSON.stringify({})})
|
||||
expect(jasmine.Ajax.requests.all().length).toBe(1)
|
||||
expect(modal.$el.has('.has-error').length).toBeTruthy()
|
||||
|
||||
|
||||
describe 'clicking the gplus button', ->
|
||||
|
||||
signupButton = null
|
||||
|
||||
beforeEach ->
|
||||
application.gplusHandler.trigger 'render-login-buttons'
|
||||
signupButton = modal.$('#gplus-signup-btn')
|
||||
expect(signupButton.attr('disabled')).toBeFalsy()
|
||||
signupButton.click()
|
||||
application.gplusHandler.fakeGPlusLogin()
|
||||
application.gplusHandler.trigger 'person-loaded', { firstName: 'Mr', lastName: 'Bean', gplusID: 'abcd', email: 'some@email.com' }
|
||||
|
||||
it 'checks to see if the user already exists in our system', ->
|
||||
requests = jasmine.Ajax.requests.all()
|
||||
expect(requests.length).toBe(1)
|
||||
expect(signupButton.attr('disabled')).toBeTruthy()
|
||||
|
||||
|
||||
describe 'and finding the given person is already a user', ->
|
||||
beforeEach ->
|
||||
expect(modal.$('#gplus-account-exists-row').hasClass('hide')).toBe(true)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({status: 200, responseText: JSON.stringify({_id: 'existinguser'})})
|
||||
|
||||
it 'shows a message saying you are connected with Google+, with a button for logging in', ->
|
||||
expect(modal.$('#gplus-account-exists-row').hasClass('hide')).toBe(false)
|
||||
loginBtn = modal.$('#gplus-login-btn')
|
||||
expect(loginBtn.attr('disabled')).toBeFalsy()
|
||||
loginBtn.click()
|
||||
expect(loginBtn.attr('disabled')).toBeTruthy()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('POST')
|
||||
expect(request.params).toBe('gplusID=abcd&gplusAccessToken=1234')
|
||||
expect(request.url).toBe('/auth/login-gplus')
|
||||
|
||||
describe 'and the user finishes signup anyway with new info', ->
|
||||
beforeEach ->
|
||||
forms.objectToForm(modal.$el, { email: 'some@email.com', schoolName: 'Hogwarts' })
|
||||
modal.$('form:first').submit()
|
||||
|
||||
it 'upserts the values to the new user', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234')
|
||||
expect(JSON.parse(request.params).schoolName).toBe('Hogwarts')
|
||||
|
||||
|
||||
describe 'and finding the given person is not yet a user', ->
|
||||
beforeEach ->
|
||||
expect(modal.$('#gplus-logged-in-row').hasClass('hide')).toBe(true)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({status: 404})
|
||||
|
||||
it 'shows a message saying you are connected with Google+', ->
|
||||
expect(modal.$('#gplus-logged-in-row').hasClass('hide')).toBe(false)
|
||||
|
||||
describe 'and the user finishes signup', ->
|
||||
beforeEach ->
|
||||
modal.$('form:first').submit()
|
||||
|
||||
it 'creates the user with the gplus attributes', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234')
|
||||
expect(_.string.startsWith(request.url, '/db/user')).toBe(true)
|
||||
expect(modal.$('#signup-button').is(':disabled')).toBe(true)
|
||||
|
||||
|
||||
describe 'clicking the facebook button', ->
|
||||
|
||||
signupButton = null
|
||||
|
||||
beforeEach ->
|
||||
Backbone.Mediator.publish 'auth:facebook-api-loaded', {}
|
||||
signupButton = modal.$('#facebook-signup-btn')
|
||||
expect(signupButton.attr('disabled')).toBeFalsy()
|
||||
signupButton.click()
|
||||
application.facebookHandler.fakeFacebookLogin()
|
||||
application.facebookHandler.trigger 'person-loaded', { firstName: 'Mr', lastName: 'Bean', facebookID: 'abcd', email: 'some@email.com' }
|
||||
|
||||
it 'checks to see if the user already exists in our system', ->
|
||||
requests = jasmine.Ajax.requests.all()
|
||||
expect(requests.length).toBe(1)
|
||||
expect(signupButton.attr('disabled')).toBeTruthy()
|
||||
|
||||
|
||||
describe 'and finding the given person is already a user', ->
|
||||
beforeEach ->
|
||||
expect(modal.$('#facebook-account-exists-row').hasClass('hide')).toBe(true)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({status: 200, responseText: JSON.stringify({_id: 'existinguser'})})
|
||||
|
||||
it 'shows a message saying you are connected with Facebook, with a button for logging in', ->
|
||||
expect(modal.$('#facebook-account-exists-row').hasClass('hide')).toBe(false)
|
||||
loginBtn = modal.$('#facebook-login-btn')
|
||||
expect(loginBtn.attr('disabled')).toBeFalsy()
|
||||
loginBtn.click()
|
||||
expect(loginBtn.attr('disabled')).toBeTruthy()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('POST')
|
||||
expect(request.params).toBe('facebookID=abcd&facebookAccessToken=1234')
|
||||
expect(request.url).toBe('/auth/login-facebook')
|
||||
|
||||
describe 'and the user finishes signup anyway with new info', ->
|
||||
beforeEach ->
|
||||
forms.objectToForm(modal.$el, { email: 'some@email.com', schoolName: 'Hogwarts' })
|
||||
modal.$('form:first').submit()
|
||||
|
||||
it 'upserts the values to the new user', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234')
|
||||
expect(JSON.parse(request.params).schoolName).toBe('Hogwarts')
|
||||
|
||||
|
||||
describe 'and finding the given person is not yet a user', ->
|
||||
beforeEach ->
|
||||
expect(modal.$('#facebook-logged-in-row').hasClass('hide')).toBe(true)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({status: 404})
|
||||
|
||||
it 'shows a message saying you are connected with Facebook', ->
|
||||
expect(modal.$('#facebook-logged-in-row').hasClass('hide')).toBe(false)
|
||||
|
||||
describe 'and the user finishes signup', ->
|
||||
beforeEach ->
|
||||
modal.$('form:first').submit()
|
||||
|
||||
it 'creates the user with the facebook attributes', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.method).toBe('PUT')
|
||||
expect(_.string.startsWith(request.url, '/db/user')).toBe(true)
|
||||
expect(modal.$('#signup-button').is(':disabled')).toBe(true)
|
||||
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234')
|
Loading…
Reference in a new issue