Compare commits

..

17 Commits

Author SHA1 Message Date
thehollidayinn 3e0e448250 4.2.0 2017-10-04 18:35:05 -05:00
SabreCat 3377f8a916 Merge branch 'develop' into release 2017-10-04 23:30:39 +00:00
Keith Holliday 22f83d09c4 Release with develop (#9162)
* Client: fix Apidoc and move email files (#9139)

* fix apidoc

* move emails files

* quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)

* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings

* Be consistent with capitalization of Check-In. (#9118)

* limit for inlined svg images and make home leaner by not bundling it with the rest of static pages

* sep 27 fixes (#9088)

* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height

* Oct 3 fixes (#9148)

* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting

* fix some text describing menu items (#9145)

* 4.1.3

* use `selectGearToPin` instead of updateStore in migration-script (#9102)

* Tags redesign in edit-task modal (#9122)

* Truncate tags list to maximum number of tasks

This commit truncates the list of tags in the edit task modal and
displays the remaining selected tasks as a number.

* Align tags-select dropdown with "Tags" label

* Add tags popup component

* Use solid purple for tags-select dropdown

* Remove shadow when tags-select is active
* Add border-radius to tags-select
* Re-add previously disabled transitions
* Remove unused template element

* Add Clear Tags button to footer of tags popup

* Decrease column size for tags to better match design
* Truncate tag name to avoid overflows
* Add tag name as title to show full name on hover

* Grow inline tags select from left to right

* Style none button
* Add spacing to streak reset button to line up with tags select

* Add top offset to tags dropdown toggle to line up with label

* Ability to collapse checklists (#9158)

* ability to collapse checklists

* typo

* show warning to use the mobile apps (#9152)

* Oct 4 fixes (#9159)

* Added gem purchase note

* Added notification count

* Added party reload

* Added description when user can no longer purchase gems this month

* Prevent modal from showing when pruchasing recently purchased items

* Added progress bar

* Prevented non leader from loading approvals

* Added group billing

* Release fix (#9161)

* Merge Develop onto Release (#9123)

* Some random quick (#9111)

* Switch group button directions

* Allowed admins to export challenges

* Added scoping to some stable styles

* Fixed challenge cloning

* Tasks tags (#9112)

* Added auto apply and exit

* Add challenge tag editing

* Fixed lint

* Skill fixes (#9113)

* Added local storage setting for spell drawer

* Added new spell styles

* Fixed typo

* Reset local creds if access is denied (#9114)

* various fixes: group leader's name at top of edit drop-down; Members List; etc (#9117)

* fix text describing location of subscription/gem gift box

* disable Copy As To-Do in Tavern, guilds, party because it's not working

* change members label on group pages to Member List

* remove outdated info about seeing number of Gems available to buy

* allow Danger Zone to be seen by players without local authentication

Also add an hr because the Danger Zone heading was crammed up against the button above it.

* put current group leader's name at top of Leader change drop-down

* Client Fixes (#9120)

* unduplicate logout code

* re-enable debug menu

* fix pets badge and equipping mounts

* close gift modal after sending gems

* armoire notifications

* Oct 1 fixes (#9121)

* Added default tags to task

* Added seasonal gear check and show spooky

* Disabled spooky sparkles

* Fixed challenge remove tasks modal

* Hid checklist

* Added group gems modal

* Purchase with amazon

* Added check for user health

* Added missing notification file

* 4.1.1

* 4.1.2

* Merge develop into release (#9154)

* Client: fix Apidoc and move email files (#9139)

* fix apidoc

* move emails files

* quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)

* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings

* Be consistent with capitalization of Check-In. (#9118)

* limit for inlined svg images and make home leaner by not bundling it with the rest of static pages

* sep 27 fixes (#9088)

* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height

* Oct 3 fixes (#9148)

* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting

* fix some text describing menu items (#9145)
2017-10-04 18:26:05 -05:00
Keith Holliday 496534ab4b Release fix (#9161)
* Merge Develop onto Release (#9123)

* Some random quick (#9111)

* Switch group button directions

* Allowed admins to export challenges

* Added scoping to some stable styles

* Fixed challenge cloning

* Tasks tags (#9112)

* Added auto apply and exit

* Add challenge tag editing

* Fixed lint

* Skill fixes (#9113)

* Added local storage setting for spell drawer

* Added new spell styles

* Fixed typo

* Reset local creds if access is denied (#9114)

* various fixes: group leader's name at top of edit drop-down; Members List; etc (#9117)

* fix text describing location of subscription/gem gift box

* disable Copy As To-Do in Tavern, guilds, party because it's not working

* change members label on group pages to Member List

* remove outdated info about seeing number of Gems available to buy

* allow Danger Zone to be seen by players without local authentication

Also add an hr because the Danger Zone heading was crammed up against the button above it.

* put current group leader's name at top of Leader change drop-down

* Client Fixes (#9120)

* unduplicate logout code

* re-enable debug menu

* fix pets badge and equipping mounts

* close gift modal after sending gems

* armoire notifications

* Oct 1 fixes (#9121)

* Added default tags to task

* Added seasonal gear check and show spooky

* Disabled spooky sparkles

* Fixed challenge remove tasks modal

* Hid checklist

* Added group gems modal

* Purchase with amazon

* Added check for user health

* Added missing notification file

* 4.1.1

* 4.1.2

* Merge develop into release (#9154)

* Client: fix Apidoc and move email files (#9139)

* fix apidoc

* move emails files

* quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)

* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings

* Be consistent with capitalization of Check-In. (#9118)

* limit for inlined svg images and make home leaner by not bundling it with the rest of static pages

* sep 27 fixes (#9088)

* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height

* Oct 3 fixes (#9148)

* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting

* fix some text describing menu items (#9145)
2017-10-04 17:39:34 -05:00
Keith Holliday 7325d2020e Oct 4 fixes (#9159)
* Added gem purchase note

* Added notification count

* Added party reload

* Added description when user can no longer purchase gems this month

* Prevent modal from showing when pruchasing recently purchased items

* Added progress bar

* Prevented non leader from loading approvals

* Added group billing
2017-10-04 16:47:19 -05:00
negue 1a0d39e566 show warning to use the mobile apps (#9152) 2017-10-04 21:19:10 +02:00
Matteo Pagliazzi dc62ab7577 Ability to collapse checklists (#9158)
* ability to collapse checklists

* typo
2017-10-04 20:35:59 +02:00
Michael Hibbs c4e5633e48 Tags redesign in edit-task modal (#9122)
* Truncate tags list to maximum number of tasks

This commit truncates the list of tags in the edit task modal and
displays the remaining selected tasks as a number.

* Align tags-select dropdown with "Tags" label

* Add tags popup component

* Use solid purple for tags-select dropdown

* Remove shadow when tags-select is active
* Add border-radius to tags-select
* Re-add previously disabled transitions
* Remove unused template element

* Add Clear Tags button to footer of tags popup

* Decrease column size for tags to better match design
* Truncate tag name to avoid overflows
* Add tag name as title to show full name on hover

* Grow inline tags select from left to right

* Style none button
* Add spacing to streak reset button to line up with tags select

* Add top offset to tags dropdown toggle to line up with label
2017-10-04 12:33:35 -05:00
negue d977656e96 use selectGearToPin instead of updateStore in migration-script (#9102) 2017-10-04 10:59:44 -05:00
Keith Holliday b0a980d56e Merge develop into release (#9154)
* Client: fix Apidoc and move email files (#9139)

* fix apidoc

* move emails files

* quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)

* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings

* Be consistent with capitalization of Check-In. (#9118)

* limit for inlined svg images and make home leaner by not bundling it with the rest of static pages

* sep 27 fixes (#9088)

* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height

* Oct 3 fixes (#9148)

* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting

* fix some text describing menu items (#9145)
2017-10-03 21:15:00 -05:00
Alys 3d75c99f8d fix some text describing menu items (#9145) 2017-10-04 09:24:44 +10:00
Keith Holliday 0aa16d7021 Oct 3 fixes (#9148)
* Only show level after yesterdailies modal

* Fixed zindex

* Added spcial spells to rewards column

* Added single click buy for health and armoire

* Prevented task scoring when casting a spell

* Renamed generic purchase method

* Updated nav for small screen

* Hide checklist while casting
2017-10-03 16:33:01 -05:00
negue 4fa3046104 sep 27 fixes (#9088)
* fix item paddings / drawer width

* expand the width of item-rows by the margin of an item

* fix hatchedPet-dialog

* fix hatching-modal

* remove min-height
2017-10-03 21:44:45 +02:00
Matteo Pagliazzi fda4be01b8 limit for inlined svg images and make home leaner by not bundling it with the rest of static pages 2017-10-03 17:31:53 +02:00
Brian Murray 67436fbef1 Be consistent with capitalization of Check-In. (#9118) 2017-10-04 00:25:58 +10:00
Alys ffb92a5faa quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140)
* enable link to markdown info on group and challenge edit screen

* allow admin (moderators and staff) to edit challenges

* allow admin (moderators and staff) to edit guilds

Also add some unrelated TODO comments.

* allow any party member (not just leader) to start quest from party page

* allow quest owner to cancel, begin, abort quest

Previously only the party leader could see those buttons. The leader still can.

This also hides those buttons from all other party members.

* enable reverse chat in guilds and party

* remove outdated videos from press kit

* adjust various wordings
2017-10-04 00:19:49 +10:00
Matteo Pagliazzi 4a44d60fac Client: fix Apidoc and move email files (#9139)
* fix apidoc

* move emails files
2017-10-03 15:34:12 +02:00
65 changed files with 990 additions and 421 deletions
+2 -7
View File
@@ -47,10 +47,5 @@ gulp.task('build:prod', [
'build:server',
'prepare:staticNewStuff',
'build:client',
], (done) => {
runSequence(
'grunt-build:prod',
'apidoc',
done
);
});
'apidoc',
]);
+3 -2
View File
@@ -1,4 +1,5 @@
var updateStore = require('../website/common/script/libs/updateStore');
import { selectGearToPin } from '../website/common/script/ops/pinnedGearUtils';
var getItemInfo = require('../website/common/script/libs/getItemInfo');
var migrationName = '20170928_redesign_launch.js';
@@ -69,7 +70,7 @@ function updateUser (user) {
var set = {'migration': migrationName};
var oldRewardsList = updateStore(user);
var oldRewardsList = selectGearToPin(user);
var newPinnedItems = [
{
type: 'armoire',
+55 -42
View File
@@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "4.1.2",
"version": "4.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -68,6 +68,15 @@
"@types/mime": "1.3.1"
}
},
"JSONStream": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz",
"integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=",
"requires": {
"jsonparse": "1.3.1",
"through": "2.3.8"
}
},
"abbrev": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
@@ -782,6 +791,14 @@
"is-buffer": "1.1.5"
}
},
"axios-progress-bar": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/axios-progress-bar/-/axios-progress-bar-0.1.7.tgz",
"integrity": "sha512-xStxJUtcQUH0ulLni5qc8YwvNMTUjO7rmUTsvnxS1bD2GJKEemozP6sJ5OeoC6jjd6MS5FZyx5BlkU/lD8JLUw==",
"requires": {
"nprogress": "0.2.0"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -2325,9 +2342,9 @@
"resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.2.tgz",
"integrity": "sha1-+GzWzvT1MAyOY+B6TVEvZfv/RTE=",
"requires": {
"JSONStream": "1.3.1",
"combine-source-map": "0.7.2",
"defined": "1.0.0",
"JSONStream": "1.3.1",
"through2": "2.0.3",
"umd": "3.0.1"
}
@@ -2357,6 +2374,7 @@
"resolved": "https://registry.npmjs.org/browserify/-/browserify-12.0.2.tgz",
"integrity": "sha1-V/IeXm4wj/WYfE2v1EhAsrmPehk=",
"requires": {
"JSONStream": "1.3.1",
"assert": "1.3.0",
"browser-pack": "6.0.2",
"browser-resolve": "1.11.2",
@@ -2378,7 +2396,6 @@
"inherits": "2.0.3",
"insert-module-globals": "7.0.1",
"isarray": "0.0.1",
"JSONStream": "1.3.1",
"labeled-stream-splicer": "2.0.0",
"module-deps": "4.1.1",
"os-browserify": "0.1.2",
@@ -7389,13 +7406,6 @@
}
}
},
"string_decoder": {
"version": "1.0.1",
"bundled": true,
"requires": {
"safe-buffer": "5.0.1"
}
},
"string-width": {
"version": "1.0.2",
"bundled": true,
@@ -7405,6 +7415,13 @@
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
"version": "1.0.1",
"bundled": true,
"requires": {
"safe-buffer": "5.0.1"
}
},
"stringstream": {
"version": "0.0.5",
"bundled": true,
@@ -10353,10 +10370,10 @@
"resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz",
"integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=",
"requires": {
"JSONStream": "1.3.1",
"combine-source-map": "0.7.2",
"concat-stream": "1.5.2",
"is-buffer": "1.1.5",
"JSONStream": "1.3.1",
"lexical-scope": "1.2.0",
"process": "0.11.10",
"through2": "2.0.3",
@@ -11349,15 +11366,6 @@
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk="
},
"JSONStream": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz",
"integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=",
"requires": {
"jsonparse": "1.3.1",
"through": "2.3.8"
}
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -13257,6 +13265,7 @@
"resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz",
"integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=",
"requires": {
"JSONStream": "1.3.1",
"browser-resolve": "1.11.2",
"cached-path-relative": "1.0.1",
"concat-stream": "1.5.2",
@@ -13264,7 +13273,6 @@
"detective": "4.5.0",
"duplexer2": "0.1.4",
"inherits": "2.0.3",
"JSONStream": "1.3.1",
"parents": "1.0.1",
"readable-stream": "2.0.6",
"resolve": "1.4.0",
@@ -14640,6 +14648,11 @@
"set-blocking": "2.0.0"
}
},
"nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
"integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E="
},
"nth-check": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
@@ -17234,22 +17247,6 @@
}
}
},
"require_optional": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
"integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
"requires": {
"resolve-from": "2.0.0",
"semver": "5.4.1"
},
"dependencies": {
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
}
}
},
"require-again": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-again/-/require-again-2.0.0.tgz",
@@ -17289,6 +17286,22 @@
}
}
},
"require_optional": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
"integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
"requires": {
"resolve-from": "2.0.0",
"semver": "5.4.1"
},
"dependencies": {
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
}
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -18655,11 +18668,6 @@
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"string-length": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-0.1.2.tgz",
@@ -18693,6 +18701,11 @@
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"stringify-object": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-1.0.1.tgz",
+2 -1
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.1.2",
"version": "4.2.0",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -14,6 +14,7 @@
"autoprefixer": "^6.4.0",
"aws-sdk": "^2.0.25",
"axios": "^0.16.0",
"axios-progress-bar": "^0.1.7",
"babel-core": "^6.0.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^6.0.0",
+6 -1
View File
@@ -105,7 +105,12 @@ const baseConfig = {
{
test: /\.svg$/,
use: [
{ loader: 'svg-url-loader' },
{
loader: 'svg-url-loader',
options: {
limit: 10000,
},
},
{ loader: 'svgo-loader' },
],
include: [path.resolve(projectRoot, 'website/client/assets/svg/for-css')],
+9 -1
View File
@@ -68,6 +68,8 @@
<script>
import axios from 'axios';
import { loadProgressBar } from 'axios-progress-bar';
import AppMenu from './components/appMenu';
import AppHeader from './components/appHeader';
import AppFooter from './components/appFooter';
@@ -129,6 +131,7 @@ export default {
this.$refs.sound.load();
});
// @TODO: I'm not sure these should be at the app level. Can we move these back into shop/inventory or maybe they need a lateral move?
this.$root.$on('buyModal::showItem', (item) => {
this.selectedItemToBuy = item;
this.$root.$emit('show::modal', 'buy-modal');
@@ -140,6 +143,9 @@ export default {
});
// @TODO split up this file, it's too big
loadProgressBar();
// Set up Error interceptors
axios.interceptors.response.use((response) => {
if (this.user && response.data && response.data.notifications) {
@@ -320,10 +326,12 @@ export default {
}
}
},
memberSelected (member) {
async memberSelected (member) {
this.$store.dispatch('user:castSpell', {key: this.selectedSpellToBuy.key, targetId: member.id});
this.selectedSpellToBuy = null;
this.$store.dispatch('party:getMembers', {forceLoad: true});
this.$root.$emit('hide::modal', 'select-member-modal');
},
hideLoadingScreen () {
+1
View File
@@ -13,6 +13,7 @@
@import './loading-screen';
// Global styles
@import './misc';
@import './typography';
@import './markdown';
@import './form';
+1 -1
View File
@@ -2,7 +2,7 @@
.items > div {
display: inline-block;
margin-right: 23px;
margin-right: 24px;
}
.items > div:last-of-type {
+17
View File
@@ -0,0 +1,17 @@
.expand-toggle:after {
display: inline-block;
width: 0;
height: 0;
content: "";
border-bottom: 4px solid transparent;
border-top: 4px solid transparent;
border-right: 4px solid transparent;
border-left: 4px solid;
}
.expand-toggle.open:after {
border-top: 4px solid;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
border-bottom: 0;
}
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8">
<path fill="#878190" fill-rule="evenodd" d="M3 6h9v2H3V6zm0-3h7v2H3V3zm0-3h5v2H3V0zM0 6h2v2H0V6zm0-3h2v2H0V3zm0-3h2v2H0V0z"/>
</svg>

After

Width:  |  Height:  |  Size: 219 B

+23
View File
@@ -29,11 +29,27 @@ div
br
// TODO link to party creation or party page if partying solo
button.btn.btn-primary(@click='openPartyModal()') {{ partyMembers && partyMembers.length > 1 ? $t('startAParty') : $t('inviteFriends') }}
a.useMobileApp(v-if="isAndroidMobile()", v-once, href="https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica") {{ $t('useMobileApps') }}
a.useMobileApp(v-if="isIOSMobile()", v-once, href="https://itunes.apple.com/us/app/habitica-gamified-task-manager/id994882113?mt=8") {{ $t('useMobileApps') }}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.useMobileApp {
background: red;
color: white;
z-index: 10;
width: 100%;
margin: 10px 5px 0 0;
height: 64px;
text-align: center;
display: flex;
align-items: center;
}
#app-header {
padding-left: 14px;
margin-top: 56px;
@@ -117,6 +133,7 @@ export default {
user: 'user:data',
partyMembers: 'party:members',
}),
showHeader () {
if (this.$store.state.hideHeader) return false;
return true;
@@ -129,6 +146,12 @@ export default {
...mapActions({
getPartyMembers: 'party:getMembers',
}),
isAndroidMobile () {
return navigator.userAgent.match(/Android/i);
},
isIOSMobile () {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
},
expandMember (memberId) {
if (this.expandedMember === memberId) {
this.expandedMember = null;
+27 -50
View File
@@ -3,9 +3,10 @@ div
inbox-modal
creator-intro
profile
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-lg
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-md
.navbar-header
.logo.svg-icon(v-html="icons.logo")
.logo.svg-icon.hidden-lg-down(v-html="icons.logo")
.svg-icon.gryphon.hidden-xl-up
b-collapse#nav_collapse.collapse.navbar-collapse(is-nav)
ul.navbar-nav.mr-auto
router-link.nav-item(tag="li", :to="{name: 'tasks'}", exact)
@@ -93,58 +94,34 @@ div
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/utils.scss';
/* Less than Desktops and laptops ----------- */
@media only screen and (max-width : 1224px) {
#nav_collapse {
background: $purple-100;
margin-top: 1em;
margin-left: 70%;
padding-bottom: 1em;
a {
padding: .5em !important;
}
}
.dropdown-menu {
position: absolute !important;
left: -10em;
top: -.5em;
}
}
@media only screen and (max-width : 1224px) and (min-width: 1200px) {
#nav_collapse {
margin-top: 37em !important;
a {
width: 100%;
}
}
.navbar-collapse.collapse {
display: none !important;
}
.navbar-collapse.collapse.show {
display: block !important;
}
.navbar-toggler, .navbar-nav {
display: block;
}
.navbar-toggleable-lg .navbar-collapse {
display: block;
}
}
@media only screen and (max-width: 1280px) {
.nav-link {
padding: .8em 1em !important;
}
}
@media only screen and (max-width: 1200px) {
.navbar-header {
margin-right: 0px;
}
.gryphon {
background-image: url('~assets/images/melior@3x.png');
width: 30px;
height: 30px;
background-size: cover;
color: $white;
margin: 0 auto;
}
}
@media only screen and (max-width: 990px) {
#nav_collapse {
margin-top: 1.3em;
background-color: $purple-200;
}
}
nav.navbar {
background: $purple-100 url(~assets/svg/for-css/bits.svg) right no-repeat;
padding-left: 25px;
@@ -354,7 +331,6 @@ export default {
hourglasses: svgHourglasses,
logo,
}),
groupPlans: [],
};
},
computed: {
@@ -364,6 +340,7 @@ export default {
...mapState({
user: 'user.data',
userHourglasses: 'user.data.purchased.plan.consecutive.trinkets',
groupPlans: 'groupPlans',
}),
},
mounted () {
@@ -390,7 +367,7 @@ export default {
this.$root.$emit('show::modal', 'profile');
},
async getUserGroupPlans () {
this.groupPlans = await this.$store.dispatch('guilds:getGroupPlans');
this.$store.state.groupPlans = await this.$store.dispatch('guilds:getGroupPlans');
},
openPartyModal () {
this.$root.$emit('show::modal', 'create-party-modal');
@@ -35,7 +35,7 @@
b-dropdown.create-dropdown(text="Select a Participant")
b-dropdown-item(v-for="member in members", :key="member._id", @click="openMemberProgressModal(member._id)")
| {{ member.profile.name }}
span(v-if='isLeader')
span(v-if='isLeader || isAdmin')
b-dropdown.create-dropdown(:text="$t('addTaskToChallenge')", :variant="'success'")
b-dropdown-item(v-for="type in columns", :key="type", @click="createTask(type)")
| {{$t(type)}}
@@ -64,13 +64,13 @@
button.btn.btn-success(v-once, @click='joinChallenge()') {{$t('joinChallenge')}}
div(v-if='isMember')
button.btn.btn-danger(v-once, @click='leaveChallenge()') {{$t('leaveChallenge')}}
div(v-if='isLeader')
div(v-if='isLeader || isAdmin')
button.btn.btn-secondary(v-once, @click='edit()') {{$t('editChallenge')}}
div(v-if='isLeader')
div(v-if='isLeader || isAdmin')
button.btn.btn-danger(v-once, @click='closeChallenge()') {{$t('endChallenge')}}
div(v-if='isLeader || isAdmin')
button.btn.btn-secondary(v-once, @click='exportChallengeCsv()') {{$t('exportChallengeCsv')}}
div(v-if='isLeader')
div(v-if='isLeader || isAdmin')
button.btn.btn-secondary(v-once, @click='cloneChallenge()') {{$t('clone')}}
.description-section
h2 {{$t('challengeSummary')}}
@@ -17,7 +17,7 @@
.form-group
label
strong(v-once) {{$t('challengeDescription')}} *
a.float-right {{ $t('markdownFormattingHelp') }}
a.float-right(v-markdown='$t("markdownFormattingHelp")')
textarea.description-textarea.form-control(:placeholder="$t('challengeDescriptionPlaceholder')", v-model="workingChallenge.description")
.form-group(v-if='creating')
label
@@ -136,6 +136,8 @@ import bDropdown from 'bootstrap-vue/lib/components/dropdown';
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import bFormInput from 'bootstrap-vue/lib/components/form-input';
import markdownDirective from 'client/directives/markdown';
import { TAVERN_ID, MIN_SHORTNAME_SIZE_FOR_CHALLENGES, MAX_SUMMARY_SIZE_FOR_CHALLENGES } from '../../../common/script/constants';
import { mapState } from 'client/libs/store';
@@ -147,6 +149,9 @@ export default {
bDropdownItem,
bFormInput,
},
directives: {
markdown: markdownDirective,
},
data () {
let categoryOptions = [
{
@@ -0,0 +1,74 @@
<template lang="pug">
.row.standard-page(v-if='groupIsSubscribed && isLeader')
.col-12.col-md-6.offset-md-3
table.table.alert.alert-info
tr(v-if='group.purchased.plan.dateTerminated')
td.alert.alert-warning
span.noninteractive-button.btn-danger {{ $t('canceledGroupPlan') }}
i.glyphicon.glyphicon-time {{ $t('groupPlanCanceled') }}
strong {{dateTerminated}}
tr(v-if='!group.purchased.plan.dateTerminated')
td
h3 {{ $t('paymentDetails') }}
p(v-if='group.purchased.plan.planId') {{ $t('groupSubscriptionPrice') }}
tr(v-if='group.purchased.plan.extraMonths')
td
span.glyphicon.glyphicon-credit-card
| {{ $t('purchasedGroupPlanPlanExtraMonths', purchasedGroupPlanPlanExtraMonths) }}
tr(v-if='group.purchased.plan.consecutive.count || group.purchased.plan.consecutive.offset')
td
span.glyphicon.glyphicon-forward
| {{ $t('consecutiveSubscription') }}
ul.list-unstyled
li {{ $t('consecutiveMonths') }} {{group.purchased.plan.consecutive.count + group.purchased.plan.consecutive.offset}}
li {{ $t('gemCapExtra') }} {{group.purchased.plan.consecutive.gemCapExtra}}
li {{ $t('mysticHourglasses') }} {{group.purchased.plan.consecutive.trinkets}}
.col-12.col-md-6.offset-md-3
.btn.btn-primary(v-if='!group.purchased.plan.dateTerminated && group.purchased.plan.paymentMethod === "Stripe"',
@click='showStripeEdit({groupId: group.id})') {{ $t('subUpdateCard') }}
.btn.btn-sm.btn-danger(v-if='!group.purchased.plan.dateTerminated',
@click='cancelSubscription({group: group})') {{ $t('cancelGroupSub') }}
</template>
<script>
import moment from 'moment';
import { mapState } from 'client/libs/store';
import paymentsMixin from 'client/mixins/payments';
export default {
mixins: [paymentsMixin],
props: ['groupId'],
data () {
return {
group: {},
};
},
mounted () {
this.loadGroup();
},
computed: {
...mapState({user: 'user.data'}),
isLeader () {
return this.user._id === this.group.leader._id;
},
groupIsSubscribed () {
return this.group.purchased && this.group.purchased.plan && this.group.purchased.plan.customerId;
},
dateTerminated () {
if (!this.user.preferences || !this.user.preferences.dateFormat) return moment(this.group.purchased.plan.dateTerminated);
return moment(this.group.purchased.plan.dateTerminated).format(this.user.preferences.dateFormat.toUpperCase());
},
purchasedGroupPlanPlanExtraMonths () {
return {
months: parseFloat(this.group.purchased.plan.extraMonths).toFixed(2),
};
},
},
methods: {
async loadGroup () {
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.groupId});
this.group = Object.assign({}, group);
},
},
};
</script>
@@ -2,9 +2,14 @@
.row
secondary-menu.col-12
router-link.nav-link(:to="{name: 'groupPlanDetailTaskInformation', params: {groupId}}",
exact, :class="{'active': $route.name === 'groupPlanDetailTaskInformation'}") Task Board
exact, :class="{'active': $route.name === 'groupPlanDetailTaskInformation'}") {{ $t('groupTaskBoard') }}
router-link.nav-link(:to="{name: 'groupPlanDetailInformation', params: {groupId}}",
exact, :class="{'active': $route.name === 'groupPlanDetailInformation'}") Group Information
exact, :class="{'active': $route.name === 'groupPlanDetailInformation'}") {{ $t('groupInformation') }}
router-link.nav-link(
v-if='isLeader',
:to="{name: 'groupPlanBilling', params: {groupId}}",
exact,
:class="{'active': $route.name === 'groupPlanBilling'}") {{ $t('groupBilling') }}
.col-12
router-view
@@ -12,11 +17,26 @@
<script>
import SecondaryMenu from 'client/components/secondaryMenu';
import { mapState } from 'client/libs/store';
export default {
props: ['groupId'],
components: {
SecondaryMenu,
},
computed: {
...mapState({
user: 'user.data',
groupPlans: 'groupPlans',
}),
isLeader () {
let groupFound = this.groupPlans.find(group => {
return group._id === this.groupId;
});
if (!groupFound) return false;
return groupFound.leader === this.user._id;
},
},
};
</script>
@@ -349,15 +349,21 @@ export default {
groupId: this.searchId,
});
let groupedApprovals = this.loadApprovals();
tasks.forEach((task) => {
if (groupedApprovals.length > 0) task.approvals = groupedApprovals[task._id];
this.tasksByType[task.type].push(task);
});
},
async loadApprovals () {
if (this.group.leader._id !== this.user._id) return [];
let approvalRequests = await this.$store.dispatch('tasks:getGroupApprovals', {
groupId: this.searchId,
});
let groupedApprovals = groupBy(approvalRequests, 'group.taskId');
tasks.forEach((task) => {
task.approvals = groupedApprovals[task._id];
this.tasksByType[task.type].push(task);
});
return groupBy(approvalRequests, 'group.taskId');
},
editTask (task) {
this.taskFormPurpose = 'edit';
+16 -7
View File
@@ -56,15 +56,16 @@
button.btn.btn-success(class='btn-success', v-if='isLeader && !group.purchased.active', @click='upgradeGroup()')
| {{ $t('upgrade') }}
.button-container
button.btn.btn-primary(b-btn, @click="updateGuild", v-once, v-if='isLeader') {{ $t('edit') }}
button.btn.btn-primary(b-btn, @click="updateGuild", v-once, v-if='isLeader || isAdmin') {{ $t('edit') }}
.button-container
button.btn.btn-success(class='btn-success', v-if='!isMember', @click='join()') {{ $t('join') }}
.button-container
button.btn.btn-primary(v-once, @click='showInviteModal()') {{$t('invite')}}
// @TODO: hide the invitation button if there's an active group plan and the player is not the leader
.button-container
// @TODO: V2 button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}}
// @TODO: V2 button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}} // Suggest making the button visible to the leader too - useful for them to test how the feature works or to send a note to themself. -- Alys
.button-container
// @TODO: V2 button.btn.btn-primary(v-once, v-if='isMember && !isParty') {{$t('donateGems')}}
// @TODO: V2 button.btn.btn-primary(v-once, v-if='isMember && !isParty') {{$t('donateGems')}} // Suggest removing the isMember restriction - it's okay if non-members donate to a public guild. Also probably allow it for parties if parties can buy imagery. -- Alys
.section-header(v-if='isParty')
.row
@@ -81,7 +82,7 @@
.svg-icon(v-html="icons.questIcon")
h4(v-once) {{ $t('youAreNotOnQuest') }}
p(v-once) {{ $t('questDescription') }}
button.btn.btn-secondary(v-once, @click="openStartQuestModal()", v-if='isLeader') {{ $t('startAQuest') }}
button.btn.btn-secondary(v-once, @click="openStartQuestModal()") {{ $t('startAQuest') }}
.row.quest-active-section(v-if='isParty && onPendingQuest && !onActiveQuest')
.col-2
.quest(:class='`inventory_quest_scroll_${questData.key}`')
@@ -129,7 +130,7 @@
.col-6
span.float-left
| Rage {{questData.boss.rage.value}}
button.btn.btn-secondary(v-once, @click="questAbort()", v-if='isLeader') {{ $t('abort') }}
button.btn.btn-secondary(v-once, @click="questAbort()", v-if='canEditQuest') {{ $t('abort') }}
.section-header(v-if='!isParty')
.row
@@ -562,12 +563,17 @@ export default {
isLeader () {
return this.user._id === this.group.leader._id;
},
isAdmin () {
return Boolean(this.user.contributor.admin);
},
isMember () {
return this.isMemberOfGroup(this.user, this.group);
},
canEditQuest () {
let isQuestLeader = this.group.quest && this.group.quest.leader === this.user._id;
return isQuestLeader;
if (!this.group.quest) return false;
let isQuestLeader = this.group.quest.leader === this.user._id;
let isPartyLeader = this.group.leader._id === this.user._id;
return isQuestLeader || isPartyLeader;
},
isMemberOfPendingQuest () {
let userid = this.user._id;
@@ -710,6 +716,9 @@ export default {
fetchRecentMessages () {
this.fetchGuild();
},
reverseChat () {
this.group.chat.reverse();
},
updateGuild () {
this.$store.state.editingGroup = this.group;
this.$root.$emit('show::modal', 'guild-form');
@@ -63,7 +63,7 @@
.form-group
label
strong(v-once) {{$t('groupDescription')}} *
a.float-right {{ $t('markdownFormattingHelp') }}
a.float-right(v-markdown='$t("markdownFormattingHelp")')
textarea.form-control.description-textarea(type="text", textarea, :placeholder="isParty ? $t('partyDescriptionPlaceholder') : $t('guildDescriptionPlaceholder')", v-model="workingGroup.description")
.form-group(v-if='creatingParty && !workingGroup.id')
@@ -177,6 +177,7 @@ import bTooltip from 'bootstrap-vue/lib/components/tooltip';
import { mapState } from 'client/libs/store';
import toggleSwitch from 'client/components/ui/toggleSwitch';
import markdownDirective from 'client/directives/markdown';
import gemIcon from 'assets/svg/gem.svg';
import informationIcon from 'assets/svg/information.svg';
@@ -198,6 +199,9 @@ export default {
bTooltip,
toggleSwitch,
},
directives: {
markdown: markdownDirective,
},
data () {
let data = {
workingGroup: {
@@ -13,9 +13,10 @@
.pending.float-right(v-if='member.accepted === null') {{ $t('pending') }}
div(v-if='questData')
questDialogContent(:item="questData")
div.text-center.actions
div.text-center.actions(v-if='canEditQuest')
div
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
// @TODO don't allow the party leader to start the quest until the leader has accepted or rejected the invitation (users get confused and think "begin" means "join quest")
div
.cancel(v-once, @click="questCancel()") {{ $t('cancel') }}
.side-panel(v-if='questData')
@@ -189,6 +190,12 @@ export default {
};
});
},
canEditQuest () {
if (!this.group.quest) return false;
let isQuestLeader = this.group.quest.leader === this.user._id;
let isPartyLeader = this.group.leader._id === this.user._id;
return isQuestLeader || isPartyLeader;
},
},
methods: {
async questForceStart () {
@@ -127,10 +127,7 @@
:count="context.item.quantity"
)
hatchedPetDialog(
:pet="hatchedPet",
@closed="closeHatchedPetDialog()"
)
hatchedPetDialog()
div.hatchingPotionInfo(ref="draggingPotionInfo")
div(v-if="currentDraggingPotion != null")
@@ -251,7 +248,6 @@ export default {
currentDraggingPotion: null,
potionClickMode: false,
hatchedPet: null,
cardOptions: {
cardType: '',
messageOptions: 0,
@@ -345,7 +341,7 @@ export default {
},
hatchPet (potion, egg) {
this.$store.dispatch('common:hatch', {egg: egg.key, hatchingPotion: potion.key});
this.hatchedPet = createAnimal(egg, potion, 'pet', this.content, this.user.items);
this.$root.$emit('hatchedPet::open', createAnimal(egg, potion, 'pet', this.content, this.user.items));
},
onDragEnd () {
this.currentDraggingPotion = null;
@@ -405,9 +401,6 @@ export default {
this.potionClickMode = false;
}
},
closeHatchedPetDialog () {
this.hatchedPet = null;
},
async itemClicked (groupKey, item) {
if (item.type && item.type === 'card') {
@@ -1,12 +1,10 @@
<template lang="pug">
b-modal#hatchedPet-modal(
:visible="true",
v-if="pet != null",
:hide-header="true"
)
div.content
div.dialog-header.title You hatched a new pet!
div.content(v-if="pet != null")
div.dialog-header.title(v-once) {{ $t('hatchedPetGeneric') }}
div.inner-content
@@ -45,7 +43,7 @@
}
.inner-content {
margin: 33px auto auto;
margin: 24px auto auto;
display: flex;
flex-direction: column;
align-items: center;
@@ -77,16 +75,33 @@
components: {
bModal,
},
data () {
return {
pet: null,
};
},
created () {
},
mounted () {
this.$root.$on('hatchedPet::open', this.openDialog);
},
destroyed () {
this.$root.$off('hatchedPet::open', this.openDialog);
},
methods: {
openDialog (item) {
this.pet = item;
this.$root.$emit('show::modal', 'hatchedPet-modal');
},
close () {
this.$emit('closed', this.item);
this.$root.$emit('hide::modal', 'hatchedPet-modal');
this.pet = null;
},
},
props: {
pet: {
type: Object,
},
hideText: {
type: Boolean,
},
@@ -210,7 +210,6 @@
div.content-text(v-once) {{ $t('welcomeStableText') }}
b-modal#hatching-modal(
:visible="hatchablePet != null",
@change="resetHatchablePet($event)"
)
div.content(v-if="hatchablePet")
@@ -230,9 +229,7 @@
button.btn.btn-secondary.btn-flat(@click="closeHatchPetDialog()") {{ $t('cancel') }}
hatchedPetDialog(
:pet="hatchedPet",
:hideText="true",
@closed="closeHatchedPetDialog()"
)
div.foodInfo(ref="dragginFoodInfo")
@@ -337,11 +334,6 @@
padding-right:0;
}
.drawer-container {
// 3% padding + 252px sidebar width
left: calc(3% + 252px) !important;
}
.svg-icon.inline.icon-16 {
vertical-align: bottom;
}
@@ -392,7 +384,6 @@
}
.title {
height: 24px;
margin-top: 24px;
font-family: Roboto;
font-size: 20px;
@@ -588,7 +579,6 @@
highlightPet: '',
hatchablePet: null,
hatchedPet: null,
foodClickMode: false,
currentDraggingFood: null,
@@ -918,7 +908,8 @@
hatchPet (pet) {
this.$store.dispatch('common:hatch', {egg: pet.eggKey, hatchingPotion: pet.potionKey});
this.hatchedPet = pet;
this.$root.$emit('hatchedPet::open', pet);
this.closeHatchPetDialog();
},
@@ -974,6 +965,8 @@
}
// opens the hatch dialog
this.hatchablePet = pet;
this.$root.$emit('show::modal', 'hatching-modal');
}
},
@@ -988,9 +981,6 @@
closeHatchPetDialog () {
this.$root.$emit('hide::modal', 'hatching-modal');
},
closeHatchedPetDialog () {
this.hatchedPet = null;
},
resetHatchablePet ($event) {
if (!$event) {
+45 -27
View File
@@ -1,8 +1,7 @@
<template lang="pug">
div.item-with-icon.item-notifications.dropdown
span.message-count.top-count(v-if='notificationsCount > 0') {{ notificationsCount }}
.svg-icon.notifications(v-html="icons.notifications")
// span.glyphicon(:class='iconClasses()')
// span.notification-counter(v-if='getNotificationsCount()') {{getNotificationsCount()}}
.dropdown-menu.dropdown-menu-right.user-dropdown
h4.dropdown-item.dropdown-separated(v-if='!hasNoNotifications()') {{ $t('notifications') }}
h4.dropdown-item.toolbar-notifs-no-messages(v-if='hasNoNotifications()') {{ $t('noNotifications') }}
@@ -58,6 +57,25 @@ div.item-with-icon.item-notifications.dropdown
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
.message-count {
background-color: #46a7d9;
border-radius: 50%;
height: 20px;
width: 20px;
float: right;
color: #fff;
text-align: center;
font-weight: bold;
font-size: 12px;
}
.message-count.top-count {
position: absolute;
right: -.5em;
top: .5em;
padding: .2em;
}
.clear-button {
margin-left: .5em;
}
@@ -168,6 +186,31 @@ export default {
}
return userNewMessages;
},
notificationsCount () {
let count = 0;
if (this.user.invitations.parties) {
count += this.user.invitations.parties.length;
}
if (this.user.purchased.plan && this.user.purchased.plan.mysteryItems.length) {
count++;
}
if (this.user.invitations.guilds) {
count += this.user.invitations.guilds.length;
}
if (this.user.flags.classSelected && !this.user.preferences.disableClasses && this.user.stats.points) {
count += this.user.stats.points > 0 ? 1 : 0;
}
if (this.userNewMessages) {
count += Object.keys(this.userNewMessages).length;
}
return count;
},
},
methods: {
// @TODO: I hate this function, we can do better with a hashmap
@@ -237,31 +280,6 @@ export default {
clearCards () {
this.$store.dispatch('chat:clearCards');
},
getNotificationsCount () {
let count = 0;
if (this.user.invitations.parties) {
count += this.user.invitations.parties.length;
}
if (this.user.purchased.plan && this.user.purchased.plan.mysteryItems.length) {
count++;
}
if (this.user.invitations.guilds) {
count += this.user.invitations.guilds.length;
}
if (this.user.flags.classSelected && !this.user.preferences.disableClasses && this.user.stats.points) {
count += this.user.stats.points > 0 ? 1 : 0;
}
if (this.userNewMessages) {
count += Object.keys(this.userNewMessages).length;
}
return count;
},
iconClasses () {
return this.selectNotificationValue(
'glyphicon-gift',
+15 -6
View File
@@ -158,6 +158,7 @@ export default {
return {
yesterDailies: [],
levelBeforeYesterdailies: 0,
notificationData: {},
unlockLevels,
lastShownNotifications,
@@ -256,12 +257,8 @@ export default {
this.mp(mana);
},
userLvl (after, before) {
if (after <= before) return;
this.lvl();
this.playSound('Level_Up');
if (this.user._tmp && this.user._tmp.drop && this.user._tmp.drop.type === 'Quest') return;
if (this.unlockLevels[`${after}`]) return;
if (!this.user.preferences.suppressModals.levelUp) this.$root.$emit('show::modal', 'level-up');
if (after <= before || this.isRunningYesterdailies) return;
this.showLevelUpNotifications(after);
},
userClassSelect (after) {
if (!after) return;
@@ -334,6 +331,13 @@ export default {
});
},
methods: {
showLevelUpNotifications (newlevel) {
this.lvl();
this.playSound('Level_Up');
if (this.user._tmp && this.user._tmp.drop && this.user._tmp.drop.type === 'Quest') return;
if (this.unlockLevels[`${newlevel}`]) return;
if (!this.user.preferences.suppressModals.levelUp) this.$root.$emit('show::modal', 'level-up');
},
playSound (sound) {
this.$root.$emit('playSound', sound);
},
@@ -390,6 +394,7 @@ export default {
return;
}
this.levelBeforeYesterdailies = this.user.stats.lvl;
this.$root.$emit('show::modal', 'yesterdaily');
},
async runYesterDailiesAction () {
@@ -405,6 +410,10 @@ export default {
this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}),
]);
if (this.levelBeforeYesterdailies < this.user.stats.lvl) {
this.showLevelUpNotifications(this.user.stats.lvl);
}
this.handleUserNotifications(this.user.notifications);
this.scheduleNextCron();
},
@@ -21,6 +21,12 @@
.nav-item(@click='selectedPage = "subscribe"', :class="{active: selectedPage === 'subscribe'}") {{ $t('subscribe') }}
.nav-item(@click='selectedPage = "gems"', :class="{active: selectedPage === 'gems'}") {{ $t('buyGems') }}
div(v-show='selectedPage === "gems"')
div(v-if='hasSubscription')
.row.text-center
h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }}
.row.text-center
.col-6.offset-3
p {{ $t("gemsPurchaseNote") }}
.row.text-center
h2.mx-auto.text-leadin {{ $t('gemBenefitLeadin') }}
.row
@@ -112,7 +112,6 @@ import filter from 'lodash/filter';
import sortBy from 'lodash/sortBy';
import min from 'lodash/min';
import { mapState } from 'client/libs/store';
import encodeParams from 'client/libs/encodeParams';
import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks';
import planGemLimits from '../../../common/script/libs/planGemLimits';
@@ -261,42 +260,6 @@ export default {
subs.basic_6mo.discount = true;
subs.google_6mo.discount = false;
},
async cancelSubscription (config) {
if (config && config.group && !confirm(this.$t('confirmCancelGroupPlan'))) return;
if (!confirm(this.$t('sureCancelSub'))) return;
let group;
if (config && config.group) {
group = config.group;
}
let paymentMethod = this.user.purchased.plan.paymentMethod;
if (group) {
paymentMethod = group.purchased.plan.paymentMethod;
}
if (paymentMethod === 'Amazon Payments') {
paymentMethod = 'amazon';
} else {
paymentMethod = paymentMethod.toLowerCase();
}
let queryParams = {
_id: this.user._id,
apiToken: this.credentials.API_TOKEN,
noRedirect: true,
};
if (group) {
queryParams.groupId = group._id;
}
let cancelUrl = `/${paymentMethod}/subscribe/cancel?${encodeParams(queryParams)}`;
await axios.get(cancelUrl);
// Success
alert(this.$t('paypalCanceled'));
this.$router.push('/');
},
getCancelSubInfo () {
// @TODO: String 'cancelSubInfoGroup Plan' not found. ?
return this.$t(`cancelSubInfo${this.user.purchased.plan.paymentMethod}`);
+22 -12
View File
@@ -14,7 +14,6 @@
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close", @click="hideDialog()")
div.content(v-if="item != null")
div.inner-content
slot(name="item", :item="item")
div(v-if="showAvatar")
@@ -46,6 +45,11 @@
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
span.value(:class="getPriceClass()") {{ item.value }}
.gems-left(v-if='item.key === "gem"')
strong(v-if='gemsLeft > 0') {{ gemsLeft }} {{ $t('gemsRemaining') }}
strong(v-if='gemsLeft === 0') {{ $t('maxBuyGems') }}
button.btn.btn-primary(
@click="purchaseGems()",
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value)"
@@ -54,6 +58,7 @@
button.btn.btn-primary(
@click="buyItem()",
v-else,
:disabled='item.key === "gem" && gemsLeft === 0',
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value)}"
) {{ $t('buyNow') }}
@@ -203,12 +208,18 @@
.bordered {
margin-top: 8px;
}
.gems-left {
margin-top: .5em;
}
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import * as Analytics from 'client/libs/analytics';
import spellsMixin from 'client/mixins/spells';
import planGemLimits from 'common/script/libs/planGemLimits';
import svgClose from 'assets/svg/close.svg';
import svgGold from 'assets/svg/gold.svg';
@@ -220,6 +231,7 @@
import BalanceInfo from './balanceInfo.vue';
import currencyMixin from './_currencyMixin';
import notifications from 'client/mixins/notifications';
import buyMixin from 'client/mixins/buy';
import { mapState } from 'client/libs/store';
@@ -233,7 +245,7 @@
import moment from 'moment';
export default {
mixins: [currencyMixin, notifications],
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
components: {
bModal,
BalanceInfo,
@@ -290,6 +302,10 @@
limitedString () {
return this.$t('limitedOffer', {date: moment(seasonalShopConfig.dateRange.end).format('LL')});
},
gemsLeft () {
if (!this.user.purchased.plan) return 0;
return planGemLimits.convCap + this.user.purchased.plan.consecutive.gemCapExtra - this.user.purchased.plan.gemsBought;
},
},
watch: {
item: function itemChanged () {
@@ -301,17 +317,11 @@
this.$emit('change', $event);
},
buyItem () {
if (this.genericPurchase) {
this.$store.dispatch('shops:genericPurchase', {
pinType: this.item.pinType,
type: this.item.purchaseType,
key: this.item.key,
currency: this.item.currency,
});
if (this.item.cast) {
this.castStart(this.item);
} else if (this.genericPurchase) {
this.makeGenericPurchase(this.item);
this.purchased(this.item.text);
this.$root.$emit('buyModal::boughtItem', this.item);
this.$root.$emit('playSound', 'Reward');
}
this.$emit('buyPressed', this.item);
@@ -383,6 +383,8 @@
const sortGearTypes = ['sortByType', 'sortByPrice', 'sortByCon', 'sortByPer', 'sortByStr', 'sortByInt'];
import notifications from 'client/mixins/notifications';
import buyMixin from 'client/mixins/buy';
import currencyMixin from '../_currencyMixin';
const sortGearTypeMap = {
sortByType: 'type',
@@ -393,7 +395,7 @@
};
export default {
mixins: [notifications],
mixins: [notifications, buyMixin, currencyMixin],
components: {
ShopItem,
Item,
@@ -694,6 +696,11 @@ export default {
}
},
itemSelected (item) {
if (item.purchaseType !== 'gear' && this.$store.state.recentlyPurchased[item.key]) {
this.makeGenericPurchase(item);
return;
}
this.$root.$emit('buyModal::showItem', item);
},
featuredItemSelected (item) {
@@ -43,11 +43,9 @@
:currencyNeeded="priceType",
:amountNeeded="item.value"
).float-right
</template>
<style lang="scss">
<style lang="scss">
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/modal.scss';
@@ -173,7 +171,6 @@
</style>
<script>
import {mapState} from 'client/libs/store';
import bModal from 'bootstrap-vue/lib/components/modal';
@@ -188,12 +185,13 @@
import currencyMixin from '../_currencyMixin';
import QuestInfo from './questInfo.vue';
import notifications from 'client/mixins/notifications';
import buyMixin from 'client/mixins/buy';
import questDialogDrops from './questDialogDrops';
import questDialogContent from './questDialogContent';
export default {
mixins: [currencyMixin, notifications],
mixins: [currencyMixin, notifications, buyMixin],
components: {
bModal,
BalanceInfo,
@@ -243,15 +241,8 @@
this.$emit('change', $event);
},
buyItem () {
this.$store.dispatch('shops:genericPurchase', {
pinType: this.item.pinType,
type: this.item.purchaseType,
key: this.item.key,
currency: this.item.currency,
});
this.makeGenericPurchase(this.item, 'buyQuestModal');
this.purchased(this.item.text);
this.$root.$emit('playSound', 'Reward');
this.$emit('buyPressed', this.item);
this.hideDialog();
},
togglePinned () {
@@ -328,6 +328,8 @@
import ItemRows from 'client/components/ui/itemRows';
import toggleSwitch from 'client/components/ui/toggleSwitch';
import Avatar from 'client/components/avatar';
import buyMixin from 'client/mixins/buy';
import currencyMixin from '../_currencyMixin';
import BuyModal from './buyQuestModal.vue';
import QuestInfo from './questInfo.vue';
@@ -348,6 +350,7 @@
import _map from 'lodash/map';
export default {
mixins: [buyMixin, currencyMixin],
components: {
ShopItem,
Item,
@@ -470,6 +473,11 @@ export default {
selectItem (item) {
this.selectedItemToBuy = item;
if (this.$store.state.recentlyPurchased[item.key]) {
this.makeGenericPurchase(item);
return;
}
this.$root.$emit('show::modal', 'buy-quest-modal');
},
},
@@ -274,6 +274,8 @@
import ItemRows from 'client/components/ui/itemRows';
import toggleSwitch from 'client/components/ui/toggleSwitch';
import Avatar from 'client/components/avatar';
import buyMixin from 'client/mixins/buy';
import currencyMixin from '../_currencyMixin';
import bPopover from 'bootstrap-vue/lib/components/popover';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
@@ -302,6 +304,7 @@
import shops from 'common/script/libs/shops';
export default {
mixins: [buyMixin, currencyMixin],
components: {
ShopItem,
Item,
@@ -485,9 +488,14 @@
}
},
itemSelected (item) {
if (!item.locked) {
this.$root.$emit('buyModal::showItem', item);
if (item.locked) return;
if (this.$store.state.recentlyPurchased[item.key]) {
this.makeGenericPurchase(item);
return;
}
this.$root.$emit('buyModal::showItem', item);
},
},
};
@@ -71,9 +71,6 @@
p.span
span {{ $t('marketing4Lead3-2') }}
a.btn.btn-primary(href='/static/plans',target='_blank') {{ $t('contactUs') }}
p.span
span {{ $t('marketing4Lead3-3') }}
a.btn.btn-primary(href='/static/videos') {{ $t('watchVideos') }}
</template>
<style lang='scss' scoped>
+47 -6
View File
@@ -46,6 +46,11 @@
min-height: 556px;
}
.task-wrapper {
position: relative;
z-index: 2;
}
.task-wrapper + .reward-items {
margin-top: 16px;
}
@@ -104,7 +109,7 @@
.column-background {
position: absolute;
bottom: 32px;
z-index: 7;
z-index: 1;
&.initial-description {
top: 30%;
@@ -160,19 +165,25 @@
<script>
import Task from './task';
import sortBy from 'lodash/sortBy';
import throttle from 'lodash/throttle';
import bModal from 'bootstrap-vue/lib/components/modal';
import sortable from 'client/directives/sortable.directive';
import buyMixin from 'client/mixins/buy';
import { mapState, mapActions } from 'client/libs/store';
import shopItem from '../shops/shopItem';
import { shouldDo } from 'common/script/cron';
import inAppRewards from 'common/script/libs/inAppRewards';
import spells from 'common/script/content/spells';
import habitIcon from 'assets/svg/habit.svg';
import dailyIcon from 'assets/svg/daily.svg';
import todoIcon from 'assets/svg/todo.svg';
import rewardIcon from 'assets/svg/reward.svg';
import bModal from 'bootstrap-vue/lib/components/modal';
import shopItem from '../shops/shopItem';
import throttle from 'lodash/throttle';
import sortable from 'client/directives/sortable.directive';
export default {
mixins: [buyMixin],
components: {
Task,
bModal,
@@ -252,8 +263,30 @@ export default {
},
inAppRewards () {
let watchRefresh = this.forceRefresh; // eslint-disable-line
let rewards = inAppRewards(this.user);
return inAppRewards(this.user);
// Add season rewards if user is affected
// @TODO: Add buff coniditional
const seasonalSkills = {
snowball: 'salt',
spookySparkles: 'opaquePotion',
shinySeed: 'petalFreePotion',
seafoam: 'sand',
};
for (let key in seasonalSkills) {
if (this.user.stats.buffs[key]) {
let debuff = seasonalSkills[key];
let item = Object.assign({}, spells.special[debuff]);
item.text = item.text();
item.notes = item.notes();
item.class = `shop_${key}`;
rewards.push(item);
}
}
return rewards;
},
hasRewardsList () {
return this.isUser === true && this.type === 'reward' && this.activeFilters[this.type].label !== 'custom';
@@ -378,6 +411,14 @@ export default {
}
},
openBuyDialog (rewardItem) {
// Buy armoire and health potions immediately
let itemsToPurchaseImmediately = ['potion', 'armoire'];
if (itemsToPurchaseImmediately.indexOf(rewardItem.key) !== -1) {
this.makeGenericPurchase(rewardItem);
this.$emit('buyPressed', rewardItem);
return;
}
if (rewardItem.purchaseType !== 'gear' || !rewardItem.locked) {
this.$emit('openBuyDialog', rewardItem);
}
+2 -119
View File
@@ -148,14 +148,13 @@ div(v-if='user.stats.lvl > 10')
</style>
<script>
import axios from 'axios';
import isArray from 'lodash/isArray';
import bPopover from 'bootstrap-vue/lib/directives/popover';
import spells from '../../../common/script/content/spells';
import { mapState } from 'client/libs/store';
import notifications from 'client/mixins/notifications';
import spellsMixin from 'client/mixins/spells';
import Drawer from 'client/components/ui/drawer';
import MouseMoveDirective from 'client/directives/mouseposition.directive';
@@ -164,7 +163,7 @@ import quests from 'common/script/content/quests';
import { CONSTANTS, setLocalSetting, getLocalSetting } from 'client/libs/userlocalManager';
export default {
mixins: [notifications],
mixins: [notifications, spellsMixin],
components: {
Drawer,
},
@@ -245,122 +244,6 @@ export default {
return notes;
},
async castStart (spell) {
if (this.$store.state.spellOptions.castingSpell) {
this.castCancel();
return;
}
if (this.user.stats.mp < spell.mana) return this.text(this.$t('notEnoughMana'));
if (spell.immediateUse && this.user.stats.gp < spell.value) {
return this.text('Not enough gold.');
}
this.potionClickMode = true;
this.applyingAction = true;
this.$store.state.spellOptions.castingSpell = true;
this.spell = spell;
if (spell.target === 'self') {
this.castEnd(null, spell.target);
} else if (spell.target === 'party') {
if (!this.user.party._id) {
let party = [this.user];
this.castEnd(party, spell.target);
return;
}
let party = this.$store.state.party.members;
party = isArray(party) ? party : [];
party = party.concat(this.user);
this.castEnd(party, spell.target);
} else if (spell.target === 'tasks') {
let userTasks = this.$store.state.tasks.data;
// exclude rewards
let tasks = userTasks.habits
.concat(userTasks.dailys)
.concat(userTasks.todos);
// exclude challenge and group plan tasks
tasks = tasks.filter((task) => {
if (task.challenge && task.challenge.id && !task.challenge.broken) return false;
if (task.group && task.group.id && !task.group.broken) return false;
return true;
});
this.castEnd(tasks, spell.target);
}
},
async castEnd (target, type) {
if (!this.$store.state.spellOptions.castingSpell) return;
let beforeQuestProgress = this.questProgress();
if (!this.applyingAction) return 'No applying action';
if (this.spell.target !== type) return this.text(this.$t('invalidTarget'));
if (target && target.type && target.type === 'reward') return this.text(this.$t('invalidTarget'));
if (target && target.challenge && target.challenge.id) return this.text(this.$t('invalidTarget'));
if (target && target.group && target.group.id) return this.text(this.$t('invalidTarget'));
// @TODO: just call castCancel?
this.$store.state.spellOptions.castingSpell = false;
this.potionClickMode = false;
this.spell.cast(this.user, target);
// User.save(); // @TODO:
let spell = this.spell;
let targetId = target ? target._id : null;
this.spell = null;
this.applyingAction = false;
let spellUrl = `/api/v3/user/class/cast/${spell.key}`;
if (targetId) spellUrl += `?targetId=${targetId}`;
await axios.post(spellUrl);
let msg = this.$t('youCast', {
spell: spell.text(),
});
switch (type) {
case 'task':
msg = this.$t('youCastTarget', {
spell: spell.text(),
target: target.text,
});
break;
case 'user':
msg = this.$t('youCastTarget', {
spell: spell.text(),
target: target.profile.name,
});
break;
case 'party':
msg = this.$t('youCastParty', {
spell: spell.text(),
});
break;
}
this.markdown(msg); // @TODO: mardown directive?
// @TODO:
let questProgress = this.questProgress() - beforeQuestProgress;
if (questProgress > 0) {
let userQuest = this.quests[this.user.party.quest.key];
if (userQuest.boss) {
this.quest('questDamage', questProgress.toFixed(1));
} else if (userQuest.collection && userQuest.collect) {
this.quest('questCollection', questProgress);
}
}
// @TOOD: User.sync();
},
castCancel () {
this.potionClickMode = false;
this.applyingAction = false;
this.spell = null;
document.querySelector('body').style.cursor = 'initial';
this.$store.state.spellOptions.castingSpell = false;
},
questProgress () {
let user = this.user;
if (!user.party.quest) return 0;
@@ -0,0 +1,107 @@
<template lang="pug">
.tags-popup
.tags-category.d-flex
.tags-header
strong(v-once) {{ $t('tags') }}
.tags-list.container
.row
.col-4(v-for="tag in tags")
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", :value="tag.id", v-model="selectedTags")
span.custom-control-indicator
span.custom-control-description(:title='tag.name') {{tag.name}}
.tags-footer
span.clear-tags(@click="clearTags()") {{$t("clearTags")}}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.tags-popup {
padding-left: 24px;
padding-right: 24px;
max-width: 593px;
z-index: 9999;
background: $white;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
font-size: 14px;
line-height: 1.43;
text-overflow: ellipsis;
.tags-category {
border-bottom: 1px solid $gray-600;
padding-bottom: 24px;
padding-top: 24px;
}
.tags-header {
flex-basis: 96px;
flex-shrink: 0;
a {
font-size: 12px;
line-height: 1.33;
color: $blue-10;
margin-top: 4px;
&:focus, &:hover, &:active {
text-decoration: underline;
}
}
}
.tags-list {
.custom-control-description {
color: $gray-50 !important;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
width: 8em;
}
}
.tags-footer {
border-top: 1px solid $gray-600;
display: flex;
justify-content: center;
.clear-tags {
cursor: pointer;
margin: 1.1em 0;
color: $red-50;
font-size: 14px;
&:hover {
text-decoration: underline;
}
}
}
}
</style>
<script>
export default {
props: ['tags', 'value'],
data () {
return {
selectedTags: [],
};
},
watch: {
selectedTags () {
this.$emit('input', this.selectedTags);
},
},
mounted () {
this.selectedTags = this.value;
},
methods: {
clearTags () {
this.selectedTags = [];
},
},
};
</script>
+50 -3
View File
@@ -17,7 +17,16 @@
h3.task-title(:class="{ 'has-notes': task.notes }", v-markdown="task.text")
.task-notes.small-text(v-markdown="task.notes")
.checklist(v-if="canViewchecklist")
.d-inline-flex
.collapse-checklist.d-flex.align-items-center.expand-toggle(
v-if="isUser",
@click="collapseChecklist(task)",
:class="{open: !task.collapseChecklist}",
)
.svg-icon(v-html="icons.checklist")
span {{ checklistProgress }}
label.custom-control.custom-checkbox.checklist-item(
v-if='!castingSpell && !task.collapseChecklist',
v-for="item in task.checklist", :class="{'checklist-item-done': item.completed}",
)
input.custom-control-input(type="checkbox", :checked="item.completed", @change="toggleChecklistItem(item)")
@@ -118,6 +127,26 @@
margin-top: 8px;
}
.collapse-checklist {
padding: 2px 6px;
margin-bottom: 9px;
border-radius: 1px;
background-color: $gray-600;
font-size: 10px;
line-height: 1.2;
text-align: center;
color: $gray-200;
span {
margin: 0px 4px;
}
.svg-icon {
width: 12px;
height: 8px;
}
}
.checklist-item {
color: $gray-50;
font-size: 14px;
@@ -307,6 +336,7 @@ import calendarIcon from 'assets/svg/calendar.svg';
import challengeIcon from 'assets/svg/challenge.svg';
import tagsIcon from 'assets/svg/tags.svg';
import checkIcon from 'assets/svg/check.svg';
import checklistIcon from 'assets/svg/checklist.svg';
import bPopover from 'bootstrap-vue/lib/components/popover';
import markdownDirective from 'client/directives/markdown';
import notifications from 'client/mixins/notifications';
@@ -335,11 +365,15 @@ export default {
challenge: challengeIcon,
tags: tagsIcon,
check: checkIcon,
checklist: checklistIcon,
}),
};
},
computed: {
...mapState({user: 'user.data'}),
...mapState({
user: 'user.data',
castingSpell: 'spellOptions.castingSpell',
}),
...mapGetters({
getTagsFor: 'tasks:getTagsFor',
getTaskClasses: 'tasks:getTaskClasses',
@@ -349,6 +383,13 @@ export default {
let userIsTaskUser = this.task.userId ? this.task.userId === this.user._id : true;
return hasChecklist && userIsTaskUser;
},
checklistProgress () {
const totalItems = this.task.checklist.length;
const completedItems = this.task.checklist.reduce((total, item) => {
return item.completed ? total + 1 : total;
}, 0);
return `${completedItems}/${totalItems}`;
},
leftControl () {
const task = this.task;
if (task.type === 'reward') return false;
@@ -388,9 +429,13 @@ export default {
},
},
methods: {
...mapActions({scoreChecklistItem: 'tasks:scoreChecklistItem'}),
...mapActions({
scoreChecklistItem: 'tasks:scoreChecklistItem',
collapseChecklist: 'tasks:collapseChecklist',
}),
toggleChecklistItem (item) {
item.completed = !item.completed;
if (this.castingSpell) return;
item.completed = !item.completed; // @TODO this should go into the action?
this.scoreChecklistItem({taskId: this.task._id, itemId: item.id});
},
edit (e, task) {
@@ -407,6 +452,8 @@ export default {
this.$root.$emit('castEnd', task, 'task', e);
},
async score (direction) {
if (this.castingSpell) return;
// TODO move to an action
const Content = this.$store.state.content;
const user = this.user;
+107 -20
View File
@@ -110,29 +110,22 @@
span.custom-control-indicator
span.custom-control-description {{ $t('dayOfWeek') }}
.option(v-if="isUserTask")
label(v-once) {{ $t('tags') }}
.category-wrap(@click="showTagsSelect = !showTagsSelect")
span.category-select(v-if='task.tags && task.tags.length === 0') {{$t('none')}}
span.category-select(v-else)
.category-label(v-for='tagName in getTagsFor(task)') {{tagName}}
.category-box(v-if="showTagsSelect")
.container
.row
.form-check.col-6(
v-for="tag in user.tags",
:key="tag.id",
)
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", :value="tag.id", v-model="task.tags")
span.custom-control-indicator
span.custom-control-description(v-once) {{ tag.name }}
.row
button.btn.btn-primary(@click="showTagsSelect = !showTagsSelect") {{$t('close')}}
.tags-select.option(v-if="isUserTask")
.tags-inline
label(v-once) {{ $t('tags') }}
.category-wrap(@click="showTagsSelect = !showTagsSelect", v-bind:class="{ active: showTagsSelect }")
span.category-select(v-if='task.tags && task.tags.length === 0')
.tags-none {{$t('none')}}
.dropdown-toggle
span.category-select(v-else)
.category-label(v-for='tagName in truncatedSelectedTags', :title="tagName") {{ tagName }}
.tags-more(v-if='remainingSelectedTags.length > 0') +{{ $t('more', { count: remainingSelectedTags.length }) }}
.dropdown-toggle
tags-popup(v-if="showTagsSelect", :tags="user.tags", v-model="task.tags")
.option(v-if="task.type === 'habit'")
label(v-once) {{ $t('resetStreak') }}
b-dropdown(:text="$t(task.frequency)")
b-dropdown.streak-dropdown(:text="$t(task.frequency)")
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}")
| {{ $t(frequency) }}
@@ -328,6 +321,88 @@
}
}
.tags-select {
position: relative;
.tags-inline {
align-items: center;
display: flex;
justify-content: flex-start;
label {
margin: 0;
}
.category-wrap {
cursor: inherit;
position: relative;
border: 1px solid transparent;
border-radius: 2px;
margin-left: 4em;
&.active {
border-color: $purple-500;
.category-select {
box-shadow: none;
}
}
.category-select {
align-items: center;
display: flex;
padding: .6em;
padding-right: 2.8em;
width: 100%;
.tags-none {
margin: .26em 0 .26em .6em;
& + .dropdown-toggle {
right: 1.3em;
}
}
.tags-more {
color: #a5a1ac;
flex: 0 1 auto;
font-size: 12px;
text-align: left;
position: relative;
left: .5em;
width: 100%;
}
.dropdown-toggle {
position: absolute;
right: 1em;
top: .8em;
}
.category-label {
min-width: 68px;
overflow: hidden;
padding: .5em 1em;
text-overflow: ellipsis;
white-space: nowrap;
width: 68px;
word-wrap: break-word;
}
}
}
}
.tags-popup {
position: absolute;
top: 3.5em;
left: 6.2em;
}
}
.streak-dropdown {
margin-left: .5em;
}
.checklist-group {
border-top: 1px solid $gray-500;
}
@@ -418,6 +493,7 @@
</style>
<script>
import TagsPopup from './tagsPopup';
import bModal from 'bootstrap-vue/lib/components/modal';
import { mapGetters, mapActions, mapState } from 'client/libs/store';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
@@ -441,6 +517,7 @@ import goldIcon from 'assets/svg/gold.svg';
export default {
components: {
TagsPopup,
bModal,
bDropdown,
bDropdownItem,
@@ -453,6 +530,7 @@ export default {
props: ['task', 'purpose', 'challengeId', 'groupId'], // purpose is either create or edit, task is the task created or edited
data () {
return {
maxTags: 3,
showTagsSelect: false,
showAssignedSelect: false,
newChecklistItem: null,
@@ -574,6 +652,15 @@ export default {
}
},
},
selectedTags () {
return this.getTagsFor(this.task);
},
truncatedSelectedTags () {
return this.selectedTags.slice(0, this.maxTags);
},
remainingSelectedTags () {
return this.selectedTags.slice(this.maxTags);
},
},
methods: {
...mapActions({saveTask: 'tasks:save', destroyTask: 'tasks:destroy', createTask: 'tasks:create'}),
+2 -2
View File
@@ -26,9 +26,9 @@
max-width: 80%;
@media screen and (min-width: 1241px) {
max-width: 968px;
max-width: 978px;
// 16.67% is the width of the .col-2 sidebar
left: calc((100% + 16.67% - 968px) / 2);
left: calc((100% + 16.67% - 978px) / 2);
right: 0%;
}
}
@@ -23,6 +23,10 @@
.fill-height {
height: 38px; // button + margin + padding
}
.item-rows {
margin-right: -24px;
}
</style>
<script>
+25
View File
@@ -0,0 +1,25 @@
export default {
methods: {
makeGenericPurchase (item, type = 'buyModal') {
this.$store.dispatch('shops:genericPurchase', {
pinType: item.pinType,
type: item.purchaseType,
key: item.key,
currency: item.currency,
});
if (item.purchaseType !== 'gear') {
this.$store.state.recentlyPurchased[item.key] = true;
}
this.$root.$emit('playSound', 'Reward');
if (type !== 'buyModal') {
this.$emit('buyPressed', this.item);
return;
}
this.$root.$emit('buyModal::boughtItem', item);
},
},
};
+37
View File
@@ -3,6 +3,7 @@ import axios from 'axios';
const STRIPE_PUB_KEY = process.env.STRIPE_PUB_KEY; // eslint-disable-line
import subscriptionBlocks from '../../common/script/content/subscriptionBlocks';
import { mapState } from 'client/libs/store';
import encodeParams from 'client/libs/encodeParams';
import notificationsMixin from 'client/mixins/notifications';
export default {
@@ -170,5 +171,41 @@ export default {
this.$root.$emit('show::modal', 'amazon-payment');
},
async cancelSubscription (config) {
if (config && config.group && !confirm(this.$t('confirmCancelGroupPlan'))) return;
if (!confirm(this.$t('sureCancelSub'))) return;
let group;
if (config && config.group) {
group = config.group;
}
let paymentMethod = this.user.purchased.plan.paymentMethod;
if (group) {
paymentMethod = group.purchased.plan.paymentMethod;
}
if (paymentMethod === 'Amazon Payments') {
paymentMethod = 'amazon';
} else {
paymentMethod = paymentMethod.toLowerCase();
}
let queryParams = {
_id: this.user._id,
apiToken: this.credentials.API_TOKEN,
noRedirect: true,
};
if (group) {
queryParams.groupId = group._id;
}
let cancelUrl = `/${paymentMethod}/subscribe/cancel?${encodeParams(queryParams)}`;
await axios.get(cancelUrl);
// Success
alert(this.$t('paypalCanceled'));
this.$router.push('/');
},
},
};
+128
View File
@@ -0,0 +1,128 @@
import axios from 'axios';
import isArray from 'lodash/isArray';
// @TODO: Let's separate some of the business logic out of Vue if possible
export default {
methods: {
async castStart (spell) {
if (this.$store.state.spellOptions.castingSpell) {
this.castCancel();
return;
}
if (this.user.stats.mp < spell.mana) return this.text(this.$t('notEnoughMana'));
if (spell.immediateUse && this.user.stats.gp < spell.value) {
return this.text('Not enough gold.');
}
this.potionClickMode = true;
this.applyingAction = true;
this.$store.state.spellOptions.castingSpell = true;
this.spell = spell;
if (spell.target === 'self') {
this.castEnd(null, spell.target);
} else if (spell.target === 'party') {
if (!this.user.party._id) {
let party = [this.user];
this.castEnd(party, spell.target);
return;
}
let party = this.$store.state.party.members;
party = isArray(party) ? party : [];
party = party.concat(this.user);
this.castEnd(party, spell.target);
} else if (spell.target === 'tasks') {
let userTasks = this.$store.state.tasks.data;
// exclude rewards
let tasks = userTasks.habits
.concat(userTasks.dailys)
.concat(userTasks.todos);
// exclude challenge and group plan tasks
tasks = tasks.filter((task) => {
if (task.challenge && task.challenge.id && !task.challenge.broken) return false;
if (task.group && task.group.id && !task.group.broken) return false;
return true;
});
this.castEnd(tasks, spell.target);
}
},
async castEnd (target, type) {
if (!this.$store.state.spellOptions.castingSpell) return;
let beforeQuestProgress;
if (this.spell.target === 'party') beforeQuestProgress = this.questProgress();
if (!this.applyingAction) return 'No applying action';
if (this.spell.target !== type) return this.text(this.$t('invalidTarget'));
if (target && target.type && target.type === 'reward') return this.text(this.$t('invalidTarget'));
if (target && target.challenge && target.challenge.id) return this.text(this.$t('invalidTarget'));
if (target && target.group && target.group.id) return this.text(this.$t('invalidTarget'));
// @TODO: just call castCancel?
this.$store.state.spellOptions.castingSpell = false;
this.potionClickMode = false;
this.spell.cast(this.user, target);
// User.save(); // @TODO:
let spell = this.spell;
let targetId = target ? target._id : null;
this.spell = null;
this.applyingAction = false;
let spellUrl = `/api/v3/user/class/cast/${spell.key}`;
if (targetId) spellUrl += `?targetId=${targetId}`;
let spellText = typeof spell.text === 'function' ? spell.text() : spell.text;
await axios.post(spellUrl);
let msg = this.$t('youCast', {
spell: spellText,
});
switch (type) {
case 'task':
msg = this.$t('youCastTarget', {
spell: spellText,
target: target.text,
});
break;
case 'user':
msg = this.$t('youCastTarget', {
spell: spellText,
target: target.profile.name,
});
break;
case 'party':
msg = this.$t('youCastParty', {
spell: spellText,
});
break;
}
this.markdown(msg); // @TODO: mardown directive?
// @TODO:
if (!beforeQuestProgress) return;
let questProgress = this.questProgress() - beforeQuestProgress;
if (questProgress > 0) {
let userQuest = this.quests[this.user.party.quest.key];
if (userQuest.boss) {
this.quest('questDamage', questProgress.toFixed(1));
} else if (userQuest.collection && userQuest.collect) {
this.quest('questCollection', questProgress);
}
}
// @TOOD: User.sync();
},
castCancel () {
this.potionClickMode = false;
this.applyingAction = false;
this.spell = null;
document.querySelector('body').style.cursor = 'initial';
this.$store.state.spellOptions.castingSpell = false;
},
},
};
+10 -2
View File
@@ -9,14 +9,15 @@ import * as Analytics from 'client/libs/analytics';
import ParentPage from './components/parentPage';
// Static Pages
const StaticWrapper = () => import(/* webpackChunkName: "static" */'./components/static/staticWrapper');
const StaticWrapper = () => import(/* webpackChunkName: "entry" */'./components/static/staticWrapper');
const HomePage = () => import(/* webpackChunkName: "entry" */'./components/static/home');
const AppPage = () => import(/* webpackChunkName: "static" */'./components/static/app');
const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'./components/static/clearBrowserData');
const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'./components/static/communityGuidelines');
const ContactPage = () => import(/* webpackChunkName: "static" */'./components/static/contact');
const FAQPage = () => import(/* webpackChunkName: "static" */'./components/static/faq');
const FeaturesPage = () => import(/* webpackChunkName: "static" */'./components/static/features');
const HomePage = () => import(/* webpackChunkName: "static" */'./components/static/home');
const GroupPlansPage = () => import(/* webpackChunkName: "static" */'./components/static/groupPlans');
const MerchPage = () => import(/* webpackChunkName: "static" */'./components/static/merch');
const NewsPage = () => import(/* webpackChunkName: "static" */'./components/static/newStuff');
@@ -72,6 +73,7 @@ const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ './compo
// Group Plans
const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ './components/group-plans/index');
const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ './components/group-plans/taskInformation');
const GroupPlanBilling = () => import(/* webpackChunkName: "group-plans" */ './components/group-plans/billing');
// Challenges
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ './components/challenges/index');
@@ -144,6 +146,12 @@ const router = new VueRouter({
component: GroupPage,
props: true,
},
{
name: 'groupPlanBilling',
path: '/group-plans/:groupId/billing',
component: GroupPlanBilling,
props: true,
},
],
},
{
+7
View File
@@ -124,6 +124,13 @@ export async function scoreChecklistItem (store, {taskId, itemId}) {
await axios.post(`/api/v3/tasks/${taskId}/checklist/${itemId}/score`);
}
export async function collapseChecklist (store, task) {
task.collapseChecklist = !task.collapseChecklist;
await axios.put(`/api/v3/tasks/${task._id}`, {
collapseChecklist: task.collapseChecklist,
});
}
export async function destroy (store, task) {
const list = store.state.tasks.data[`${task.type}s`];
const taskIndex = list.findIndex(t => t._id === task._id);
+2
View File
@@ -135,6 +135,8 @@ export default function () {
userIdToMessage: '',
brokenChallengeTask: {},
equipmentDrawerOpen: true,
recentlyPurchased: {},
groupPlans: [],
},
});
+1 -1
View File
@@ -116,7 +116,7 @@
"shortName": "Short Name",
"shortNamePlaceholder": "What short tag should be used to identify your Challenge?",
"updateChallenge": "Update Challenge",
"haveNoChallenges": "You don't have any Challenges",
"haveNoChallenges": "This group has no Challenges",
"loadMore": "Load More",
"exportChallengeCsv": "Export Challenge",
"editingChallenge": "Editing Challenge",
+2 -2
View File
@@ -149,7 +149,7 @@
"optOutOfPMs": "Opt Out",
"chooseClass": "Choose your Class",
"chooseClassLearnMarkdown": "[Learn more about Habitica's class system](http://habitica.wikia.com/wiki/Class_System)",
"optOutOfClassesText": "Can't be bothered with classes? Want to choose later? Opt out - you'll be a warrior with no special abilities. You can read about the class system later on the wiki and enable classes at any time under User -> Stats.",
"optOutOfClassesText": "Can't be bothered with classes? Want to choose later? Opt out - you'll be a warrior with no special abilities. You can read about the class system later on the wiki and enable classes at any time under User Icon > Settings.",
"selectClass": "Select <%= heroClass %>",
"select": "Select",
"stealth": "Stealth",
@@ -196,7 +196,7 @@
"int": "INT",
"showQuickAllocation": "Show stat allocation",
"hideQuickAllocation": "Hide stat allocation",
"quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats.",
"quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User Icon > Stats.",
"invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
"notEnoughAttrPoints": "You don't have enough attribute points.",
"style": "Style",
+2 -1
View File
@@ -312,5 +312,6 @@
"signup": "Sign Up",
"getStarted": "Get Started",
"mobileApps": "Mobile Apps",
"learnMore": "Learn More"
"learnMore": "Learn More",
"useMobileApps": "Habitica is not optimized for a mobile browser. We recommend downloading our mobile apps."
}
+2 -1
View File
@@ -68,6 +68,7 @@
"subscriberItemText": "Each month, subscribers will receive a mystery item. This is usually released about one week before the end of the month. See the wiki's 'Mystery Item' page for more information.",
"all": "All",
"none": "None",
"more": "<%= count %> more",
"and": "and",
"loginSuccess": "Login successful!",
"youSure": "Are you sure?",
@@ -125,7 +126,7 @@
"error": "Error",
"menu": "Menu",
"notifications": "Notifications",
"noNotifications": "You have no new messages.",
"noNotifications": "You have no notifications.",
"clear": "Clear",
"endTour": "End Tour",
"audioTheme": "Audio Theme",
+6 -3
View File
@@ -358,7 +358,7 @@
"guildSummaryPlaceholder": "Write a short description advertising your Guild to other Habiticans. What is the main purpose of your Guild and why should people join it? Try to include useful keywords in the summary so that Habiticans can easily find it when they search!",
"groupDescription": "Description",
"guildDescriptionPlaceholder": "Use this section to go into more detail about everything that Guild members should know about your Guild. Useful tips, helpful links, and encouraging statements all go here!",
"markdownFormattingHelp": "Markdown formatting help",
"markdownFormattingHelp": "[Markdown formatting help](http://habitica.wikia.com/wiki/Markdown_Cheat_Sheet)",
"partyDescriptionPlaceholder": "This is our party's description. It describes what we do in this party. If you want to learn more about what we do in this party, read the description. Party on.",
"guildGemCostInfo": "A Gem cost promotes high quality Guilds and is transferred into your Guild's bank.",
"noGuildsTitle": "You aren't a member of any Guilds.",
@@ -391,7 +391,10 @@
"reverseChat": "Reverse Chat",
"invites": "Invites",
"details": "Details",
"participantDesc": "Once all members have either accepted or declined, the Quest begins. Only those that clicked 'accept' will be able to participate in the Quest and receive the drops.",
"participantDesc": "Once all members have either accepted or declined, the Quest begins. Only those who clicked 'accept' will be able to participate in the Quest and receive the rewards.",
"groupGems": "Group Gems",
"groupGemsDesc": "Guild Gems can be spent to make Challenges! In the future, you will be able to add more Guild Gems."
"groupGemsDesc": "Guild Gems can be spent to make Challenges! In the future, you will be able to add more Guild Gems.",
"groupTaskBoard": "Task Board",
"groupInformation": "Group Information",
"groupBilling": "Group Billing"
}
+1
View File
@@ -73,6 +73,7 @@
"useGems": "If you've got your eye on a pet, but can't wait any longer for it to drop, use Gems in <strong>Inventory > Market</strong> to buy one!",
"hatchAPot": "Hatch a new <%= potion %> <%= egg %>?",
"hatchedPet": "You hatched a new <%= potion %> <%= egg %>!",
"hatchedPetGeneric": "You hatched a new pet!",
"displayNow": "Display Now",
"displayLater": "Display Later",
"petNotOwned": "You do not own this pet.",
+3 -1
View File
@@ -202,5 +202,7 @@
"haveCouponCode": "Do you have a coupon code?",
"subscriptionAlreadySubscribedLeadIn": "Thanks for subscribing!",
"subscriptionAlreadySubscribed1": "To see your subscription details and cancel, renew, or change your subscription, please go to <a href='/user/settings/subscription'>User icon &gt; Settings &gt; Subscription</a>.",
"purchaseAll": "Purchase All"
"purchaseAll": "Purchase All",
"gemsPurchaseNote": "Subscribers can buy gems for gold in the Market! For easy access, you can also pin the gem to your Rewards column.",
"gemsRemaining": "gems remaining"
}
@@ -1,10 +1,10 @@
{
"unlockedReward": "You have received <%= reward %>",
"earnedRewardForDevotion": "You have earned <%= reward %> for being committed to improving your life.",
"nextRewardUnlocksIn": "Check-ins until your next prize: <%= numberOfCheckinsLeft %>",
"nextRewardUnlocksIn": "Check-Ins until your next prize: <%= numberOfCheckinsLeft %>",
"awesome": "Awesome!",
"totalCount": "<%= count %> total count",
"countLeft": "Check-ins until next reward: <%= count %>",
"countLeft": "Check-Ins until next reward: <%= count %>",
"incentivesDescription": "When it comes to building habits, consistency is key. Each day you check-in, you get closer to a prize.",
"totalCheckins": "<%= count %> Check-Ins",
"checkinEarned": "Your Check-In Counter went up!",
@@ -179,5 +179,6 @@ module.exports = {
removePinnedItemsByOwnedGear,
togglePinnedItem,
removeItemByPath,
selectGearToPin,
isPinned,
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB