Merge iDEAL link for nl on home

This commit is contained in:
Nick Winter 2016-03-18 11:16:28 -07:00
commit 13928884b0
40 changed files with 697 additions and 728 deletions

View file

@ -7,5 +7,5 @@ module.exports = class TrialRequestCollection extends CocoCollection
fetchOwn: (options) ->
options = _.extend({data: {}}, options)
options.url = _.result(@, 'url') + '/-/own'
options.data.applicant = me.id
@fetch(options)

View file

@ -5,7 +5,6 @@ module.exports = class CocoRouter extends Backbone.Router
initialize: ->
# http://nerds.airbnb.com/how-to-add-google-analytics-page-tracking-to-57536
@bind 'route', @_trackPageView
Backbone.Mediator.subscribe 'auth:gplus-api-loaded', @onGPlusAPILoaded, @
Backbone.Mediator.subscribe 'router:navigate', @onNavigate, @
@initializeSocialMediaServices = _.once @initializeSocialMediaServices
@ -170,7 +169,6 @@ module.exports = class CocoRouter extends Backbone.Router
$('#page-container').empty().append view.el
window.currentView = view
@activateTab()
@renderLoginButtons() if view.usesSocialMedia
view.afterInsert()
view.didReappear()
@ -187,21 +185,19 @@ module.exports = class CocoRouter extends Backbone.Router
$('body')[0].scrollTop = 0
), 10
onGPlusAPILoaded: =>
@renderLoginButtons()
initializeSocialMediaServices: ->
return if application.testing or application.demoing
require('core/services/facebook')()
application.facebookHandler.loadAPI()
application.gplusHandler.loadAPI()
require('core/services/twitter')()
renderLoginButtons: =>
renderSocialButtons: =>
# TODO: Refactor remaining services to Handlers, use loadAPI success callback
@initializeSocialMediaServices()
$('.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
application.facebookHandler.renderButtons()
application.gplusHandler.renderButtons()
twttr?.widgets?.load?()
application.gplusHandler.renderLoginButtons()
activateTab: ->
base = _.string.words(document.location.pathname[1..], '/')[0]

View file

@ -49,6 +49,7 @@ module.exports.loginUser = (userObject, failure=genericFailure, nextURL=null) ->
jqxhr.fail(failure)
module.exports.logoutUser = ->
# TODO: Refactor to use User.logout
FB?.logout?()
callback = ->
location = _.result(currentView, 'logoutRedirectURL')

View file

@ -21,7 +21,7 @@ module.exports.formToObject = ($el, options) ->
obj
module.exports.objectToForm = ($el, obj, options={}) ->
options = _.extend({ overwriteExisting: false })
options = _.extend({ overwriteExisting: false }, options)
inputs = $('input, textarea, select', $el)
for input in inputs
input = $(input)
@ -84,7 +84,8 @@ module.exports.setErrorToProperty = setErrorToProperty = (el, property, message,
module.exports.scrollToFirstError = ($el=$('body')) ->
$first = $el.find('.has-error, .alert-danger, .error-help-block, .has-warning, .alert-warning, .warning-help-block').filter(':visible').first()
$('html, body').animate({ scrollTop: $first.offset().top - 20 }, 300)
if $first.length
$('html, body').animate({ scrollTop: $first.offset().top - 20 }, 300)
module.exports.clearFormAlerts = (el) ->
$('.has-error', el).removeClass('has-error')

View file

@ -1,41 +0,0 @@
module.exports = initializeFacebook = ->
# Additional JS functions here
window.fbAsyncInit = ->
FB.init
appId: (if document.location.origin is 'http://localhost:3000' then '607435142676437' else '148832601965463') # App ID
channelUrl: document.location.origin + '/channel.html' # Channel File
status: true # check login status
cookie: true # enable cookies to allow the server to access the session
xfbml: true # parse XFBML
Backbone.Mediator.publish 'auth:facebook-api-loaded', {}
# This is fired for any auth related change, such as login, logout or session refresh.
FB.Event.subscribe 'auth.authResponseChange', (response) ->
# Here we specify what we do with the response anytime this event occurs.
if response.status is 'connected'
# They have logged in to the app.
Backbone.Mediator.publish 'auth:logged-in-with-facebook', response: response
#else if response.status is 'not_authorized'
# #
#else
# #
# Load the SDK asynchronously
((d) ->
js = undefined
id = 'facebook-jssdk'
ref = d.getElementsByTagName('script')[0]
return if d.getElementById(id)
js = d.createElement('script')
js.id = id
js.async = true
js.src = '//connect.facebook.net/en_US/all.js'
#js.src = '//connect.facebook.net/en_US/all/debug.js'
ref.parentNode.insertBefore js, ref
return
) document

View file

@ -13,44 +13,94 @@ userPropsToSave =
module.exports = FacebookHandler = class FacebookHandler extends CocoClass
subscriptions:
'auth:logged-in-with-facebook': 'onFacebookLoggedIn'
loggedIn: false
token: -> @authResponse?.accessToken
fakeFacebookLogin: ->
@onFacebookLoggedIn({
response:
authResponse: { accessToken: '1234' }
})
startedLoading: false
apiLoaded: false
connected: false
person: null
fakeAPI: ->
window.FB =
login: (cb, options) ->
cb({status: 'connected', authResponse: { accessToken: '1234' }})
api: (url, options, cb) ->
cb({
first_name: 'Mr'
last_name: 'Bean'
id: 'abcd'
email: 'some@email.com'
})
onFacebookLoggedIn: (e) ->
# user is logged in also when the page first loads, so check to see
# if we really need to do the lookup
@loggedIn = false
@authResponse = e.response.authResponse
for fbProp, userProp of userPropsToSave
unless me.get(userProp)
@loggedIn = true
break
@startedLoading = true
@apiLoaded = true
@trigger 'logged-into-facebook'
loginThroughFacebook: ->
if @loggedIn
return true
loadAPI: (options={}) ->
options.success ?= _.noop
options.context ?= options
if @apiLoaded
options.success.bind(options.context)()
else
FB.login ((response) ->
console.log 'Received FB login response:', response
), scope: 'email'
@once 'load-api', options.success, options.context
if not @startedLoading
# Load the SDK asynchronously
@startedLoading = true
((d) ->
js = undefined
id = 'facebook-jssdk'
ref = d.getElementsByTagName('script')[0]
return if d.getElementById(id)
js = d.createElement('script')
js.id = id
js.async = true
js.src = '//connect.facebook.net/en_US/all.js'
#js.src = '//connect.facebook.net/en_US/all/debug.js'
ref.parentNode.insertBefore js, ref
return
)(document)
loadPerson: ->
window.fbAsyncInit = =>
FB.init
appId: (if document.location.origin is 'http://localhost:3000' then '607435142676437' else '148832601965463') # App ID
channelUrl: document.location.origin + '/channel.html' # Channel File
cookie: true # enable cookies to allow the server to access the session
xfbml: true # parse XFBML
FB.getLoginStatus (response) =>
if response.status is 'connected'
@connected = true
@authResponse = response.authResponse
@trigger 'connect', { response: response }
@apiLoaded = true
@trigger 'load-api'
connect: (options={}) ->
options.success ?= _.noop
options.context ?= options
FB.login ((response) =>
if response.status is 'connected'
@connected = true
@authResponse = response.authResponse
@trigger 'connect', { response: response }
options.success.bind(options.context)()
), scope: 'email'
loadPerson: (options={}) ->
options.success ?= _.noop
options.context ?= options
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
@trigger 'load-person', attrs
options.success.bind(options.context)(attrs)
renderButtons: ->
setTimeout(FB.XFBML.parse, 10) if FB?.XFBML?.parse # Handles FB login and Like

View file

@ -20,109 +20,122 @@ 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()
token: -> @accessToken?.access_token
startedLoading: false
apiLoaded: false
connected: false
person: null
loadAPI: ->
return if @loadedAPI
@loadedAPI = true
(=>
fakeAPI: ->
window.gapi =
client:
load: (api, version, cb) -> cb()
plus:
people:
get: -> {
execute: (cb) ->
cb({
name: {
givenName: 'Mr'
familyName: 'Bean'
}
id: 'abcd'
emails: [{value: 'some@email.com'}]
})
}
auth:
authorize: (opts, cb) ->
cb({access_token: '1234'})
@startedLoading = true
@apiLoaded = true
fakeConnect: ->
@accessToken = {access_token: '1234'}
@trigger 'connect'
loadAPI: (options={}) ->
options.success ?= _.noop
options.context ?= options
if @apiLoaded
options.success.bind(options.context)()
else
@once 'load-api', options.success, options.context
if not @startedLoading
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
)()
@startedLoading = true
window.onGPlusLoaded = =>
@apiLoaded = true
if @accessToken and me.get('gplusID')
# We need to check the current state, given our access token
gapi.auth.setToken 'token', @accessToken
session_state = @accessToken.session_state
gapi.auth.checkSessionState {client_id: clientID, session_state: session_state}, (connected) =>
@connected = connected
@trigger 'load-api'
else
@connected = false
@trigger 'load-api'
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
gapi.auth.setToken 'token', @accessToken
session_state = @accessToken.session_state
gapi.auth.checkSessionState({client_id: clientID, session_state: session_state}, @onCheckedSessionState)
else
# If we ran checkSessionState, it might return true, that the user is logged into Google, but has not authorized us
@loggedIn = false
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'
reauthorize: ->
params =
'client_id' : clientID
'scope' : scope
gapi.auth.authorize params, @onGPlusLogin
fakeGPlusLogin: ->
@onGPlusLogin({
access_token: '1234'
})
onGPlusLogin: (e) ->
return unless e.access_token
@loggedIn = true
Backbone.Mediator.publish 'auth:logged-in-with-gplus', e
try
connect: (options={}) ->
options.success ?= _.noop
options.context ?= options
authOptions = {
client_id: clientID
scope: 'https://www.googleapis.com/auth/plus.login email'
}
gapi.auth.authorize authOptions, (e) =>
return unless e.access_token
@connected = true
try
# Without removing this, we sometimes get a cross-domain error
d = _.omit(e, 'g-oauth-window')
storage.save(GPLUS_TOKEN_KEY, d, 0)
catch e
console.error 'Unable to save G+ token key', e
@accessToken = e
@trigger 'logged-in'
@trigger 'logged-into-google'
d = _.omit(e, 'g-oauth-window')
storage.save(GPLUS_TOKEN_KEY, d, 0)
catch e
console.error 'Unable to save G+ token key', e
@accessToken = e
@trigger 'connect'
options.success.bind(options.context)()
loadPerson: (options={}) ->
@reloadOnLogin = options.reloadOnLogin
options.success ?= _.noop
options.context ?= options
# email and profile data loaded separately
gapi.client.load('plus', 'v1', =>
gapi.client.plus.people.get({userId: 'me'}).execute(@onPersonReceived))
gapi.client.load 'plus', 'v1', =>
gapi.client.plus.people.get({userId: 'me'}).execute (r) =>
attrs = {}
for gpProp, userProp of userPropsToSave
keys = gpProp.split('.')
value = r
for key in keys
value = value[key]
if value
attrs[userProp] = value
if r.emails?.length
attrs.email = r.emails[0].value
@trigger 'load-person', attrs
options.success.bind(options.context)(attrs)
onPersonReceived: (r) =>
attrs = {}
for gpProp, userProp of userPropsToSave
keys = gpProp.split('.')
value = r
for key in keys
value = value[key]
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)
if r.emails?.length
attrs.email = r.emails[0].value
@trigger 'person-loaded', attrs
renderButtons: ->
return false unless gapi?.plusone?
gapi.plusone.go?() # Handles +1 button
# Friends logic, not in use
loadFriends: (friendsCallback) ->
return friendsCallback() unless @loggedIn
expiresIn = if @accessToken then parseInt(@accessToken.expires_at) - new Date().getTime()/1000 else -1
@ -133,3 +146,9 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
@listenToOnce(@, 'logged-in', onReauthorized)
else
onReauthorized()
reauthorize: ->
params =
'client_id' : clientID
'scope' : scope
gapi.auth.authorize params, @onGPlusLogin

View file

@ -194,6 +194,9 @@ module.exports = class User extends CocoModel
isOnPremiumServer: ->
me.get('country') in ['china', 'brazil']
# Function meant for "me"
spy: (user, options={}) ->
user = user.id or user # User instance, user ID, email or username
options.url = '/auth/spy'
@ -207,6 +210,18 @@ module.exports = class User extends CocoModel
options.type = 'POST'
@fetch(options)
logout: (options={}) ->
options.type = 'POST'
options.url = '/auth/logout'
FB?.logout?()
options.success ?= ->
location = _.result(currentView, 'logoutRedirectURL')
if location
window.location = location
else
window.location.reload()
@fetch(options)
fetchGPlusUser: (gplusID, options={}) ->
options.data ?= {}
options.data.gplusID = gplusID

View file

@ -4,40 +4,10 @@ module.exports =
'auth:me-synced': c.object {required: ['me']},
me: {type: 'object'}
'auth:facebook-api-loaded': c.object {}
'auth:logging-in-with-facebook': c.object {}
'auth:signed-up': c.object {}
'auth:logging-out': c.object {}
'auth:logged-in-with-facebook': c.object {title: 'Facebook logged in', description: 'Published when you successfully logged in with Facebook', required: ['response']},
response:
type: 'object'
properties:
status: {type: 'string'}
authResponse:
type: 'object'
properties:
accessToken: {type: 'string'}
expiresIn: {type: 'number'}
signedRequest: {type: 'string'}
userID: {type: 'string'}
'auth:linkedin-api-loaded': c.object {}
'auth:gplus-api-loaded': c.object {}
'auth:logging-in-with-gplus': c.object {}
'auth:logged-in-with-gplus':
title: 'G+ logged in'
description: 'Published when you successfully logged in with G+'
type: 'object'
required: ['access_token']
properties:
access_token: {type: 'string'}
# Could be some other stuff
'auth:log-in-with-github': c.object {}

View file

@ -201,6 +201,10 @@ $forest: #20572B
border: 1px solid $purple
color: $purple
.btn-burgandy
background-color: $burgandy
color: white
.btn-lg
font-size: 18px
@ -217,6 +221,43 @@ $forest: #20572B
a.btn-primary-alt
color: $navy
// Spacing
// Copied spacing classes in bootstrap v4 alpha 2
// http://v4-alpha.getbootstrap.com/components/utilities/#spacing
$spacer: 1rem !default
$spacer-x: $spacer !default
$spacer-y: $spacer !default
$spacers: ( 0: ( x: 0, y: 0 ), 1: ( x: $spacer-x, y: $spacer-y ), 2: ( x: ($spacer-x * 1.5), y: ($spacer-y * 1.5) ), 3: ( x: ($spacer-x * 3), y: ($spacer-y * 3) ) ) !default
.m-x-auto
margin-right: auto !important
margin-left: auto !important
@each $prop, $abbrev in (margin: m, padding: p)
@each $size, $lengths in $spacers
$length-x: map-get($lengths, x)
$length-y: map-get($lengths, y)
.#{$abbrev}-a-#{$size}
#{$prop}: $length-y $length-x !important // a = All sides
.#{$abbrev}-t-#{$size}
#{$prop}-top: $length-y !important
.#{$abbrev}-r-#{$size}
#{$prop}-right: $length-x !important
.#{$abbrev}-b-#{$size}
#{$prop}-bottom: $length-y !important
.#{$abbrev}-l-#{$size}
#{$prop}-left: $length-x !important
// Axes
.#{$abbrev}-x-#{$size}
#{$prop}-right: $length-x !important
#{$prop}-left: $length-x !important
.#{$abbrev}-y-#{$size}
#{$prop}-top: $length-y !important
#{$prop}-bottom: $length-y !important
// base-flat.jade

View file

@ -2,15 +2,9 @@
background-color: #eee
margin: 0 20px
padding: 0
h2
#test-h2
background: #add8e6
font-family: Arial, Geneva, sans-serif
padding: 20px
font-weight: bold
#test-wrapper
width: 78%
#test-nav
width: 20%

View file

@ -77,7 +77,7 @@
p If this is showing, you dun goofed
block footer
#character-lineup.text-center
#character-lineup.text-center.m-t-3
img(src="/images/pages/home/character_lineup.png")
.container-fluid
#footer.small

View file

@ -42,7 +42,7 @@
#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")
#gplus-login-btn.btn.btn-danger.btn-lg.btn-illustrated.network-login(disabled=true)
#gplus-login-btn.btn.btn-danger.btn-lg.btn-illustrated.network-login
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

View file

@ -6,7 +6,7 @@
div#close-modal
span.glyphicon.glyphicon-remove
.auth-form-content
form.auth-form-content
if view.options.showRequiredError
#required-error-alert.alert.alert-success
span(data-i18n="signup.required")
@ -16,20 +16,19 @@
#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)
.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
@ -37,7 +36,7 @@
#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)
#gplus-signup-btn.btn.btn-danger.btn-lg.btn-illustrated.network-login
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
@ -59,52 +58,51 @@
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#class-code-input.input-large.form-control(name="classCode", value=view.previousFormInputs.classCode || '', tabindex=5)
.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)
.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")
.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#class-code-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")
.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")
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")

View file

@ -1,11 +1,11 @@
extends /templates/core/modal-base
block modal-header-content
h3 #{confirmTitle}
h3= view.title
block modal-body-content
p #{confirmBody}
p= view.body
block modal-footer-content
button.btn.btn-secondary#decline-button(type="button", data-dismiss="modal") #{confirmDecline}
button.btn.btn-primary#confirm-button(type="button", data-dismiss=closeOnConfirm === true ? "modal" : undefined) #{confirmConfirm}
button.btn.btn-secondary#decline-button(type="button", data-dismiss="modal")= view.decline
button.btn.btn-primary#confirm-button(type="button", data-dismiss=closeOnConfirm === true ? "modal" : undefined)= view.confirm

View file

@ -1,4 +1,4 @@
h2 Testing Page
h2#test-h2 Testing Page
ol.breadcrumb
for path in parentFolders
@ -6,26 +6,29 @@ ol.breadcrumb
a(href=path.url)= path.name
li.active= currentFolder
#test-wrapper.well.pull-left
#demo-area
#testing-area
.nav.nav-pills.nav-stacked.pull-right.well#test-nav
if view.demosOn
button#hide-demos-btn.btn.btn-danger.btn-block Hide Demos
else
button#show-demos-btn.btn.btn-info.btn-block Show Demos
hr
for child in children
li(class=child.type)
a(href=child.url).small
if child.type == 'folder'
span.glyphicon.glyphicon-folder-close
else
span.glyphicon.glyphicon-file
span.spl= child.name
if child.type == 'folder'
strong (#{child.size})
.container-fluid
.row
.col-md-8
#test-wrapper.well
#demo-area
#testing-area
.clearfix
.col-md-4.hidden-sm.hidden-xs
.nav.nav-pills.nav-stacked.well#test-nav
if view.demosOn
button#hide-demos-btn.btn.btn-danger.btn-block Hide Demos
else
button#show-demos-btn.btn.btn-info.btn-block Show Demos
hr
for child in children
li(class=child.type)
a(href=child.url).small
if child.type == 'folder'
span.glyphicon.glyphicon-folder-close
else
span.glyphicon.glyphicon-file
span.spl= child.name
if child.type == 'folder'
strong (#{child.size})

View file

@ -81,6 +81,7 @@ module.exports = TestView = class TestView extends RootView
jasmine.Ajax.requests.reset()
Backbone.Mediator.init()
Backbone.Mediator.setValidationEnabled false
spyOn(application.tracker, 'trackEvent')
# TODO Stubbify more things
# * document.location
# * firebase

View file

@ -64,10 +64,10 @@ module.exports = class AccountSettingsView extends CocoView
onClickDeleteAccountButton: (e) ->
@validateCredentialsForDestruction @$el.find('#delete-account-form'), =>
renderData =
confirmTitle: 'Are you really sure?'
confirmBody: 'This will completely delete your account. This action CANNOT be undone. Are you entirely sure?'
confirmDecline: 'Cancel'
confirmConfirm: 'DELETE Your Account'
title: 'Are you really sure?'
body: 'This will completely delete your account. This action CANNOT be undone. Are you entirely sure?'
decline: 'Cancel'
confirm: 'DELETE Your Account'
confirmModal = new ConfirmModal renderData
confirmModal.on 'confirm', @deleteAccount
@openModalView confirmModal
@ -75,10 +75,10 @@ module.exports = class AccountSettingsView extends CocoView
onClickResetProgressButton: ->
@validateCredentialsForDestruction @$el.find('#reset-progress-form'), =>
renderData =
confirmTitle: 'Are you really sure?'
confirmBody: 'This will completely erase your progress: code, levels, achievements, earned gems, and course work. This action CANNOT be undone. Are you entirely sure?'
confirmDecline: 'Cancel'
confirmConfirm: 'Erase ALL Progress'
title: 'Are you really sure?'
body: 'This will completely erase your progress: code, levels, achievements, earned gems, and course work. This action CANNOT be undone. Are you entirely sure?'
decline: 'Cancel'
confirm: 'Erase ALL Progress'
confirmModal = new ConfirmModal renderData
confirmModal.on 'confirm', @resetProgress
@openModalView confirmModal

View file

@ -11,29 +11,19 @@ module.exports = class AuthModal extends ModalView
template: template
events:
# login buttons
'click #switch-to-signup-btn': 'onSignupInstead'
'click #github-login-button': 'onGitHubLoginClicked'
'submit form': 'onSubmitForm' # handles both submit buttons
'submit form': 'onSubmitForm'
'keyup #name': 'onNameChange'
'click #gplus-login-btn': 'onClickGPlusLogin'
'click #gplus-login-btn': 'onClickGPlusLoginButton'
'click #facebook-login-btn': 'onClickFacebookLoginButton'
'click #close-modal': 'hide'
subscriptions:
'errors:server-error': 'onServerError'
'auth:facebook-api-loaded': 'onFacebookAPILoaded'
# 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()
@ -47,19 +37,11 @@ module.exports = class AuthModal extends ModalView
afterRender: ->
super()
@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
onGPlusRenderLoginButtons: ->
@$('#gplus-login-btn').attr('disabled', false)
onFacebookAPILoaded: ->
@$('#facebook-login-btn').attr('disabled', false)
onSignupInstead: (e) ->
CreateAccountModal = require('./CreateAccountModal')
modal = new CreateAccountModal({initialValues: forms.formToObject @$el})
@ -81,61 +63,71 @@ module.exports = class AuthModal extends ModalView
# Google Plus
onClickGPlusLogin: ->
@clickedGPlusLogin = true
onGPlusHandlerLoggedIntoGoogle: ->
return unless @clickedGPlusLogin
application.gplusHandler.loadPerson()
onClickGPlusLoginButton: ->
btn = @$('#gplus-login-btn')
btn.find('.sign-in-blurb').text($.i18n.t('login.logging_in'))
btn.attr('disabled', true)
onGPlusPersonLoaded: (gplusAttrs) ->
existingUser = new User()
existingUser.fetchGPlusUser(gplusAttrs.gplusID, {
success: =>
me.loginGPlusUser(gplusAttrs.gplusID, {
success: -> window.location.reload()
error: @onGPlusLoginError
application.gplusHandler.loadAPI({
context: @
success: ->
btn.attr('disabled', false)
application.gplusHandler.connect({
context: @
success: ->
btn.find('.sign-in-blurb').text($.i18n.t('login.logging_in'))
btn.attr('disabled', true)
application.gplusHandler.loadPerson({
context: @
success: (gplusAttrs) ->
existingUser = new User()
existingUser.fetchGPlusUser(gplusAttrs.gplusID, {
success: =>
me.loginGPlusUser(gplusAttrs.gplusID, {
success: -> window.location.reload()
error: @onGPlusLoginError
})
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...)
errors.showNotyNetworkError(arguments...)
# Facebook
onClickFacebookLoginButton: ->
@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
application.facebookHandler.loadAPI({
context: @
success: ->
btn.attr('disabled', false)
application.facebookHandler.connect({
context: @
success: ->
btn.find('.sign-in-blurb').text($.i18n.t('login.logging_in'))
btn.attr('disabled', true)
application.facebookHandler.loadPerson({
context: @
success: (facebookAttrs) ->
existingUser = new User()
existingUser.fetchFacebookUser(facebookAttrs.facebookID, {
success: =>
me.loginFacebookUser(facebookAttrs.facebookID, {
success: -> window.location.reload()
error: @onFacebookLoginError
})
error: @onFacebookLoginError
})
})
})
error: @onFacebookLoginError
})
onFacebookLoginError: =>
btn = @$('#facebook-login-btn')
btn.find('.sign-in-blurb').text($.i18n.t('login.sign_in_with_facebook'))

View file

@ -444,6 +444,9 @@ module.exports = class CocoView extends Backbone.View
scrollToLink: (link, speed=300) ->
scrollTo = $(link).offset().top
$('html, body').animate({ scrollTop: scrollTo }, speed)
scrollToTop: (speed=300) ->
$('html, body').animate({ scrollTop: 0 }, speed)
toggleFullscreen: (e) ->
# https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode?redirectlocale=en-US&redirectslug=Web/Guide/DOM/Using_full_screen_mode

View file

@ -7,8 +7,6 @@ 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'
@ -24,37 +22,21 @@ module.exports = class CreateAccountModal extends ModalView
'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
@ -151,28 +133,35 @@ module.exports = class CreateAccountModal extends ModalView
# 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, {
application.gplusHandler.loadAPI({
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)
success: ->
btn.attr('disabled', false)
application.gplusHandler.connect({
context: @
success: ->
btn.find('.sign-in-blurb').text($.i18n.t('signup.creating'))
btn.attr('disabled', true)
application.gplusHandler.loadPerson({
context: @
success: (@gplusAttrs) ->
existingUser = new User()
existingUser.fetchGPlusUser(@gplusAttrs.gplusID, {
context: @
complete: ->
@$('#email-password-row').remove()
success: =>
@$('#gplus-account-exists-row').removeClass('hide')
error: (user, jqxhr) =>
if jqxhr.status is 404
@$('#gplus-logged-in-row').toggleClass('hide')
else
errors.showNotyNetworkError(jqxhr)
})
})
})
})
onClickGPlusLoginButton: ->
@ -190,31 +179,35 @@ module.exports = class CreateAccountModal extends ModalView
# 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)
application.facebookHandler.loadAPI({
context: @
success: ->
btn.attr('disabled', false)
application.facebookHandler.connect({
context: @
success: ->
btn.find('.sign-in-blurb').text($.i18n.t('signup.creating'))
btn.attr('disabled', true)
application.facebookHandler.loadPerson({
context: @
success: (@facebookAttrs) ->
existingUser = new User()
existingUser.fetchFacebookUser(@facebookAttrs.facebookID, {
context: @
complete: ->
@$('#email-password-row').remove()
success: =>
@$('#facebook-account-exists-row').removeClass('hide')
error: (user, jqxhr) =>
if jqxhr.status is 404
@$('#facebook-logged-in-row').toggleClass('hide')
else
errors.showNotyNetworkError(jqxhr)
})
})
})
})
onClickFacebookLoginButton: ->

View file

@ -99,11 +99,6 @@ module.exports = class RootView extends CocoView
#location.hash = hash
@renderScrollbar()
getRenderData: ->
c = super()
c.usesSocialMedia = @usesSocialMedia
c
afterRender: ->
if @$el.find('#site-nav').length # hack...
@$el.addClass('site-chrome')

View file

@ -85,10 +85,10 @@ module.exports = class AchievementEditView extends RootView
confirmRecalculation: (e, all=false) ->
renderData =
'confirmTitle': 'Are you really sure?'
'confirmBody': "This will trigger recalculation of #{if all then 'all achievements' else 'the achievement'} for all users. Are you really sure you want to go down this path?"
'confirmDecline': 'Not really'
'confirmConfirm': 'Definitely'
title: 'Are you really sure?'
body: "This will trigger recalculation of #{if all then 'all achievements' else 'the achievement'} for all users. Are you really sure you want to go down this path?"
decline: 'Not really'
confirm: 'Definitely'
confirmModal = new ConfirmModal renderData
confirmModal.on 'confirm', @recalculateAchievement
@ -100,10 +100,10 @@ module.exports = class AchievementEditView extends RootView
confirmDeletion: ->
renderData =
'confirmTitle': 'Are you really sure?'
'confirmBody': 'This will completely delete the achievement, potentially breaking a lot of stuff you don\'t want breaking. Are you entirely sure?'
'confirmDecline': 'Not really'
'confirmConfirm': 'Definitely'
title: 'Are you really sure?'
body: 'This will completely delete the achievement, potentially breaking a lot of stuff you don\'t want breaking. Are you entirely sure?'
decline: 'Not really'
confirm: 'Definitely'
confirmModal = new ConfirmModal renderData
confirmModal.on 'confirm', @deleteAchievement

View file

@ -8,19 +8,12 @@ module.exports = class ConfirmModal extends ModalView
closeOnConfirm: true
events:
'click #decline-button': 'onDecline'
'click #confirm-button': 'onConfirm'
'click #decline-button': 'onClickDecline'
'click #confirm-button': 'onClickConfirm'
constructor: (@renderData={}, options={}) ->
super(options)
initialize: (options) ->
_.assign @, _.pick(options, 'title', 'body', 'decline', 'confirm', 'closeOnConfirm', 'closeButton')
getRenderData: ->
context = super()
context.closeOnConfirm = @closeOnConfirm
_.extend context, @renderData
onClickDecline: -> @trigger 'decline'
setRenderData: (@renderData) ->
onDecline: -> @trigger 'decline'
onConfirm: -> @trigger 'confirm'
onClickConfirm: -> @trigger 'confirm'

View file

@ -98,10 +98,10 @@ module.exports = class PollEditView extends RootView
confirmDeletion: ->
renderData =
'confirmTitle': 'Are you really sure?'
'confirmBody': 'This will completely delete the poll, potentially breaking a lot of stuff you don\'t want breaking. Are you entirely sure?'
'confirmDecline': 'Not really'
'confirmConfirm': 'Definitely'
title: 'Are you really sure?'
body: 'This will completely delete the poll, potentially breaking a lot of stuff you don\'t want breaking. Are you entirely sure?'
decline: 'Not really'
confirm: 'Definitely'
confirmModal = new ConfirmModal renderData
confirmModal.on 'confirm', @deletePoll

View file

@ -22,11 +22,12 @@ module.exports = class LadderTabView extends CocoView
'click .spectate-cell': 'onClickSpectateCell'
'click .load-more-ladder-entries': 'onLoadMoreLadderEntries'
subscriptions:
'auth:facebook-api-loaded': 'checkFriends'
'auth:gplus-api-loaded': 'checkFriends'
'auth:logged-in-with-facebook': 'onConnectedWithFacebook'
'auth:logged-in-with-gplus': 'onConnectedWithGPlus'
# Refactored, to-reimplement
# subscriptions:
# 'auth:facebook-api-loaded': 'checkFriends'
# 'auth:gplus-api-loaded': 'checkFriends'
# 'auth:logged-in-with-facebook': 'onConnectedWithFacebook'
# 'auth:logged-in-with-gplus': 'onConnectedWithGPlus'
constructor: (options, @level, @sessions) ->
super(options)

View file

@ -36,8 +36,8 @@ ArticleSchema.plugin(plugins.SearchablePlugin, {searchable: ['body', 'name']})
ArticleSchema.plugin(plugins.TranslationCoveragePlugin)
ArticleSchema.plugin(plugins.PatchablePlugin)
ArticleSchema.postEditableProperties = []
ArticleSchema.editableProperties = ['body', 'name', 'i18n', 'i18nCoverage']
ArticleSchema.jsonSchema = require '../../app/schemas/models/article'
ArticleSchema.statics.postEditableProperties = []
ArticleSchema.statics.editableProperties = ['body', 'name', 'i18n', 'i18nCoverage']
ArticleSchema.statics.jsonSchema = require '../../app/schemas/models/article'
module.exports = mongoose.model('article', ArticleSchema)

View file

@ -56,6 +56,7 @@ module.exports =
doc.set('original', doc._id)
doc.set('creator', req.user._id)
return doc
applyCustomSearchToDBQ: (req, dbq) ->
specialParameters = ['term', 'project', 'conditions']
@ -125,11 +126,11 @@ module.exports =
assignBody: (req, doc, options={}) ->
if _.isEmpty(req.body)
throw new errors.UnprocessableEntity('No input')
props = doc.schema.editableProperties.slice()
props = doc.schema.statics.editableProperties.slice()
if doc.isNew
props = props.concat doc.schema.postEditableProperties
props = props.concat doc.schema.statics.postEditableProperties
if doc.schema.uses_coco_permissions and req.user
isOwner = doc.getAccessForUserObjectId(req.user._id) is 'owner'
@ -152,7 +153,7 @@ module.exports =
# so that validation doesn't get hung up on Date objects in the documents.
delete obj.dateCreated
tv4 = require('tv4').tv4
result = tv4.validateMultiple(obj, doc.schema.jsonSchema)
result = tv4.validateMultiple(obj, doc.schema.statics.jsonSchema)
if not result.valid
throw new errors.UnprocessableEntity('JSON-schema validation failed', { validationErrors: result.errors })

View file

@ -29,7 +29,6 @@ module.exports.handlers =
'poll': 'polls/poll_handler'
'prepaid': 'prepaids/prepaid_handler'
'subscription': 'payments/subscription_handler'
'trial_request': 'trial_requests/trial_request_handler'
'user_polls_record': 'polls/user_polls_record_handler'
module.exports.handlerUrlOverrides =
@ -44,7 +43,6 @@ module.exports.handlerUrlOverrides =
'user_remark': 'user.remark'
'mail_sent': 'mail.sent'
'user_polls_record': 'user.polls.record'
'trial_request': 'trial.request'
'user_code_problem': 'user.code.problem'
module.exports.routes =

View file

@ -4,5 +4,6 @@ module.exports =
named: require './named'
patchable: require './patchable'
rest: require './rest'
trialRequests: require './trial-requests'
users: require './users'
versions: require './versions'

View file

@ -0,0 +1,44 @@
utils = require '../lib/utils'
errors = require '../commons/errors'
wrap = require 'co-express'
Promise = require 'bluebird'
database = require '../commons/database'
mongoose = require 'mongoose'
TrialRequest = require '../models/TrialRequest'
module.exports =
post: wrap (req, res) ->
if req.user.isAnonymous()
email = req.body?.properties?.email
throw new errors.UnprocessableEntity('Email not provided.') unless email
email = email.toLowerCase()
user = yield User.findOne({emailLower: email})
throw new errors.Conflict('User with this email already exists.') if user
trialRequest = database.initDoc(req, TrialRequest)
trialRequest.set 'applicant', req.user._id
trialRequest.set 'created', new Date()
trialRequest.set 'status', 'submitted'
database.assignBody(req, trialRequest)
database.validateDoc(trialRequest)
trialRequest = yield trialRequest.save()
res.status(201).send(trialRequest.toObject({req: req}))
put: wrap (req, res) ->
trialRequest = yield database.getDocFromHandle(req, TrialRequest)
throw new errors.NotFound('Trial Request not found.') if not trialRequest
database.assignBody(req, trialRequest)
trialRequest.set('reviewDate', new Date())
trialRequest.set('reviewer', req.user.get('_id'))
database.validateDoc(trialRequest)
trialRequest = yield trialRequest.save()
res.status(200).send(trialRequest.toObject({req: req}))
fetchByApplicant: wrap (req, res, next) ->
applicantID = req.query.applicant
return next() unless applicantID
throw new errors.UnprocessableEntity('Bad applicant id') unless utils.isID(applicantID)
throw new errors.Forbidden('May not fetch for anyone but yourself') unless req.user.id is applicantID
trialRequests = yield TrialRequest.find({applicant: mongoose.Types.ObjectId(applicantID)})
trialRequests = (tr.toObject({req: req}) for tr in trialRequests)
res.status(200).send(trialRequests)

View file

@ -81,7 +81,7 @@ module.exports.setup = (app) ->
app.post('/auth/logout', (req, res) ->
req.logout()
res.end()
res.send({})
)
app.post('/auth/reset', (req, res) ->

View file

@ -26,6 +26,12 @@ module.exports.setup = (app) ->
app.get('/db/user', mw.users.fetchByGPlusID, mw.users.fetchByFacebookID)
app.get '/db/products', require('./db/product').get
TrialRequest = require '../models/TrialRequest'
app.get('/db/trial.request', mw.trialRequests.fetchByApplicant, mw.auth.checkHasPermission(['admin']), mw.rest.get(TrialRequest))
app.post('/db/trial.request', mw.auth.checkLoggedIn(), mw.trialRequests.post)
app.get('/db/trial.request/:handle', mw.auth.checkHasPermission(['admin']), mw.rest.getByHandle(TrialRequest))
app.put('/db/trial.request/:handle', mw.auth.checkHasPermission(['admin']), mw.trialRequests.put)
app.get '/healthcheck', (req, res) ->
try

View file

@ -1,54 +0,0 @@
async = require 'async'
log = require 'winston'
mongoose = require 'mongoose'
Handler = require '../commons/Handler'
TrialRequest = require './TrialRequest'
User = require '../users/User'
TrialRequestHandler = class TrialRequestHandler extends Handler
modelClass: TrialRequest
jsonSchema: require '../../app/schemas/models/trial_request.schema'
hasAccess: (req) ->
req.method in ['POST'] or req.user?.isAdmin()
hasAccessToDocument: (req, document, method=null) ->
return false unless document?
return true if req.user?.isAdmin()
false
makeNewInstance: (req) ->
instance = super(req)
instance.set 'applicant', req.user._id
instance.set 'created', new Date()
instance.set 'status', 'submitted'
instance
post: (req, res) ->
return @sendForbiddenError(res) unless req.user?
if req.user.isAnonymous()
email = req.body?.properties?.email
return @sendBadInputError(res, 'email not provided') unless email
User.findOne({emailLower: req.body.properties.email}).exec (err, user) =>
return @sendDatabaseError(res, err) if err
return @sendError(res, 409, 'User with this email already exists.') if user
super(req, res)
else
super(req, res)
put: (req, res, id) ->
req.body.reviewDate = new Date()
req.body.reviewer = req.user.get('_id')
super(req, res, id)
getByRelationship: (req, res, args...) ->
return @getOwn(req, res) if args[1] is 'own'
super(arguments...)
getOwn: (req, res) ->
return @sendForbiddenError(res) unless req.user?
TrialRequest.find {applicant: req.user.get('_id')}, (err, documents) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, documents)
module.exports = new TrialRequestHandler()

View file

@ -506,6 +506,8 @@ UserHandler = class UserHandler extends Handler
hipchat.sendHipChatMessage "#{req.body.githubUsername or req.user.get('name')} just signed the CLA.", ['main']
avatar: (req, res, id) ->
if not isID(id)
return @sendBadInputError(res, 'Invalid avatar id')
@modelClass.findById(id).exec (err, document) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless document

View file

@ -32,7 +32,7 @@ models_path = [
'../../server/achievements/EarnedAchievement'
'../../server/payments/Payment'
'../../server/prepaids/Prepaid'
'../../server/trial_requests/TrialRequest'
'../../server/models/TrialRequest'
]
for m in models_path

View file

@ -1,172 +1,156 @@
require '../common'
utils = require '../utils'
_ = require 'lodash'
Promise = require 'bluebird'
describe 'Trial Requests', ->
fixture = {
type: 'subscription'
properties:
location: 'SF, CA'
age: '14-17'
numStudents: 14
heardAbout: 'magical interwebs'
}
describe 'POST /db/trial.request', ->
URL = getURL('/db/trial.request')
ownURL = getURL('/db/trial.request/-/own')
createTrialRequest = (user, type, properties, done) ->
requestBody =
type: type
properties: properties
request.post {uri: URL, json: requestBody }, (err, res, body) ->
expect(err).toBeNull()
beforeEach utils.wrap (done) ->
yield utils.clearModels([User, TrialRequest])
@user = yield utils.initUser()
yield utils.loginUser(@user)
fixture.properties.email = @user.get('email')
[res, body] = yield request.postAsync(getURL('/db/trial.request'), { json: fixture })
expect(res.statusCode).toBe(201)
expect(body._id).toBeDefined()
@trialRequest = yield TrialRequest.findById(body._id)
done()
it 'sets type and properties given', ->
expect(@trialRequest.get('type')).toBe('subscription')
expect(@trialRequest.get('properties').location).toBe('SF, CA')
it 'sets applicant to the user\'s id', ->
expect(@trialRequest.get('applicant').equals(@user._id)).toBe(true)
describe 'GET /db/trial.request', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels([User, TrialRequest])
@user = yield utils.initUser()
yield utils.loginUser(@user)
fixture.properties.email = @user.get('email')
[res, body] = yield request.postAsync(getURL('/db/trial.request'), { json: fixture })
@trialRequest = yield TrialRequest.findById(body._id)
done()
it 'returns 403 to non-admins', utils.wrap (done) ->
[res, body] = yield request.getAsync(getURL('/db/trial.request'))
expect(res.statusCode).toEqual(403)
done()
it 'returns trial requests to admins', utils.wrap (done) ->
@admin = yield utils.initAdmin()
yield utils.loginUser(@admin)
[res, body] = yield request.getAsync(getURL('/db/trial.request'), { json: true })
expect(res.statusCode).toEqual(200)
expect(body.length).toBe(1)
done()
describe 'GET /db/trial.request?applicant=:userID', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels([User, TrialRequest])
@user1 = yield utils.initUser()
@user2 = yield utils.initUser()
yield utils.loginUser(@user1)
@trialRequest1 = new TrialRequest({applicant: @user1._id})
yield @trialRequest1.save()
@trialRequest2 = yield new TrialRequest({applicant: @user2._id}).save()
done()
it 'returns trial requests for the given applicant', utils.wrap (done) ->
[res, body] = yield request.getAsync(getURL('/db/trial.request?applicant='+@user1.id), { json: true })
expect(res.statusCode).toEqual(200)
expect(body.length).toBe(1)
expect(body[0]._id).toBe(@trialRequest1.id)
done()
it 'returns 403 when non-admins request other people\'s trial requests', utils.wrap (done) ->
[res, body] = yield request.getAsync(getURL('/db/trial.request?applicant='+@user2.id), { json: true })
expect(res.statusCode).toEqual(403)
done()
describe 'PUT /db/trial.request/:handle', ->
putURL = null
beforeEach utils.wrap (done) ->
yield utils.clearModels([User, TrialRequest])
@user = yield utils.initUser()
yield utils.loginUser(@user)
fixture.properties.email = @user.get('email')
[res, body] = yield request.postAsync(getURL('/db/trial.request'), { json: fixture })
@trialRequest = yield TrialRequest.findById(body._id)
putURL = getURL('/db/trial.request/'+@trialRequest.id)
done()
it 'returns 403 to non-admins', ->
[res, body] = yield request.putAsync(getURL("/db/trial.request/#{@trialRequest.id}"))
expect(res.statusCode).toEqual(403)
done()
describe 'set status to "approved"', ->
beforeEach utils.wrap (done) ->
@admin = yield utils.initAdmin()
yield utils.loginUser(@admin)
[res, body] = yield request.putAsync(putURL, { json: { status: 'approved' } })
expect(res.statusCode).toBe(200)
expect(body.type).toEqual(type)
expect(body.properties).toEqual(properties)
expect(body.applicant).toEqual(user.id)
expect(body.status).toEqual('submitted')
TrialRequest.findById body._id, (err, doc) ->
expect(err).toBeNull()
expect(doc.get('type')).toEqual(type)
expect(doc.get('properties')).toEqual(properties)
expect(doc.get('applicant')).toEqual(user._id)
expect(doc.get('status')).toEqual('submitted')
done(doc)
it 'Clear database', (done) ->
clearModels [User, TrialRequest], (err) ->
throw err if err
expect(body.status).toBe('approved')
setTimeout done, 10 # let changes propagate
it 'sets reviewDate and reviewer', utils.wrap (done) ->
trialRequest = yield TrialRequest.findById(@trialRequest.id)
expect(trialRequest.get('reviewDate')).toBeDefined()
expect(trialRequest.get('reviewer').equals(@admin._id))
expect(new Date(trialRequest.get('reviewDate'))).toBeLessThan(new Date())
done()
it 'gives the user two enrollments', utils.wrap (done) ->
prepaids = yield Prepaid.find({'properties.trialRequestID': @trialRequest._id})
expect(prepaids.length).toEqual(1)
prepaid = prepaids[0]
expect(prepaid.get('type')).toEqual('course')
expect(prepaid.get('creator')).toEqual(@user.get('_id'))
expect(prepaid.get('maxRedeemers')).toEqual(2)
done()
it 'enables teacherNews for the user', utils.wrap (done) ->
user = yield User.findById(@user._id)
expect(user.get('emails')?.teacherNews?.enabled).toEqual(true)
done()
it 'Create trial request', (done) ->
loginNewUser (user) ->
properties =
email: user.get('email')
location: 'SF, CA'
age: '14-17'
numStudents: 14
heardAbout: 'magical interwebs'
createTrialRequest user, 'subscription', properties, (trialRequest) ->
done()
describe 'set status to "denied"', ->
it 'Get trial requests, non-admin', (done) ->
loginNewUser (user) ->
properties =
email: user.get('email')
location: 'SF, CA'
age: '14-17'
numStudents: 14
heardAbout: 'magical interwebs'
createTrialRequest user, 'subscription', properties, (trialRequest) ->
request.get URL, (err, res, body) ->
expect(res.statusCode).toEqual(403)
done()
beforeEach utils.wrap (done) ->
@admin = yield utils.initAdmin()
yield utils.loginUser(@admin)
[res, body] = yield request.putAsync(putURL, { json: { status: 'denied' } })
expect(res.statusCode).toBe(200)
expect(body.status).toBe('denied')
setTimeout done, 10 # let changes propagate
it 'Get trial requests, admin', (done) ->
loginNewUser (user) ->
properties =
email: user.get('email')
location: 'SF, CA'
age: '14-17'
numStudents: 14
heardAbout: 'magical interwebs'
createTrialRequest user, 'subscription', properties, (trialRequest) ->
loginNewUser (admin) ->
admin.set('permissions', ['admin'])
admin.save (err, user) ->
request.get URL, (err, res, body) ->
expect(res.statusCode).toEqual(200)
expect(body.length).toBeGreaterThan(0)
done()
it 'Get own trial requests', (done) ->
loginNewUser (user) ->
properties =
email: user.get('email')
location: 'SF, CA'
age: '14-17'
numStudents: 14
heardAbout: 'magical interwebs'
createTrialRequest user, 'subscription', properties, (trialRequest) ->
request.get ownURL, (err, res, body) ->
expect(res.statusCode).toEqual(200)
ownRequests = JSON.parse(body)
expect(ownRequests.length).toEqual(1)
expect(ownRequests[0]._id).toEqual(trialRequest.id)
done()
it 'Get non-owned trial request, non-admin', (done) ->
loginNewUser (user) ->
properties =
email: user.get('email')
location: 'SF, CA'
age: '14-17'
numStudents: 14
heardAbout: 'magical interwebs'
createTrialRequest user, 'subscription', properties, (trialRequest) ->
loginNewUser (user2) ->
request.get URL + "/#{trialRequest.id}", (err, res, body) ->
expect(res.statusCode).toEqual(403)
done()
it 'Approve trial request', (done) ->
loginNewUser (user) ->
properties =
email: user.get('email')
location: 'SF, CA'
age: '14-17'
numStudents: 14
heardAbout: 'magical interwebs'
createTrialRequest user, 'subscription', properties, (trialRequest) ->
loginNewUser (admin) ->
admin.set('permissions', ['admin'])
admin.save (err, admin) ->
requestBody = trialRequest.toObject()
requestBody.status = 'approved'
request.put {uri: URL, json: requestBody }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
expect(body.status).toEqual('approved')
expect(body.reviewDate).toBeDefined()
expect(new Date(body.reviewDate)).toBeLessThan(new Date())
expect(body.reviewer).toEqual(admin.id)
TrialRequest.findById body._id, (err, doc) ->
expect(err).toBeNull()
expect(doc.get('status')).toEqual('approved')
expect(doc.get('reviewDate')).toBeDefined()
expect(new Date(doc.get('reviewDate'))).toBeLessThan(new Date())
expect(doc.get('reviewer')).toEqual(admin._id)
Prepaid.find {'properties.trialRequestID': doc.get('_id')}, (err, prepaids) ->
expect(err).toBeNull()
return done(err) if err
expect(prepaids.length).toEqual(1)
prepaid = prepaids[0]
expect(prepaid.get('type')).toEqual('course')
expect(prepaid.get('creator')).toEqual(user.get('_id'))
expect(prepaid.get('maxRedeemers')).toEqual(2)
User.findById user._id, (err, user) =>
expect(err).toBeNull()
return done(err) if err
expect(user.get('emails')?.teacherNews?.enabled).toEqual(true)
done()
it 'Deny trial request', (done) ->
loginNewUser (user) ->
properties =
email: user.get('email')
location: 'SF, CA'
age: '14-17'
numStudents: 14
heardAbout: 'magical interwebs'
createTrialRequest user, 'subscription', properties, (trialRequest) ->
loginNewUser (admin) ->
admin.set('permissions', ['admin'])
admin.save (err, user) ->
requestBody = trialRequest.toObject()
requestBody.status = 'denied'
request.put {uri: URL, json: requestBody }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
expect(body.status).toEqual('denied')
expect(body.reviewDate).toBeDefined()
expect(new Date(body.reviewDate)).toBeLessThan(new Date())
expect(body.reviewer).toEqual(admin.id)
expect(body.prepaidCode).not.toBeDefined()
TrialRequest.findById body._id, (err, doc) ->
expect(err).toBeNull()
expect(doc.get('status')).toEqual('denied')
expect(doc.get('reviewDate')).toBeDefined()
expect(new Date(doc.get('reviewDate'))).toBeLessThan(new Date())
expect(doc.get('reviewer')).toEqual(admin._id)
expect(doc.get('prepaidCode')).not.toBeDefined()
done()
it 'sets reviewDate and reviewer', utils.wrap (done) ->
trialRequest = yield TrialRequest.findById(@trialRequest.id)
expect(trialRequest.get('reviewDate')).toBeDefined()
expect(trialRequest.get('reviewer').equals(@admin._id))
expect(new Date(trialRequest.get('reviewDate'))).toBeLessThan(new Date())
done()
it 'does not give the user two enrollments', utils.wrap (done) ->
prepaids = yield Prepaid.find({'properties.trialRequestID': @trialRequest._id})
expect(prepaids.length).toEqual(0)
done()

View file

@ -1,26 +0,0 @@
FacebookHandler = require 'core/social-handlers/FacebookHandler'
mockAuthEvent =
response:
authResponse:
accessToken: 'aksdhjflkqjrj245234b52k345q344le4j4k5l45j45s4dkljvdaskl'
userID: '4301938'
expiresIn: 5138
signedRequest: 'akjsdhfjkhea.3423nkfkdsejnfkd'
status: 'connected'
window.FB ?= {
api: ->
login: ->
}
describe 'lib/FacebookHandler.coffee', ->
it 'on facebook-logged-in, gets data from FB and sends a patch to the server', ->
me.clear({silent: true})
me.markToRevert()
me.set({_id: '12345'})
facebookHandler = new FacebookHandler()
facebookHandler.loginThroughFacebook()
Backbone.Mediator.publish 'auth:logged-in-with-facebook', mockAuthEvent

View file

@ -6,16 +6,12 @@ describe 'CreateAccountModal', ->
modal = null
initModal = (options) ->
application.facebookHandler.fakeAPI()
application.gplusHandler.fakeAPI()
modal = new CreateAccountModal(options)
modal.render()
modal.render = _.noop
jasmine.demoModal(modal)
window.gapi =
client:
load: _.noop
window.FB =
login: _.noop
api: _.noop
afterEach ->
modal.stopListening()
@ -37,21 +33,21 @@ describe 'CreateAccountModal', ->
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()
modal.$('form').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()
modal.$('form').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()
modal.$('form').submit()
requests = jasmine.Ajax.requests.all()
expect(requests.length).toBe(1)
expect(modal.$el.has('.has-warning').length).toBeFalsy()
@ -62,7 +58,7 @@ describe 'CreateAccountModal', ->
beforeEach ->
modal.$('form').each (i, el) -> el.reset()
forms.objectToForm(modal.$el, { email: 'some@email.com', password: 'xyzzy', classCode: 'qwerty' })
modal.$('form:first').submit()
modal.$('form').submit()
expect(jasmine.Ajax.requests.all().length).toBe(1)
it 'checks for Classroom existence if a class code was entered', ->
@ -84,11 +80,9 @@ describe 'CreateAccountModal', ->
describe 'the Classroom does not exist', ->
it 'shows an error and clears the field', ->
request = jasmine.Ajax.requests.mostRecent()
console.log 'school input?', modal.$('#class-code-input').val()
request.respondWith({status: 404, responseText: JSON.stringify({})})
expect(jasmine.Ajax.requests.all().length).toBe(1)
expect(modal.$el.has('.has-error').length).toBeTruthy()
console.log 'school input?', modal.$('#class-code-input').val()
expect(modal.$('#class-code-input').val()).toBe('')
@ -98,12 +92,9 @@ describe 'CreateAccountModal', ->
beforeEach ->
initModal()
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()
@ -131,7 +122,7 @@ describe 'CreateAccountModal', ->
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()
modal.$('form').submit()
it 'upserts the values to the new user', ->
request = jasmine.Ajax.requests.mostRecent()
@ -151,7 +142,7 @@ describe 'CreateAccountModal', ->
describe 'and the user finishes signup', ->
beforeEach ->
modal.$('form:first').submit()
modal.$('form').submit()
it 'creates the user with the gplus attributes', ->
request = jasmine.Ajax.requests.mostRecent()
@ -167,12 +158,9 @@ describe 'CreateAccountModal', ->
beforeEach ->
initModal()
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()
@ -200,7 +188,7 @@ describe 'CreateAccountModal', ->
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()
modal.$('form').submit()
it 'upserts the values to the new user', ->
request = jasmine.Ajax.requests.mostRecent()
@ -220,7 +208,7 @@ describe 'CreateAccountModal', ->
describe 'and the user finishes signup', ->
beforeEach ->
modal.$('form:first').submit()
modal.$('form').submit()
it 'creates the user with the facebook attributes', ->
request = jasmine.Ajax.requests.mostRecent()