diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 2d856e6d8..0d33095be 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -318,6 +318,8 @@ write_this_down: "Write this down:" start_playing: "Start Playing!" sso_connected: "Successfully connected with:" + select_your_starting_hero: "Select Your Starting Hero:" + you_can_always_change_your_hero_later: "You can always change your hero later." recover: recover_account_title: "Recover Account" diff --git a/app/styles/core/hero-select-view.sass b/app/styles/core/hero-select-view.sass new file mode 100644 index 000000000..d029579e8 --- /dev/null +++ b/app/styles/core/hero-select-view.sass @@ -0,0 +1,25 @@ +@import "app/styles/style-flat-variables" + +#hero-select-view + .hero-list + display: flex + flex-wrap: wrap + justify-content: center + margin-bottom: -50px + + .hero-option + display: flex + flex-direction: column + align-items: center + margin: 0 48px 50px + + .hero-avatar + margin: 6px + background-color: #f8f8f8 + box-shadow: 0 0 0 1px gray + + .current .hero-avatar + box-shadow: 0 0 0 6px gray + + .selected .hero-avatar + box-shadow: 0 0 0 6px $gold diff --git a/app/styles/courses/hero-select-modal.sass b/app/styles/courses/hero-select-modal.sass index c2488d816..0db3c730e 100644 --- a/app/styles/courses/hero-select-modal.sass +++ b/app/styles/courses/hero-select-modal.sass @@ -1,5 +1,3 @@ -@import "app/styles/style-flat-variables" - #hero-select-modal .modal-dialog width: auto @@ -15,26 +13,3 @@ h4 max-width: 500px - - .hero-list - display: flex - flex-wrap: wrap - justify-content: center - margin-bottom: -50px - - .hero-option - display: flex - flex-direction: column - align-items: center - margin: 0 48px 50px - - .hero-avatar - margin: 6px - background-color: #f8f8f8 - box-shadow: 0 0 0 1px gray - - .current .hero-avatar - box-shadow: 0 0 0 6px gray - - .selected .hero-avatar - box-shadow: 0 0 0 6px $gold diff --git a/app/styles/modal/create-account-modal/create-account-modal.sass b/app/styles/modal/create-account-modal/create-account-modal.sass index 34607c9f4..373dbbd78 100644 --- a/app/styles/modal/create-account-modal/create-account-modal.sass +++ b/app/styles/modal/create-account-modal/create-account-modal.sass @@ -69,7 +69,7 @@ a span text-decoration: underline - #choose-account-type-view, #segment-check-view, #basic-info-view, #coppa-deny-view, #single-sign-on-already-exists-view, #single-sign-on-confirm-view, #confirmation-view + #choose-account-type-view, #segment-check-view, #basic-info-view, #coppa-deny-view, #single-sign-on-already-exists-view, #single-sign-on-confirm-view, #extras-view, #confirmation-view display: flex flex-direction: column flex-grow: 1 diff --git a/app/styles/modal/create-account-modal/extras-view.sass b/app/styles/modal/create-account-modal/extras-view.sass new file mode 100644 index 000000000..dc84016c6 --- /dev/null +++ b/app/styles/modal/create-account-modal/extras-view.sass @@ -0,0 +1,8 @@ +#extras-view + justify-content: center + + #hero-select-view + margin-top: 20px + + .hero-option + margin: 0 30px 50px diff --git a/app/templates/core/create-account-modal/create-account-modal.jade b/app/templates/core/create-account-modal/create-account-modal.jade index c55477571..d6bea4542 100644 --- a/app/templates/core/create-account-modal/create-account-modal.jade +++ b/app/templates/core/create-account-modal/create-account-modal.jade @@ -37,12 +37,10 @@ block modal-body #single-sign-on-already-exists-view when 'sso-confirm' #single-sign-on-confirm-view + when 'extras' + #extras-view when 'confirmation' #confirmation-view - //- This is not yet implemented - //- when 'extras' - //- #extras-view - block modal-footer //- diff --git a/app/templates/core/create-account-modal/extras-view.jade b/app/templates/core/create-account-modal/extras-view.jade new file mode 100644 index 000000000..2c1cbdf3e --- /dev/null +++ b/app/templates/core/create-account-modal/extras-view.jade @@ -0,0 +1,11 @@ +.modal-body + .modal-body-content + .text-center + h4(data-i18n="signup.select_your_starting_hero") + .small(data-i18n="signup.you_can_always_change_your_hero_later") + #hero-select-view + + // In reverse order for tabbing purposes + .history-nav-buttons + button.next-button.btn.btn-lg.btn-navy(type='button') + span(data-i18n="about.next") diff --git a/app/templates/core/hero-select-view.jade b/app/templates/core/hero-select-view.jade new file mode 100644 index 000000000..ace3f2e32 --- /dev/null +++ b/app/templates/core/hero-select-view.jade @@ -0,0 +1,20 @@ +mixin heroOption(hero) + - var heroOriginal = hero.get('original') + - var selectedState + if state.get('selectedHeroOriginal') === heroOriginal + - selectedState = 'selected' + else if view.options.showCurrentHero && state.get('currentHeroOriginal') === heroOriginal + - selectedState = 'current' + else + - selectedState = '' + .hero-option(data-hero-original=heroOriginal, class=selectedState) + .hero-avatar + img(src=hero.getPortraitURL()) + .text-h5.hero-name + span= hero.getHeroShortName() + +.hero-list + if view.heroes.loaded + each hero in view.heroes.models + if hero.get('heroClass') === 'Warrior' + +heroOption(hero) diff --git a/app/templates/courses/hero-select-modal.jade b/app/templates/courses/hero-select-modal.jade index 9d918bb41..a4341b4c2 100644 --- a/app/templates/courses/hero-select-modal.jade +++ b/app/templates/courses/hero-select-modal.jade @@ -6,20 +6,7 @@ block modal-header-content h4(data-i18n="courses.select_your_hero_description") block modal-body-content - .hero-list - if view.heroes.loaded - each hero in view.heroes.models - if hero.get('heroClass') === 'Warrior' - +heroOption(hero) - -mixin heroOption(hero) - - var heroID = hero.id - - var selectedState = (state.get('selectedHeroID') === heroID ? 'selected' : (state.get('currentHeroID') === heroID ? 'current' : '')) - .hero-option(data-hero-id=heroID class=selectedState) - .hero-avatar - img(src=hero.getPortraitURL()) - .text-h5.hero-name - span= hero.getHeroShortName() + #hero-select-view block modal-footer-content .select-hero-btn.btn.btn-lg.btn-forest diff --git a/app/views/core/CreateAccountModal/CreateAccountModal.coffee b/app/views/core/CreateAccountModal/CreateAccountModal.coffee index 9a86d979e..90a4b8fb7 100644 --- a/app/views/core/CreateAccountModal/CreateAccountModal.coffee +++ b/app/views/core/CreateAccountModal/CreateAccountModal.coffee @@ -6,6 +6,7 @@ CoppaDenyView = require './CoppaDenyView' BasicInfoView = require './BasicInfoView' SingleSignOnAlreadyExistsView = require './SingleSignOnAlreadyExistsView' SingleSignOnConfirmView = require './SingleSignOnConfirmView' +ExtrasView = require './ExtrasView' ConfirmationView = require './ConfirmationView' State = require 'models/State' template = require 'templates/core/create-account-modal/create-account-modal' @@ -63,6 +64,7 @@ module.exports = class CreateAccountModal extends ModalView classCode birthday: new Date('') # so that birthday.getTime() is NaN authModalInitialValues: {} + accountCreated: false } { startOnPath } = options @@ -92,15 +94,26 @@ module.exports = class CreateAccountModal extends ModalView 'sso-connect:already-in-use': -> @signupState.set { screen: 'sso-already-exists' } 'sso-connect:new-user': -> @signupState.set {screen: 'sso-confirm'} 'nav-back': -> @signupState.set { screen: 'segment-check' } - 'signup': -> @signupState.set { screen: 'confirmation' } + 'signup': -> + if @signupState.get('path') is 'student' + @signupState.set { screen: 'extras', accountCreated: true } + else + @signupState.set { screen: 'confirmation', accountCreated: true } @listenTo @insertSubView(new SingleSignOnAlreadyExistsView({ @signupState })), 'nav-back': -> @signupState.set { screen: 'basic-info' } @listenTo @insertSubView(new SingleSignOnConfirmView({ @signupState })), 'nav-back': -> @signupState.set { screen: 'basic-info' } - 'signup': -> @signupState.set { screen: 'confirmation' } + 'signup': -> + if @signupState.get('path') is 'student' + @signupState.set { screen: 'extras', accountCreated: true } + else + @signupState.set { screen: 'confirmation', accountCreated: true } + @listenTo @insertSubView(new ExtrasView({ @signupState })), + 'nav-forward': -> @signupState.set { screen: 'confirmation' } + @insertSubView(new ConfirmationView({ @signupState })) # TODO: Switch to promises and state, rather than using defer to hackily enable buttons after render @@ -108,7 +121,7 @@ module.exports = class CreateAccountModal extends ModalView application.gplusHandler.loadAPI({ success: => @signupState.set { gplusEnabled: true } unless @destroyed }) @once 'hidden', -> - if @signupState.get('screen') is 'confirmation' and not application.testing + if @signupState.get('accountCreated') and not application.testing # ensure logged in state propagates through the entire app document.location.reload() diff --git a/app/views/core/CreateAccountModal/ExtrasView.coffee b/app/views/core/CreateAccountModal/ExtrasView.coffee new file mode 100644 index 000000000..00b357913 --- /dev/null +++ b/app/views/core/CreateAccountModal/ExtrasView.coffee @@ -0,0 +1,15 @@ +CocoView = require 'views/core/CocoView' +HeroSelectView = require 'views/core/HeroSelectView' +template = require 'templates/core/create-account-modal/extras-view' +State = require 'models/State' + +module.exports = class ExtrasView extends CocoView + id: 'extras-view' + template: template + retainSubviews: true + + events: + 'click .next-button': -> @trigger 'nav-forward' + + initialize: ({ @signupState } = {}) -> + @insertSubView(new HeroSelectView({ showCurrentHero: false })) diff --git a/app/views/core/HeroSelectView.coffee b/app/views/core/HeroSelectView.coffee new file mode 100644 index 000000000..aecaa061b --- /dev/null +++ b/app/views/core/HeroSelectView.coffee @@ -0,0 +1,45 @@ +CocoView = require 'views/core/CocoView' +template = require 'templates/core/hero-select-view' +Classroom = require 'models/Classroom' +ThangTypes = require 'collections/ThangTypes' +State = require 'models/State' +ThangType = require 'models/ThangType' +User = require 'models/User' + +module.exports = class HeroSelectView extends CocoView + id: 'hero-select-view' + template: template + + events: + 'click .hero-option': 'onClickHeroOption' + + initialize: (@options = {}) -> + defaultHeroOriginal = ThangType.heroes.captain + currentHeroOriginal = me.get('heroConfig')?.thangType or defaultHeroOriginal + + @debouncedRender = _.debounce @render, 0 + + @state = new State({ + currentHeroOriginal + selectedHeroOriginal: currentHeroOriginal + }) + + @heroes = new ThangTypes({}, { project: ['original', 'name', 'heroClass'] }) + @supermodel.trackRequest @heroes.fetchHeroes() + + @listenTo @state, 'all', -> @debouncedRender() + @listenTo @heroes, 'all', -> @debouncedRender() + + onClickHeroOption: (e) -> + heroOriginal = $(e.currentTarget).data('hero-original') + @state.set selectedHeroOriginal: heroOriginal + @saveHeroSelection(heroOriginal) + + saveHeroSelection: (heroOriginal) -> + me.set(heroConfig: {}) unless me.get('heroConfig') + heroConfig = _.assign me.get('heroConfig'), { thangType: heroOriginal } + me.set({ heroConfig }) + + hero = @heroes.findWhere({ original: heroOriginal }) + me.save().then => + @trigger 'hero-select:success', hero diff --git a/app/views/courses/HeroSelectModal.coffee b/app/views/courses/HeroSelectModal.coffee index c658b6941..350f24364 100644 --- a/app/views/courses/HeroSelectModal.coffee +++ b/app/views/courses/HeroSelectModal.coffee @@ -1,4 +1,5 @@ ModalView = require 'views/core/ModalView' +HeroSelectView = require 'views/core/HeroSelectView' template = require 'templates/courses/hero-select-modal' Classroom = require 'models/Classroom' ThangTypes = require 'collections/ThangTypes' @@ -9,34 +10,15 @@ User = require 'models/User' module.exports = class HeroSelectModal extends ModalView id: 'hero-select-modal' template: template + retainSubviews: true events: 'click .select-hero-btn': 'onClickSelectHeroButton' - 'click .hero-option': 'onClickHeroOption' - initialize: ({ currentHeroID }) -> - @debouncedRender = _.debounce @render, 0 - - @state = new State({ - currentHeroID - selectedHeroID: currentHeroID - }) - - @heroes = new ThangTypes({}, { project: ['original', 'name', 'heroClass'] }) - @supermodel.trackRequest @heroes.fetchHeroes() - - @listenTo @state, 'all', -> @debouncedRender() - @listenTo @heroes, 'all', -> @debouncedRender() - - onClickHeroOption: (e) -> - heroID = $(e.currentTarget).data('hero-id') - @state.set selectedHeroID: heroID - hero = @heroes.get(heroID) - me.set(heroConfig: {}) unless me.get('heroConfig') - heroConfig = _.assign me.get('heroConfig'), { thangType: hero.get('original') } - me.set({ heroConfig }) - me.save().then => - @trigger 'hero-select:success', hero + initialize: -> + @listenTo @insertSubView(new HeroSelectView({ showCurrentHero: true })), + 'hero-select:success', (hero) -> + @trigger('hero-select:success', hero) onClickSelectHeroButton: () -> @hide() diff --git a/test/app/views/courses/HeroSelectModal.spec.coffee b/test/app/views/courses/HeroSelectModal.spec.coffee index 7a933bf51..1c4bf52ed 100644 --- a/test/app/views/courses/HeroSelectModal.spec.coffee +++ b/test/app/views/courses/HeroSelectModal.spec.coffee @@ -13,8 +13,9 @@ describe 'HeroSelectModal', -> beforeEach (done) -> window.me = user = factories.makeUser({ heroConfig: { thangType: hero1.get('original') } }) - modal = new HeroSelectModal({ currentHeroID: hero1.id }) - modal.heroes.fakeRequests[0].respondWith({ status: 200, responseText: heroesResponse }) + modal = new HeroSelectModal() + subview = modal.subviews.hero_select_view + subview.heroes.fakeRequests[0].respondWith({ status: 200, responseText: heroesResponse }) jasmine.demoModal(modal) _.defer -> modal.render() @@ -24,10 +25,10 @@ describe 'HeroSelectModal', -> modal.stopListening() it 'highlights the current hero', -> - expect(modal.$(".hero-option[data-hero-id='#{hero1.id}']")[0].className.split(" ")).toContain('selected') + expect(modal.$(".hero-option[data-hero-original='#{hero1.get('original')}']")[0].className.split(" ")).toContain('selected') it 'saves when you change heroes', (done) -> - modal.$(".hero-option[data-hero-id='#{hero2.id}']").click() + modal.$(".hero-option[data-hero-original='#{hero2.get('original')}']").click() _.defer -> expect(user.fakeRequests.length).toBe(1) request = user.fakeRequests[0]