Merge branch 'master' into production
This commit is contained in:
commit
98d8a2cfb3
43 changed files with 587 additions and 805 deletions
app
Router.coffee
collections
lib
locale
models
styles
templates
account
account-settings-root-view.jadeaccount-settings-view.jadeaccount_home.jademain-account-view.jadepayments-view.jade
base.jadei18n
play/modal
user
views
server
commons
levels
payments
plugins
routes
|
@ -21,10 +21,10 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'about': go('AboutView')
|
'about': go('AboutView')
|
||||||
|
|
||||||
'account': go('account/MainAccountView')
|
'account': go('account/MainAccountView')
|
||||||
'account/settings': go('account/AccountSettingsView')
|
'account/settings': go('account/AccountSettingsRootView')
|
||||||
'account/unsubscribe': go('account/UnsubscribeView')
|
'account/unsubscribe': go('account/UnsubscribeView')
|
||||||
'account/profile': go('user/JobProfileView') # legacy URL, sent in emails
|
'account/profile': go('user/JobProfileView') # legacy URL, sent in emails
|
||||||
#'account/payment'
|
'account/payments': go('account/PaymentsView')
|
||||||
|
|
||||||
'admin': go('admin/MainAdminView')
|
'admin': go('admin/MainAdminView')
|
||||||
'admin/candidates': go('admin/CandidatesView')
|
'admin/candidates': go('admin/CandidatesView')
|
||||||
|
@ -99,9 +99,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'test(/*subpath)': go('TestView')
|
'test(/*subpath)': go('TestView')
|
||||||
|
|
||||||
'user/:slugOrID': go('user/MainUserView')
|
'user/:slugOrID': go('user/MainUserView')
|
||||||
'user/:slugOrID/stats': go('user/AchievementsView')
|
|
||||||
'user/:slugOrID/profile': go('user/JobProfileView')
|
'user/:slugOrID/profile': go('user/JobProfileView')
|
||||||
#'user/:slugOrID/code': go('user/CodeView')
|
|
||||||
|
|
||||||
'*name': 'showNotFoundView'
|
'*name': 'showNotFoundView'
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,13 @@ module.exports = class ThangNamesCollection extends CocoCollection
|
||||||
model: ThangType
|
model: ThangType
|
||||||
isCachable: false
|
isCachable: false
|
||||||
|
|
||||||
constructor: (@ids) -> super()
|
constructor: (@ids) ->
|
||||||
|
super()
|
||||||
|
@ids.sort()
|
||||||
|
if @ids.length > 55
|
||||||
|
console.error 'Too many ids, we\'ll likely go over the GET url kind-of-limit of 2000 characters.'
|
||||||
|
|
||||||
fetch: (options) ->
|
fetch: (options) ->
|
||||||
options ?= {}
|
options ?= {}
|
||||||
method = if application.isIPadApp then 'GET' else 'POST' # Not sure why this was required that one time.
|
_.extend options, {data: {ids: @ids}}
|
||||||
_.extend options, {type: method, data: {ids: @ids}}
|
|
||||||
super(options)
|
super(options)
|
||||||
|
|
|
@ -210,7 +210,7 @@ module.exports = LevelOptions =
|
||||||
hidesRealTimePlayback: true
|
hidesRealTimePlayback: true
|
||||||
hidesCodeToolbar: true
|
hidesCodeToolbar: true
|
||||||
requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||||
restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer'}
|
restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i'}
|
||||||
'village-guard':
|
'village-guard':
|
||||||
hidesCodeToolbar: true
|
hidesCodeToolbar: true
|
||||||
requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||||
|
@ -244,7 +244,7 @@ module.exports = LevelOptions =
|
||||||
|
|
||||||
# Ranger branch
|
# Ranger branch
|
||||||
'munchkin-harvest':
|
'munchkin-harvest':
|
||||||
requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
|
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
|
||||||
restrictedGear: {}
|
restrictedGear: {}
|
||||||
'swift-dagger':
|
'swift-dagger':
|
||||||
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'crude-crossbow', 'left-hand': 'crude-dagger', wrists: 'sundial-wristwatch'}
|
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'crude-crossbow', 'left-hand': 'crude-dagger', wrists: 'sundial-wristwatch'}
|
||||||
|
@ -255,7 +255,7 @@ module.exports = LevelOptions =
|
||||||
|
|
||||||
# Wizard branch
|
# Wizard branch
|
||||||
'arcane-ally':
|
'arcane-ally':
|
||||||
requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
|
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
|
||||||
restrictedGear: {eyes: 'crude-glasses'}
|
restrictedGear: {eyes: 'crude-glasses'}
|
||||||
'touch-of-death':
|
'touch-of-death':
|
||||||
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'enchanted-stick', 'left-hand': 'unholy-tome-i', wrists: 'sundial-wristwatch'}
|
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'enchanted-stick', 'left-hand': 'unholy-tome-i', wrists: 'sundial-wristwatch'}
|
||||||
|
|
|
@ -791,6 +791,13 @@
|
||||||
account:
|
account:
|
||||||
recently_played: "Recently Played"
|
recently_played: "Recently Played"
|
||||||
no_recent_games: "No games played during the past two weeks."
|
no_recent_games: "No games played during the past two weeks."
|
||||||
|
payments: "Payments"
|
||||||
|
service_apple: "Apple"
|
||||||
|
service_web: "Web"
|
||||||
|
paid_on: "Paid On"
|
||||||
|
service: "Service"
|
||||||
|
price: "Price"
|
||||||
|
gems: "Gems"
|
||||||
|
|
||||||
loading_error:
|
loading_error:
|
||||||
could_not_load: "Error loading from server"
|
could_not_load: "Error loading from server"
|
||||||
|
|
5
app/models/Payment.coffee
Normal file
5
app/models/Payment.coffee
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
CocoModel = require('./CocoModel')
|
||||||
|
|
||||||
|
module.exports = class Payment extends CocoModel
|
||||||
|
@className: "Payment"
|
||||||
|
urlRoot: "/db/payment"
|
|
@ -232,7 +232,9 @@ module.exports = class ThangType extends CocoModel
|
||||||
stage?.toDataURL()
|
stage?.toDataURL()
|
||||||
|
|
||||||
getPortraitStage: (spriteOptionsOrKey, size=100) ->
|
getPortraitStage: (spriteOptionsOrKey, size=100) ->
|
||||||
return unless @isFullyLoaded()
|
canvas = $("<canvas width='#{size}' height='#{size}'></canvas>")
|
||||||
|
stage = new createjs.Stage(canvas[0])
|
||||||
|
return stage unless @isFullyLoaded()
|
||||||
key = spriteOptionsOrKey
|
key = spriteOptionsOrKey
|
||||||
key = if _.isString(key) then key else @spriteSheetKey(@fillOptions(key))
|
key = if _.isString(key) then key else @spriteSheetKey(@fillOptions(key))
|
||||||
spriteSheet = @spriteSheets[key]
|
spriteSheet = @spriteSheets[key]
|
||||||
|
@ -242,8 +244,6 @@ module.exports = class ThangType extends CocoModel
|
||||||
spriteSheet = @buildSpriteSheet(options)
|
spriteSheet = @buildSpriteSheet(options)
|
||||||
return if _.isString spriteSheet
|
return if _.isString spriteSheet
|
||||||
return unless spriteSheet
|
return unless spriteSheet
|
||||||
canvas = $("<canvas width='#{size}' height='#{size}'></canvas>")
|
|
||||||
stage = new createjs.Stage(canvas[0])
|
|
||||||
sprite = new createjs.Sprite(spriteSheet)
|
sprite = new createjs.Sprite(spriteSheet)
|
||||||
pt = @actions.portrait?.positions?.registration
|
pt = @actions.portrait?.positions?.registration
|
||||||
sprite.regX = pt?.x or 0
|
sprite.regX = pt?.x or 0
|
||||||
|
|
49
app/styles/account/account-settings-view.sass
Normal file
49
app/styles/account/account-settings-view.sass
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#account-settings-root-view
|
||||||
|
|
||||||
|
//- Fixed save button
|
||||||
|
|
||||||
|
#site-content-area
|
||||||
|
padding-bottom: 44px
|
||||||
|
|
||||||
|
#save-button-container
|
||||||
|
position: fixed
|
||||||
|
bottom: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
z-index: 10
|
||||||
|
background: gray
|
||||||
|
padding: 5px
|
||||||
|
|
||||||
|
#save-button
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
&.btn-info, &.btn-danger
|
||||||
|
opacity: 1.0
|
||||||
|
|
||||||
|
#account-settings-view
|
||||||
|
|
||||||
|
.row
|
||||||
|
padding-top: 20px
|
||||||
|
|
||||||
|
//- Panels
|
||||||
|
|
||||||
|
.panel-heading
|
||||||
|
font-family: Open Sans Condensed
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
.panel-title
|
||||||
|
font-size: 20px
|
||||||
|
|
||||||
|
//- Panel specific stuff
|
||||||
|
|
||||||
|
.profile-photo
|
||||||
|
max-width: 100%
|
||||||
|
max-height: 200px
|
||||||
|
display: block
|
||||||
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
#email-panel
|
||||||
|
#specific-notification-settings
|
||||||
|
padding-left: 20px
|
||||||
|
margin-left: 20px
|
||||||
|
border-left: 1px solid gray
|
|
@ -1,32 +0,0 @@
|
||||||
@import "app/styles/bootstrap/variables"
|
|
||||||
@import "app/styles/mixins"
|
|
||||||
|
|
||||||
#account-home
|
|
||||||
dl
|
|
||||||
margin-bottom: 0px
|
|
||||||
|
|
||||||
img#picture
|
|
||||||
max-width: 100%
|
|
||||||
|
|
||||||
.panel
|
|
||||||
margin-bottom: 10px
|
|
||||||
|
|
||||||
h2
|
|
||||||
margin-bottom: 0px
|
|
||||||
|
|
||||||
a
|
|
||||||
font-size: 28px
|
|
||||||
margin-left: 5px
|
|
||||||
|
|
||||||
.panel-title > a
|
|
||||||
margin-left: 5px
|
|
||||||
color: rgb(11, 99, 188)
|
|
||||||
|
|
||||||
.panel-me
|
|
||||||
td
|
|
||||||
padding-left: 15px
|
|
||||||
|
|
||||||
.panel-emails
|
|
||||||
h4
|
|
||||||
font-family: $font-family-base
|
|
||||||
|
|
9
app/styles/account/main-account-view.sass
Normal file
9
app/styles/account/main-account-view.sass
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
@import "app/styles/bootstrap/variables"
|
||||||
|
@import "app/styles/mixins"
|
||||||
|
|
||||||
|
#main-account-view
|
||||||
|
#account-links
|
||||||
|
width: 300px
|
||||||
|
|
||||||
|
#account-links .btn
|
||||||
|
width: 100%
|
|
@ -1,91 +0,0 @@
|
||||||
#account-settings-view
|
|
||||||
.nav
|
|
||||||
margin-bottom: 10px
|
|
||||||
|
|
||||||
.tab-content
|
|
||||||
border: 1px solid #aaa
|
|
||||||
padding: 20px
|
|
||||||
background: #eee
|
|
||||||
border-radius: 5px
|
|
||||||
|
|
||||||
#save-button-container
|
|
||||||
position: fixed
|
|
||||||
top: 100px
|
|
||||||
width: 1000px
|
|
||||||
z-index: 10
|
|
||||||
|
|
||||||
#save-button
|
|
||||||
float: right
|
|
||||||
|
|
||||||
&.btn-info, &.btn-danger
|
|
||||||
opacity: 1.0
|
|
||||||
|
|
||||||
.gravatar-fallback
|
|
||||||
margin-top: 10px
|
|
||||||
|
|
||||||
input.range
|
|
||||||
position: relative
|
|
||||||
top: 4px
|
|
||||||
|
|
||||||
div.range-color
|
|
||||||
position: relative
|
|
||||||
top: 6px
|
|
||||||
height: 16px
|
|
||||||
width: 16px
|
|
||||||
display: inline-block
|
|
||||||
margin-left: 10px
|
|
||||||
|
|
||||||
.help-inline
|
|
||||||
position: relative
|
|
||||||
top: 3px
|
|
||||||
left: 10px
|
|
||||||
font-size: 12px
|
|
||||||
|
|
||||||
.form
|
|
||||||
max-width: 600px
|
|
||||||
|
|
||||||
#email-pane
|
|
||||||
#specific-notification-settings
|
|
||||||
padding-left: 20px
|
|
||||||
margin-left: 20px
|
|
||||||
border-left: 1px solid gray
|
|
||||||
|
|
||||||
#job-profile-view
|
|
||||||
.profile-preview-button
|
|
||||||
&.bottom-preview
|
|
||||||
margin: 15px 0 0 0
|
|
||||||
|
|
||||||
.sample-profile-thumbnail
|
|
||||||
margin-top: -60px
|
|
||||||
|
|
||||||
.profile-completion-progress
|
|
||||||
width: 100%
|
|
||||||
display: inline-block
|
|
||||||
height: 33px
|
|
||||||
|
|
||||||
.progress-bar
|
|
||||||
line-height: 33px
|
|
||||||
|
|
||||||
.progress-next-item
|
|
||||||
margin-top: -20px
|
|
||||||
margin-bottom: 15px
|
|
||||||
|
|
||||||
#job-profile-treema
|
|
||||||
background-color: white
|
|
||||||
|
|
||||||
input
|
|
||||||
width: 790px
|
|
||||||
|
|
||||||
.treema-description
|
|
||||||
font-size: 14px
|
|
||||||
line-height: 22px
|
|
||||||
opacity: 1
|
|
||||||
|
|
||||||
.treema-row
|
|
||||||
padding-top: 6px
|
|
||||||
|
|
||||||
.treema-image-file
|
|
||||||
img
|
|
||||||
display: block
|
|
||||||
clear: both
|
|
||||||
max-width: 300px
|
|
|
@ -66,37 +66,6 @@ $user-achievements-scale: 0.8
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
text-overflow: ellipsis
|
text-overflow: ellipsis
|
||||||
|
|
||||||
// Specific to the user stats page
|
|
||||||
#user-achievements-view
|
|
||||||
.achievement-body
|
|
||||||
width: 335px
|
|
||||||
height: 120px
|
|
||||||
margin: 10px 0px
|
|
||||||
|
|
||||||
.achievement-icon
|
|
||||||
width: $overall-scale * $icon-size * $user-achievements-scale
|
|
||||||
height: $overall-scale * $icon-size * $user-achievements-scale
|
|
||||||
top: -5px
|
|
||||||
|
|
||||||
.achievement-image
|
|
||||||
img
|
|
||||||
width: $overall-scale * $user-achievements-scale * $icon-image-size
|
|
||||||
|
|
||||||
.achievement-content
|
|
||||||
margin-left: 60px
|
|
||||||
margin-right: 5px
|
|
||||||
width: 260px
|
|
||||||
height: 100px
|
|
||||||
padding: 15px 10px 20px 60px
|
|
||||||
|
|
||||||
.achievement-title
|
|
||||||
font-size: 20px
|
|
||||||
|
|
||||||
.achievement-description
|
|
||||||
font-size: 12px
|
|
||||||
line-height: 1.3em
|
|
||||||
max-height: 2.6em
|
|
||||||
|
|
||||||
.achievement-popup
|
.achievement-popup
|
||||||
padding: $overall-scale * 20px 0px
|
padding: $overall-scale * 20px 0px
|
||||||
position: relative
|
position: relative
|
||||||
|
|
|
@ -75,6 +75,7 @@ a
|
||||||
.progress
|
.progress
|
||||||
width: 50%
|
width: 50%
|
||||||
margin: 0 25%
|
margin: 0 25%
|
||||||
|
margin-bottom: 20px
|
||||||
|
|
||||||
// all loading screens
|
// all loading screens
|
||||||
.loading-container
|
.loading-container
|
||||||
|
|
|
@ -6,10 +6,17 @@
|
||||||
&.show-background
|
&.show-background
|
||||||
background: url(/images/pages/base/background.jpg) top center no-repeat
|
background: url(/images/pages/base/background.jpg) top center no-repeat
|
||||||
background-color: rgb(150,202,68)
|
background-color: rgb(150,202,68)
|
||||||
|
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
background-position: center -226px
|
||||||
|
|
||||||
padding-top: 185px
|
padding-top: 185px
|
||||||
max-width: 1920px
|
max-width: 1920px
|
||||||
margin: 0 auto
|
margin: 0 auto
|
||||||
|
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
padding-top: 50px
|
||||||
|
|
||||||
//- Nav
|
//- Nav
|
||||||
|
|
||||||
#site-nav
|
#site-nav
|
||||||
|
@ -22,7 +29,10 @@
|
||||||
text-align: center
|
text-align: center
|
||||||
min-width: 1024px
|
min-width: 1024px
|
||||||
z-index: 1
|
z-index: 1
|
||||||
|
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
top: -80px
|
||||||
|
|
||||||
#nav-logo
|
#nav-logo
|
||||||
position: absolute
|
position: absolute
|
||||||
margin-right: auto
|
margin-right: auto
|
||||||
|
@ -31,22 +41,31 @@
|
||||||
right: 0
|
right: 0
|
||||||
top: -45px
|
top: -45px
|
||||||
|
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
display: none
|
||||||
|
|
||||||
|
#small-nav-logo
|
||||||
|
display: none
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
display: inline-block
|
||||||
|
height: 30px
|
||||||
|
|
||||||
#site-nav-links
|
#site-nav-links
|
||||||
position: absolute
|
position: absolute
|
||||||
bottom: 21px
|
bottom: 21px
|
||||||
left: 0
|
left: 0
|
||||||
right: 0
|
right: 0
|
||||||
|
|
||||||
a
|
& > a
|
||||||
color: rgb(158,135,119)
|
color: rgb(158,135,119)
|
||||||
&:hover
|
&:hover
|
||||||
color: $white
|
color: $white
|
||||||
|
|
||||||
a, button, select
|
& > a, button, select
|
||||||
font-size: 18px
|
font-size: 18px
|
||||||
text-transform: uppercase
|
text-transform: uppercase
|
||||||
font-family: Open Sans Condensed
|
font-family: Open Sans Condensed
|
||||||
margin: 0 7px
|
margin: 0 7px
|
||||||
|
|
||||||
button, select
|
button, select
|
||||||
position: relative
|
position: relative
|
||||||
|
@ -81,57 +100,44 @@
|
||||||
width: 18px
|
width: 18px
|
||||||
|
|
||||||
.dropdown-menu
|
.dropdown-menu
|
||||||
//left: auto // this busts it, not sure why it's in
|
width: 180px
|
||||||
width: 280px
|
|
||||||
padding: 0px
|
padding: 0px
|
||||||
border-radius: 0px
|
border-radius: 0px
|
||||||
font-family: Open Sans Condensed
|
|
||||||
font-variant: small-caps
|
|
||||||
|
|
||||||
> .user-dropdown-header
|
.user-dropdown-header
|
||||||
position: relative
|
|
||||||
background: #E4CF8C
|
background: #E4CF8C
|
||||||
height: 160px
|
height: 160px
|
||||||
padding: 10px
|
padding: 10px
|
||||||
text-align: center
|
text-align: center
|
||||||
color: black
|
color: black
|
||||||
border-bottom: #32281e 1px solid
|
border-bottom: #32281e 1px solid
|
||||||
> a:hover
|
|
||||||
background-color: transparent
|
|
||||||
img
|
img
|
||||||
border: #e3be7a 8px solid
|
border: #e3be7a 8px solid
|
||||||
height: 98px // Includes the border
|
height: 98px // Includes the border
|
||||||
&:hover
|
&:hover
|
||||||
box-shadow: 0 0 20px #e3be7a
|
box-shadow: 0 0 20px #e3be7a
|
||||||
> h3
|
|
||||||
|
h3
|
||||||
|
font-variant: small-caps
|
||||||
|
font-family: Open Sans Condensed
|
||||||
margin-top: 10px
|
margin-top: 10px
|
||||||
text-shadow: 2px 2px 3px white
|
text-shadow: 2px 2px 3px white
|
||||||
color: #31281E
|
color: #31281E
|
||||||
|
|
||||||
.user-level
|
.user-level
|
||||||
position: absolute
|
position: absolute
|
||||||
top: 73px
|
top: 73px
|
||||||
right: 86px
|
right: 40px
|
||||||
color: gold
|
color: gold
|
||||||
text-shadow: 1px 1px black, -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black
|
text-shadow: 1px 1px black, -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black
|
||||||
|
|
||||||
.user-dropdown-body
|
li
|
||||||
color: black
|
color: black
|
||||||
padding: 15px
|
font-size: 16px
|
||||||
letter-spacing: 1px
|
|
||||||
font: 15px 'Helvetica Neue', Helvetica, Arial, sans-serif
|
|
||||||
+clearfix()
|
|
||||||
|
|
||||||
.user-dropdown-footer
|
|
||||||
padding: 10px
|
|
||||||
margin-left: 0px
|
|
||||||
font-size: 14px
|
|
||||||
+clearfix()
|
|
||||||
|
|
||||||
.btn-flat
|
|
||||||
border: #ddd 1px solid
|
|
||||||
border-radius: 0px
|
|
||||||
margin: 0px
|
|
||||||
|
|
||||||
|
#logout-button
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
//- Content
|
//- Content
|
||||||
|
|
||||||
|
@ -141,6 +147,7 @@
|
||||||
width: 1024px
|
width: 1024px
|
||||||
border: 5px solid rgb(110,88,41)
|
border: 5px solid rgb(110,88,41)
|
||||||
padding: 20px 12px
|
padding: 20px 12px
|
||||||
|
min-height: 300px
|
||||||
|
|
||||||
|
|
||||||
//- Footer
|
//- Footer
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
#home-view
|
#home-view
|
||||||
|
|
||||||
#spacer
|
#spacer
|
||||||
//height: 750px // No one could see this; let's shrink it as much as we can.
|
height: 626px
|
||||||
height: 606px
|
@media screen and ( max-height: 800px )
|
||||||
|
height: 510px
|
||||||
|
|
||||||
#play-button, #or-ipad, #apple-store-button, #slogan, .alert
|
#play-button, #or-ipad, #apple-store-button, #slogan, .alert
|
||||||
text-align: center
|
text-align: center
|
||||||
|
@ -27,6 +28,9 @@
|
||||||
top: 308px
|
top: 308px
|
||||||
width: 218px
|
width: 218px
|
||||||
height: 219px
|
height: 219px
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
top: 78px
|
||||||
|
|
||||||
background-image: url(/images/pages/home/play_button.png)
|
background-image: url(/images/pages/home/play_button.png)
|
||||||
background-position: 0 219px
|
background-position: 0 219px
|
||||||
|
|
||||||
|
@ -40,11 +44,17 @@
|
||||||
color: rgb(119,101,84)
|
color: rgb(119,101,84)
|
||||||
font-size: 17px
|
font-size: 17px
|
||||||
max-width: 211px
|
max-width: 211px
|
||||||
|
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
top: 310px
|
||||||
|
|
||||||
#apple-store-button
|
#apple-store-button
|
||||||
top: 593px
|
top: 593px
|
||||||
height: 63px
|
height: 63px
|
||||||
|
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
top: 363px
|
||||||
|
|
||||||
#slogan
|
#slogan
|
||||||
top: 681px
|
top: 681px
|
||||||
height: 132px
|
height: 132px
|
||||||
|
@ -53,6 +63,9 @@
|
||||||
font-size: 28px
|
font-size: 28px
|
||||||
line-height: 32px
|
line-height: 32px
|
||||||
color: rgb(50,40,31)
|
color: rgb(50,40,31)
|
||||||
|
|
||||||
|
@media screen and ( max-height: 800px )
|
||||||
|
top: 451px
|
||||||
|
|
||||||
.alert
|
.alert
|
||||||
top: 213px
|
top: 213px
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
#play-account-modal
|
#play-account-modal
|
||||||
.account-view
|
.modal-dialog
|
||||||
color: black
|
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
|
|
@ -46,6 +46,9 @@
|
||||||
color: #555555
|
color: #555555
|
||||||
font-size: 15px
|
font-size: 15px
|
||||||
margin-left: 5px
|
margin-left: 5px
|
||||||
|
|
||||||
|
.panel-footer
|
||||||
|
text-align: right
|
||||||
|
|
||||||
.contributor-categories
|
.contributor-categories
|
||||||
list-style: none
|
list-style: none
|
19
app/templates/account/account-settings-root-view.jade
Normal file
19
app/templates/account/account-settings-root-view.jade
Normal file
|
@ -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
|
|
@ -1,34 +1,13 @@
|
||||||
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
|
else
|
||||||
|
.row
|
||||||
h2(data-i18n="account_settings.title") Account Settings
|
.col-md-6
|
||||||
|
.panel.panel-default
|
||||||
if me.get('anonymous')
|
.panel-heading
|
||||||
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
|
.panel-title(data-i18n="account_settings.me_tab")
|
||||||
|
.panel-body
|
||||||
else
|
|
||||||
#save-button-container
|
|
||||||
button.btn#save-button.disabled(data-i18n="general.save" disabled="true") No Changes
|
|
||||||
|
|
||||||
ul.nav.nav-pills#settings-tabs
|
|
||||||
li
|
|
||||||
a(href="#general-pane", data-toggle="tab", data-i18n="account_settings.me_tab") Me
|
|
||||||
li
|
|
||||||
a(href="#picture-pane", data-toggle="tab", data-i18n="account_settings.picture_tab") Picture
|
|
||||||
li
|
|
||||||
a(href="#wizard-pane", data-toggle="tab", data-i18n="account_settings.wizard_tab") Wizard
|
|
||||||
li
|
|
||||||
a(href="#password-pane", data-toggle="tab", data-i18n="account_settings.password_tab") Password
|
|
||||||
li
|
|
||||||
a(href="#email-pane", data-toggle="tab", data-i18n="account_settings.emails_tab") Emails
|
|
||||||
if showsJobProfileTab
|
|
||||||
li
|
|
||||||
a(href="#job-profile-pane", data-toggle="tab", data-i18n="account_settings.job_profile_tab") Job Profile
|
|
||||||
|
|
||||||
.tab-content#settings-panes
|
|
||||||
#general-pane.tab-pane
|
|
||||||
p
|
|
||||||
.form
|
.form
|
||||||
- var name = me.get('name') || '';
|
- var name = me.get('name') || '';
|
||||||
- var email = me.get('email');
|
- var email = me.get('email');
|
||||||
|
@ -43,19 +22,20 @@ block content
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label(for="admin", data-i18n="account_settings.admin") Admin
|
label(for="admin", data-i18n="account_settings.admin") Admin
|
||||||
input#admin(name="admin", type="checkbox", checked=admin)
|
input#admin(name="admin", type="checkbox", checked=admin)
|
||||||
|
|
||||||
|
|
||||||
#picture-pane.tab-pane
|
.panel.panel-default
|
||||||
h3(data-i18n="account_settings.upload_picture") Upload a picture
|
.panel-heading
|
||||||
#picture-treema
|
.panel-title(data-i18n="account_settings.picture_tab")
|
||||||
.gravatar-fallback
|
.panel-body
|
||||||
img(src=me.getPhotoURL(256), alt="Gravatar", title="Gravatar fallback image")
|
img.profile-photo(src=me.getPhotoURL(230), draggable="false")
|
||||||
|
input#photoURL(type="hidden", value=me.get('photoURL')||'')
|
||||||
#wizard-pane.tab-pane
|
button#upload-photo-button.btn.form-control.btn-primary(data-i18n="account_settings.upload_picture")
|
||||||
#wizard-settings-view
|
|
||||||
|
.panel.panel-default
|
||||||
#password-pane.tab-pane
|
.panel-heading
|
||||||
p
|
.panel-title(data-i18n="account_settings.password_tab")
|
||||||
|
.panel-body
|
||||||
.form
|
.form
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="password", data-i18n="account_settings.new_password") New Password
|
label.control-label(for="password", data-i18n="account_settings.new_password") New Password
|
||||||
|
@ -63,11 +43,14 @@ block content
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="password2", data-i18n="account_settings.new_password_verify") Verify
|
label.control-label(for="password2", data-i18n="account_settings.new_password_verify") Verify
|
||||||
input#password2.form-control(name="password2", type="password")
|
input#password2.form-control(name="password2", type="password")
|
||||||
|
|
||||||
#email-pane.tab-pane
|
|
||||||
h3(data-i18n="account_settings.email_subscriptions") Email Subscriptions
|
|
||||||
|
|
||||||
p
|
.col-md-6
|
||||||
|
|
||||||
|
#email-panel.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
.panel-title(data-i18n="account_settings.emails_tab")
|
||||||
|
.panel-body
|
||||||
|
|
||||||
.form
|
.form
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label.control-label(for="email_generalNews", data-i18n="account_settings.email_announcements") Announcements
|
label.control-label(for="email_generalNews", data-i18n="account_settings.email_announcements") Announcements
|
||||||
|
@ -85,19 +68,19 @@ block content
|
||||||
span.help-block(data-i18n="account_settings.email_any_notes_description") Disable to stop all activity notification emails.
|
span.help-block(data-i18n="account_settings.email_any_notes_description") Disable to stop all activity notification emails.
|
||||||
|
|
||||||
fieldset#specific-notification-settings
|
fieldset#specific-notification-settings
|
||||||
|
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label.control-label(for="email_recruitNotes", data-i18n="account_settings.email_recruit_notes") Job Opportunities
|
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)
|
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.
|
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
|
hr
|
||||||
|
|
||||||
h4(data-i18n="account_settings.contributor_emails") Contributor Class Emails
|
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
|
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
|
a(href="/contribute", data-i18n="account_settings.contribute_page") contribute page
|
||||||
span(data-i18n="account_settings.contribute_suffix") to find out more.
|
span(data-i18n="account_settings.contribute_suffix") to find out more.
|
||||||
|
|
||||||
.form
|
.form
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label.control-label(for="email_archmageNews")
|
label.control-label(for="email_archmageNews")
|
||||||
|
@ -108,7 +91,7 @@ block content
|
||||||
| (Coder)
|
| (Coder)
|
||||||
input#email_archmageNews(name="email_archmageNews", type="checkbox", checked=subs.archmageNews)
|
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.
|
span(data-i18n="contribute.archmage_subscribe_desc").help-block Get emails about general news and announcements about CodeCombat.
|
||||||
|
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label.control-label(for="email_artisanNews")
|
label.control-label(for="email_artisanNews")
|
||||||
span(data-i18n="classes.artisan_title")
|
span(data-i18n="classes.artisan_title")
|
||||||
|
@ -118,7 +101,7 @@ block content
|
||||||
| (Level Builder)
|
| (Level Builder)
|
||||||
input#email_artisanNews(name="email_artisanNews", type="checkbox", checked=subs.artisanNews)
|
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.
|
span(data-i18n="contribute.artisan_subscribe_desc").help-block Get emails on level editor updates and announcements.
|
||||||
|
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label.control-label(for="email_adventurerNews")
|
label.control-label(for="email_adventurerNews")
|
||||||
span(data-i18n="classes.adventurer_title")
|
span(data-i18n="classes.adventurer_title")
|
||||||
|
@ -128,7 +111,7 @@ block content
|
||||||
| (Level Playtester)
|
| (Level Playtester)
|
||||||
input#email_adventurerNews(name="email_adventurerNews", type="checkbox", checked=subs.adventurerNews)
|
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.
|
span(data-i18n="contribute.adventurer_subscribe_desc").help-block Get emails when there are new levels to test.
|
||||||
|
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label.control-label(for="email_scribeNews")
|
label.control-label(for="email_scribeNews")
|
||||||
span(data-i18n="classes.scribe_title")
|
span(data-i18n="classes.scribe_title")
|
||||||
|
@ -138,7 +121,7 @@ block content
|
||||||
| (Article Editor)
|
| (Article Editor)
|
||||||
input#email_scribeNews(name="email_scribeNews", type="checkbox", checked=subs.scribeNews)
|
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.
|
span(data-i18n="contribute.scribe_subscribe_desc").help-block Get emails about article writing announcements.
|
||||||
|
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label.control-label(for="email_diplomatNews")
|
label.control-label(for="email_diplomatNews")
|
||||||
span(data-i18n="classes.diplomat_title")
|
span(data-i18n="classes.diplomat_title")
|
||||||
|
@ -148,7 +131,7 @@ block content
|
||||||
| (Translator)
|
| (Translator)
|
||||||
input#email_diplomatNews(name="email_diplomatNews", type="checkbox", checked=subs.diplomatNews)
|
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.
|
span(data-i18n="contribute.diplomat_subscribe_desc").help-block Get emails about i18n developments and, eventually, levels to translate.
|
||||||
|
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label.control-label(for="email_ambassadorNews")
|
label.control-label(for="email_ambassadorNews")
|
||||||
span(data-i18n="classes.ambassador_title")
|
span(data-i18n="classes.ambassador_title")
|
||||||
|
@ -159,7 +142,6 @@ block content
|
||||||
input#email_ambassadorNews(name="email_ambassadorNews", type="checkbox", checked=subs.ambassadorNews)
|
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.
|
span(data-i18n="contribute.ambassador_subscribe_desc").help-block Get emails on support updates and multiplayer developments.
|
||||||
|
|
||||||
button.btn#toggle-all-button(data-i18n="account_settings.email_toggle") Toggle All
|
button#toggle-all-button.btn.btn-primary.form-control(data-i18n="account_settings.email_toggle") Toggle All
|
||||||
|
|
||||||
#job-profile-pane.tab-pane
|
.clearfix
|
||||||
#job-profile-view
|
|
|
@ -1,141 +0,0 @@
|
||||||
extends /templates/base
|
|
||||||
|
|
||||||
block content
|
|
||||||
if !me.isAnonymous()
|
|
||||||
.clearfix
|
|
||||||
.col-sm-6.clearfix
|
|
||||||
h2
|
|
||||||
span(data-i18n="account_settings.title") Account Settings
|
|
||||||
a.spl(href="/account/settings")
|
|
||||||
i.glyphicon.glyphicon-cog
|
|
||||||
hr
|
|
||||||
.row
|
|
||||||
.col-xs-6
|
|
||||||
.panel.panel-default
|
|
||||||
.panel-heading
|
|
||||||
h3.panel-title
|
|
||||||
i.glyphicon.glyphicon-picture
|
|
||||||
a(href="account/settings#picture" data-i18n="account_settings.picture_tab") Picture
|
|
||||||
.panel-body.text-center
|
|
||||||
img#picture(src="#{me.getPhotoURL(150)}" alt="Picture")
|
|
||||||
.col-xs-6
|
|
||||||
.panel.panel-default
|
|
||||||
.panel-heading
|
|
||||||
h3.panel-title
|
|
||||||
i.glyphicon.glyphicon-user
|
|
||||||
a(href="account/settings#wizard" data-i18n="account_settings.wizard_tab") Wizard
|
|
||||||
if (wizardSource)
|
|
||||||
.panel-body.text-center
|
|
||||||
img(src="#{wizardSource}")
|
|
||||||
.panel.panel-default.panel-me
|
|
||||||
.panel-heading
|
|
||||||
h3.panel-title
|
|
||||||
i.glyphicon.glyphicon-user
|
|
||||||
a(href="account/settings#me" data-i18n="account_settings.me_tab") Me
|
|
||||||
.panel-body
|
|
||||||
table
|
|
||||||
tr
|
|
||||||
th(data-i18n="general.name") Name
|
|
||||||
td=me.displayName()
|
|
||||||
tr
|
|
||||||
th(data-i18n="general.email") Email
|
|
||||||
td=me.get('email')
|
|
||||||
.panel.panel-default.panel-emails
|
|
||||||
.panel-heading
|
|
||||||
h3.panel-title
|
|
||||||
i.glyphicon.glyphicon-envelope
|
|
||||||
a(href="account/settings#emails" data-i18n="account_settings.emails_tab") Emails
|
|
||||||
.panel-body
|
|
||||||
if !hasEmailNotes && !hasEmailNews && !hasGeneralNews
|
|
||||||
p(data-i18n="account_settings.email_subscriptions_none") No email subscriptions.
|
|
||||||
if hasGeneralNews
|
|
||||||
h4(data-i18n="account_settings.email_news") News
|
|
||||||
ul
|
|
||||||
li(data-i18n="account_settings.email_announcements") Announcements
|
|
||||||
if hasEmailNotes
|
|
||||||
h4(data-i18n="account_settings.email_notifications") Notifications
|
|
||||||
ul
|
|
||||||
if subs.anyNotes
|
|
||||||
li(data-i18n="account_settings.email_any_notes") Any Notifications
|
|
||||||
if subs.recruitNotes
|
|
||||||
li(data-i18n="account_settings.email_recruit_notes") Job Opportunities
|
|
||||||
if hasEmailNews
|
|
||||||
h4(data-i18n="account_settings.contributor_emails") Contributor Emails
|
|
||||||
ul
|
|
||||||
if (subs.archmageNews)
|
|
||||||
li
|
|
||||||
span(data-i18n="classes.archmage_title")
|
|
||||||
| Archmage
|
|
||||||
span(data-i18n="classes.archmage_title_description")
|
|
||||||
| (Coder)
|
|
||||||
if (subs.artisanNews)
|
|
||||||
li
|
|
||||||
span.spr(data-i18n="classes.artisan_title")
|
|
||||||
| Artisan
|
|
||||||
span(data-i18n="classes.artisan_title_description")
|
|
||||||
| (Level Builder)
|
|
||||||
if (subs.adventurerNews)
|
|
||||||
li
|
|
||||||
span.spr(data-i18n="classes.adventurer_title")
|
|
||||||
| Adventurer
|
|
||||||
span(data-i18n="classes.adventurer_title_description")
|
|
||||||
| (Level Playtester)
|
|
||||||
if (subs.scribeNews)
|
|
||||||
li
|
|
||||||
span.spr(data-i18n="classes.scribe_title")
|
|
||||||
| Scribe
|
|
||||||
span(data-i18n="classes.scribe_title_description")
|
|
||||||
| (Article Editor)
|
|
||||||
if (subs.diplomatNews)
|
|
||||||
li
|
|
||||||
span.spr(data-i18n="classes.diplomat_title")
|
|
||||||
| Diplomat
|
|
||||||
span(data-i18n="classes.diplomat_title_description")
|
|
||||||
| (Translator)
|
|
||||||
if (subs.ambassadorNews)
|
|
||||||
li
|
|
||||||
span.spr(data-i18n="classes.ambassador_title")
|
|
||||||
| Ambassador
|
|
||||||
span(data-i18n="classes.ambassador_title_description")
|
|
||||||
| (Support)
|
|
||||||
|
|
||||||
.panel.panel-default
|
|
||||||
.panel-heading
|
|
||||||
h3.panel-title
|
|
||||||
i.glyphicon.glyphicon-wrench
|
|
||||||
a(href="account/settings#password" data-i18n="general.password") Password
|
|
||||||
//.panel.panel-default
|
|
||||||
// .panel-heading
|
|
||||||
// h3.panel-title
|
|
||||||
// i.glyphicon.glyphicon-briefcase
|
|
||||||
// a(href="account/settings#job-profile" data-i18n="account_settings.job_profile") Job Profile
|
|
||||||
.col-sm-6
|
|
||||||
h2(data-i18n="user.recently_played") Recently Played
|
|
||||||
hr
|
|
||||||
if !recentlyPlayed
|
|
||||||
div(data-i18n="common.loading") Loading...
|
|
||||||
else if recentlyPlayed.length
|
|
||||||
table.table
|
|
||||||
tr
|
|
||||||
th(data-i18n="resources.level") Level
|
|
||||||
th(data-i18n="user.last_played") Last Played
|
|
||||||
th(data-i18n="user.status") Status
|
|
||||||
each session in recentlyPlayed
|
|
||||||
if session.get('levelName')
|
|
||||||
tr
|
|
||||||
td
|
|
||||||
- var posturl = ''
|
|
||||||
- if (session.get('team')) posturl = '?team=' + session.get('team')
|
|
||||||
a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '')
|
|
||||||
td= moment(session.get('changed')).fromNow()
|
|
||||||
if session.get('state').complete === true
|
|
||||||
td(data-i18n="user.status_completed") Completed
|
|
||||||
else if ! session.isMultiplayer()
|
|
||||||
td(data-i18n="user.status_unfinished") Unfinished
|
|
||||||
else
|
|
||||||
td
|
|
||||||
|
|
||||||
else
|
|
||||||
.panel.panel-default
|
|
||||||
.panel-body
|
|
||||||
div(data-i18n="account.no_recent_games") No games played during the past two weeks.
|
|
21
app/templates/account/main-account-view.jade
Normal file
21
app/templates/account/main-account-view.jade
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
extends /templates/base
|
||||||
|
|
||||||
|
block content
|
||||||
|
|
||||||
|
if me.get('anonymous')
|
||||||
|
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
|
||||||
|
|
||||||
|
else
|
||||||
|
ol.breadcrumb
|
||||||
|
li
|
||||||
|
a(href="/")
|
||||||
|
span.glyphicon.glyphicon-home
|
||||||
|
li.active(data-i18n="nav.account")
|
||||||
|
|
||||||
|
#account-links.panel.panel-default
|
||||||
|
.panel-heading(data-i18n="nav.account")
|
||||||
|
ul.list-group
|
||||||
|
li.list-group-item
|
||||||
|
a.btn.btn-lg.btn-primary(href="/account/settings", data-i18n="play.settings")
|
||||||
|
li.list-group-item
|
||||||
|
a.btn.btn-lg.btn-primary(href="/account/payments", data-i18n="account.payments")
|
30
app/templates/account/payments-view.jade
Normal file
30
app/templates/account/payments-view.jade
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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.payments")
|
||||||
|
|
||||||
|
if payments.models.length
|
||||||
|
table.table.table-striped
|
||||||
|
tr
|
||||||
|
th(data-i18n="account.paid_on")
|
||||||
|
th(data-i18n="account.service")
|
||||||
|
th(data-i18n="account.price")
|
||||||
|
th(data-i18n="account.gems")
|
||||||
|
for payment in payments.models
|
||||||
|
- var service = payment.get('service')
|
||||||
|
tr
|
||||||
|
td= moment(payment.getCreationDate()).format('lll')
|
||||||
|
if service === 'ios'
|
||||||
|
td(data-i18n="account.service_apple")
|
||||||
|
td= payment.get('ios').localPrice
|
||||||
|
else
|
||||||
|
td(data-i18n="account.service_web")
|
||||||
|
td $#{(payment.get('amount')/100).toFixed(2)}
|
||||||
|
td= payment.get('gems')
|
|
@ -1,7 +1,10 @@
|
||||||
block header
|
block header
|
||||||
#site-nav
|
#site-nav
|
||||||
img#nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
|
a(href="/")
|
||||||
|
img#nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
|
||||||
div#site-nav-links
|
div#site-nav-links
|
||||||
|
a(href="/")
|
||||||
|
img#small-nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
|
||||||
a(href="/")
|
a(href="/")
|
||||||
span.glyphicon.glyphicon-home
|
span.glyphicon.glyphicon-home
|
||||||
a(href="/about", data-i18n="nav.about")
|
a(href="/about", data-i18n="nav.about")
|
||||||
|
@ -17,7 +20,7 @@ block header
|
||||||
img.account-settings-image(src=me.getPhotoURL(18), alt="")
|
img.account-settings-image(src=me.getPhotoURL(18), alt="")
|
||||||
else
|
else
|
||||||
i.glyphicon.glyphicon-user
|
i.glyphicon.glyphicon-user
|
||||||
span.spl.spr(data-i18n="nav.account" href="/account") Account
|
span.spl.spr(data-i18n="nav.account" href="/account")
|
||||||
span.caret
|
span.caret
|
||||||
ul.dropdown-menu(role="menu")
|
ul.dropdown-menu(role="menu")
|
||||||
li.user-dropdown-header
|
li.user-dropdown-header
|
||||||
|
@ -25,18 +28,14 @@ block header
|
||||||
a(href="/user/#{me.getSlugOrID()}")
|
a(href="/user/#{me.getSlugOrID()}")
|
||||||
img.img-circle(src="#{me.getPhotoURL()}" alt="")
|
img.img-circle(src="#{me.getPhotoURL()}" alt="")
|
||||||
h3=me.displayName()
|
h3=me.displayName()
|
||||||
li.user-dropdown-body
|
li
|
||||||
.col-xs-4.text-center
|
a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile")
|
||||||
a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile") Profile
|
li
|
||||||
.col-xs-4.text-center
|
a(href="/account/settings", data-i18n="play.settings")
|
||||||
a(href="/user/#{me.getSlugOrID()}/stats" data-i18n="nav.stats") Stats
|
li
|
||||||
.col-xs-4.text-center
|
a(href="/account/payments", data-i18n="account.payments")
|
||||||
a.disabled(data-i18n="nav.code") Code
|
li
|
||||||
li.user-dropdown-footer
|
a#logout-button(data-i18n="login.log_out")
|
||||||
.pull-left
|
|
||||||
a.btn.btn-default.btn-flat(href="/account" data-i18n="nav.account") Account
|
|
||||||
.pull-right
|
|
||||||
button#logout-button.btn.btn-default.btn-flat(data-i18n="login.log_out") Log Out
|
|
||||||
|
|
||||||
else
|
else
|
||||||
button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up")
|
button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up")
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
extends /templates/base
|
extends /templates/base
|
||||||
|
|
||||||
block content
|
block content
|
||||||
.progress
|
if selectedLanguage
|
||||||
.progress-bar.progress-bar-info(role="progressbar" aria-valuenow=progress aria-valuemin="0" aria-valuemax="100" style="width: "+progress+"%")= progress+"%"
|
.progress
|
||||||
|
.progress-bar.progress-bar-info(role="progressbar" aria-valuenow=progress aria-valuemin="0" aria-valuemax="100" style="width: "+progress+"%")= progress+"%"
|
||||||
|
|
||||||
table.table.table-condensed
|
table.table.table-condensed
|
||||||
tr
|
tr
|
||||||
th
|
th
|
||||||
select#language-select.form-control.input-sm
|
select#language-select.form-control.input-sm
|
||||||
|
option(value='') Select one...
|
||||||
|
|
||||||
th Type
|
th Type
|
||||||
th Specifically Covered
|
th Specifically Covered
|
||||||
th Generally Covered
|
th Generally Covered
|
||||||
|
|
||||||
for model in collection.models
|
if selectedLanguage
|
||||||
tr
|
for model in collection.models
|
||||||
td
|
tr
|
||||||
a(href=model.i18nURLBase+model.get('slug'))= model.get('name')
|
td
|
||||||
td= model.constructor.className
|
a(href=model.i18nURLBase+model.get('slug'))= model.get('name')
|
||||||
td(class=model.specificallyCovered ? 'success' : 'danger')= model.specificallyCovered ? 'Yes' : 'No'
|
td= model.constructor.className
|
||||||
td(class=model.generallyCovered ? 'success' : 'danger')= model.generallyCovered ? 'Yes' : 'No'
|
td(class=model.specificallyCovered ? 'success' : 'danger')= model.specificallyCovered ? 'Yes' : 'No'
|
||||||
|
td(class=model.generallyCovered ? 'success' : 'danger')= model.generallyCovered ? 'Yes' : 'No'
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,7 @@ block modal-header-content
|
||||||
h3(data-i18n="play.account") Account
|
h3(data-i18n="play.account") Account
|
||||||
|
|
||||||
block modal-body-content
|
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
|
|
@ -1,54 +0,0 @@
|
||||||
extends /templates/kinds/user
|
|
||||||
|
|
||||||
block append content
|
|
||||||
.btn-group.pull-right
|
|
||||||
button#grid-layout-button.btn.btn-default(data-layout='grid', class=activeLayout==='grid' ? 'active' : '')
|
|
||||||
i.glyphicon.glyphicon-th
|
|
||||||
button#table-layout-button.btn.btn-default(data-layout='table', class=activeLayout==='table' ? 'active' : '')
|
|
||||||
i.glyphicon.glyphicon-th-list
|
|
||||||
if achievementsByCategory
|
|
||||||
if activeLayout === 'grid'
|
|
||||||
.grid-layout
|
|
||||||
each achievements, category in achievementsByCategory
|
|
||||||
.row
|
|
||||||
h2.achievement-category-title(data-i18n="achievements.category_#{category}")=category
|
|
||||||
each achievement, index in achievements
|
|
||||||
- var title = achievement.i18nName();
|
|
||||||
- var description = achievement.i18nDescription();
|
|
||||||
- var locked = ! achievement.get('unlocked');
|
|
||||||
- var style = achievement.getStyle()
|
|
||||||
- var imgURL = achievement.getImageURL();
|
|
||||||
if locked
|
|
||||||
- var imgURL = achievement.getLockedImageURL();
|
|
||||||
else
|
|
||||||
- var imgURL = achievement.getImageURL();
|
|
||||||
.col-lg-4.col-xs-12
|
|
||||||
include ../achievements/achievement-popup
|
|
||||||
else if activeLayout === 'table'
|
|
||||||
.table-layout
|
|
||||||
if earnedAchievements.length
|
|
||||||
table.table
|
|
||||||
tr
|
|
||||||
th(data-i18n="general.name") Name
|
|
||||||
th(data-i18n="general.description") Description
|
|
||||||
th(data-i18n="general.date") Date
|
|
||||||
th(data-i18n="achievements.amount_achieved") Amount
|
|
||||||
th XP
|
|
||||||
each earnedAchievement in earnedAchievements.models
|
|
||||||
- var achievement = earnedAchievement.get('achievement');
|
|
||||||
if achievement.get('category')
|
|
||||||
// No level-specific achievements in here.
|
|
||||||
tr
|
|
||||||
td= achievement.i18nName()
|
|
||||||
td= achievement.i18nDescription()
|
|
||||||
td= moment().format("MMMM Do YYYY", earnedAchievement.get('changed'))
|
|
||||||
if achievement.isRepeatable()
|
|
||||||
td= earnedAchievement.get('achievedAmount')
|
|
||||||
else
|
|
||||||
td
|
|
||||||
td= earnedAchievement.get('earnedPoints')
|
|
||||||
else
|
|
||||||
.panel#no-achievements
|
|
||||||
.panel-body(data-i18n="user.no_achievements") No achievements earned yet.
|
|
||||||
else
|
|
||||||
div How did you even do that?
|
|
|
@ -14,17 +14,8 @@ block append content
|
||||||
span(data-i18n="user.favorite_prefix") Favorite language is
|
span(data-i18n="user.favorite_prefix") Favorite language is
|
||||||
strong.favorite-language= favoriteLanguage
|
strong.favorite-language= favoriteLanguage
|
||||||
span(data-i18n="user.favorite_postfix") .
|
span(data-i18n="user.favorite_postfix") .
|
||||||
.btn-group-vertical.profile-menu
|
- var emails = user.getEnabledEmails()
|
||||||
a.btn.btn-default(href="/user/#{user.getSlugOrID()}/profile")
|
// TODO: fix this, use some other method for finding contributor classes other than email settings, since they're private... Maybe achievements?
|
||||||
i.glyphicon.glyphicon-briefcase
|
|
||||||
span(data-i18n="account_settings.job_profile") Job Profile
|
|
||||||
a.btn.btn-default(href="/user/#{user.getSlugOrID()}/stats")
|
|
||||||
i.glyphicon.glyphicon-certificate
|
|
||||||
span(data-i18n="user.stats") Stats
|
|
||||||
a.btn.btn-default.disabled(href="#")
|
|
||||||
i.glyphicon.glyphicon-pencil
|
|
||||||
span(data-i18n="general.code") Code
|
|
||||||
- var emails = user.get('emails')
|
|
||||||
if emails
|
if emails
|
||||||
ul.contributor-categories
|
ul.contributor-categories
|
||||||
//li.contributor-category
|
//li.contributor-category
|
||||||
|
@ -69,9 +60,11 @@ block append content
|
||||||
th.col-xs-4(data-i18n="resources.level") Level
|
th.col-xs-4(data-i18n="resources.level") Level
|
||||||
th.col-xs-4(data-i18n="user.last_played") Last Played
|
th.col-xs-4(data-i18n="user.last_played") Last Played
|
||||||
th.col-xs-4(data-i18n="user.status") Status
|
th.col-xs-4(data-i18n="user.status") Status
|
||||||
each session in singlePlayerSessions
|
- var count = 0
|
||||||
|
each session, index in singlePlayerSessions
|
||||||
if session.get('levelName')
|
if session.get('levelName')
|
||||||
tr
|
tr(class=count > 4 ? 'hide' : '')
|
||||||
|
- count++;
|
||||||
td
|
td
|
||||||
a(href="/play/level/#{session.get('levelID')}")= session.get('levelName')
|
a(href="/play/level/#{session.get('levelID')}")= session.get('levelName')
|
||||||
td= moment(session.get('changed')).fromNow()
|
td= moment(session.get('changed')).fromNow()
|
||||||
|
@ -79,6 +72,9 @@ block append content
|
||||||
td(data-i18n="user.status_completed") Completed
|
td(data-i18n="user.status_completed") Completed
|
||||||
else
|
else
|
||||||
td(data-i18n="user.status_unfinished") Unfinished
|
td(data-i18n="user.status_unfinished") Unfinished
|
||||||
|
if count > 4
|
||||||
|
.panel-footer
|
||||||
|
button.btn.btn-info.btn-sm.more-button(data-i18n="editor.more")
|
||||||
else
|
else
|
||||||
.panel-body
|
.panel-body
|
||||||
p(data-i18n="user.no_singleplayer") No Singleplayer games played yet.
|
p(data-i18n="user.no_singleplayer") No Singleplayer games played yet.
|
||||||
|
@ -94,17 +90,20 @@ block append content
|
||||||
th.col-xs-4(data-i18n="resources.level") Level
|
th.col-xs-4(data-i18n="resources.level") Level
|
||||||
th.col-xs-4(data-i18n="user.last_played") Last Played
|
th.col-xs-4(data-i18n="user.last_played") Last Played
|
||||||
th.col-xs-4(data-i18n="general.score") Score
|
th.col-xs-4(data-i18n="general.score") Score
|
||||||
each session in multiPlayerSessions
|
each session, index in multiPlayerSessions
|
||||||
tr
|
tr(class=index > 4 ? 'hide' : '')
|
||||||
td
|
td
|
||||||
- var posturl = ''
|
- var posturl = ''
|
||||||
- if (session.get('team')) posturl = '?team=' + session.get('team')
|
- if (session.get('team')) posturl = '?team=' + session.get('team')
|
||||||
a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '')
|
a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '')
|
||||||
td= moment(session.get('changed')).fromNow()
|
td= moment(session.get('changed')).fromNow()
|
||||||
if session.get('totalScore')
|
if session.get('totalScore')
|
||||||
td= session.get('totalScore') * 100
|
td= parseInt(session.get('totalScore') * 100)
|
||||||
else
|
else
|
||||||
td(data-i18n="user.status_unfinished") Unfinished
|
td(data-i18n="user.status_unfinished") Unfinished
|
||||||
|
if multiPlayerSessions.length > 4
|
||||||
|
.panel-footer
|
||||||
|
button.btn.btn-info.btn-sm.more-button(data-i18n="editor.more")
|
||||||
else
|
else
|
||||||
.panel-body
|
.panel-body
|
||||||
p(data-i18n="user.no_multiplayer") No Multiplayer games played yet.
|
p(data-i18n="user.no_multiplayer") No Multiplayer games played yet.
|
||||||
|
@ -123,11 +122,15 @@ block append content
|
||||||
th.col-xs-4(data-i18n="achievements.achievement") Achievement
|
th.col-xs-4(data-i18n="achievements.achievement") Achievement
|
||||||
th.col-xs-4(data-i18n="achievements.last_earned") Last Earned
|
th.col-xs-4(data-i18n="achievements.last_earned") Last Earned
|
||||||
th.col-xs-4(data-i18n="achievements.amount_achieved") Amount
|
th.col-xs-4(data-i18n="achievements.amount_achieved") Amount
|
||||||
each achievement in earnedAchievements.models
|
each achievement, index in earnedAchievements.models
|
||||||
tr
|
tr(class=index > 4 ? 'hide' : '')
|
||||||
td= achievement.get('achievementName')
|
td= achievement.get('achievementName')
|
||||||
td= moment().format("MMMM Do YYYY", achievement.get('changed'))
|
td= moment().format("MMMM Do YYYY", achievement.get('changed'))
|
||||||
if achievement.get('achievedAmount')
|
if achievement.get('achievedAmount')
|
||||||
td= achievement.get('achievedAmount')
|
td= achievement.get('achievedAmount')
|
||||||
else
|
else
|
||||||
td
|
td
|
||||||
|
if earnedAchievements.length > 4
|
||||||
|
.panel-footer
|
||||||
|
button.btn.btn-info.btn-sm.more-button(data-i18n="editor.more")
|
||||||
|
|
48
app/views/account/AccountSettingsRootView.coffee
Normal file
48
app/views/account/AccountSettingsRootView.coffee
Normal file
|
@ -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)
|
||||||
|
|
|
@ -1,72 +1,48 @@
|
||||||
RootView = require 'views/kinds/RootView'
|
CocoView = require 'views/kinds/CocoView'
|
||||||
template = require 'templates/account/settings'
|
template = require 'templates/account/account-settings-view'
|
||||||
{me} = require 'lib/auth'
|
{me} = require 'lib/auth'
|
||||||
forms = require 'lib/forms'
|
forms = require 'lib/forms'
|
||||||
User = require 'models/User'
|
User = require 'models/User'
|
||||||
AuthModal = require 'views/modal/AuthModal'
|
AuthModal = require 'views/modal/AuthModal'
|
||||||
|
|
||||||
WizardSettingsView = require './WizardSettingsView'
|
module.exports = class AccountSettingsView extends CocoView
|
||||||
JobProfileTreemaView = require './JobProfileTreemaView'
|
|
||||||
|
|
||||||
module.exports = class AccountSettingsView extends RootView
|
|
||||||
id: 'account-settings-view'
|
id: 'account-settings-view'
|
||||||
template: template
|
template: template
|
||||||
changedFields: [] # DOM input fields
|
className: 'countainer-fluid'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click #save-button': 'save'
|
'change .panel input': 'onInputChanged'
|
||||||
'change #settings-panes input:checkbox': (e) -> @trigger 'checkboxToggled', e
|
'change #name': 'checkNameExists'
|
||||||
'keyup #settings-panes input:text, #settings-panes input:password': (e) -> @trigger 'inputChanged', e
|
|
||||||
'keyup #name': 'onNameChange'
|
|
||||||
'click #toggle-all-button': 'toggleEmailSubscriptions'
|
'click #toggle-all-button': 'toggleEmailSubscriptions'
|
||||||
'keypress #settings-panes': 'onKeyPress'
|
'click .profile-photo': 'onEditProfilePhoto'
|
||||||
|
'click #upload-photo-button': 'onEditProfilePhoto'
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
@save = _.debounce(@save, 200)
|
|
||||||
@onNameChange = _.debounce @checkNameExists, 500
|
|
||||||
super options
|
super options
|
||||||
return unless me
|
require('lib/services/filepicker')() unless window.application.isIPadApp # Initialize if needed
|
||||||
|
@uploadFilePath = "db/user/#{me.id}"
|
||||||
|
|
||||||
@listenTo(me, 'invalid', (errors) -> forms.applyErrorsToForm(@$el, me.validationError))
|
afterInsert: ->
|
||||||
@on 'checkboxToggled', @onToggle
|
super()
|
||||||
@on 'checkboxToggled', @onInputChanged
|
@openModalView new AuthModal() if me.get('anonymous')
|
||||||
@on 'inputChanged', @onInputChanged
|
|
||||||
@on 'enterPressed', @onEnter
|
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
c = super()
|
||||||
|
return c unless me
|
||||||
|
c.subs = {}
|
||||||
|
c.subs[sub] = 1 for sub in me.getEnabledEmails()
|
||||||
|
c
|
||||||
|
|
||||||
|
|
||||||
|
#- Form input callbacks
|
||||||
|
|
||||||
onInputChanged: (e) ->
|
onInputChanged: (e) ->
|
||||||
return @enableSaveButton() unless e?.currentTarget
|
$(e.target).addClass 'changed'
|
||||||
that = e.currentTarget
|
@trigger 'input-changed'
|
||||||
$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) ->
|
toggleEmailSubscriptions: =>
|
||||||
$that = $(e.currentTarget)
|
subs = @getSubscriptions()
|
||||||
$that.val $that[0].checked
|
$('#email-panel input[type="checkbox"]', @$el).prop('checked', not _.any(_.values(subs))).addClass('changed')
|
||||||
|
|
||||||
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: =>
|
checkNameExists: =>
|
||||||
name = $('#name', @$el).val()
|
name = $('#name', @$el).val()
|
||||||
|
@ -79,88 +55,53 @@ module.exports = class AccountSettingsView extends RootView
|
||||||
@suggestedName = newName
|
@suggestedName = newName
|
||||||
forms.setErrorToProperty @$el, 'name', "That name is taken! How about #{newName}?", true
|
forms.setErrorToProperty @$el, 'name', "That name is taken! How about #{newName}?", true
|
||||||
|
|
||||||
afterRender: ->
|
|
||||||
super()
|
|
||||||
$('#settings-tabs a', @$el).click((e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
$(e.target).tab('show')
|
|
||||||
|
|
||||||
# make sure errors show up in the general pane, but keep the password pane clean
|
|
||||||
$('#password-pane input').val('')
|
|
||||||
#@save() unless $(e.target).attr('href') is '#password-pane'
|
|
||||||
forms.clearFormAlerts($('#password-pane', @$el))
|
|
||||||
)
|
|
||||||
|
|
||||||
@chooseTab(location.hash.replace('#', ''))
|
|
||||||
|
|
||||||
wizardSettingsView = new WizardSettingsView()
|
|
||||||
@listenTo wizardSettingsView, 'change', @enableSaveButton
|
|
||||||
@insertSubView wizardSettingsView
|
|
||||||
|
|
||||||
@jobProfileTreemaView = new JobProfileTreemaView()
|
|
||||||
@listenTo @jobProfileTreemaView, 'change', @enableSaveButton
|
|
||||||
@insertSubView @jobProfileTreemaView
|
|
||||||
_.defer => @buildPictureTreema() # Not sure why, but the Treemas don't fully build without this if you reload the page.
|
|
||||||
|
|
||||||
afterInsert: ->
|
|
||||||
super()
|
|
||||||
$('#email-pane input[type="checkbox"]').on 'change', ->
|
|
||||||
$(@).addClass 'changed'
|
|
||||||
if me.get('anonymous')
|
|
||||||
@openModalView new AuthModal()
|
|
||||||
@updateSavedValues()
|
|
||||||
|
|
||||||
chooseTab: (category) ->
|
|
||||||
id = "##{category}-pane"
|
|
||||||
pane = $(id, @$el)
|
|
||||||
return @chooseTab('general') unless pane.length or category is 'general'
|
|
||||||
loc = "a[href=#{id}]"
|
|
||||||
$(loc, @$el).tab('show')
|
|
||||||
$('.tab-pane').removeClass('active')
|
|
||||||
pane.addClass('active')
|
|
||||||
@currentTab = category
|
|
||||||
|
|
||||||
getRenderData: ->
|
|
||||||
c = super()
|
|
||||||
return c unless me
|
|
||||||
c.subs = {}
|
|
||||||
c.subs[sub] = 1 for sub in c.me.getEnabledEmails()
|
|
||||||
c.showsJobProfileTab = me.isAdmin() or me.get('jobProfile') or location.hash.search('job-profile-') isnt -1
|
|
||||||
c
|
|
||||||
|
|
||||||
getSubscriptions: ->
|
|
||||||
inputs = ($(i) for i in $('#email-pane input[type="checkbox"].changed', @$el))
|
|
||||||
emailNames = (i.attr('name').replace('email_', '') for i in inputs)
|
|
||||||
enableds = (i.prop('checked') for i in inputs)
|
|
||||||
_.zipObject emailNames, enableds
|
|
||||||
|
|
||||||
toggleEmailSubscriptions: =>
|
|
||||||
subs = @getSubscriptions()
|
|
||||||
$('#email-pane input[type="checkbox"]', @$el).prop('checked', not _.any(_.values(subs))).addClass('changed')
|
|
||||||
@save()
|
|
||||||
|
|
||||||
buildPictureTreema: ->
|
|
||||||
data = photoURL: me.get('photoURL')
|
|
||||||
data.photoURL = null if data.photoURL?.search('gravatar') isnt -1 # Old style
|
|
||||||
schema = $.extend true, {}, me.schema()
|
|
||||||
schema.properties = _.pick me.schema().properties, 'photoURL'
|
|
||||||
schema.required = ['photoURL']
|
|
||||||
treemaOptions =
|
|
||||||
filePath: "db/user/#{me.id}"
|
|
||||||
schema: schema
|
|
||||||
data: data
|
|
||||||
callbacks: {change: @onPictureChanged}
|
|
||||||
|
|
||||||
@pictureTreema = @$el.find('#picture-treema').treema treemaOptions
|
|
||||||
@pictureTreema?.build()
|
|
||||||
@pictureTreema?.open()
|
|
||||||
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
|
|
||||||
|
|
||||||
onPictureChanged: (e) =>
|
onPictureChanged: (e) =>
|
||||||
@trigger 'inputChanged', e
|
@trigger 'inputChanged', e
|
||||||
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
|
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
|
||||||
|
|
||||||
save: (e) ->
|
|
||||||
|
#- Just copied from OptionsView, TODO refactor
|
||||||
|
|
||||||
|
onEditProfilePhoto: (e) ->
|
||||||
|
return if window.application.isIPadApp # TODO: have an iPad-native way of uploading a photo, since we don't want to load FilePicker on iPad (memory)
|
||||||
|
photoContainer = @$el.find('.profile-photo')
|
||||||
|
onSaving = =>
|
||||||
|
photoContainer.addClass('saving')
|
||||||
|
onSaved = (uploadingPath) =>
|
||||||
|
@$el.find('#photoURL').val(uploadingPath)
|
||||||
|
@onInputChanged() # cause for some reason editing the value doesn't trigger the jquery event
|
||||||
|
me.set('photoURL', uploadingPath)
|
||||||
|
photoContainer.removeClass('saving').attr('src', me.getPhotoURL(photoContainer.width()))
|
||||||
|
filepicker.pick {mimetypes: 'image/*'}, @onImageChosen(onSaving, onSaved)
|
||||||
|
|
||||||
|
formatImagePostData: (inkBlob) ->
|
||||||
|
url: inkBlob.url, filename: inkBlob.filename, mimetype: inkBlob.mimetype, path: @uploadFilePath, force: true
|
||||||
|
|
||||||
|
onImageChosen: (onSaving, onSaved) ->
|
||||||
|
(inkBlob) =>
|
||||||
|
onSaving()
|
||||||
|
uploadingPath = [@uploadFilePath, inkBlob.filename].join('/')
|
||||||
|
data = @formatImagePostData(inkBlob)
|
||||||
|
success = @onImageUploaded(onSaved, uploadingPath)
|
||||||
|
$.ajax '/file', type: 'POST', data: data, success: success
|
||||||
|
|
||||||
|
onImageUploaded: (onSaved, uploadingPath) ->
|
||||||
|
(e) =>
|
||||||
|
onSaved uploadingPath
|
||||||
|
|
||||||
|
|
||||||
|
#- Misc
|
||||||
|
|
||||||
|
getSubscriptions: ->
|
||||||
|
inputs = ($(i) for i in $('#email-panel input[type="checkbox"].changed', @$el))
|
||||||
|
emailNames = (i.attr('name').replace('email_', '') for i in inputs)
|
||||||
|
enableds = (i.prop('checked') for i in inputs)
|
||||||
|
_.zipObject emailNames, enableds
|
||||||
|
|
||||||
|
|
||||||
|
#- Saving changes
|
||||||
|
|
||||||
|
save: ->
|
||||||
$('#settings-tabs input').removeClass 'changed'
|
$('#settings-tabs input').removeClass 'changed'
|
||||||
forms.clearFormAlerts(@$el)
|
forms.clearFormAlerts(@$el)
|
||||||
@grabData()
|
@grabData()
|
||||||
|
@ -168,23 +109,23 @@ module.exports = class AccountSettingsView extends RootView
|
||||||
if res?
|
if res?
|
||||||
console.error 'Couldn\'t save because of validation errors:', res
|
console.error 'Couldn\'t save because of validation errors:', res
|
||||||
forms.applyErrorsToForm(@$el, res)
|
forms.applyErrorsToForm(@$el, res)
|
||||||
|
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
|
||||||
return
|
return
|
||||||
|
|
||||||
return unless me.hasLocalChanges()
|
return unless me.hasLocalChanges()
|
||||||
|
|
||||||
res = me.patch()
|
res = me.patch()
|
||||||
return unless res
|
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)
|
errors = JSON.parse(res.responseText)
|
||||||
forms.applyErrorsToForm(@$el, errors)
|
forms.applyErrorsToForm(@$el, errors)
|
||||||
save.text($.i18n.t('account_settings.error_saving', defaultValue: 'Error Saving')).removeClass('btn-success').addClass('btn-danger', 500)
|
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
|
||||||
|
@trigger 'save-user-error'
|
||||||
res.success (model, response, options) =>
|
res.success (model, response, options) =>
|
||||||
@changedFields = []
|
@trigger 'save-user-success'
|
||||||
@updateSavedValues()
|
|
||||||
save.text($.i18n.t('account_settings.saved', defaultValue: 'Changes Saved')).removeClass('btn-success', 500).attr('disabled', 'true')
|
@trigger 'save-user-began'
|
||||||
|
|
||||||
grabData: ->
|
grabData: ->
|
||||||
@grabPasswordData()
|
@grabPasswordData()
|
||||||
|
@ -198,6 +139,7 @@ module.exports = class AccountSettingsView extends RootView
|
||||||
message = $.i18n.t('account_settings.password_mismatch', defaultValue: 'Password does not match.')
|
message = $.i18n.t('account_settings.password_mismatch', defaultValue: 'Password does not match.')
|
||||||
err = [message: message, property: 'password2', formatted: true]
|
err = [message: message, property: 'password2', formatted: true]
|
||||||
forms.applyErrorsToForm(@$el, err)
|
forms.applyErrorsToForm(@$el, err)
|
||||||
|
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
|
||||||
return
|
return
|
||||||
if bothThere
|
if bothThere
|
||||||
me.set('password', password1)
|
me.set('password', password1)
|
||||||
|
@ -205,36 +147,19 @@ module.exports = class AccountSettingsView extends RootView
|
||||||
message = $.i18n.t('account_settings.password_repeat', defaultValue: 'Please repeat your password.')
|
message = $.i18n.t('account_settings.password_repeat', defaultValue: 'Please repeat your password.')
|
||||||
err = [message: message, property: 'password2', formatted: true]
|
err = [message: message, property: 'password2', formatted: true]
|
||||||
forms.applyErrorsToForm(@$el, err)
|
forms.applyErrorsToForm(@$el, err)
|
||||||
|
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
|
||||||
|
|
||||||
grabOtherData: ->
|
grabOtherData: ->
|
||||||
$('#name', @$el).val @suggestedName if @suggestedName
|
@$el.find('#name').val @suggestedName if @suggestedName
|
||||||
me.set 'name', $('#name', @$el).val()
|
me.set 'name', @$el.find('#name').val()
|
||||||
me.set 'email', $('#email', @$el).val()
|
me.set 'email', @$el.find('#email').val()
|
||||||
for emailName, enabled of @getSubscriptions()
|
for emailName, enabled of @getSubscriptions()
|
||||||
me.setEmailSubscription emailName, enabled
|
me.setEmailSubscription emailName, enabled
|
||||||
me.set 'photoURL', @pictureTreema.get('/photoURL')
|
|
||||||
|
me.set('photoURL', @$el.find('#photoURL').val())
|
||||||
|
|
||||||
adminCheckbox = @$el.find('#admin')
|
adminCheckbox = @$el.find('#admin')
|
||||||
if adminCheckbox.length
|
if adminCheckbox.length
|
||||||
permissions = []
|
permissions = []
|
||||||
permissions.push 'admin' if adminCheckbox.prop('checked')
|
permissions.push 'admin' if adminCheckbox.prop('checked')
|
||||||
me.set('permissions', permissions)
|
me.set('permissions', permissions)
|
||||||
|
|
||||||
jobProfile = me.get('jobProfile') ? {}
|
|
||||||
updated = false
|
|
||||||
for key, val of @jobProfileTreemaView.getData()
|
|
||||||
updated = updated or not _.isEqual jobProfile[key], val
|
|
||||||
jobProfile[key] = val
|
|
||||||
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
|
|
||||||
|
|
||||||
destroy: ->
|
|
||||||
@pictureTreema?.destroy()
|
|
||||||
super()
|
|
|
@ -1,38 +1,6 @@
|
||||||
View = require 'views/kinds/RootView'
|
RootView = require 'views/kinds/RootView'
|
||||||
template = require 'templates/account/account_home'
|
template = require 'templates/account/main-account-view'
|
||||||
{me} = require 'lib/auth'
|
|
||||||
User = require 'models/User'
|
|
||||||
AuthModalView = require 'views/modal/AuthModal'
|
|
||||||
RecentlyPlayedCollection = require 'collections/RecentlyPlayedCollection'
|
|
||||||
ThangType = require 'models/ThangType'
|
|
||||||
|
|
||||||
module.exports = class MainAccountView extends View
|
module.exports = class MainAccountView extends RootView
|
||||||
id: 'account-home'
|
id: 'main-account-view'
|
||||||
template: template
|
template: template
|
||||||
|
|
||||||
constructor: (options) ->
|
|
||||||
super options
|
|
||||||
return unless me
|
|
||||||
@wizardType = ThangType.loadUniversalWizard()
|
|
||||||
@recentlyPlayed = new RecentlyPlayedCollection me.get('_id')
|
|
||||||
@supermodel.loadModel @wizardType, 'thang'
|
|
||||||
@supermodel.loadCollection @recentlyPlayed, 'recentlyPlayed'
|
|
||||||
|
|
||||||
onLoaded: ->
|
|
||||||
super()
|
|
||||||
|
|
||||||
getRenderData: ->
|
|
||||||
c = super()
|
|
||||||
c.subs = {}
|
|
||||||
enabledEmails = c.me.getEnabledEmails()
|
|
||||||
c.subs[sub] = 1 for sub in enabledEmails
|
|
||||||
c.hasEmailNotes = _.any enabledEmails, (sub) -> sub.contains 'Notes'
|
|
||||||
c.hasEmailNews = _.any enabledEmails, (sub) -> sub.contains('News') and sub isnt 'generalNews'
|
|
||||||
c.hasGeneralNews = 'generalNews' in enabledEmails
|
|
||||||
c.wizardSource = @wizardType.getPortraitSource colorConfig: me.get('wizard')?.colorConfig if @wizardType.loaded
|
|
||||||
c.recentlyPlayed = @recentlyPlayed.models
|
|
||||||
c
|
|
||||||
|
|
||||||
afterRender: ->
|
|
||||||
super()
|
|
||||||
@openModalView new AuthModalView if me.isAnonymous()
|
|
18
app/views/account/PaymentsView.coffee
Normal file
18
app/views/account/PaymentsView.coffee
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
RootView = require 'views/kinds/RootView'
|
||||||
|
template = require 'templates/account/payments-view'
|
||||||
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
|
Payment = require 'models/Payment'
|
||||||
|
|
||||||
|
module.exports = class PaymentsView extends RootView
|
||||||
|
id: "payments-view"
|
||||||
|
template: template
|
||||||
|
|
||||||
|
constructor: (options) ->
|
||||||
|
super(options)
|
||||||
|
@payments = new CocoCollection([], { url: '/db/payment', model: Payment })
|
||||||
|
@supermodel.loadCollection(@payments, 'payments')
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
c = super()
|
||||||
|
c.payments = @payments
|
||||||
|
c
|
|
@ -47,6 +47,7 @@ module.exports = class I18NEditModelView extends RootView
|
||||||
@hush = true
|
@hush = true
|
||||||
$select = @$el.find('#language-select').empty()
|
$select = @$el.find('#language-select').empty()
|
||||||
@addLanguagesToSelect($select, @selectedLanguage)
|
@addLanguagesToSelect($select, @selectedLanguage)
|
||||||
|
@$el.find('option[value="en-US"]').remove()
|
||||||
@hush = false
|
@hush = false
|
||||||
editors = []
|
editors = []
|
||||||
|
|
||||||
|
@ -123,6 +124,9 @@ module.exports = class I18NEditModelView extends RootView
|
||||||
onLanguageSelectChanged: (e) ->
|
onLanguageSelectChanged: (e) ->
|
||||||
return if @hush
|
return if @hush
|
||||||
@selectedLanguage = $(e.target).val()
|
@selectedLanguage = $(e.target).val()
|
||||||
|
if @selectedLanguage
|
||||||
|
me.set('preferredLanguage', @selectedLanguage)
|
||||||
|
me.patch()
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
onSubmitPatch: (e) ->
|
onSubmitPatch: (e) ->
|
||||||
|
|
|
@ -19,7 +19,7 @@ module.exports = class I18NHomeView extends RootView
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super(options)
|
super(options)
|
||||||
@selectedLanguage = me.get('preferredLanguage', true)
|
@selectedLanguage = me.get('preferredLanguage') or ''
|
||||||
|
|
||||||
#-
|
#-
|
||||||
@aggregateModels = new Backbone.Collection()
|
@aggregateModels = new Backbone.Collection()
|
||||||
|
@ -92,7 +92,12 @@ module.exports = class I18NHomeView extends RootView
|
||||||
afterRender: ->
|
afterRender: ->
|
||||||
super()
|
super()
|
||||||
@addLanguagesToSelect(@$el.find('#language-select'), @selectedLanguage)
|
@addLanguagesToSelect(@$el.find('#language-select'), @selectedLanguage)
|
||||||
|
@$el.find('option[value="en-US"]').remove()
|
||||||
|
|
||||||
onLanguageSelectChanged: (e) ->
|
onLanguageSelectChanged: (e) ->
|
||||||
@selectedLanguage = $(e.target).val()
|
@selectedLanguage = $(e.target).val()
|
||||||
|
if @selectedLanguage
|
||||||
|
# simplest solution, see if this actually ends up being not what people want
|
||||||
|
me.set('preferredLanguage', @selectedLanguage)
|
||||||
|
me.patch()
|
||||||
@render()
|
@render()
|
||||||
|
|
|
@ -76,7 +76,7 @@ module.exports = class RootView extends CocoView
|
||||||
@openModalView new ModalClass {}
|
@openModalView new ModalClass {}
|
||||||
|
|
||||||
showLoading: ($el) ->
|
showLoading: ($el) ->
|
||||||
$el ?= @$el.find('.main-content-area')
|
$el ?= @$el.find('#site-content-area')
|
||||||
super($el)
|
super($el)
|
||||||
|
|
||||||
afterInsert: ->
|
afterInsert: ->
|
||||||
|
|
|
@ -25,6 +25,7 @@ module.exports = class AuthModal extends ModalView
|
||||||
'auth:logging-in-with-facebook': 'onLoggingInWithFacebook'
|
'auth:logging-in-with-facebook': 'onLoggingInWithFacebook'
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
|
options ?= {}
|
||||||
@onNameChange = _.debounce @checkNameExists, 500
|
@onNameChange = _.debounce @checkNameExists, 500
|
||||||
super options
|
super options
|
||||||
@mode = options.mode if options.mode
|
@mode = options.mode if options.mode
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
ModalView = require 'views/kinds/ModalView'
|
ModalView = require 'views/kinds/ModalView'
|
||||||
template = require 'templates/play/modal/play-account-modal'
|
template = require 'templates/play/modal/play-account-modal'
|
||||||
|
AccountSettingsView = require 'views/account/AccountSettingsView'
|
||||||
|
|
||||||
module.exports = class PlayAccountModal extends ModalView
|
module.exports = class PlayAccountModal extends ModalView
|
||||||
className: 'modal fade play-modal'
|
className: 'modal fade play-modal'
|
||||||
template: template
|
template: template
|
||||||
modalWidthPercent: 90
|
plain: true
|
||||||
id: 'play-account-modal'
|
id: 'play-account-modal'
|
||||||
#instant: true
|
|
||||||
|
|
||||||
#events:
|
events:
|
||||||
# 'change input.select': 'onSelectionChanged'
|
'click #save-button': -> @accountSettingsView.save()
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super options
|
super options
|
||||||
|
@ -22,7 +22,32 @@ module.exports = class PlayAccountModal extends ModalView
|
||||||
super()
|
super()
|
||||||
return unless @supermodel.finished()
|
return unless @supermodel.finished()
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-open', volume: 1
|
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: ->
|
onHidden: ->
|
||||||
super()
|
super()
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-close', volume: 1
|
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)
|
||||||
|
|
|
@ -54,7 +54,6 @@ module.exports = class PlayItemsModal extends ModalView
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super options
|
super options
|
||||||
me.set('spent', 0)
|
|
||||||
@items = new Backbone.Collection()
|
@items = new Backbone.Collection()
|
||||||
@itemCategoryCollections = {}
|
@itemCategoryCollections = {}
|
||||||
|
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
UserView = require 'views/kinds/UserView'
|
|
||||||
template = require 'templates/user/achievements'
|
|
||||||
{me} = require 'lib/auth'
|
|
||||||
Achievement = require 'models/Achievement'
|
|
||||||
EarnedAchievement = require 'models/EarnedAchievement'
|
|
||||||
AchievementCollection = require 'collections/AchievementCollection'
|
|
||||||
EarnedAchievementCollection = require 'collections/EarnedAchievementCollection'
|
|
||||||
|
|
||||||
module.exports = class AchievementsView extends UserView
|
|
||||||
id: 'user-achievements-view'
|
|
||||||
template: template
|
|
||||||
viewName: 'Stats'
|
|
||||||
activeLayout: 'grid'
|
|
||||||
|
|
||||||
events:
|
|
||||||
'click #grid-layout-button': 'layoutChanged'
|
|
||||||
'click #table-layout-button': 'layoutChanged'
|
|
||||||
|
|
||||||
constructor: (userID, options) ->
|
|
||||||
super options, userID
|
|
||||||
|
|
||||||
onLoaded: ->
|
|
||||||
unless @achievements or @earnedAchievements
|
|
||||||
@supermodel.resetProgress()
|
|
||||||
@achievements = new AchievementCollection
|
|
||||||
@earnedAchievements = new EarnedAchievementCollection @user.getSlugOrID()
|
|
||||||
@supermodel.loadCollection @achievements, 'achievements'
|
|
||||||
@supermodel.loadCollection @earnedAchievements, 'earnedAchievements'
|
|
||||||
else
|
|
||||||
for earned in @earnedAchievements.models
|
|
||||||
return unless relatedAchievement = _.find @achievements.models, (achievement) ->
|
|
||||||
achievement.get('_id') is earned.get 'achievement'
|
|
||||||
relatedAchievement.set 'unlocked', true
|
|
||||||
earned.set 'achievement', relatedAchievement
|
|
||||||
deferredImages = (achievement.cacheLockedImage() for achievement in @achievements.models when not achievement.get 'unlocked')
|
|
||||||
whenever = $.when deferredImages...
|
|
||||||
whenever.done => @render()
|
|
||||||
super()
|
|
||||||
|
|
||||||
layoutChanged: (e) ->
|
|
||||||
@activeLayout = $(e.currentTarget).data 'layout'
|
|
||||||
@render()
|
|
||||||
|
|
||||||
getRenderData: ->
|
|
||||||
context = super()
|
|
||||||
context.activeLayout = @activeLayout
|
|
||||||
|
|
||||||
# After user is loaded
|
|
||||||
if @user and not @user.isAnonymous()
|
|
||||||
context.earnedAchievements = @earnedAchievements
|
|
||||||
context.achievementsByCategory = {}
|
|
||||||
for achievement in @achievements.models when achievement.get('category')
|
|
||||||
context.achievementsByCategory[achievement.get('category')] ?= []
|
|
||||||
context.achievementsByCategory[achievement.get('category')].push achievement
|
|
||||||
context
|
|
|
@ -1,7 +1,7 @@
|
||||||
UserView = require 'views/kinds/UserView'
|
UserView = require 'views/kinds/UserView'
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
LevelSession = require 'models/LevelSession'
|
LevelSession = require 'models/LevelSession'
|
||||||
template = require 'templates/user/user_home'
|
template = require 'templates/user/main-user-view'
|
||||||
{me} = require 'lib/auth'
|
{me} = require 'lib/auth'
|
||||||
EarnedAchievementCollection = require 'collections/EarnedAchievementCollection'
|
EarnedAchievementCollection = require 'collections/EarnedAchievementCollection'
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ class LevelSessionsCollection extends CocoCollection
|
||||||
module.exports = class MainUserView extends UserView
|
module.exports = class MainUserView extends UserView
|
||||||
id: 'user-home'
|
id: 'user-home'
|
||||||
template: template
|
template: template
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click .more-button': 'onClickMoreButton'
|
||||||
|
|
||||||
constructor: (userID, options) ->
|
constructor: (userID, options) ->
|
||||||
super options
|
super options
|
||||||
|
@ -54,3 +57,8 @@ module.exports = class MainUserView extends UserView
|
||||||
@supermodel.loadCollection @levelSessions, 'levelSessions'
|
@supermodel.loadCollection @levelSessions, 'levelSessions'
|
||||||
@supermodel.loadCollection @earnedAchievements, 'earnedAchievements'
|
@supermodel.loadCollection @earnedAchievements, 'earnedAchievements'
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
onClickMoreButton: (e) ->
|
||||||
|
panel = $(e.target).closest('.panel')
|
||||||
|
panel.find('tr.hide').removeClass('hide')
|
||||||
|
panel.find('.panel-footer').remove()
|
|
@ -48,7 +48,13 @@ module.exports = class Handler
|
||||||
flattened = deltasLib.flattenDelta(delta)
|
flattened = deltasLib.flattenDelta(delta)
|
||||||
_.all flattened, (delta) ->
|
_.all flattened, (delta) ->
|
||||||
# sometimes coverage gets moved around... allow other changes to happen to i18nCoverage
|
# sometimes coverage gets moved around... allow other changes to happen to i18nCoverage
|
||||||
return _.isArray(delta.o) and (('i18n' in delta.dataPath and delta.o.length is 1) or 'i18nCoverage' in delta.dataPath)
|
return false unless _.isArray(delta.o)
|
||||||
|
return true if 'i18nCoverage' in delta.dataPath
|
||||||
|
return false unless delta.o.length is 1
|
||||||
|
index = delta.deltaPath.indexOf('i18n')
|
||||||
|
return false if index is -1
|
||||||
|
return false if delta.deltaPath[index+1] is 'en-US'
|
||||||
|
return true
|
||||||
|
|
||||||
formatEntity: (req, document) -> document?.toObject()
|
formatEntity: (req, document) -> document?.toObject()
|
||||||
getEditableProperties: (req, document) ->
|
getEditableProperties: (req, document) ->
|
||||||
|
|
|
@ -310,6 +310,7 @@ LevelHandler = class LevelHandler extends Handler
|
||||||
@sendSuccess res, data
|
@sendSuccess res, data
|
||||||
|
|
||||||
hasAccessToDocument: (req, document, method=null) ->
|
hasAccessToDocument: (req, document, method=null) ->
|
||||||
|
method ?= req.method
|
||||||
return true if method is null or method is 'get'
|
return true if method is null or method is 'get'
|
||||||
super(req, document, method)
|
super(req, document, method)
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,13 @@ products = {
|
||||||
gems: 5000
|
gems: 5000
|
||||||
id: 'gems_5'
|
id: 'gems_5'
|
||||||
}
|
}
|
||||||
|
|
||||||
'gems_10': {
|
'gems_10': {
|
||||||
amount: 999
|
amount: 999
|
||||||
gems: 11000
|
gems: 11000
|
||||||
id: 'gems_10'
|
id: 'gems_10'
|
||||||
}
|
}
|
||||||
|
|
||||||
'gems_20': {
|
'gems_20': {
|
||||||
amount: 1999
|
amount: 1999
|
||||||
gems: 25000
|
gems: 25000
|
||||||
|
@ -43,7 +43,7 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
payment.set 'recipient', req.user._id
|
payment.set 'recipient', req.user._id
|
||||||
payment.set 'created', new Date().toISOString()
|
payment.set 'created', new Date().toISOString()
|
||||||
payment
|
payment
|
||||||
|
|
||||||
post: (req, res) ->
|
post: (req, res) ->
|
||||||
appleReceipt = req.body.apple?.rawReceipt
|
appleReceipt = req.body.apple?.rawReceipt
|
||||||
appleTransactionID = req.body.apple?.transactionID
|
appleTransactionID = req.body.apple?.transactionID
|
||||||
|
@ -51,46 +51,46 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
stripeToken = req.body.stripe?.token
|
stripeToken = req.body.stripe?.token
|
||||||
stripeTimestamp = parseInt(req.body.stripe?.timestamp)
|
stripeTimestamp = parseInt(req.body.stripe?.timestamp)
|
||||||
productID = req.body.productID
|
productID = req.body.productID
|
||||||
|
|
||||||
if not (appleReceipt or (stripeTimestamp and productID))
|
if not (appleReceipt or (stripeTimestamp and productID))
|
||||||
return @sendBadInputError(res, 'Need either apple.rawReceipt or stripe.timestamp and productID')
|
return @sendBadInputError(res, 'Need either apple.rawReceipt or stripe.timestamp and productID')
|
||||||
|
|
||||||
if stripeTimestamp and not productID
|
if stripeTimestamp and not productID
|
||||||
return @sendBadInputError(res, 'Need productID if paying with Stripe.')
|
return @sendBadInputError(res, 'Need productID if paying with Stripe.')
|
||||||
|
|
||||||
if stripeTimestamp and (not stripeToken) and (not user.get('stripeCustomerID'))
|
if stripeTimestamp and (not stripeToken) and (not user.get('stripeCustomerID'))
|
||||||
return @sendBadInputError(res, 'Need stripe.token if new customer.')
|
return @sendBadInputError(res, 'Need stripe.token if new customer.')
|
||||||
|
|
||||||
if appleReceipt
|
if appleReceipt
|
||||||
if not appleTransactionID
|
if not appleTransactionID
|
||||||
return @sendBadInputError(res, 'Apple purchase? Need to specify which transaction.')
|
return @sendBadInputError(res, 'Apple purchase? Need to specify which transaction.')
|
||||||
@handleApplePaymentPost(req, res, appleReceipt, appleTransactionID, appleLocalPrice)
|
@handleApplePaymentPost(req, res, appleReceipt, appleTransactionID, appleLocalPrice)
|
||||||
|
|
||||||
else
|
else
|
||||||
@handleStripePaymentPost(req, res, stripeTimestamp, productID, stripeToken)
|
@handleStripePaymentPost(req, res, stripeTimestamp, productID, stripeToken)
|
||||||
|
|
||||||
|
|
||||||
#- Apple payments
|
#- Apple payments
|
||||||
|
|
||||||
handleApplePaymentPost: (req, res, receipt, transactionID, localPrice) ->
|
handleApplePaymentPost: (req, res, receipt, transactionID, localPrice) ->
|
||||||
formFields = { 'receipt-data': receipt }
|
formFields = { 'receipt-data': receipt }
|
||||||
|
|
||||||
#- verify receipt with Apple
|
#- verify receipt with Apple
|
||||||
|
|
||||||
verifyReq = request.post({url: config.apple.verifyURL, json: formFields}, (err, verifyRes, body) =>
|
verifyReq = request.post({url: config.apple.verifyURL, json: formFields}, (err, verifyRes, body) =>
|
||||||
if err or not body?.receipt?.in_app or (not body?.bundle_id is 'com.codecombat.CodeCombat')
|
if err or not body?.receipt?.in_app or (not body?.bundle_id is 'com.codecombat.CodeCombat')
|
||||||
console.warn 'apple receipt error?', err, body
|
console.warn 'apple receipt error?', err, body
|
||||||
@sendBadInputError(res, 'Unable to verify Apple receipt.')
|
@sendBadInputError(res, 'Unable to verify Apple receipt.')
|
||||||
return
|
return
|
||||||
|
|
||||||
transaction = _.find body.receipt.in_app, { transaction_id: transactionID }
|
transaction = _.find body.receipt.in_app, { transaction_id: transactionID }
|
||||||
return @sendBadInputError(res, 'Invalid transactionID.') unless transaction
|
return @sendBadInputError(res, 'Invalid transactionID.') unless transaction
|
||||||
|
|
||||||
#- Check existence
|
#- Check existence
|
||||||
transactionID = transaction.transaction_id
|
transactionID = transaction.transaction_id
|
||||||
criteria = { 'ios.transactionID': transactionID }
|
criteria = { 'ios.transactionID': transactionID }
|
||||||
Payment.findOne(criteria).exec((err, payment) =>
|
Payment.findOne(criteria).exec((err, payment) =>
|
||||||
|
|
||||||
if payment
|
if payment
|
||||||
unless payment.get('recipient').equals(req.user._id)
|
unless payment.get('recipient').equals(req.user._id)
|
||||||
return @sendForbiddenError(res)
|
return @sendForbiddenError(res)
|
||||||
|
@ -119,17 +119,18 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
@incrementGemsFor(req.user, product.gems, (err) =>
|
@incrementGemsFor(req.user, product.gems, (err) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@sendPaymentHipChatMessage user: req.user, payment: payment
|
||||||
@sendCreated(res, @formatEntity(req, payment))
|
@sendCreated(res, @formatEntity(req, payment))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#- Stripe payments
|
#- Stripe payments
|
||||||
|
|
||||||
handleStripePaymentPost: (req, res, timestamp, productID, token) ->
|
handleStripePaymentPost: (req, res, timestamp, productID, token) ->
|
||||||
|
|
||||||
# First, make sure we save the payment info as a Customer object, if we haven't already.
|
# First, make sure we save the payment info as a Customer object, if we haven't already.
|
||||||
if not req.user.get('stripeCustomerID')
|
if not req.user.get('stripeCustomerID')
|
||||||
stripe.customers.create({
|
stripe.customers.create({
|
||||||
|
@ -145,10 +146,10 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
(err) =>
|
(err) =>
|
||||||
return @sendDatabaseError(res, err)
|
return @sendDatabaseError(res, err)
|
||||||
)
|
)
|
||||||
|
|
||||||
else
|
else
|
||||||
@beginStripePayment(req, res, timestamp, productID)
|
@beginStripePayment(req, res, timestamp, productID)
|
||||||
|
|
||||||
|
|
||||||
beginStripePayment: (req, res, timestamp, productID) ->
|
beginStripePayment: (req, res, timestamp, productID) ->
|
||||||
product = products[productID]
|
product = products[productID]
|
||||||
|
@ -168,29 +169,30 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
||||||
((err, results) =>
|
((err, results) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
[payment, charge] = results
|
[payment, charge] = results
|
||||||
|
|
||||||
if not (payment or charge)
|
if not (payment or charge)
|
||||||
# Proceed normally from the beginning
|
# Proceed normally from the beginning
|
||||||
@chargeStripe(req, res, payment, product)
|
@chargeStripe(req, res, payment, product)
|
||||||
|
|
||||||
else if charge and not payment
|
else if charge and not payment
|
||||||
# Initialized Payment. Start from charging.
|
# Initialized Payment. Start from charging.
|
||||||
@recordStripeCharge(req, res, payment, product, charge)
|
@recordStripeCharge(req, res, payment, product, charge)
|
||||||
|
|
||||||
else
|
else
|
||||||
# Charged Stripe and recorded it. Recalculate gems to make sure credited the purchase.
|
# Charged Stripe and recorded it. Recalculate gems to make sure credited the purchase.
|
||||||
@recalculateGemsFor(req.user, (err) =>
|
@recalculateGemsFor(req.user, (err) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@sendPaymentHipChatMessage user: req.user, payment: payment
|
||||||
@sendSuccess(res, @formatEntity(req, payment))
|
@sendSuccess(res, @formatEntity(req, payment))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
chargeStripe: (req, res, payment, product) ->
|
chargeStripe: (req, res, payment, product) ->
|
||||||
stripe.charges.create({
|
stripe.charges.create({
|
||||||
amount: product.amount
|
amount: product.amount
|
||||||
|
@ -206,7 +208,7 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
}).then(
|
}).then(
|
||||||
# success case
|
# success case
|
||||||
((charge) => @recordStripeCharge(req, res, payment, product, charge)),
|
((charge) => @recordStripeCharge(req, res, payment, product, charge)),
|
||||||
|
|
||||||
# error case
|
# error case
|
||||||
((err) =>
|
((err) =>
|
||||||
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
|
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
|
||||||
|
@ -214,8 +216,8 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
else
|
else
|
||||||
@sendDatabaseError(res, 'Error charging card, please retry.'))
|
@sendDatabaseError(res, 'Error charging card, please retry.'))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
recordStripeCharge: (req, res, payment, product, charge) ->
|
recordStripeCharge: (req, res, payment, product, charge) ->
|
||||||
return @sendError(res, 500, 'Fake db error for testing.') if req.body.breakAfterCharging
|
return @sendError(res, 500, 'Fake db error for testing.') if req.body.breakAfterCharging
|
||||||
payment = @makeNewInstance(req)
|
payment = @makeNewInstance(req)
|
||||||
|
@ -241,9 +243,9 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#- Incrementing/recalculating gems
|
#- Incrementing/recalculating gems
|
||||||
|
|
||||||
incrementGemsFor: (user, gems, done) ->
|
incrementGemsFor: (user, gems, done) ->
|
||||||
purchased = _.clone(user.get('purchased'))
|
purchased = _.clone(user.get('purchased'))
|
||||||
if not purchased?.gems
|
if not purchased?.gems
|
||||||
|
@ -251,10 +253,10 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
purchased.gems = gems
|
purchased.gems = gems
|
||||||
user.set('purchased', purchased)
|
user.set('purchased', purchased)
|
||||||
user.save((err) -> done(err))
|
user.save((err) -> done(err))
|
||||||
|
|
||||||
else
|
else
|
||||||
user.update({$inc: {'purchased.gems': gems}}, {}, (err) -> done(err))
|
user.update({$inc: {'purchased.gems': gems}}, {}, (err) -> done(err))
|
||||||
|
|
||||||
recalculateGemsFor: (user, done) ->
|
recalculateGemsFor: (user, done) ->
|
||||||
|
|
||||||
Payment.find({recipient: user._id}).select('gems').exec((err, payments) ->
|
Payment.find({recipient: user._id}).select('gems').exec((err, payments) ->
|
||||||
|
@ -264,7 +266,14 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
purchased.gems = gems
|
purchased.gems = gems
|
||||||
user.set('purchased', purchased)
|
user.set('purchased', purchased)
|
||||||
user.save((err) -> done(err))
|
user.save((err) -> done(err))
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sendPaymentHipChatMessage: (options) ->
|
||||||
|
try
|
||||||
|
message = "#{options.user?.get('name')} bought #{options.payment?.get('amount')} via #{options.payment?.get('service'}."
|
||||||
|
hipchat.sendHipChatMessage message
|
||||||
|
catch e
|
||||||
|
log.error "Couldn't send HipChat message on payment because of error: #{e}"
|
||||||
|
|
||||||
module.exports = new PaymentHandler()
|
module.exports = new PaymentHandler()
|
||||||
|
|
|
@ -21,6 +21,10 @@ AchievablePlugin = (schema, options) ->
|
||||||
|
|
||||||
# Check if an achievement has been earned
|
# Check if an achievement has been earned
|
||||||
schema.post 'save', (doc) ->
|
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)
|
isNew = not doc.isInit('_id') or not (doc.id of before)
|
||||||
originalDocObj = before[doc.id] unless isNew
|
originalDocObj = before[doc.id] unless isNew
|
||||||
|
|
||||||
|
|
|
@ -192,7 +192,8 @@ module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) ->
|
||||||
module.exports.makeNewUser = makeNewUser = (req) ->
|
module.exports.makeNewUser = makeNewUser = (req) ->
|
||||||
user = new User({anonymous: true})
|
user = new User({anonymous: true})
|
||||||
user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth
|
user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth
|
||||||
user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
|
lang = languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
|
||||||
|
user.set 'preferredLanguage', lang if lang[...2] isnt 'en'
|
||||||
user.set 'lastIP', req.connection.remoteAddress
|
user.set 'lastIP', req.connection.remoteAddress
|
||||||
|
|
||||||
createMailOptions = (receiver, password) ->
|
createMailOptions = (receiver, password) ->
|
||||||
|
|
Reference in a new issue