This commit is contained in:
Scott Erickson 2014-07-14 15:13:52 -07:00
commit 9d22a35194
11 changed files with 212 additions and 25 deletions

View file

@ -19,8 +19,10 @@ module.exports = class CocoRouter extends Backbone.Router
# Direct links
'test': go('TestView')
'test/*subpath': go('TestView')
'demo': go('DemoView')
'demo/*subpath': go('DemoView')
'play/ladder/:levelID': go('play/ladder/ladder_view')
'play/ladder': go('play/ladder_home')

View file

@ -9,12 +9,12 @@ module.exports.formToObject = (el) ->
obj
module.exports.applyErrorsToForm = (el, errors) ->
module.exports.applyErrorsToForm = (el, errors, warning=false) ->
errors = [errors] if not $.isArray(errors)
missingErrors = []
for error in errors
if error.dataPath
prop = error.dataPath[1..]
console.log prop
message = error.message
else
@ -23,16 +23,28 @@ module.exports.applyErrorsToForm = (el, errors) ->
message = error.message if error.formatted
prop = error.property
input = $("[name='#{prop}']", el)
if not input.length
missingErrors.push(error)
continue
formGroup = input.closest('.form-group')
formGroup.addClass 'has-error'
formGroup.append($("<span class='help-block error-help-block'>#{message}</span>"))
return missingErrors
setErrorToProperty el, prop, message, warning
module.exports.setErrorToField = setErrorToField = (el, message, warning=false) ->
formGroup = el.closest('.form-group')
unless formGroup.length
return console.error "#{el} did not contain a form group"
kind = if warning then 'warning' else 'error'
formGroup.addClass "has-#{kind}"
formGroup.append $("<span class='help-block #{kind}-help-block'>#{message}</span>")
module.exports.setErrorToProperty = setErrorToProperty = (el, property, message, warning=false) ->
input = $("[name='#{property}']", el)
unless input.length
return console.error "#{property} not found in #{el}"
setErrorToField input, message, warning
module.exports.clearFormAlerts = (el) ->
$('.has-error', el).removeClass('has-error')
$('.has-warning', el).removeClass('has-warning')
$('.alert.alert-danger', el).remove()
$('.alert.alert-warning', el).remove()
el.find('.help-block.error-help-block').remove()
el.find('.help-block.warning-help-block').remove()

View file

@ -69,6 +69,13 @@ module.exports = class User extends CocoModel
cache[idOrSlug] = user
user
@getUnconflictedName: (name, done) ->
$.ajax "/auth/name/#{name}",
success: (data) -> done data.name
statusCode: 409: (data) ->
response = JSON.parse data.responseText
done response.name
getEnabledEmails: ->
@migrateEmails()
emails = _.clone(@get('emails')) or {}

View file

@ -9,7 +9,7 @@ block content
else
#save-button-container
button.btn#save-button.disabled.secret(data-i18n="account_settings.autosave") Changes Save Automatically
button.btn#save-button.disabled(data-i18n="general.save" disabled="true") No Changes
ul.nav.nav-pills#settings-tabs
li
@ -30,16 +30,19 @@ block content
#general-pane.tab-pane
p
.form
- var name = me.get('name') || '';
- var email = me.get('email');
- var admin = me.get('permissions').indexOf('admin') != -1;
.form-group
label.control-label(for="name", data-i18n="general.name") Name
input#name.form-control(name="name", type="text", value="#{me.get('name') || ''}")
input#name.form-control(name="name", type="text", value="#{name}")
.form-group
label.control-label(for="email", data-i18n="general.email") Email
input#email.form-control(name="email", type="text", value="#{me.get('email')}")
input#email.form-control(name="email", type="text", value="#{email}")
if !isProduction
.form-group.checkbox
label(for="admin", data-i18n="account_settings.admin") Admin
input#admin(name="admin", type="checkbox", checked=me.get('permissions').indexOf('admin') != -1)
input#admin(name="admin", type="checkbox", checked=admin)
#picture-pane.tab-pane

View file

@ -34,6 +34,12 @@ block modal-body-content
input#password.input-large.form-control(name="password", type="password", value=formValues.password)
if mode === 'signup'
.form-group
label.control-label(for="name", data-i18n="general.name") Name
if me.get('name')
input#name.input-large.form-control(name="name", type="text", value="#{me.get('name')}")
else
input#name.input-large.form-control(name="name", type="text", value="", placeholder="Anoner")
.form-group.checkbox
label.control-label(for="subscribe")
input#subscribe(name="subscribe", type="checkbox", checked='checked')

View file

@ -11,17 +11,73 @@ JobProfileView = require './job_profile_view'
module.exports = class SettingsView extends View
id: 'account-settings-view'
template: template
changedFields: [] # DOM input fields
events:
'click #save-button': 'save'
'change #settings-panes input': 'save'
'change #settings-panes input:checkbox': (e) -> @trigger 'checkboxToggled', e
'keyup #settings-panes input:text, #settings-panes input:password': (e) -> @trigger 'inputChanged', e
'keyup #name': 'onNameChange'
'click #toggle-all-button': 'toggleEmailSubscriptions'
'keypress #settings-panes': 'onKeyPress'
constructor: (options) ->
@save = _.debounce(@save, 200)
@onNameChange = _.debounce @checkNameExists, 500
super options
return unless me
@listenTo(me, 'invalid', (errors) -> forms.applyErrorsToForm(@$el, me.validationError))
@on 'checkboxToggled', @onToggle
@on 'checkboxToggled', @onInputChanged
@on 'inputChanged', @onInputChanged
@on 'enterPressed', @onEnter
onInputChanged: (e) ->
return @enableSaveButton() unless e?.currentTarget
that = e.currentTarget
$that = $(that)
savedValue = $that.data 'saved-value'
currentValue = $that.val()
if savedValue isnt currentValue
@changedFields.push that unless that in @changedFields
@enableSaveButton()
else
_.pull @changedFields, that
@disableSaveButton() if _.isEmpty @changedFields
onToggle: (e) ->
$that = $(e.currentTarget)
$that.val $that[0].checked
onEnter: ->
@save()
onKeyPress: (e) ->
@trigger 'enterPressed', e if e.which is 13
enableSaveButton: ->
$('#save-button', @$el).removeClass 'disabled'
$('#save-button', @$el).removeClass 'btn-danger'
$('#save-button', @$el).removeAttr 'disabled'
$('#save-button', @$el).text 'Save'
disableSaveButton: ->
$('#save-button', @$el).addClass 'disabled'
$('#save-button', @$el).removeClass 'btn-danger'
$('#save-button', @$el).attr 'disabled', "true"
$('#save-button', @$el).text 'No Changes'
checkNameExists: =>
name = $('#name', @$el).val()
return if name is me.get 'name'
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
afterRender: ->
super()
@ -50,6 +106,7 @@ module.exports = class SettingsView extends View
super()
if me.get('anonymous')
@openModalView new AuthModalView()
@updateSavedValues()
chooseTab: (category) ->
id = "##{category}-pane"
@ -98,7 +155,7 @@ module.exports = class SettingsView extends View
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
onPictureChanged: (e) =>
@trigger 'change'
@trigger 'inputChanged', e
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
save: (e) ->
@ -122,7 +179,9 @@ module.exports = class SettingsView extends View
errors = JSON.parse(res.responseText)
forms.applyErrorsToForm(@$el, errors)
save.text($.i18n.t('account_settings.error_saving', defaultValue: 'Error Saving')).removeClass('btn-success').addClass('btn-danger', 500)
res.success (model, response, options) ->
res.success (model, response, options) =>
@changedFields = []
@updateSavedValues()
save.text($.i18n.t('account_settings.saved', defaultValue: 'Changes Saved')).removeClass('btn-success', 500)
grabData: ->
@ -142,6 +201,7 @@ module.exports = class SettingsView extends View
me.set('password', password1)
grabOtherData: ->
$('#name', @$el).val @suggestedName if @suggestedName
me.set 'name', $('#name', @$el).val()
me.set 'email', $('#email', @$el).val()
for emailName, enabled of @getSubscriptions()
@ -162,3 +222,9 @@ module.exports = class SettingsView extends View
if updated
jobProfile.updated = (new Date()).toISOString()
me.set 'jobProfile', jobProfile
updateSavedValues: ->
$('#settings-panes input:text').each ->
$(@).data 'saved-value', $(@).val()
$('#settings-panes input:checkbox').each ->
$(@).data 'saved-value', JSON.stringify $(@)[0].checked

View file

@ -15,11 +15,16 @@ module.exports = class AuthModalView extends View
'click #switch-to-signup-button': 'onSignupInstead'
'click #signup-confirm-age': 'checkAge'
'submit': 'onSubmitForm' # handles both submit buttons
'keyup #name': 'onNameChange'
subscriptions:
'server-error': 'onServerError'
'logging-in-with-facebook': 'onLoggingInWithFacebook'
constructor: (options) ->
@onNameChange = _.debounce @checkNameExists, 500
super options
getRenderData: ->
c = super()
c.showRequiredError = @options.showRequiredError
@ -31,6 +36,7 @@ module.exports = class AuthModalView extends View
c.mode = @mode
c.formValues = @previousFormInputs or {}
c.onEmployersPage = Backbone.history.fragment is "employers"
c.me = me
c
afterInsert: ->
@ -64,6 +70,8 @@ module.exports = class AuthModalView extends View
userObject = forms.formToObject @$el
delete userObject.subscribe
delete userObject['confirm-age']
delete userObject.name if userObject.name is ''
userObject.name = @suggestedName if @suggestedName
for key, val of me.attributes when key in ['preferredLanguage', 'testGroupNumber', 'dateCreated', 'wizardColor1', 'name', 'music', 'volume', 'emails']
userObject[key] ?= val
subscribe = @$el.find('#signup-subscribe').prop('checked')
@ -82,3 +90,14 @@ module.exports = class AuthModalView extends View
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

View file

@ -4,20 +4,22 @@ WizardSprite = require 'lib/surface/WizardSprite'
ThangType = require 'models/ThangType'
{me} = require 'lib/auth'
forms = require 'lib/forms'
User = require 'models/User'
module.exports = class WizardSettingsModal extends View
id: 'wizard-settings-modal'
template: template
closesOnClickOutside: false
events:
'keyup #wizard-settings-name': -> @trigger 'nameChanged'
'click #wizard-settings-done': 'onWizardSettingsDone'
constructor: (options) ->
@onNameChange = _.debounce(@checkNameExists, 500)
@on 'nameChanged', @onNameChange
super options
events:
'keyup #wizard-settings-name': 'onNameChange'
'click #wizard-settings-done': 'onWizardSettingsDone'
afterRender: ->
WizardSettingsView = require 'views/account/wizard_settings_view'
view = new WizardSettingsView()
@ -27,10 +29,10 @@ module.exports = class WizardSettingsModal extends View
checkNameExists: =>
forms.clearFormAlerts(@$el)
name = $('#wizard-settings-name').val()
success = (id) =>
User.getUnconflictedName name, (newName) =>
forms.clearFormAlerts(@$el)
forms.applyErrorsToForm(@$el, {property: 'name', message: 'is already taken'}) if id and id isnt me.id
$.ajax("/db/user/#{name}/nameToID", {success: success})
if name isnt newName
forms.setErrorToProperty @$el, 'name', 'This name is already taken so you won\'t be able to keep it.', true
onWizardSettingsDone: ->
me.set('name', $('#wizard-settings-name').val())

View file

@ -157,13 +157,27 @@ module.exports.setup = (app) ->
res.send msg + '<p><a href="/account/settings">Account settings</a></p>'
res.end()
app.get '/auth/name*', (req, res) ->
parts = req.path.split '/'
originalName = decodeURI parts[3]
return errors.badInput res, 'No name provided.' unless parts.length > 3 and originalName? and originalName isnt ''
return errors.notFound res if parts.length isnt 4
User.unconflictName originalName, (err, name) ->
return errors.serverError res, err if err
response = name: name
if originalName is name
res.send 200, response
else
errors.conflict res, response
module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) ->
user.save((err) ->
return errors.serverError res, err if err?
req.logIn(user, (err) ->
return errors.serverError res, err if err?
return res.send user if send
return res.send(user) and res.end() if send
next() if next
)
)

File diff suppressed because one or more lines are too long

View file

@ -152,3 +152,27 @@ describe '/auth/unsubscribe', ->
expect(user.get('emails').recruitNotes.enabled).toBe(false)
expect(user.isEmailSubscriptionEnabled('generalNews')).toBeTruthy()
done()
describe '/auth/name', ->
url = '/auth/name'
it 'must provide a name to check with', (done) ->
request.get {url: getURL(url + '/'), json: {}}, (err, response) ->
expect(err).toBeNull()
expect(response.statusCode).toBe 422
done()
it 'can GET a non-conflicting name', (done) ->
request.get {url: getURL(url + '/Gandalf'), json: {}}, (err, response) ->
expect(err).toBeNull()
expect(response.statusCode).toBe 200
expect(response.body.name).toBe 'Gandalf'
done()
it 'can GET a new name in case of conflict', (done) ->
request.get {url: getURL(url + '/joe'), json: {}}, (err, response) ->
expect(err).toBeNull()
expect(response.statusCode).toBe 409
expect(response.body.name).not.toBe 'joe'
expect(response.body.name.length).toBe 4 # 'joe' and a random number
done()