diff --git a/app/Router.coffee b/app/Router.coffee index a075424f2..07ddec112 100644 --- a/app/Router.coffee +++ b/app/Router.coffee @@ -21,7 +21,7 @@ module.exports = class CocoRouter extends Backbone.Router 'about': go('AboutView') 'account': go('account/MainAccountView') - 'account/settings': go('account/AccountSettingsView') + 'account/settings': go('account/AccountSettingsRootView') 'account/unsubscribe': go('account/UnsubscribeView') 'account/profile': go('user/JobProfileView') # legacy URL, sent in emails 'account/payments': go('account/PaymentsView') diff --git a/app/styles/account/account-settings-view.sass b/app/styles/account/account-settings-view.sass index 103fbe880..66e8ac787 100644 --- a/app/styles/account/account-settings-view.sass +++ b/app/styles/account/account-settings-view.sass @@ -1,6 +1,5 @@ -#account-settings-view +#account-settings-root-view - //- Fixed save button #site-content-area @@ -21,6 +20,10 @@ &.btn-info, &.btn-danger opacity: 1.0 +#account-settings-view + + .row + padding-top: 20px //- Panels diff --git a/app/styles/play/modal/play-account-modal.sass b/app/styles/play/modal/play-account-modal.sass index b30d2d3a2..e72a2d66a 100644 --- a/app/styles/play/modal/play-account-modal.sass +++ b/app/styles/play/modal/play-account-modal.sass @@ -1,4 +1,13 @@ #play-account-modal - .account-view - color: black - + .modal-dialog + min-width: 90% + + .modal-header + margin-bottom: 20px + + .modal-body + max-height: 500px + overflow: scroll + border-width: 2px 0 + border-color: black + border-style: solid \ No newline at end of file diff --git a/app/templates/account/account-settings-root-view.jade b/app/templates/account/account-settings-root-view.jade new file mode 100644 index 000000000..d63d14190 --- /dev/null +++ b/app/templates/account/account-settings-root-view.jade @@ -0,0 +1,19 @@ +extends /templates/base + +block content + + ol.breadcrumb + li + a(href="/") + span.glyphicon.glyphicon-home + li + a(href="/account")(data-i18n="nav.account") + li.active(data-i18n="account_settings.title") + + if !me.get('anonymous', true) + #save-button-container + button#save-button.btn-lg.btn.disabled(data-i18n="general.save" disabled="true") No Changes + + #account-settings-view + +block footer \ No newline at end of file diff --git a/app/templates/account/account-settings-view.jade b/app/templates/account/account-settings-view.jade index d52f4c654..00db82515 100644 --- a/app/templates/account/account-settings-view.jade +++ b/app/templates/account/account-settings-view.jade @@ -1,164 +1,147 @@ -extends /templates/base +if me.get('anonymous') + .alert.alert-danger(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings. -block content - - ol.breadcrumb - li - a(href="/") - span.glyphicon.glyphicon-home - li - a(href="/account")(data-i18n="nav.account") - li.active(data-i18n="account_settings.title") - - if me.get('anonymous') - .alert.alert-danger(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings. - - else - #save-button-container - button#save-button.btn-lg.btn.disabled(data-i18n="general.save" disabled="true") No Changes +else + .row + .col-md-6 + .panel.panel-default + .panel-heading + .panel-title(data-i18n="account_settings.me_tab") + .panel-body + .form + - var name = me.get('name') || ''; + - var email = me.get('email'); + - var admin = me.get('permissions', true).indexOf('admin') != -1; + .form-group + label.control-label(for="name", data-i18n="general.name") 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="#{email}") + if !isProduction + .form-group.checkbox + label(for="admin", data-i18n="account_settings.admin") Admin + input#admin(name="admin", type="checkbox", checked=admin) + - .row - .col-md-6 - .panel.panel-default - .panel-heading - .panel-title(data-i18n="account_settings.me_tab") - .panel-body - .form - - var name = me.get('name') || ''; - - var email = me.get('email'); - - var admin = me.get('permissions', true).indexOf('admin') != -1; - .form-group - label.control-label(for="name", data-i18n="general.name") 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="#{email}") - if !isProduction - .form-group.checkbox - label(for="admin", data-i18n="account_settings.admin") Admin - input#admin(name="admin", type="checkbox", checked=admin) - - - .panel.panel-default - .panel-heading - .panel-title(data-i18n="account_settings.picture_tab") - .panel-body - img.profile-photo(src=me.getPhotoURL(230), draggable="false") - input#photoURL(type="hidden", value=me.get('photoURL')||'') - button#upload-photo-button.btn.form-control.btn-primary(data-i18n="account_settings.upload_picture") - - .panel.panel-default - .panel-heading - .panel-title(data-i18n="account_settings.password_tab") - .panel-body - .form - .form-group - label.control-label(for="password", data-i18n="account_settings.new_password") New Password - input#password.form-control(name="password", type="password") - .form-group - label.control-label(for="password2", data-i18n="account_settings.new_password_verify") Verify - input#password2.form-control(name="password2", type="password") - - .col-md-6 - - #email-panel.panel.panel-default - .panel-heading - .panel-title(data-i18n="account_settings.emails_tab") - .panel-body - - .form - .form-group.checkbox - label.control-label(for="email_generalNews", data-i18n="account_settings.email_announcements") Announcements - input#email_generalNews(name="email_generalNews", type="checkbox", checked=subs.generalNews) - span.help-block(data-i18n="account_settings.email_announcements_description") Get emails on the latest news and developments at CodeCombat. - - hr - h4(data-i18n="account_settings.email_notifications") Notifications - span(data-i18n="account_settings.email_notifications_summary") Controls for personalized, automatic email notifications related to your CodeCombat activity. - - .form - .form-group.checkbox - label.control-label(for="email_anyNotes", data-i18n="account_settings.email_any_notes") Any Notifications - input#email_anyNotes(name="email_anyNotes", type="checkbox", checked=subs.anyNotes) - span.help-block(data-i18n="account_settings.email_any_notes_description") Disable to stop all activity notification emails. - - fieldset#specific-notification-settings - - .form-group.checkbox - label.control-label(for="email_recruitNotes", data-i18n="account_settings.email_recruit_notes") Job Opportunities - input#email_recruitNotes(name="email_recruitNotes", type="checkbox", checked=subs.recruitNotes) - span.help-block(data-i18n="account_settings.email_recruit_notes_description") If you play really well, we may contact you about getting you a (better) job. - - hr - - h4(data-i18n="account_settings.contributor_emails") Contributor Class Emails - span(data-i18n="account_settings.contribute_prefix") We're looking for people to join our party! Check out the - a(href="/contribute", data-i18n="account_settings.contribute_page") contribute page - span(data-i18n="account_settings.contribute_suffix") to find out more. - - .form - .form-group.checkbox - label.control-label(for="email_archmageNews") - span(data-i18n="classes.archmage_title") - | Archmage - | - span(data-i18n="classes.archmage_title_description") - | (Coder) - input#email_archmageNews(name="email_archmageNews", type="checkbox", checked=subs.archmageNews) - span(data-i18n="contribute.archmage_subscribe_desc").help-block Get emails about general news and announcements about CodeCombat. - - .form-group.checkbox - label.control-label(for="email_artisanNews") - span(data-i18n="classes.artisan_title") - | Artisan - | - span(data-i18n="classes.artisan_title_description") - | (Level Builder) - input#email_artisanNews(name="email_artisanNews", type="checkbox", checked=subs.artisanNews) - span(data-i18n="contribute.artisan_subscribe_desc").help-block Get emails on level editor updates and announcements. - - .form-group.checkbox - label.control-label(for="email_adventurerNews") - span(data-i18n="classes.adventurer_title") - | Adventurer - | - span(data-i18n="classes.adventurer_title_description") - | (Level Playtester) - input#email_adventurerNews(name="email_adventurerNews", type="checkbox", checked=subs.adventurerNews) - span(data-i18n="contribute.adventurer_subscribe_desc").help-block Get emails when there are new levels to test. - - .form-group.checkbox - label.control-label(for="email_scribeNews") - span(data-i18n="classes.scribe_title") - | Scribe - | - span(data-i18n="classes.scribe_title_description") - | (Article Editor) - input#email_scribeNews(name="email_scribeNews", type="checkbox", checked=subs.scribeNews) - span(data-i18n="contribute.scribe_subscribe_desc").help-block Get emails about article writing announcements. - - .form-group.checkbox - label.control-label(for="email_diplomatNews") - span(data-i18n="classes.diplomat_title") - | Diplomat - | - span(data-i18n="classes.diplomat_title_description") - | (Translator) - input#email_diplomatNews(name="email_diplomatNews", type="checkbox", checked=subs.diplomatNews) - span(data-i18n="contribute.diplomat_subscribe_desc").help-block Get emails about i18n developments and, eventually, levels to translate. - - .form-group.checkbox - label.control-label(for="email_ambassadorNews") - span(data-i18n="classes.ambassador_title") - | Ambassador - | - span(data-i18n="classes.ambassador_title_description") - | (Support) - input#email_ambassadorNews(name="email_ambassadorNews", type="checkbox", checked=subs.ambassadorNews) - span(data-i18n="contribute.ambassador_subscribe_desc").help-block Get emails on support updates and multiplayer developments. - - button#toggle-all-button.btn.btn-primary.form-control(data-i18n="account_settings.email_toggle") Toggle All + .panel.panel-default + .panel-heading + .panel-title(data-i18n="account_settings.picture_tab") + .panel-body + img.profile-photo(src=me.getPhotoURL(230), draggable="false") + input#photoURL(type="hidden", value=me.get('photoURL')||'') + button#upload-photo-button.btn.form-control.btn-primary(data-i18n="account_settings.upload_picture") + + .panel.panel-default + .panel-heading + .panel-title(data-i18n="account_settings.password_tab") + .panel-body + .form + .form-group + label.control-label(for="password", data-i18n="account_settings.new_password") New Password + input#password.form-control(name="password", type="password") + .form-group + label.control-label(for="password2", data-i18n="account_settings.new_password_verify") Verify + input#password2.form-control(name="password2", type="password") - .clearfix + .col-md-6 + + #email-panel.panel.panel-default + .panel-heading + .panel-title(data-i18n="account_settings.emails_tab") + .panel-body + + .form + .form-group.checkbox + label.control-label(for="email_generalNews", data-i18n="account_settings.email_announcements") Announcements + input#email_generalNews(name="email_generalNews", type="checkbox", checked=subs.generalNews) + span.help-block(data-i18n="account_settings.email_announcements_description") Get emails on the latest news and developments at CodeCombat. + + hr + h4(data-i18n="account_settings.email_notifications") Notifications + span(data-i18n="account_settings.email_notifications_summary") Controls for personalized, automatic email notifications related to your CodeCombat activity. + + .form + .form-group.checkbox + label.control-label(for="email_anyNotes", data-i18n="account_settings.email_any_notes") Any Notifications + input#email_anyNotes(name="email_anyNotes", type="checkbox", checked=subs.anyNotes) + span.help-block(data-i18n="account_settings.email_any_notes_description") Disable to stop all activity notification emails. + + fieldset#specific-notification-settings + + .form-group.checkbox + label.control-label(for="email_recruitNotes", data-i18n="account_settings.email_recruit_notes") Job Opportunities + input#email_recruitNotes(name="email_recruitNotes", type="checkbox", checked=subs.recruitNotes) + span.help-block(data-i18n="account_settings.email_recruit_notes_description") If you play really well, we may contact you about getting you a (better) job. + + hr + + h4(data-i18n="account_settings.contributor_emails") Contributor Class Emails + span(data-i18n="account_settings.contribute_prefix") We're looking for people to join our party! Check out the + a(href="/contribute", data-i18n="account_settings.contribute_page") contribute page + span(data-i18n="account_settings.contribute_suffix") to find out more. + + .form + .form-group.checkbox + label.control-label(for="email_archmageNews") + span(data-i18n="classes.archmage_title") + | Archmage + | + span(data-i18n="classes.archmage_title_description") + | (Coder) + input#email_archmageNews(name="email_archmageNews", type="checkbox", checked=subs.archmageNews) + span(data-i18n="contribute.archmage_subscribe_desc").help-block Get emails about general news and announcements about CodeCombat. + + .form-group.checkbox + label.control-label(for="email_artisanNews") + span(data-i18n="classes.artisan_title") + | Artisan + | + span(data-i18n="classes.artisan_title_description") + | (Level Builder) + input#email_artisanNews(name="email_artisanNews", type="checkbox", checked=subs.artisanNews) + span(data-i18n="contribute.artisan_subscribe_desc").help-block Get emails on level editor updates and announcements. + + .form-group.checkbox + label.control-label(for="email_adventurerNews") + span(data-i18n="classes.adventurer_title") + | Adventurer + | + span(data-i18n="classes.adventurer_title_description") + | (Level Playtester) + input#email_adventurerNews(name="email_adventurerNews", type="checkbox", checked=subs.adventurerNews) + span(data-i18n="contribute.adventurer_subscribe_desc").help-block Get emails when there are new levels to test. + + .form-group.checkbox + label.control-label(for="email_scribeNews") + span(data-i18n="classes.scribe_title") + | Scribe + | + span(data-i18n="classes.scribe_title_description") + | (Article Editor) + input#email_scribeNews(name="email_scribeNews", type="checkbox", checked=subs.scribeNews) + span(data-i18n="contribute.scribe_subscribe_desc").help-block Get emails about article writing announcements. + + .form-group.checkbox + label.control-label(for="email_diplomatNews") + span(data-i18n="classes.diplomat_title") + | Diplomat + | + span(data-i18n="classes.diplomat_title_description") + | (Translator) + input#email_diplomatNews(name="email_diplomatNews", type="checkbox", checked=subs.diplomatNews) + span(data-i18n="contribute.diplomat_subscribe_desc").help-block Get emails about i18n developments and, eventually, levels to translate. + + .form-group.checkbox + label.control-label(for="email_ambassadorNews") + span(data-i18n="classes.ambassador_title") + | Ambassador + | + span(data-i18n="classes.ambassador_title_description") + | (Support) + input#email_ambassadorNews(name="email_ambassadorNews", type="checkbox", checked=subs.ambassadorNews) + span(data-i18n="contribute.ambassador_subscribe_desc").help-block Get emails on support updates and multiplayer developments. + + button#toggle-all-button.btn.btn-primary.form-control(data-i18n="account_settings.email_toggle") Toggle All -block footer \ No newline at end of file +.clearfix diff --git a/app/templates/play/modal/play-account-modal.jade b/app/templates/play/modal/play-account-modal.jade index a8d7f9800..1c40e212e 100644 --- a/app/templates/play/modal/play-account-modal.jade +++ b/app/templates/play/modal/play-account-modal.jade @@ -4,4 +4,7 @@ block modal-header-content h3(data-i18n="play.account") Account block modal-body-content - p TODO: show all dem account + #account-settings-view + +block modal-footer-content + #save-button.btn-lg.btn.disabled(data-i18n="general.save" disabled="true") No Changes \ No newline at end of file diff --git a/app/views/account/AccountSettingsRootView.coffee b/app/views/account/AccountSettingsRootView.coffee new file mode 100644 index 000000000..cc199334a --- /dev/null +++ b/app/views/account/AccountSettingsRootView.coffee @@ -0,0 +1,48 @@ +RootView = require 'views/kinds/RootView' +template = require 'templates/account/account-settings-root-view' +AccountSettingsView = require './AccountSettingsView' + +module.exports = class AccountSettingsRootView extends RootView + id: "account-settings-root-view" + template: template + + events: + 'click #save-button': -> @accountSettingsView.save() + + shortcuts: + 'enter': -> @ + + afterRender: -> + super() + @accountSettingsView = new AccountSettingsView() + @insertSubView(@accountSettingsView) + @listenTo @accountSettingsView, 'input-changed', @onInputChanged + @listenTo @accountSettingsView, 'save-user-began', @onUserSaveBegan + @listenTo @accountSettingsView, 'save-user-success', @onUserSaveSuccess + @listenTo @accountSettingsView, 'save-user-error', @onUserSaveError + + onInputChanged: -> + @$el.find('#save-button') + .text($.i18n.t('common.save', defaultValue: 'Save')) + .addClass 'btn-info' + .removeClass 'disabled btn-danger' + .removeAttr 'disabled' + + onUserSaveBegan: -> + @$el.find('#save-button') + .text($.i18n.t('common.saving', defaultValue: 'Saving...')) + .removeClass('btn-danger') + .addClass('btn-success').show() + + onUserSaveSuccess: -> + @$el.find('#save-button') + .text($.i18n.t('account_settings.saved', defaultValue: 'Changes Saved')) + .removeClass('btn-success btn-info', 1000) + .attr('disabled', 'true') + + onUserSaveError: -> + @$el.find('#save-button') + .text($.i18n.t('account_settings.error_saving', defaultValue: 'Error Saving')) + .removeClass('btn-success') + .addClass('btn-danger', 500) + diff --git a/app/views/account/AccountSettingsView.coffee b/app/views/account/AccountSettingsView.coffee index 5a753c732..a367f2361 100644 --- a/app/views/account/AccountSettingsView.coffee +++ b/app/views/account/AccountSettingsView.coffee @@ -1,26 +1,22 @@ -RootView = require 'views/kinds/RootView' +CocoView = require 'views/kinds/CocoView' template = require 'templates/account/account-settings-view' {me} = require 'lib/auth' forms = require 'lib/forms' User = require 'models/User' AuthModal = require 'views/modal/AuthModal' -module.exports = class AccountSettingsView extends RootView +module.exports = class AccountSettingsView extends CocoView id: 'account-settings-view' template: template - changedFields: [] # DOM input fields + className: 'countainer-fluid' events: 'change .panel input': 'onInputChanged' 'change #name': 'checkNameExists' 'click #toggle-all-button': 'toggleEmailSubscriptions' 'click .profile-photo': 'onEditProfilePhoto' - 'click #save-button': 'save' 'click #upload-photo-button': 'onEditProfilePhoto' - shortcuts: - 'enter': 'save' - constructor: (options) -> super options require('lib/services/filepicker')() unless window.application.isIPadApp # Initialize if needed @@ -42,7 +38,7 @@ module.exports = class AccountSettingsView extends RootView onInputChanged: (e) -> $(e.target).addClass 'changed' - return @enableSaveButton() + @trigger 'input-changed' toggleEmailSubscriptions: => subs = @getSubscriptions() @@ -95,23 +91,6 @@ module.exports = class AccountSettingsView extends RootView onSaved uploadingPath - #- Save button enable/disable - - enableSaveButton: -> - $('#save-button', @$el) - .addClass 'btn-info' - .removeClass 'disabled btn-danger' - .removeAttr 'disabled' - .text 'Save' - - disableSaveButton: -> - $('#save-button', @$el) - .addClass 'disabled' - .removeClass 'btn-danger btn-info' - .attr 'disabled', "true" - .text 'No Changes' - - #- Misc getSubscriptions: -> @@ -123,7 +102,7 @@ module.exports = class AccountSettingsView extends RootView #- Saving changes - save: (e) -> + save: -> $('#settings-tabs input').removeClass 'changed' forms.clearFormAlerts(@$el) @grabData() @@ -138,17 +117,16 @@ module.exports = class AccountSettingsView extends RootView res = me.patch() return unless res - save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...')) - .removeClass('btn-danger').addClass('btn-success').show() - res.error -> + res.error => errors = JSON.parse(res.responseText) forms.applyErrorsToForm(@$el, errors) $('.nano').nanoScroller({scrollTo: @$el.find('.has-error')}) - save.text($.i18n.t('account_settings.error_saving', defaultValue: 'Error Saving')).removeClass('btn-success').addClass('btn-danger', 500) + @trigger 'save-user-error' res.success (model, response, options) => - @changedFields = [] - save.text($.i18n.t('account_settings.saved', defaultValue: 'Changes Saved')).removeClass('btn-success btn-info', 1000).attr('disabled', 'true') + @trigger 'save-user-success' + + @trigger 'save-user-began' grabData: -> @grabPasswordData() diff --git a/app/views/play/modal/PlayAccountModal.coffee b/app/views/play/modal/PlayAccountModal.coffee index 0d577f077..c4a1743aa 100644 --- a/app/views/play/modal/PlayAccountModal.coffee +++ b/app/views/play/modal/PlayAccountModal.coffee @@ -1,15 +1,15 @@ ModalView = require 'views/kinds/ModalView' template = require 'templates/play/modal/play-account-modal' +AccountSettingsView = require 'views/account/AccountSettingsView' module.exports = class PlayAccountModal extends ModalView className: 'modal fade play-modal' template: template - modalWidthPercent: 90 + plain: true id: 'play-account-modal' - #instant: true - #events: - # 'change input.select': 'onSelectionChanged' + events: + 'click #save-button': -> @accountSettingsView.save() constructor: (options) -> super options @@ -22,7 +22,32 @@ module.exports = class PlayAccountModal extends ModalView super() return unless @supermodel.finished() Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-open', volume: 1 + @accountSettingsView = new AccountSettingsView() + @insertSubView(@accountSettingsView) + @listenTo @accountSettingsView, 'input-changed', @onInputChanged + @listenTo @accountSettingsView, 'save-user-began', @onUserSaveBegan + @listenTo @accountSettingsView, 'save-user-success', @hide + @listenTo @accountSettingsView, 'save-user-error', @onUserSaveError onHidden: -> super() Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-close', volume: 1 + + onInputChanged: -> + @$el.find('#save-button') + .text($.i18n.t('common.save', defaultValue: 'Save')) + .addClass 'btn-info' + .removeClass 'disabled btn-danger' + .removeAttr 'disabled' + + onUserSaveBegan: -> + @$el.find('#save-button') + .text($.i18n.t('common.saving', defaultValue: 'Saving...')) + .removeClass('btn-danger') + .addClass('btn-success').show() + + onUserSaveError: -> + @$el.find('#save-button') + .text($.i18n.t('account_settings.error_saving', defaultValue: 'Error Saving')) + .removeClass('btn-success') + .addClass('btn-danger', 500) diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index 8e47d9aa0..aecfc5cd6 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -21,6 +21,10 @@ AchievablePlugin = (schema, options) -> # Check if an achievement has been earned schema.post 'save', (doc) -> + # sometimes post appears to be called twice. Handle this... + # TODO: Refactor this system to make it request-specific, + # perhaps by having POST/PUT requests store the copy on the request object themselves. + return if doc.isInit('_id') and not (doc.id of before) isNew = not doc.isInit('_id') or not (doc.id of before) originalDocObj = before[doc.id] unless isNew