diff --git a/website/client/components/appMenu.vue b/website/client/components/appMenu.vue
index d515571c45..10d33fcfcf 100644
--- a/website/client/components/appMenu.vue
+++ b/website/client/components/appMenu.vue
@@ -47,8 +47,10 @@ div
h3 {{ user.profile.name }}
span.small-text {{ $t('editAvatar') }}
a.nav-link.dropdown-item(@click.prevent='showInbox()') {{ $t('inbox') }}
+ router-link.dropdown-item(:to="{name: 'backgrounds'}") {{ $t('backgrounds') }}
router-link.dropdown-item(:to="{name: 'stats'}") {{ $t('stats') }}
router-link.dropdown-item(:to="{name: 'achievements'}") {{ $t('achievements') }}
+ router-link.dropdown-item(:to="{name: 'profile'}") {{ $t('profile') }}
router-link.dropdown-item(:to="{name: 'settings'}") {{ $t('settings') }}
a.nav-link.dropdown-item(to="/", @click.prevent='logout()') {{ $t('logout') }}
diff --git a/website/client/components/userMenu/achievements.vue b/website/client/components/userMenu/achievements.vue
new file mode 100644
index 0000000000..1812438c41
--- /dev/null
+++ b/website/client/components/userMenu/achievements.vue
@@ -0,0 +1,69 @@
+
+ .standard-page
+ h1 Achievements
+ .row
+ .col-12(v-for='(category, key) in achievements')
+ h2 {{ $t(key+'Achievs') }}
+ .row
+ .col-1(v-for='achievment in category.achievements')
+ div.achievement-container(:data-popover-html='achievment.title + achievment.text',
+ popover-placement='achievPopoverPlacement',
+ popover-append-to-body='achievAppendToBody')
+ div(popover-trigger='mouseenter',
+ :data-popover-html='achievment.title + achievment.text',
+ popover-placement='achievPopoverPlacement',
+ popover-append-to-body='achievAppendToBody')
+ .achievement(:class='achievment.icon + "2x"', v-if='achievment.earned')
+ .counter.badge.badge-info.stack-count(v-if='achievment.optionalCount') {{achievment.optionalCount}}
+ .achievement(class='achievement-unearned2x', v-if='!achievment.earned')
+
+ .col-12(:class="{ muted: !user.achievements.challenges.length }")
+ h2(v-once) {{ $t('challengeWinner') }}
+ div(v-for='chal in user.achievements.challenges')
+ span {{chal}}
+
+ .col-12(:class="{ muted: !user.achievements.quests }")
+ h2(v-once) {{ $t('completedQuests') }}
+ div(v-for='(value, key) in user.achievements.quests')
+ span {{ content.quests[k].text() }}
+ span {{ value }}
+
+
+
+
+
diff --git a/website/client/components/userMenu/backgrounds.vue b/website/client/components/userMenu/backgrounds.vue
new file mode 100644
index 0000000000..377bb13732
--- /dev/null
+++ b/website/client/components/userMenu/backgrounds.vue
@@ -0,0 +1,105 @@
+
+ .standard-page
+ h1 {{ $t('backgrounds') }}
+ div.customize-menu(v-for='set in backgroundShopSets')
+ h2 {{set.text}}
+ div(v-if='showPlainBackgroundBlurb(set.identifier, set.items)') {{ $t('incentiveBackgroundsUnlockedWithCheckins') }}
+ div(v-if='!ownsSet("background", set.items) && set.identifier !== "incentiveBackgrounds"')
+ //+gemCost(7)
+ button.btn.btn-primary(@click='unlock(setKeys("background", set.items))') {{ $t('unlockSet', {cost: 15}) }}
+ span.Pet_Currency_Gem1x.inline-gems
+ button.customize-option(v-for='bg in set.items',
+ type='button',
+ :class='[`background_${bg.key}`, backgroundLockedStatus(bg.key)]',
+ ng-click='unlock("background." + bg.key)',
+ :popover-title='bg.text',
+ :popover='bg.notes',
+ popover-trigger='mouseenter')
+ i.glyphicon.glyphicon-lock(ng-if='!user.purchased.background[bg.key]')
+
+
+
+
+
diff --git a/website/client/components/userMenu/profile.vue b/website/client/components/userMenu/profile.vue
new file mode 100644
index 0000000000..eee2d68518
--- /dev/null
+++ b/website/client/components/userMenu/profile.vue
@@ -0,0 +1,136 @@
+
+.standard-page
+ h2 Profile
+ .row
+ .col-md-12(v-if='!editing')
+ button.btn.btn-default(@click='editing = true') {{ $t('edit') }}
+ h2 {{ $t('displayName') }}
+ span(v-if='user.profile.name') {{user.profile.name}}
+ p
+ small.muted {{ $t('displayNameDescription1') }}
+ |
+ a(href='/#/options/settings/settings') {{ $t('displayNameDescription2') }}
+ |
+ | {{ $t('displayNameDescription3') }}
+ span.muted(ng-hide='user.profile.name') -
+ | {{ $t('none') }}
+ | -
+
+ h2 {{ $t('displayPhoto') }}
+ img.img-rendering-auto(v-if='user.profile.imageUrl', :src='user.profile.imageUrl')
+ span.muted(ng-hide='user.profile.imageUrl') -
+ | {{ $t('none') }}
+ | -
+
+ h2 {{ $t('displayBlurb') }}
+ markdown(v-if='user.profile.blurb', text='user.profile.blurb')
+ span.muted(ng-hide='user.profile.blurb') -
+ | {{ $t('none') }}
+ | -
+ //{{user.profile.blurb | linky:'_blank'}}
+ .col-md-12(v-if='editing')
+ .alert.alert-info.alert-sm
+ | {{ $t("communityGuidelinesWarning", managerEmail) }}
+ button.btn.btn-primary(type='submit', @click='save()') {{ $t("save") }}
+ // TODO use photo-upload instead: https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/xMmADvxBOak
+ .form-group
+ label {{ $t('displayName') }}
+ input.form-control(type='text', :placeholder="$t('fullName')", v-model='editingProfile.name')
+ .form-group
+ label {{ $t('photoUrl') }}
+ input.form-control(type='url', v-model='editingProfile.imageUrl', :placeholder="$t('imageUrl')")
+ .form-group
+ label {{ $t('displayBlurb') }}
+ textarea.form-control(rows=5, :placeholder="$t('displayBlurbPlaceholder')", v-model='editingProfile.blurb')
+ // include ../../shared/formatting-help
+ .row
+ .col-md-6
+ h2 {{ $t('totalCheckinsTitle') }}
+ span {{ $t('totalCheckins', {count: user.loginIncentives}) }}
+ .col-md-6
+ h2
+ | {{getProgressDisplay()}}
+ .progress
+ .progress-bar(role='progressbar', :aria-valuenow='incentivesProgress', aria-valuemin='0', aria-valuemax='100', :style='{width: incentivesProgress + "%"}')
+ span.sr-only {{ incentivesProgress }}% Complete
+
+
+
+
+
diff --git a/website/client/components/userMenu/stats.vue b/website/client/components/userMenu/stats.vue
new file mode 100644
index 0000000000..d626445608
--- /dev/null
+++ b/website/client/components/userMenu/stats.vue
@@ -0,0 +1,279 @@
+
+ .standard-page
+ h1 Stats
+ .row
+ .col-4
+ div
+ h3 Basics
+ ul
+ li Health: {{user.stats.hp}}/{{user.stats.maxHealth}}
+ li Mana: {{user.stats.mp}}/{{user.stats.maxMP}}
+ li Gold: {{user.stats.gp}}
+ li Level: {{user.stats.lvl}}
+ li Experience: {{user.stats.exp}}
+
+ div(v-if='user.flags.itemsEnabled')
+ h3 Battle Gear
+ ul
+ li(v-for='(key, itemType) in user.items.gear.equipped', v-if='flatGear[key]')
+ strong {{ flatGear[key].text() }}
+ strong(v-if='flatGear[key].str || flatGear[key].con || flatGear[key].per || flatGear[key].int') :
+ span(v-for='stat in ["str","con","per","int"]', v-if='flatGear[key][stat]') {{flatGear[key][stat]}} {{ $t(stat) }}
+
+ div(v-if='user.preferences.costume')
+ h4(v-once) {{ $t('costume') }}
+ div
+ div(ng-repeat='(key, itemType) in user.items.gear.costume')
+ strong {{flatGear[key].text()}}
+
+ div
+ h3 Background
+ ul
+ li(v-if='!user.preferences.background') None
+ li(v-if='user.preferences.background') {{ user.preferences.background }}
+
+ div(ng-if='user.flags.dropsEnabled')
+ h3(v-once) {{ $t('pets') }}
+ ul
+ li(ng-if='user.items.currentPet')
+ | {{ $t('activePet') }}:
+ | {{ formatAnimal(user.items.currentPet, 'pet') }}
+ li
+ | {{ $t('petsFound') }}:
+ | {{ totalCount(user.items.pets) }}
+ li
+ | {{ $t('beastMasterProgress') }}:
+ | {{ beastMasterProgress(user.items.pets) }}
+
+ h3(v-once) {{ $t('mounts') }}
+ ul
+ li(v-if='user.items.currentMount')
+ | {{ $t('activeMount') }}:
+ | {{ formatAnimal(user.items.currentMount, 'mount') }}
+ li
+ | {{ $t('mountsTamed') }}:
+ | {{ totalCount(user.items.mounts) }}
+ li
+ | {{ $t('mountMasterProgress') }}:
+ | {{ mountMasterProgress(user.items.mounts) }}
+
+ .col-4
+ h3(v-once) {{ $t('attributes') }}
+ .row(v-for="(statInfo, stat) in stats")
+ .col-4
+ span.hint(:popover-title='$t(statInfo.title)', popover-placement='right',
+ :popover='$t(statInfo.popover)', popover-trigger='mouseenter')
+ strong {{ $t(statInfo.title) }}
+ strong : {{ statsComputed[stat] }}
+ .col-6
+ ul.bonus-stats
+ li
+ | {{ $t('levelBonus') }}
+ | {{statsComputed.levelBonus[stat]}}
+ li
+ | {{ $t('gearBonus') }}
+ | {{statsComputed.gearBonus[stat]}}
+ li
+ | {{ $t('classBonus') }}
+ | {{statsComputed.classBonus[stat]}}
+ li
+ | {{ $t('allocatedPoints') }}
+ | {{user.stats[stat]}}
+ li
+ | {{ $t('buffs') }}
+ | {{user.stats.buffs[stat]}}
+
+ div(v-if='user.stats.buffs.stealth')
+ strong.hint(:popover-title="$t('stealth')", popover-trigger='mouseenter',
+ popover-placement='right', :popover="$t('stealthNewDay')", v-once)
+ | {{ $t('stealth') }}
+ strong : {{user.stats.buffs.stealth}}
+
+ div(v-if='user.stats.buffs.streaks')
+ div
+ strong.hint(popover-title="$t('streaksFrozen')", popover-trigger='mouseenter',
+ popover-placement='right', :popover="$t('streaksFrozenText')")
+ | {{ $t('streaksFrozen') }}
+
+ .col-4(v-if='user.flags.classSelected && !user.preferences.disableClasses')
+ h3(v-once) {{ $t('characterBuild') }}
+ h4(v-once) {{ $t('class') + ': ' }}
+ span {{ classText }}
+ button.btn.btn-danger.btn-xs(@click='changeClass(null)', v-once) {{ $t('changeClass') }}
+ small.cost 3
+ span.Pet_Currency_Gem1x.inline-gems
+
+ div
+ div
+ p(v-if='userLevel100Plus', v-once) {{ $t('noMoreAllocate') }}
+ p(v-if='user.stats.points || userLevel100Plus')
+ strong.inline
+ | {{user.stats.points}}
+ strong.hint(popover-trigger='mouseenter',
+ popover-placement='right', :popover="$t('levelPopover')") {{ $t('unallocated') }}
+ div
+ fieldset.auto-allocate
+ .checkbox
+ label
+ input(type='checkbox', v-model='user.preferences.automaticAllocation',
+ @change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation, "preferences.allocationMode": "taskbased"})')
+ span.hint(popover-trigger='mouseenter', popover-placement='right', :popover="$t('autoAllocationPop')") {{ $t('autoAllocation') }}
+ form(v-if='user.preferences.automaticAllocation', style='margin-left:1em')
+ .radio
+ label
+ input(type='radio', name='allocationMode', value='flat', v-model='user.preferences.allocationMode',
+ @change='set({"preferences.allocationMode": "flat"})')
+ span.hint(popover-trigger='mouseenter', popover-placement='right', :popover="$t('evenAllocationPop')") {{ $t('evenAllocation') }}
+ .radio
+ label
+ input(type='radio', name='allocationMode', value='classbased',
+ v-model='user.preferences.allocationMode', @change='set({"preferences.allocationMode": "classbased"})')
+ span.hint(popover-trigger='mouseenter', popover-placement='right', :popover="$t('classAllocationPop')") {{ $t('classAllocation') }}
+ .radio
+ label
+ input(type='radio', name='allocationMode', value='taskbased', v-model='user.preferences.allocationMode', @change='set({"preferences.allocationMode": "taskbased"})')
+ span.hint(popover-trigger='mouseenter', popover-placement='right', :popover="$t('taskAllocationPop')") {{ $t('taskAllocation') }}
+ div(v-if='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)')
+ button.btn.btn-primary.btn-xs(@click='allocateNow({})', popover-trigger='mouseenter', popover-placement='right', :popover="$t('distributePointsPop')")
+ span.glyphicon.glyphicon-download
+ |
+ | {{ $t('distributePoints') }}
+ .row(v-for='(statInfo, stat) in allocateStatsList')
+ .col-8
+ span.hint(popover-trigger='mouseenter', popover-placement='right', :popover='$t(statInfo.popover)')
+ | {{ $t(statInfo.title) + user.stats[stat] }}
+ .col-4(v-if='user.stats.points', @click='allocate(stat)')
+ button.btn.btn-primary(popover-trigger='mouseenter', popover-placement='right',
+ :popover='$t(statInfo.allocatepop)') +
+
+
+
+
+
diff --git a/website/client/router.js b/website/client/router.js
index e481e33129..bfa4837108 100644
--- a/website/client/router.js
+++ b/website/client/router.js
@@ -12,7 +12,12 @@ import Page from './components/page';
const Home = () => import(/* webpackChunkName: "static" */'./components/static/home');
const RegisterLogin = () => import(/* webpackChunkName: "auth" */'./components/auth/registerLogin');
+// User Pages
const CreatorIntro = () => import(/* webpackChunkName: "creator" */'./components/creatorIntro');
+const BackgroundsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/backgrounds');
+const StatsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/stats');
+const AchievementsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/achievements');
+const ProfilePage = () => import(/* webpackChunkName: "user" */'./components/userMenu/profile');
// Except for tasks that are always loaded all the other main level
// All the main level
@@ -114,8 +119,10 @@ const router = new VueRouter({
},
],
},
- { name: 'stats', path: 'stats', component: Page },
- { name: 'achievements', path: 'achievements', component: Page },
+ { name: 'backgrounds', path: 'backgrounds', component: BackgroundsPage },
+ { name: 'stats', path: 'stats', component: StatsPage },
+ { name: 'achievements', path: 'achievements', component: AchievementsPage },
+ { name: 'profile', path: 'profile', component: ProfilePage },
{ name: 'settings', path: 'settings', component: Page },
],
},