Compare commits

..

115 Commits

Author SHA1 Message Date
SabreCat 69c538858b 4.271.2 2023-05-25 14:42:53 -05:00
SabreCat 17072dcc45 Merge branch 'due-dates-in-todos' into release 2023-05-25 14:42:46 -05:00
SabreCat 2448f401f2 Merge branch 'increment-component' into release 2023-05-25 14:42:42 -05:00
SabreCat 5745e3df5f 4.271.1 2023-05-24 13:30:21 -05:00
Phillip Thelen d4a5823916 Fix one-off issue for monthly subs (#14643)
* Fix initial plan.consecutive.offset for 1 month subs

* fix initial values for group plan subs

* Make perkMonthCount editable in admin panel

* Add aditional info to admin panel

* Implement automatic fix for affected users

* fix(lint): exclusive test, code style

* fixes

* fix issue with initialization

---------

Co-authored-by: SabreCat <sabe@habitica.com>
2023-05-24 13:29:42 -05:00
CuriousMagpie 86b15cb580 fix(style): css fix, today if-statement added 2023-05-23 15:19:26 -04:00
SabreCat 8e5b66a73e Merge branch 'release' into develop 2023-05-23 09:16:50 -05:00
SabreCat f755d4c133 4.271.0 2023-05-23 09:07:47 -05:00
SabreCat 102c71c4ca Merge remote-tracking branch 'CuriousMagpie/2023-05-pet-quest-bundle' into release 2023-05-22 15:14:18 -05:00
SabreCat a7bde80349 Squashed commit of the following:
commit 27287ac3aa
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon May 22 15:59:20 2023 -0400

    fix(typo): typos fixed

commit a4df8097cf
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon May 22 15:57:17 2023 -0400

    feat(content): add migration script

commit 23ff7845c1
Merge: d02644e21b 8ba7117fa5
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon May 22 15:42:32 2023 -0400

    Merge branch 'develop' into achievement-dinosaur-dynasty

commit 8ba7117fa5
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Mon May 22 12:35:07 2023 -0400

    build(deps): bump stripe from 12.5.0 to 12.6.0 (#14662)

    Bumps [stripe](https://github.com/stripe/stripe-node) from 12.5.0 to 12.6.0.
    - [Release notes](https://github.com/stripe/stripe-node/releases)
    - [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/stripe/stripe-node/compare/v12.5.0...v12.6.0)

    ---
    updated-dependencies:
    - dependency-name: stripe
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit fe5d4a0551
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Mon May 22 12:34:28 2023 -0400

    build(deps-dev): bump sinon from 15.0.4 to 15.1.0 (#14661)

    Bumps [sinon](https://github.com/sinonjs/sinon) from 15.0.4 to 15.1.0.
    - [Release notes](https://github.com/sinonjs/sinon/releases)
    - [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md)
    - [Commits](https://github.com/sinonjs/sinon/compare/v15.0.4...v15.1.0)

    ---
    updated-dependencies:
    - dependency-name: sinon
      dependency-type: direct:development
      update-type: version-update:semver-minor
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit d02644e21b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed May 17 11:36:28 2023 -0400

    feat(content): add dinosaur dynasty achievement
2023-05-22 15:13:49 -05:00
SabreCat bedce203ee 4.270.3 2023-05-22 13:28:31 -05:00
dependabot[bot] 8ba7117fa5 build(deps): bump stripe from 12.5.0 to 12.6.0 (#14662)
Bumps [stripe](https://github.com/stripe/stripe-node) from 12.5.0 to 12.6.0.
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v12.5.0...v12.6.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 12:35:07 -04:00
dependabot[bot] fe5d4a0551 build(deps-dev): bump sinon from 15.0.4 to 15.1.0 (#14661)
Bumps [sinon](https://github.com/sinonjs/sinon) from 15.0.4 to 15.1.0.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v15.0.4...v15.1.0)

---
updated-dependencies:
- dependency-name: sinon
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 12:34:28 -04:00
SabreCat deebc09a79 fix(analytics): typo 2023-05-22 09:54:10 -05:00
SabreCat b63f2fa1fa fix(analytics): add missing headers to 3 events 2023-05-19 14:54:30 -05:00
CuriousMagpie 60b180681e Merge remote-tracking branch 'origin/due-dates-in-todos' into due-dates-in-todos 2023-05-17 13:30:45 -04:00
CuriousMagpie 7c1c18a329 fix(styling): update colors to be a11y-friendly and to show items due today in gray 2023-05-17 13:29:26 -04:00
CuriousMagpie 0b0cbb45f4 feat(content): add May pet quest bundle 2023-05-17 10:49:37 -04:00
SabreCat 0e03f079a7 Merge branch 'due-dates-in-todos' of https://github.com/CuriousMagpie/habitica into due-dates-in-todos 2023-05-16 14:27:56 -05:00
SabreCat a71e44b331 fix(test): remove test for old function 2023-05-16 14:27:15 -05:00
Sabe Jones 48917fd8be Merge branch 'develop' into due-dates-in-todos 2023-05-16 14:18:22 -05:00
SabreCat 2a054a25ee fix(test): include user pref needed for date 2023-05-16 14:15:34 -05:00
SabreCat d176c31382 4.270.2 2023-05-16 12:22:21 -05:00
Phillip Thelen 8150fef993 Database Access optimisations (#14544)
* Optimize database access during spell casting

* load less data when casting spells

* Begin migrating update calls to updateOne and updateMany

* Only update user objects that don’t have notification yet

* fix test

* fix spy

* Don’t unnecessarily update user when requesting invalid guild

* fix sort order for middlewares to not load user twice every request

* fix tests

* fix integration test

* fix skill usage not always deducting mp

* addtest case for blessing spell

* fix healAll

* fix lint

* Fix error for when some spells are used outside of party

* Add check to not run bulk spells in web client

* fix(tags): change const to let

---------

Co-authored-by: SabreCat <sabe@habitica.com>
2023-05-16 12:21:45 -05:00
SabreCat f0637dcf49 4.270.1 2023-05-15 16:15:22 -05:00
SabreCat 0518b90eab Merge branch 'develop' into release 2023-05-15 16:11:30 -05:00
SabreCat f968bdd3a9 fix(analytics): re-enable group chat tracking 2023-05-15 16:02:24 -05:00
negue e3a1ea6180 save task column filter (#14587)
* save task column filter

* remove old setting

* fix tests
2023-05-15 16:01:32 -05:00
Natalie L 17f6054ef0 feat(content): add May magic hatching potions (#14641) 2023-05-15 15:59:49 -05:00
dependabot[bot] 9b8f213c63 build(deps): bump jquery from 3.6.4 to 3.7.0 in /website/client (#14653)
Bumps [jquery](https://github.com/jquery/jquery) from 3.6.4 to 3.7.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Commits](https://github.com/jquery/jquery/compare/3.6.4...3.7.0)

---
updated-dependencies:
- dependency-name: jquery
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-15 13:46:41 -04:00
CuriousMagpie daccade2e2 disabled sell button when user tries to sell more items than they own 2023-05-15 12:03:58 -04:00
dependabot[bot] 48bb3e2886 build(deps): bump core-js from 3.30.1 to 3.30.2 in /website/client (#14635)
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.30.1 to 3.30.2.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.30.2/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:47:35 -05:00
dependabot[bot] 308d557770 build(deps): bump superagent from 8.0.6 to 8.0.9 (#14473)
Bumps [superagent](https://github.com/ladjs/superagent) from 8.0.6 to 8.0.9.
- [Release notes](https://github.com/ladjs/superagent/releases)
- [Changelog](https://github.com/ladjs/superagent/blob/master/HISTORY.md)
- [Commits](https://github.com/ladjs/superagent/compare/v8.0.6...v8.0.9)

---
updated-dependencies:
- dependency-name: superagent
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:35:21 -05:00
dependabot[bot] 0f4816c674 build(deps): bump intro.js from 6.0.0 to 7.0.1 in /website/client (#14558)
Bumps [intro.js](https://github.com/usablica/intro.js) from 6.0.0 to 7.0.1.
- [Release notes](https://github.com/usablica/intro.js/releases)
- [Changelog](https://github.com/usablica/intro.js/blob/master/tsconfig.release.json)
- [Commits](https://github.com/usablica/intro.js/compare/v6.0.0...v7.0.1)

---
updated-dependencies:
- dependency-name: intro.js
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:35:06 -05:00
dependabot[bot] f1b98a530d build(deps): bump jsonwebtoken from 8.5.1 to 9.0.0 (#14418)
Bumps [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) from 8.5.1 to 9.0.0.
- [Release notes](https://github.com/auth0/node-jsonwebtoken/releases)
- [Changelog](https://github.com/auth0/node-jsonwebtoken/blob/master/CHANGELOG.md)
- [Commits](https://github.com/auth0/node-jsonwebtoken/compare/v8.5.1...v9.0.0)

---
updated-dependencies:
- dependency-name: jsonwebtoken
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:29:16 -05:00
dependabot[bot] 1498eba8d4 build(deps): bump @babel/preset-env from 7.20.2 to 7.21.5 (#14616)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.20.2 to 7.21.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.21.5/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:28:41 -05:00
dependabot[bot] fc16ffbf2d build(deps): bump dompurify from 2.4.3 to 3.0.3 in /website/client (#14639)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.4.3 to 3.0.3.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.4.3...3.0.3)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:22:06 -05:00
dependabot[bot] 021180fa59 build(deps): bump uuid from 8.3.2 to 9.0.0 in /website/client (#14227)
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.3.2 to 9.0.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.3.2...v9.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:13:53 -05:00
dependabot[bot] 102e6a64ad chore(deps): bump sass from 1.34.0 to 1.62.1 in /website/client (#14614)
Bumps [sass](https://github.com/sass/dart-sass) from 1.34.0 to 1.62.1.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.34.0...1.62.1)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:07:36 -05:00
SabreCat 79a5c2ec5f Merge branch 'develop' into due-dates-in-todos 2023-05-12 16:06:18 -05:00
dependabot[bot] 8cd6e1654f build(deps): bump @babel/core from 7.21.4 to 7.21.8 (#14630)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.21.4 to 7.21.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.21.8/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:05:20 -05:00
dependabot[bot] 63ea21c46d build(deps): bump stripe from 11.10.0 to 12.5.0 (#14646)
Bumps [stripe](https://github.com/stripe/stripe-node) from 11.10.0 to 12.5.0.
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v11.10.0...v12.5.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 16:03:29 -05:00
CuriousMagpie 0a23dd5311 attempt to fix sellModal 2023-05-11 16:25:11 -04:00
SabreCat 6e3a367832 Merge branch 'release' into develop 2023-05-11 14:13:58 -05:00
SabreCat f3348aca4c 4.270.0 2023-05-09 10:29:19 -05:00
SabreCat 90e1bc9d5e Merge branch 'sabrecat/link-tweak' into release 2023-05-09 10:29:14 -05:00
Natalie L 453bf3a961 feat(content): add May Backgrounds and Enchanted Armoire items (#14628) 2023-05-09 10:26:00 -05:00
CuriousMagpie 479cfb76ef fix(to do dates): locate a string for "Due" 2023-05-04 16:40:11 -04:00
CuriousMagpie 0e0cd99ded fix(to do dates): Add the word "Due" to the HTML 2023-05-04 16:35:27 -04:00
CuriousMagpie 7e210c56b0 fix(to do dates): change formatDueDate () to show exact due dates 2023-05-04 16:29:54 -04:00
CuriousMagpie 06ac6ae80c fix(html): fix behavior of buyModal when gems are being purchased; typo correction 2023-05-02 13:23:31 -04:00
CuriousMagpie 4a32a29bea Merge branch 'develop' into increment-component 2023-05-02 11:03:46 -04:00
SabreCat 71e165433a Merge branch 'release' into develop 2023-05-02 09:53:39 -05:00
dependabot[bot] b159182188 build(deps): bump json5 from 1.0.1 to 1.0.2 (#14441)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 16:39:05 -04:00
dependabot[bot] ca1b8370a0 build(deps): bump stopword from 2.0.7 to 2.0.8 in /website/client (#14560)
Bumps [stopword](https://github.com/fergiemcdowall/stopword) from 2.0.7 to 2.0.8.
- [Release notes](https://github.com/fergiemcdowall/stopword/releases)
- [Commits](https://github.com/fergiemcdowall/stopword/compare/v.2.0.7...v2.0.8)

---
updated-dependencies:
- dependency-name: stopword
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 16:35:11 -04:00
dependabot[bot] a397da2b93 build(deps): bump jquery from 3.6.3 to 3.6.4 in /website/client (#14540)
Bumps [jquery](https://github.com/jquery/jquery) from 3.6.3 to 3.6.4.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Commits](https://github.com/jquery/jquery/compare/3.6.3...3.6.4)

---
updated-dependencies:
- dependency-name: jquery
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 16:32:06 -04:00
dependabot[bot] b5acc0e0d6 build(deps-dev): bump @babel/plugin-proposal-optional-chaining (#14522)
Bumps [@babel/plugin-proposal-optional-chaining](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-proposal-optional-chaining) from 7.20.7 to 7.21.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.21.0/packages/babel-plugin-proposal-optional-chaining)

---
updated-dependencies:
- dependency-name: "@babel/plugin-proposal-optional-chaining"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 16:30:42 -04:00
dependabot[bot] 2635c5fcee build(deps): bump @babel/register from 7.18.9 to 7.21.0 (#14517)
Bumps [@babel/register](https://github.com/babel/babel/tree/HEAD/packages/babel-register) from 7.18.9 to 7.21.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.21.0/packages/babel-register)

---
updated-dependencies:
- dependency-name: "@babel/register"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 16:29:55 -04:00
dependabot[bot] ee2936834a build(deps): bump body-parser from 1.20.1 to 1.20.2 (#14518)
Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.1 to 1.20.2.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.1...1.20.2)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 16:28:07 -04:00
dependabot[bot] c94a5304c7 build(deps): bump xml2js from 0.4.23 to 0.5.0 (#14574)
Bumps [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) from 0.4.23 to 0.5.0.
- [Release notes](https://github.com/Leonidas-from-XIV/node-xml2js/releases)
- [Commits](https://github.com/Leonidas-from-XIV/node-xml2js/commits)

---
updated-dependencies:
- dependency-name: xml2js
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 16:07:53 -04:00
dependabot[bot] c6b004a474 build(deps): bump apple-auth from 1.0.7 to 1.0.9 (#14578)
Bumps [apple-auth](https://github.com/ananay/apple-auth) from 1.0.7 to 1.0.9.
- [Release notes](https://github.com/ananay/apple-auth/releases)
- [Commits](https://github.com/ananay/apple-auth/compare/1.0.7...1.0.9)

---
updated-dependencies:
- dependency-name: apple-auth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 16:07:01 -04:00
dependabot[bot] de918ec43b build(deps): bump core-js from 3.27.2 to 3.30.1 in /website/client (#14601)
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.27.2 to 3.30.1.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.30.1/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 15:41:26 -04:00
dependabot[bot] 069e994b25 build(deps): bump @babel/core from 7.20.12 to 7.21.4 (#14566)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.12 to 7.21.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.21.4/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 15:39:50 -04:00
dependabot[bot] 663692f2d5 chore(deps-dev): bump sinon from 15.0.1 to 15.0.4 (#14605)
Bumps [sinon](https://github.com/sinonjs/sinon) from 15.0.1 to 15.0.4.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v15.0.1...v15.0.4)

---
updated-dependencies:
- dependency-name: sinon
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 15:38:03 -04:00
dependabot[bot] 0ba4761083 chore(deps-dev): bump axios from 1.2.2 to 1.3.6 (#14606)
Bumps [axios](https://github.com/axios/axios) from 1.2.2 to 1.3.6.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/1.2.2...v1.3.6)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 15:37:30 -04:00
dependabot[bot] afad3815a2 chore(deps): bump smartbanner.js in /website/client (#14610)
Bumps [smartbanner.js](https://github.com/ain/smartbanner.js) from 1.19.1 to 1.19.2.
- [Release notes](https://github.com/ain/smartbanner.js/releases)
- [Commits](https://github.com/ain/smartbanner.js/compare/v1.19.1...v1.19.2)

---
updated-dependencies:
- dependency-name: smartbanner.js
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 15:25:55 -04:00
CuriousMagpie 0c85835dc2 update to sellModal, buyModal, and questDialogContent.vue 2023-04-07 10:41:43 -04:00
CuriousMagpie 54df8397a7 fixes to svg and other spacing 2023-04-06 17:00:37 -04:00
CuriousMagpie 0644032a4f style: more spacing updates 2023-04-05 13:50:14 -04:00
CuriousMagpie 44265ac616 style: more spacing updates 2023-04-03 14:56:43 -04:00
CuriousMagpie ac3b953633 style: vertical spacing tweaks 2023-03-30 09:07:37 -04:00
CuriousMagpie 5de2921d22 Merge branch 'develop' into increment-component 2023-03-30 08:33:24 -04:00
CuriousMagpie c1a0f8a8d1 style: buyModal, sellModal, buyQuestModal, questRewards 2023-03-28 15:07:49 -04:00
CuriousMagpie 7e9506391f more buyModal styling 2023-03-27 16:13:06 -04:00
CuriousMagpie 3c7ca56089 buyModal styling 2023-03-27 15:58:56 -04:00
CuriousMagpie 0d155535c3 Merge branch 'develop' into increment-component 2023-03-27 14:16:55 -04:00
CuriousMagpie 09a0d2b3b8 change spacing on buyModal 2023-03-23 16:01:33 -04:00
CuriousMagpie 83dcf8d56a tightening up spacing on buyModal, fixing footer 2023-03-22 13:15:11 -04:00
CuriousMagpie bfc13bc21b Merge branch 'develop' into increment-component 2023-03-22 12:14:04 -04:00
CuriousMagpie 5afb46f237 fix close icon on buy & sell and keyboard input into number increment component is now a number, not a string 2023-03-21 16:08:26 -04:00
CuriousMagpie 9cc4fc19d3 more tiny updates 2023-03-21 11:26:31 -04:00
CuriousMagpie cc81629f09 updates to buyModal styling 2023-03-18 17:18:41 -04:00
CuriousMagpie e83db7a28a Merge branch 'develop' into increment-component 2023-03-17 11:47:17 -04:00
CuriousMagpie 80e193e4ce Merge branch 'develop' into increment-component 2023-03-15 12:38:07 -04:00
CuriousMagpie 76fa6ec1b8 updates to snumberIncrement, buyModal, and sellModal 2023-03-09 15:54:26 -05:00
CuriousMagpie 1ac4466c24 trying to make more components for the buy/sell modals 2023-03-02 14:33:27 -05:00
CuriousMagpie 03f0061c85 Merge branch 'develop' into increment-component 2023-03-01 15:46:04 -05:00
CuriousMagpie c349de6908 finish up sellModal and questModal (also Time Travelers) 2023-02-15 12:54:26 -05:00
CuriousMagpie fd7f3a646e add functionality and styling to sellModal and questModal 2023-02-14 17:22:05 -05:00
CuriousMagpie 7244c1bebc Merge branch 'develop' into increment-component 2023-02-14 15:30:18 -05:00
CuriousMagpie 20df5eeb8f buy modal complete! 2023-02-08 15:16:17 -05:00
CuriousMagpie 23f7dd94b6 more stylin' of the buy modal 2023-02-07 15:24:53 -05:00
CuriousMagpie 7125da4533 Merge branch 'develop' into increment-component 2023-02-07 14:05:48 -05:00
CuriousMagpie 684cb59a7c buyModal styling/functions 2023-02-06 16:23:20 -05:00
CuriousMagpie 9274fe9a10 Merge branch 'develop' into increment-component 2023-02-06 14:30:34 -05:00
CuriousMagpie a21f083761 gem modal styling/functions 2023-01-25 16:44:14 -05:00
CuriousMagpie c7e2834fc6 Merge branch 'develop' into increment-component 2023-01-25 14:35:39 -05:00
CuriousMagpie a08c26b076 calculations working, style updates to subscriber gem modal 2022-12-22 16:50:55 -05:00
CuriousMagpie f4aa88e1ff more style updates to buy modal 2022-12-21 16:58:22 -05:00
CuriousMagpie 53eab7aa29 Working on styling 2022-12-20 16:53:51 -05:00
CuriousMagpie 8374d61f52 why does NaN ask the user to buy gems? 2022-12-19 17:26:49 -05:00
CuriousMagpie 4c943b7575 number-increment works on buy modal and there was much rejoicing... 2022-12-16 16:51:19 -05:00
CuriousMagpie 24032b57f6 why clicky click no click? 2022-12-15 16:48:59 -05:00
CuriousMagpie 8628c774e5 more css changes to buy modal 2022-12-13 10:53:38 -05:00
CuriousMagpie 523f044914 css changes to buy modal 2022-12-09 17:04:35 -05:00
CuriousMagpie 892c9ad040 trying to make clicky clicky work 2022-12-09 11:47:57 -05:00
CuriousMagpie 570f39c620 Merge branch 'develop' into increment-component 2022-12-08 10:22:08 -05:00
CuriousMagpie a73316ef9f Merge branch 'develop' into increment-component 2022-12-06 11:47:53 -05:00
CuriousMagpie 6d6195ae6a props attempt 2022-12-01 14:43:08 -05:00
CuriousMagpie 4ba66c7018 unbreaking what was broken yesterday 2022-11-30 17:16:51 -05:00
CuriousMagpie 54b9424c6e Merge branch 'develop' into increment-component 2022-11-30 12:04:09 -05:00
CuriousMagpie af574634b0 tried to make if/else logic simpler, ended up breaking everything. 2022-11-29 17:13:48 -05:00
CuriousMagpie d1e1c09b4a add conditionals, add component to buy & sell modals 2022-11-22 15:28:48 -05:00
CuriousMagpie 4f5a720c30 how to make component show up? 2022-11-18 15:27:18 -05:00
CuriousMagpie 4ddfdb84ac get most of the right parts in the same place 2022-11-17 14:57:58 -05:00
64 changed files with 2435 additions and 1248 deletions
@@ -0,0 +1,158 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20230522_pet_group_achievements';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {
migration: MIGRATION_NAME,
};
if (user && user.items && user.items.pets) {
const pets = user.items.pets;
if (pets['Parrot-Base']
&& pets['Parrot-CottonCandyBlue']
&& pets['Parrot-CottonCandyPink']
&& pets['Parrot-Desert']
&& pets['Parrot-Golden']
&& pets['Parrot-Red']
&& pets['Parrot-Shade']
&& pets['Parrot-Skeleton']
&& pets['Parrot-White']
&& pets['Parrot-Zombie']
&& pets['Rooster-Base']
&& pets['Rooster-CottonCandyBlue']
&& pets['Rooster-CottonCandyPink']
&& pets['Rooster-Desert']
&& pets['Rooster-Golden']
&& pets['Rooster-Red']
&& pets['Rooster-Shade']
&& pets['Rooster-Skeleton']
&& pets['Rooster-White']
&& pets['Rooster-Zombie']
&& pets['Triceratops-Base']
&& pets['Triceratops-CottonCandyBlue']
&& pets['Triceratops-CottonCandyPink']
&& pets['Triceratops-Desert']
&& pets['Triceratops-Golden']
&& pets['Triceratops-Red']
&& pets['Triceratops-Shade']
&& pets['Triceratops-Skeleton']
&& pets['Triceratops-White']
&& pets['Triceratops-Zombie']
&& pets['TRex-Base']
&& pets['TRex-CottonCandyBlue']
&& pets['TRex-CottonCandyPink']
&& pets['TRex-Desert']
&& pets['TRex-Golden']
&& pets['TRex-Red']
&& pets['TRex-Shade']
&& pets['TRex-Skeleton']
&& pets['TRex-White']
&& pets['TRex-Zombie']
&& pets['Pterodactyl-Base']
&& pets['Pterodactyl-CottonCandyBlue']
&& pets['Pterodactyl-CottonCandyPink']
&& pets['Pterodactyl-Desert']
&& pets['Pterodactyl-Golden']
&& pets['Pterodactyl-Red']
&& pets['Pterodactyl-Shade']
&& pets['Pterodactyl-Skeleton']
&& pets['Pterodactyl-White']
&& pets['Pterodactyl-Zombie']
&& pets['Owl-Base']
&& pets['Owl-CottonCandyBlue']
&& pets['Owl-CottonCandyPink']
&& pets['Owl-Desert']
&& pets['Owl-Golden']
&& pets['Owl-Red']
&& pets['Owl-Shade']
&& pets['Owl-Skeleton']
&& pets['Owl-White']
&& pets['Owl-Zombie']
&& pets['Velociraptor-Base']
&& pets['Velociraptor-CottonCandyBlue']
&& pets['Velociraptor-CottonCandyPink']
&& pets['Velociraptor-Desert']
&& pets['Velociraptor-Golden']
&& pets['Velociraptor-Red']
&& pets['Velociraptor-Shade']
&& pets['Velociraptor-Skeleton']
&& pets['Velociraptor-White']
&& pets['Velociraptor-Zombie']
&& pets['Penguin-Base']
&& pets['Penguin-CottonCandyBlue']
&& pets['Penguin-CottonCandyPink']
&& pets['Penguin-Desert']
&& pets['Penguin-Golden']
&& pets['Penguin-Red']
&& pets['Penguin-Shade']
&& pets['Penguin-Skeleton']
&& pets['Penguin-White']
&& pets['Penguin-Zombie']
&& pets['Falcon-Base']
&& pets['Falcon-CottonCandyBlue']
&& pets['Falcon-CottonCandyPink']
&& pets['Falcon-Desert']
&& pets['Falcon-Golden']
&& pets['Falcon-Red']
&& pets['Falcon-Shade']
&& pets['Falcon-Skeleton']
&& pets['Falcon-White']
&& pets['Falcon-Zombie']
&& pets['Peacock-Base']
&& pets['Peacock-CottonCandyBlue']
&& pets['Peacock-CottonCandyPink']
&& pets['Peacock-Desert']
&& pets['Peacock-Golden']
&& pets['Peacock-Red']
&& pets['Peacock-Shade']
&& pets['Peacock-Skeleton']
&& pets['Peacock-White']
&& pets['Peacock-Zombie']) {
set['achievements.dinosaurDynasty'] = true;
}
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({ _id: user._id }, { $set: set }).exec();
}
export default async function processUsers () {
let query = {
// migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2023-04-15') },
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1]._id,
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};
+763 -630
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -1,12 +1,12 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.270.0",
"version": "4.271.2",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"@babel/register": "^7.18.9",
"@babel/core": "^7.21.8",
"@babel/preset-env": "^7.21.5",
"@babel/register": "^7.21.0",
"@google-cloud/trace-agent": "^7.1.2",
"@parse/node-apn": "^5.1.3",
"@slack/webhook": "^6.1.0",
@@ -14,9 +14,9 @@
"amazon-payments": "^0.2.9",
"amplitude": "^6.0.0",
"apidoc": "^0.54.0",
"apple-auth": "^1.0.7",
"apple-auth": "^1.0.9",
"bcrypt": "^5.1.0",
"body-parser": "^1.20.1",
"body-parser": "^1.20.2",
"bootstrap": "^4.6.0",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
@@ -42,7 +42,7 @@
"image-size": "^1.0.2",
"in-app-purchase": "^1.11.3",
"js2xmlparser": "^5.0.0",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.0",
"jwks-rsa": "^2.1.5",
"lodash": "^4.17.21",
"merge-stream": "^2.0.0",
@@ -67,8 +67,8 @@
"remove-markdown": "^0.5.0",
"rimraf": "^3.0.2",
"short-uuid": "^4.2.2",
"stripe": "^11.10.0",
"superagent": "^8.0.6",
"stripe": "^12.6.0",
"superagent": "^8.0.9",
"universal-analytics": "^0.5.3",
"useragent": "^2.1.9",
"uuid": "^9.0.0",
@@ -76,7 +76,7 @@
"vinyl-buffer": "^1.0.1",
"winston": "^3.8.2",
"winston-loggly-bulk": "^3.2.1",
"xml2js": "^0.4.23"
"xml2js": "^0.5.0"
},
"private": true,
"engines": {
@@ -110,7 +110,7 @@
"apidoc": "gulp apidoc"
},
"devDependencies": {
"axios": "^1.2.2",
"axios": "^1.3.6",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"chai-moment": "^0.1.0",
@@ -122,7 +122,7 @@
"monk": "^7.3.4",
"require-again": "^2.0.0",
"run-rs": "^0.7.7",
"sinon": "^15.0.1",
"sinon": "^15.1.0",
"sinon-chai": "^3.7.0",
"sinon-stub-promise": "^4.0.0"
},
+11 -1
View File
@@ -748,9 +748,19 @@ describe('payments/index', () => {
});
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
data.sub.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.extraMonths).to.eql(0);
expect(user.purchased.plan.consecutive.offset).to.eql(0);
});
it('resets plans.consecutive.offset if 1 month subscription', async () => {
user.purchased.plan.consecutive.offset = 1;
await user.save();
data.sub.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.offset).to.eql(0);
});
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
+1 -1
View File
@@ -242,7 +242,7 @@ describe('cron middleware', () => {
sandbox.spy(cronLib, 'recoverCron');
sandbox.stub(User, 'update')
sandbox.stub(User, 'updateOne')
.withArgs({
_id: user._id,
$or: [
+18 -18
View File
@@ -1732,7 +1732,7 @@ describe('Group Model', () => {
});
it('updates participting members (not including user)', async () => {
sandbox.spy(User, 'update');
sandbox.spy(User, 'updateMany');
await party.startQuest(nonParticipatingMember);
@@ -1740,7 +1740,7 @@ describe('Group Model', () => {
questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
];
expect(User.update).to.be.calledWith(
expect(User.updateMany).to.be.calledWith(
{ _id: { $in: members } },
{
$set: {
@@ -1753,11 +1753,11 @@ describe('Group Model', () => {
});
it('updates non-user quest leader and decrements quest scroll', async () => {
sandbox.spy(User, 'update');
sandbox.spy(User, 'updateOne');
await party.startQuest(participatingMember);
expect(User.update).to.be.calledWith(
expect(User.updateOne).to.be.calledWith(
{ _id: questLeader._id },
{
$inc: {
@@ -1819,29 +1819,29 @@ describe('Group Model', () => {
};
it('doesn\'t retry successful operations', async () => {
sandbox.stub(User, 'update').returns(successfulMock);
sandbox.stub(User, 'updateOne').returns(successfulMock);
await party.finishQuest(quest);
expect(User.update).to.be.calledThrice;
expect(User.updateOne).to.be.calledThrice;
});
it('stops retrying when a successful update has occurred', async () => {
const updateStub = sandbox.stub(User, 'update');
const updateStub = sandbox.stub(User, 'updateOne');
updateStub.onCall(0).returns(failedMock);
updateStub.returns(successfulMock);
await party.finishQuest(quest);
expect(User.update.callCount).to.equal(4);
expect(User.updateOne.callCount).to.equal(4);
});
it('retries failed updates at most five times per user', async () => {
sandbox.stub(User, 'update').returns(failedMock);
sandbox.stub(User, 'updateOne').returns(failedMock);
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
expect(User.update.callCount).to.eql(15); // for 3 users
expect(User.updateOne.callCount).to.eql(15); // for 3 users
});
});
@@ -2088,17 +2088,17 @@ describe('Group Model', () => {
context('Party quests', () => {
it('updates participating members with rewards', async () => {
sandbox.spy(User, 'update');
sandbox.spy(User, 'updateOne');
await party.finishQuest(quest);
expect(User.update).to.be.calledThrice;
expect(User.update).to.be.calledWithMatch({
expect(User.updateOne).to.be.calledThrice;
expect(User.updateOne).to.be.calledWithMatch({
_id: questLeader._id,
});
expect(User.update).to.be.calledWithMatch({
expect(User.updateOne).to.be.calledWithMatch({
_id: participatingMember._id,
});
expect(User.update).to.be.calledWithMatch({
expect(User.updateOne).to.be.calledWithMatch({
_id: sleepingParticipatingMember._id,
});
});
@@ -2173,11 +2173,11 @@ describe('Group Model', () => {
});
it('updates all users with rewards', async () => {
sandbox.spy(User, 'update');
sandbox.spy(User, 'updateMany');
await party.finishQuest(tavernQuest);
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({});
expect(User.updateMany).to.be.calledOnce;
expect(User.updateMany).to.be.calledWithMatch({});
});
it('sets quest completed to the world quest key', async () => {
@@ -202,18 +202,86 @@ describe('POST /user/class/cast/:spellId', () => {
await group.groupLeader.post('/user/class/cast/mpheal');
promises = [];
promises.push(group.groupLeader.sync());
promises.push(group.members[0].sync());
promises.push(group.members[1].sync());
promises.push(group.members[2].sync());
promises.push(group.members[3].sync());
await Promise.all(promises);
expect(group.groupLeader.stats.mp).to.be.equal(170); // spell caster
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
expect(group.members[1].stats.mp).to.equal(0); // wizard
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
});
const spellList = [
{
className: 'warrior',
spells: [['smash', 'task'], ['defensiveStance'], ['valorousPresence'], ['intimidate']],
},
{
className: 'wizard',
spells: [['fireball', 'task'], ['mpheal'], ['earth'], ['frost']],
},
{
className: 'healer',
spells: [['heal'], ['brightness'], ['protectAura'], ['healAll']],
},
{
className: 'rogue',
spells: [['pickPocket', 'task'], ['backStab', 'task'], ['toolsOfTrade'], ['stealth']],
},
];
spellList.forEach(async habitClass => {
describe(`For a ${habitClass.className}`, async () => {
habitClass.spells.forEach(async spell => {
describe(`Using ${spell[0]}`, async () => {
it('Deducts MP from spell caster', async () => {
const { groupLeader } = await createAndPopulateGroup({
groupDetails: { type: 'party', privacy: 'private' },
members: 3,
});
await groupLeader.update({
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
});
// need this for task spells and for stealth
const task = await groupLeader.post('/tasks/user', {
text: 'test habit',
type: 'daily',
});
if (spell.length === 2 && spell[1] === 'task') {
await groupLeader.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
} else {
await groupLeader.post(`/user/class/cast/${spell[0]}`);
}
await groupLeader.sync();
expect(groupLeader.stats.mp).to.be.lessThan(200);
});
it('works without a party', async () => {
await user.update({
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
});
// need this for task spells and for stealth
const task = await user.post('/tasks/user', {
text: 'test habit',
type: 'daily',
});
if (spell.length === 2 && spell[1] === 'task') {
await user.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
} else {
await user.post(`/user/class/cast/${spell[0]}`);
}
await user.sync();
expect(user.stats.mp).to.be.lessThan(200);
});
});
});
});
});
it('cast bulk', async () => {
let { group, groupLeader } = await createAndPopulateGroup({ // eslint-disable-line prefer-const
groupDetails: { type: 'party', privacy: 'private' },
+53 -52
View File
@@ -1842,9 +1842,9 @@
}
},
"@babel/plugin-proposal-optional-chaining": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz",
"integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==",
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
"integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==",
"requires": {
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
@@ -1870,9 +1870,9 @@
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
},
"@babel/types": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
"integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
"version": "7.21.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz",
"integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==",
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
@@ -13321,14 +13321,12 @@
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"optional": true
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"optional": true
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
@@ -13339,7 +13337,6 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
@@ -13381,7 +13378,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -13390,7 +13386,6 @@
"version": "npm:vue-loader@16.8.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
@@ -13401,7 +13396,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -16907,9 +16901,9 @@
}
},
"core-js": {
"version": "3.27.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz",
"integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w=="
"version": "3.30.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz",
"integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg=="
},
"core-js-compat": {
"version": "3.11.0",
@@ -17958,9 +17952,9 @@
}
},
"dompurify": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz",
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ=="
},
"domutils": {
"version": "1.7.0",
@@ -21077,6 +21071,11 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
},
"immutable": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg=="
},
"import-cwd": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@@ -21437,9 +21436,9 @@
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="
},
"intro.js": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-6.0.0.tgz",
"integrity": "sha512-ZUiR6BoLSvPSlLG0boewnWVgji1fE1gBvP/pyw5pgCKXEDQz1mMeUxarggClPNs71UTq364LwSk9zxz17A9gaQ=="
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.0.1.tgz",
"integrity": "sha512-1oqz6aOz9cGQ3CrtVYhCSo6AkjnXUn302kcIWLaZ3TI4kKssRXDwDSz4VRoGcfC1jN+WfaSJXRBrITz+QVEBzg=="
},
"invariant": {
"version": "2.2.4",
@@ -22019,9 +22018,9 @@
}
},
"jquery": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
"integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
},
"js-message": {
"version": "1.0.5",
@@ -27367,17 +27366,19 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass": {
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.34.0.tgz",
"integrity": "sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw==",
"version": "1.62.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz",
"integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==",
"requires": {
"chokidar": ">=3.0.0 <4.0.0"
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"dependencies": {
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -27397,18 +27398,18 @@
}
},
"chokidar": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"requires": {
"anymatch": "~3.1.1",
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.5.0"
"readdirp": "~3.6.0"
}
},
"fill-range": {
@@ -27447,9 +27448,9 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"requires": {
"picomatch": "^2.2.1"
}
@@ -27801,9 +27802,9 @@
}
},
"smartbanner.js": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.1.tgz",
"integrity": "sha512-x3alFTlk6pLuqrm9PrYQv1E+86CrEIgPf/KJ+nP5342BmOWstbdR8OwD3TPmM56zHQm4MEr/eoqbEcfTKdvdKw=="
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.2.tgz",
"integrity": "sha512-hwcGNp5Hza5PJHTmqP6H8q0XBYhloIQyJgdzv0ldz3HQSeEuKB2riVraQXdKuquE6ZU/0M0yubno53xJ/ZiQQg=="
},
"snapdragon": {
"version": "0.8.2",
@@ -28175,9 +28176,9 @@
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"stopword": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.7.tgz",
"integrity": "sha512-s+uLKAxrproCLrq0Wcd3JAIjlJLx6l80b2Rzt0u8+ra5SzGkHnNG8PS3DfGmYk2TrKePDVLL4SdKYwKpgSLc+w=="
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.8.tgz",
"integrity": "sha512-btlEC2vEuhCuvshz99hSGsY8GzaP5qzDPQm56j6rR/R38p8xdsOXgU5a6tIgvU/4hcCta1Vlo/2FVXA9m0f8XA=="
},
"store2": {
"version": "2.10.0",
@@ -30271,9 +30272,9 @@
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
},
"uuid-browser": {
"version": "3.1.0",
+9 -9
View File
@@ -32,8 +32,8 @@
"bootstrap": "^4.6.0",
"bootstrap-vue": "^2.23.1",
"chai": "^4.3.7",
"core-js": "^3.27.2",
"dompurify": "^2.4.3",
"core-js": "^3.30.2",
"dompurify": "^3.0.3",
"eslint": "^6.8.0",
"eslint-config-habitrpg": "^6.2.0",
"eslint-plugin-mocha": "^5.3.0",
@@ -41,20 +41,20 @@
"habitica-markdown": "^3.0.0",
"hellojs": "^1.20.0",
"inspectpack": "^4.7.1",
"intro.js": "^6.0.0",
"jquery": "^3.6.3",
"intro.js": "^7.0.1",
"jquery": "^3.7.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"nconf": "^0.12.0",
"sass": "^1.34.0",
"sass": "^1.62.1",
"sass-loader": "^8.0.2",
"smartbanner.js": "^1.19.1",
"stopword": "^2.0.7",
"smartbanner.js": "^1.19.2",
"stopword": "^2.0.8",
"svg-inline-loader": "^0.8.2",
"svg-url-loader": "^7.1.1",
"svgo": "^1.3.2",
"svgo-loader": "^2.2.1",
"uuid": "^8.3.2",
"uuid": "^9.0.0",
"validator": "^13.9.0",
"vue": "^2.7.10",
"vue-cli-plugin-storybook": "2.1.0",
@@ -66,6 +66,6 @@
"webpack": "^4.46.0"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.20.7"
"@babel/plugin-proposal-optional-chaining": "^7.21.0"
}
}
@@ -138,6 +138,11 @@
width: 48px;
height: 52px;
}
.achievement-dinosaurDynasty2x {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-dinosaurDynasty2x.png');
width: 68px;
height: 68px;
}
.achievement-domesticated2x {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-domesticated2x.png');
width: 60px;
@@ -17,10 +17,18 @@
Payment schedule ("basic-earned" is monthly):
<strong>{{ hero.purchased.plan.planId }}</strong>
</div>
<div v-if="hero.purchased.plan.planId == 'group_plan_auto'">
Group plan ID:
<strong>{{ hero.purchased.plan.owner }}</strong>
</div>
<div v-if="hero.purchased.plan.dateCreated">
Creation date:
<strong>{{ dateFormat(hero.purchased.plan.dateCreated) }}</strong>
</div>
<div v-if="hero.purchased.plan.dateCurrentTypeCreated">
Start date for current subscription type:
<strong>{{ dateFormat(hero.purchased.plan.dateCurrentTypeCreated) }}</strong>
</div>
<div>
Termination date:
<strong
@@ -46,9 +54,16 @@
Perk offset months:
<strong>{{ hero.purchased.plan.consecutive.offset }}</strong>
</div>
<div>
<div class="form-inline">
Perk month count:
<strong>{{ hero.purchased.plan.perkMonthCount }}</strong>
<input
v-model="hero.purchased.plan.perkMonthCount"
class="form-control"
type="number"
min="0"
max="2"
step="1"
>
</div>
<div>
Next Mystic Hourglass:
@@ -126,7 +126,7 @@
<!-- the word "total" -->
<div class="buy-gem-total">
{{ $t('sendGiftTotal') }}
{{ $t('sendTotal') }}
</div>
<!-- the actual dollar amount -->
@@ -144,22 +144,6 @@
>{{ $t('startAdvCollapsed') }}</span>
</label>
</div>
<div class="checkbox">
<label>
<input
v-model="user.preferences.dailyDueDefaultView"
type="checkbox"
class="mr-2"
@change="set('dailyDueDefaultView')"
>
<span
class="hint"
popover-trigger="mouseenter"
popover-placement="right"
:popover="$t('dailyDueDefaultViewPop')"
>{{ $t('dailyDueDefaultView') }}</span>
</label>
</div>
<div
v-if="party.memberCount === 1"
class="checkbox"
@@ -0,0 +1,93 @@
<template>
<div class="item-cost">
<span
class="cost"
:class="getPriceClass()"
>
<span
class="svg-icon inline icon-24"
aria-hidden="true"
v-html="icons[getPriceClass()]"
>
</span>
<span
:class="getPriceClass()"
>{{ item.value }}</span>
</span>
</div>
</template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/mixins.scss';
.item-cost {
padding-bottom: 16px;
}
.cost {
height: 40px;
font-size: 1.25rem;
font-weight: bold;
line-height: 1.4;
vertical-align: middle;
&.gems {
color: $gems-color;
border-radius: 20px;
padding: 8px 20px 8px 20px;
margin-top: 16px;
margin-bottom: 16px;
background-color: rgba(36, 204, 143, 0.15);
}
&.gold {
color: $gold-color;
border-radius: 20px;
padding: 8px 20px 8px 20px;
margin-top: 16px;
margin-bottom: 16px;
background-color: rgba(255, 190, 93, 0.15);
}
&.hourglasses {
color: $hourglass-color;
border-radius: 20px;
padding: 8px 20px 8px 20px;
margin-top: 16px;
margin-bottom: 16px;
background-color: rgba(41, 149, 205, 0.15);
}
}
</style>
<script>
import svgClose from '@/assets/svg/close.svg';
import svgGold from '@/assets/svg/gold.svg';
import svgGem from '@/assets/svg/gem.svg';
export default {
data () {
return {
icons: Object.freeze({
close: svgClose,
gold: svgGold,
gems: svgGem,
}),
selectedAmountToBuy: 1,
selectedAmount: 1,
};
},
methods: {
getPriceClass () {
if (this.priceType && this.icons[this.priceType]) {
return this.priceType;
} if (this.item.currency && this.icons[this.item.currency]) {
return this.item.currency;
}
return 'gold';
},
},
};
</script>
@@ -0,0 +1,137 @@
<template>
<div class="d-flex flex-row align-items-center justify-content-center number-increment">
<!-- buy modal -->
<div
class="gray-circle"
@click="quantity <= 0
? quantity = 0
: quantity--"
>
<div
class="icon-negative"
v-html="icons.svgNegative"
></div>
</div>
<div class="input-group">
<div class="align-items-center">
</div>
<input
v-model="quantity"
class="form-control alignment"
step="1"
type="number"
>
</div>
<div
class="gray-circle"
@click="quantity++"
>
<div
class="icon-positive"
v-html="icons.svgPositive"
></div>
</div>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.number-increment {
padding-bottom: 0px;
}
.alignment {
text-align: center;
}
.input-group {
width: 94px;
height: 32px;
width: 48px;
margin: 0px 16px 0px 16px;
padding: 0;
border-radius: 2px;
border: solid 1px $gray-400;
background-color: $white;
}
.gray-circle {
border-radius: 100%;
border: solid 2px $gray-300;
width: 32px;
height: 32px;
cursor: pointer;
&:hover {
border-color: $purple-300;
}
}
.gray-circle:hover{
.icon-positive, .icon-negative {
& ::v-deep svg path {
fill: $purple-300;
}
}
}
.icon-positive, .icon-negative {
width: 10px;
height: 10px;
margin: 4px auto;
& ::v-deep svg path {
fill: $gray-300;
}
}
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type=number] {
-moz-appearance: textfield;
}
</style>
<script>
// icons
import svgGem from '@/assets/svg/gem.svg';
import svgGold from '@/assets/svg/gold.svg';
import svgPositive from '@/assets/svg/positive.svg';
import svgNegative from '@/assets/svg/negative.svg';
export default {
data () {
return {
icons: Object.freeze({
svgGem,
svgGold,
svgPositive,
svgNegative,
}),
item: { },
quantity: 1,
};
},
computed: {
},
watch: {
quantity () {
this.$emit('updateQuantity', this.quantity);
},
},
methods: {
setDefaults () {
this.input = 1;
},
},
};
</script>
@@ -22,10 +22,11 @@
@import '~@/assets/scss/colors.scss';
span {
font-weight: normal;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.33;
color: $gray-200;
color: $gray-100;
margin-bottom: 16px;
margin-top: -4px;
display: inline-block;
}
+363 -102
View File
@@ -17,7 +17,7 @@
</span>
<div>
<span
class="svg-icon icon-12 close-icon"
class="svg-icon close-icon icon-16 color"
aria-hidden="true"
tabindex="0"
@click="hideDialog()"
@@ -45,6 +45,13 @@
:sprites-margin="'0px auto 0px -24px'"
/>
</div>
<item
v-else-if="item.key === 'gem'"
class="flat bordered-item"
:item="item"
:item-content-class="item.class"
:show-popover="false"
/>
<item
v-else-if="item.key != 'gem'"
class="flat bordered-item"
@@ -53,10 +60,20 @@
:show-popover="false"
/>
</slot>
<div
v-if="!showAvatar && user.items[item.purchaseType]"
class="owned"
:class="totalOwned"
>
<!-- eslint-disable-next-line max-len -->
<span class="owned-text">{{ $t('owned') }}: <span class="user-amount">{{ totalOwned }}</span></span>
</div>
<h4 class="title">
{{ itemText }}
</h4>
<div v-html="itemNotes"></div>
<div class="item-notes">
{{ itemNotes }}
</div>
<slot
name="additionalInfo"
:item="item"
@@ -69,60 +86,61 @@
/>
</slot>
<div
v-if="item.value > 0"
v-if="item.value > 0 && !(item.key === 'gem' && gemsLeft < 1)"
class="purchase-amount"
>
<div
v-if="showAmountToBuy(item)"
class="how-many-to-buy"
>
<strong>{{ $t('howManyToBuy') }}</strong>
</div>
<div v-if="showAmountToBuy(item)">
<div class="box">
<input
v-model.number="selectedAmountToBuy"
class="form-control"
type="number"
min="0"
step="1"
>
</div>
<span :class="{'notEnough': notEnoughCurrency}">
<!-- this is where the pretty item cost element lives -->
<div class="item-cost">
<span
class="cost"
:class="getPriceClass()"
>
<span
class="svg-icon inline icon-32"
class="svg-icon inline icon-24"
aria-hidden="true"
v-html="icons[getPriceClass()]"
></span>
>
</span>
<span
class="cost"
:class="getPriceClass()"
>{{ item.value }}</span>
</span>
</div>
<div
v-else
class="d-flex align-items-middle"
v-if="showAmountToBuy(item)"
class="how-many-to-buy"
>
<span
class="svg-icon inline icon-32 ml-auto my-auto"
aria-hidden="true"
v-html="icons[getPriceClass()]"
></span>
<span
class="cost mr-auto my-auto"
:class="getPriceClass()"
>{{ item.value }}</span>
{{ $t('howManyToBuy') }}
</div>
<div
v-if="showAmountToBuy(item)"
>
<number-increment
class="number-increment"
@updateQuantity="selectedAmountToBuy = $event"
/>
<div
:class="{'notEnough': notEnoughCurrency}"
class="total"
>
<span class="total-text">{{ $t('sendTotal') }}</span>
<span
class="svg-icon total icon-24"
aria-hidden="true"
v-html="icons[getPriceClass()]"
></span>
<span
class="total-text"
:class="getPriceClass()"
>{{ item.value * selectedAmountToBuy }}</span>
</div>
</div>
</div>
<div
v-if="item.key === 'gem'"
class="gems-left"
v-if="item.key === 'gem' && gemsLeft < 1"
class="no-more-gems"
>
<strong v-if="gemsLeft > 0">{{ gemsLeft }} {{ $t('gemsRemaining') }}</strong>
<strong v-if="gemsLeft === 0">{{ $t('maxBuyGems') }}</strong>
</div>
<div v-if="attemptingToPurchaseMoreGemsThanAreLeft">
{{ $t('notEnoughGemsToBuy') }}
</div>
<div
@@ -147,7 +165,7 @@
{{ $t('viewSubscriptions') }}
</button>
<button
v-else
v-else-if="!(item.key === 'gem' && gemsLeft < 1)"
class="btn btn-primary"
:disabled="item.key === 'gem' && gemsLeft === 0 ||
attemptingToPurchaseMoreGemsThanAreLeft || numberInvalid || item.locked ||
@@ -165,6 +183,7 @@
<countdown-banner
v-if="item.event && item.owned == null"
:end-date="endDate"
class="limitedTime available"
/>
<div
v-if="item.key === 'rebirth_orb' && item.value > 0 && user.stats.lvl >= 100"
@@ -179,12 +198,31 @@
</div>
</div>
<div
slot="modal-footer"
class="d-flex"
v-if="item.key === 'gem'"
class="d-flex justify-content-center align-items-center"
>
<span class="balance mr-auto">{{ $t('yourBalance') }}</span>
<div
v-if="gemsLeft > 0"
class="gems-left d-flex justify-content-center align-items-center"
>
<strong>{{ $t('monthlyGems') }} &nbsp;</strong>
{{ gemsLeft }} / {{ totalGems }} {{ $t('gemsRemaining') }}
</div>
<div
v-if="gemsLeft === 0"
class="out-of-gems-banner d-flex justify-content-center align-items-center"
>
<strong>{{ $t('monthlyGems') }} &nbsp;</strong>
{{ gemsLeft }} / {{ totalGems }} {{ $t('gemsRemaining') }}
</div>
</div>
<div
slot="modal-footer"
class="clearfix"
>
<span class="user-balance float-left">{{ $t('yourBalance') }}</span>
<balanceInfo
class="ml-auto"
class="currency-totals"
:currency-needed="getPriceClass()"
:amount-needed="item.value"
/>
@@ -200,11 +238,47 @@
@include centeredModal();
.modal-body {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 0px;
}
.modal-footer {
height: 48px;
background-color: $gray-700;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
display: block;
margin: 24px 0 0 0;
padding: 16px 24px;
align-content: center;
.user-balance {
width: 150px;
height: 16px;
font-size: 0.75rem;
font-weight: bold;
line-height: 1.33;
color: $gray-100;
margin-bottom: 16px;
margin-top: -4px;
margin-left: -4px;
}
.currency-totals {
margin-right: -8px;
float: right;
}
}
.modal-dialog {
width: 330px;
width: 448px;
box-sizing: border-box;
}
.badge-dialog {
left: -8px;
top: -8px;
}
.avatar {
@@ -212,8 +286,71 @@
margin: 0 auto;
}
.owned {
height: 32px;
width: 141px;
margin-top: -36px;
margin-left: 153px;
padding-top: 6px;
background-color: $gray-600;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
display: block;
text-align: center;
position: relative;
z-index: 1;
.owned-text {
font-size: 0.75rem;
font-weight: bold;
line-height: 1.71;
}
.user-amount {
font-weight: normal !important;
}
}
.item {
width: 141px;
height: 147px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
cursor: default;
}
.item-content {
transform: scale(1.45, 1.45);
top: -25.67px;
left: 1px;
&.shop_gem {
transform: scale(1.45, 1.45);
top: -2px;
left: 0px;
}
}
.title {
height: 28px;
color: $gray-10;
font-size: 1.25rem;
margin-top: 25px;
}
.item-notes {
margin-top: 8px;
padding-left: 48.5px;
padding-right: 48.5px;
line-height: 1.71;
font-size: 0.875rem;
}
.content {
text-align: center;
width: 448px;
}
.item-wrapper {
@@ -221,15 +358,22 @@
}
.inner-content {
margin: 33px auto auto;
width: 282px;
margin: 32px auto auto;
}
.btn-primary {
margin-top: 16px;
}
.purchase-amount {
margin-top: 24px;
margin-top: 0px;
.how-many-to-buy {
margin-bottom: 16px;
font-weight: bold !important;
}
.number-increment {
margin-top: 16px;
}
.box {
@@ -255,31 +399,105 @@
}
}
}
.no-more-gems {
color: $yellow-5;
font-size: 0.875em;
line-height: 1.33;
margin: 16px 48px 0 48px;
}
span.svg-icon.inline.icon-32 {
height: 32px;
width: 32px;
// for cost icon of a single item
span.svg-icon.inline.icon-24 {
display: inline-block;
height: 24px;
width: 24px;
margin-right: 4px;
padding-top: 4px;
}
// for the total user cost
span.svg-icon.total.icon-24 {
display: inline-block;
height: 24px;
width: 24px;
margin-left: 6px;
margin-right: 8px;
padding-top: 6px;
}
vertical-align: middle;
span.svg-icon.icon-16 {
height: 16px;
width: 16px;
}
.close-icon {
color: $gray-200;
stroke-width: 0px;
&:hover {
color: $gray-100;
}
}
.attributes-group {
margin: 32px;
border-radius: 4px;
line-height: 1.71;
font-size: 0.875;
}
.attributesGrid {
margin-top: 28px;
border-radius: 2px;
background-color: $gray-500;
}
.item-cost {
display: inline-flex;
margin: 16px 0;
align-items: center;
height: 40px;
}
.cost {
width: 28px;
height: 32px;
font-size: 24px;
display: inline-block;
font-family: sans-serif;
font-size: 1.25rem;
font-weight: bold;
line-height: 1.33;
vertical-align: middle;
padding: 6px 20px;
line-height: 1.4;
border-radius: 20px;
&.gems {
color: $gems-color;
color: $green-10;
background-color: rgba(36, 204, 143, 0.15);
align-items: center;
}
&.gold {
color: $gold-color;
color: $yellow-5;
background-color: rgba(255, 190, 93, 0.15);
align-items: center;
}
&.hourglasses {
color: $hourglass-color;
background-color: rgba(41, 149, 205, 0.15);
align-items: center;
}
}
.total {
font-weight: bold;
font-size: 0.875rem;
padding-top: 2px;
margin-top: 4px;
&.gems {
color: $green-10;
}
&.gold {
color: $yellow-5;
}
&.hourglasses {
@@ -287,62 +505,84 @@
}
}
.total-text {
color: $gray-50;
font-weight: bold;
font-size: 0.875rem;
line-height: 1.71;
&.gems {
color: $green-10;
}
&.gold {
color: $yellow-5;
}
&.hourglasses {
color: $hourglass-color;
}
}
button.btn.btn-primary {
margin-top: 24px;
margin-bottom: 24px;
min-width: 6rem;
margin-top: 16px;
padding: 4px 16px;
height: 32px;
&:focus {
border: 2px solid black;
}
}
.balance {
width: 74px;
height: 16px;
font-size: 12px;
font-weight: bold;
line-height: 1.33;
color: $gray-200;
}
.notEnough {
pointer-events: none;
opacity: 0.55;
}
.modal-footer {
height: 48px;
background-color: $gray-700;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
display: block;
}
.free-rebirth {
background-color: $yellow-5;
color: $white;
height: 2rem;
line-height: 16px;
margin: auto -1rem -1rem;
}
.notEnough {
pointer-events: none;
opacity: 0.55;
}
// .pt-015 {
// padding-top: 0.15rem;
// }
.attributesGrid {
margin-top: 8px;
border-radius: 2px;
background-color: $gray-500;
margin: 10px 0 24px;
}
.gems-left {
margin-top: .5em;
height: 32px;
background-color: $green-100;
font-size: 0.75rem;
margin-top: 24px;
color: $green-1;
width: 100%;
margin-bottom: -24px;
}
.free-rebirth {
background-color: $yellow-5;
.out-of-gems-banner {
height: 32px;
font-size: 0.75rem;
margin-top: 24px;
background-color: $yellow-100;
color: $yellow-1;
width: 100%;
margin-bottom: -24px;
}
.limitedTime {
height: 32px;
width: 446px;
font-size: 0.75rem;
margin: 24px 0 0 0;
background-color: $purple-300;
color: $white;
height: 2rem;
line-height: 16px;
margin: auto -1rem -1rem;
}
.pt-015 {
padding-top: 0.15rem;
margin-bottom: -24px;
}
}
</style>
<style lang="scss" scoped>
@@ -370,6 +610,8 @@ import svgGem from '@/assets/svg/gem.svg';
import svgHourglasses from '@/assets/svg/hourglass.svg';
import svgClock from '@/assets/svg/clock.svg';
import svgWhiteClock from '@/assets/svg/clock-white.svg';
import svgPositive from '@/assets/svg/positive.svg';
import svgNegative from '@/assets/svg/negative.svg';
import BalanceInfo from './balanceInfo.vue';
import PinBadge from '@/components/ui/pinBadge';
@@ -377,6 +619,7 @@ import CountdownBanner from './countdownBanner';
import currencyMixin from './_currencyMixin';
import notifications from '@/mixins/notifications';
import buyMixin from '@/mixins/buy';
import numberIncrement from '@/components/shared/numberIncrement';
import { mapState } from '@/libs/store';
@@ -407,14 +650,17 @@ export default {
Avatar,
PinBadge,
CountdownBanner,
numberIncrement,
},
mixins: [buyMixin, currencyMixin, notifications, numberInvalid, spellsMixin],
props: {
// eslint-disable-next-line vue/require-default-prop
item: {
type: Object,
},
priceType: {
type: String,
default: '',
},
withPin: {
type: Boolean,
@@ -433,10 +679,14 @@ export default {
hourglasses: svgHourglasses,
clock: svgClock,
whiteClock: svgWhiteClock,
positive: svgPositive,
negative: svgNegative,
}),
selectedAmountToBuy: 1,
selectedAmount: 1,
isPinned: false,
quantity: 1,
};
},
computed: {
@@ -474,6 +724,11 @@ export default {
return planGemLimits.convCap
+ this.user.purchased.plan.consecutive.gemCapExtra - this.user.purchased.plan.gemsBought;
},
totalGems () {
if (!this.user.purchased.plan) return 0;
return planGemLimits.convCap
+ this.user.purchased.plan.consecutive.gemCapExtra;
},
attemptingToPurchaseMoreGemsThanAreLeft () {
if (this.item && this.item.key && this.item.key === 'gem' && this.selectedAmountToBuy > this.gemsLeft) return true;
return false;
@@ -490,6 +745,9 @@ export default {
endDate () {
return moment(this.item.event.end);
},
totalOwned () {
return this.user.items[this.item.purchaseType][this.item.key] || 0;
},
},
watch: {
item: function itemChanged () {
@@ -500,7 +758,9 @@ export default {
methods: {
onChange ($event) {
this.$emit('change', $event);
this.selectedAmountToBuy = 1;
},
buyItem () {
// @TODO: I think we should buying to the items.
// Turn the items into classes, and use polymorphism
@@ -597,6 +857,7 @@ export default {
}
},
hideDialog () {
this.selectedAmountToBuy = 1;
this.$root.$emit('bv::hide::modal', 'buy-modal');
},
getPriceClass () {
@@ -4,9 +4,9 @@
:hide-header="true"
@change="onChange($event)"
>
<div class="close">
<div>
<span
class="svg-icon inline icon-10"
class="svg-icon close-icon icon-16 color"
aria-hidden="true"
@click="hideDialog()"
v-html="icons.close"
@@ -14,60 +14,73 @@
</div>
<div
v-if="item"
class="content"
class="content bordered-item"
>
<div class="inner-content">
<item
class="flat"
class="flat bordered-item"
:item="item"
:item-content-class="itemContextToSell.itemClass"
:show-popover="false"
>
<countBadge
slot="itemBadge"
:show="true"
:count="itemContextToSell.itemCount"
/>
</item>
/>
<span class="owned">
{{ $t('owned') }}: <span class="user-amount">{{ itemContextToSell.itemCount }}</span>
</span>
<h4 class="title">
{{ itemContextToSell.itemName }}
</h4>
<div v-if="item.key === 'Saddle'">
<div class="text">
<div class="item-notes">
{{ item.sellWarningNote() }}
</div>
<br>
</div>
<div v-else>
<div>
<div class="text">
<div class="item-notes">
{{ item.notes() }}
</div>
<div>
<b class="how-many-to-sell">{{ $t('howManyToSell') }}</b>
<div class="item-cost">
<span class="cost gold">
<span
class="svg-icon inline icon-24"
aria-hidden="true"
v-html="icons.gold"
></span>
<span>{{ item.value }}</span>
</span>
</div>
<div>
<b-input
v-model="selectedAmountToSell"
class="itemsToSell"
type="number"
:max="itemContextToSell.itemCount"
min="1"
step="1"
@keyup.native="preventNegative($event)"
/>
<span
class="svg-icon inline icon-32"
class="how-many-to-sell"
>
{{ $t('howManyToSell') }}
</span>
</div>
<div>
<number-increment
@updateQuantity="selectedAmountToSell = $event"
/>
</div>
<div class="total-row">
<span class="total-text">
{{ $t('sendTotal') }}
</span>
<span
class="svg-icon total icon-24"
aria-hidden="true"
v-html="icons.gold"
></span>
<span class="value">{{ item.value }}</span>
<span class="total-text gold">
{{ item.value * selectedAmountToSell }}
</span>
</div>
<button
class="btn btn-primary"
:disabled="selectedAmountToSell > itemContextToSell.itemCount"
@click="sellItems()"
>
{{ $t('sell') }}
{{ $t('sellItems') }}
</button>
</div>
</div>
@@ -77,8 +90,10 @@
slot="modal-footer"
class="clearfix"
>
<span class="balance float-left">{{ $t('yourBalance') }}</span>
<balanceInfo class="float-right" />
<span class="user-balance float-left">{{ $t('yourBalance') }}</span>
<balanceInfo
class="float-right currency-totals"
/>
</div>
</b-modal>
</template>
@@ -95,51 +110,13 @@
}
.modal-dialog {
width: 330px;
width: 448px;
}
.content {
text-align: center;
}
.inner-content {
margin: 33px auto auto;
width: 282px;
}
span.svg-icon.inline.icon-32 {
height: 32px;
width: 32px;
margin-left: 24px;
margin-right: 8px;
vertical-align: middle;
}
.value {
width: 28px;
height: 32px;
font-size: 24px;
font-weight: bold;
line-height: 1.33;
color: #df911e;
vertical-align: middle;
}
button.btn.btn-primary {
margin-top: 24px;
margin-bottom: 24px;
}
.balance {
width: 74px;
height: 16px;
font-size: 12px;
font-weight: bold;
line-height: 1.33;
color: $gray-200;
.modal-body {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 0px;
}
.modal-footer {
@@ -148,29 +125,215 @@
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
display: block;
margin: 24px 0 0;
padding: 16px 24px;
align-content: center;
.user-balance {
width: 150px;
height: 16px;
font-size: 0.75rem;
font-weight: bold;
line-height: 1.33;
color: $gray-100;
margin-bottom: 16px;
margin-top: -4px;
margin-left: -4px;
}
.currency-totals {
margin-right: -8px;
float: right;
}
}
.how-many-to-sell {
margin-bottom: 16px;
.content {
text-align: center;
}
.inner-content {
margin: 33px auto auto;
width: 282px;
}
.owned {
font-size: 0.75rem;
font-weight: bold;
line-height: 1.33;
background-color: $gray-600;
padding: 8px 8px;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
display: block;
width: 141px;
margin-left: 71px;
margin-top: -48px;
position: relative;
z-index: 1;
.user-amount {
font-weight: normal !important;
}
}
.item-wrapper {
margin-top: -10px;
}
.item {
width: 141px;
height: 147px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
cursor: default;
margin-top: 8px;
}
.item-content {
transform: scale(1.45, 1.45);
top: -25px;
left: 1px;
}
.title {
color: $gray-10;
font-size: 1.25rem;
margin-top: 26px;
margin-bottom: 0px;
}
.item-notes {
margin-top: 12px;
line-height: 1.71;
font-size: 0.875rem;
}
// for cost icon of a single item
span.svg-icon.inline.icon-24 {
display: inline-block;
height: 24px;
width: 24px;
margin-right: 4px;
padding-top: 4px;
}
// for the total user cost
span.svg-icon.total.icon-24 {
display: inline-block;
height: 24px;
width: 24px;
margin-left: 6px;
margin-right: 8px;
padding-top: 6px;
}
span.svg-icon.icon-16 {
height: 16px;
width: 16px;
}
.close-icon {
color: $gray-200;
stroke-width: 0px;
cursor: pointer;
&:hover {
color: $gray-100;
}
}
.item-cost {
display: inline-flex;
margin: 16px 0;
align-items: center;
height: 40px;
}
.cost {
display: inline-block;
font-family: sans-serif;
font-size: 1.25rem;
font-weight: bold;
padding: 6px 20px;
line-height: 1.4;
border-radius: 20px;
&.gold {
color: $yellow-5;
background-color: rgba(255, 190, 93, 0.15);
align-items: center;
}
}
}
.how-many-to-sell {
font-weight: bold !important;
}
.number-increment {
margin-top: 16px;
}
.total-row {
font-weight: bold;
font-size: 0.875rem;
margin-top: 16px;
&.gold {
color: $yellow-5;
}
}
.total-text {
color: $gray-50;
font-weight: bold;
font-size: 0.875rem;
line-height: 1.71;
&.gold {
color: $yellow-5;
}
}
button.btn.btn-primary {
margin-top: 16px;
padding: 4px 16px;
height: 32px;
&:focus {
border: 2px solid black;
}
.balance {
width: 74px;
height: 16px;
font-size: 12px;
font-weight: bold;
line-height: 1.33;
color: $gray-200;
}
}
</style>
<script>
import svgClose from '@/assets/svg/close.svg';
import svgGold from '@/assets/svg/gold.svg';
import svgGem from '@/assets/svg/gem.svg';
import svgPositive from '@/assets/svg/positive.svg';
import svgNegative from '@/assets/svg/negative.svg';
import BalanceInfo from '../balanceInfo.vue';
import Item from '@/components/inventory/item';
import CountBadge from '@/components/ui/countBadge';
import numberIncrement from '@/components/shared/numberIncrement';
export default {
components: {
BalanceInfo,
Item,
CountBadge,
numberIncrement,
},
data () {
return {
@@ -181,6 +344,8 @@ export default {
close: svgClose,
gold: svgGold,
gem: svgGem,
svgPositive,
svgNegative,
}),
};
},
@@ -211,6 +376,10 @@ export default {
this.selectedAmountToSell = 0;
}
},
maxOwned () {
const maxOwned = this.itemContextToSell.itemCount;
return maxOwned;
},
sellItems () {
if (!Number.isInteger(Number(this.selectedAmountToSell))) {
this.selectedAmountToSell = 0;
@@ -33,6 +33,22 @@
v-if="!item.locked"
class="purchase-amount"
>
<div class="item-cost">
<span
class="cost"
:class="priceType"
>
<span
class="svg-icon inline icon-24"
aria-hidden="true"
v-html="icons[priceType]"
>
</span>
<span
:class="priceType"
>{{ item.value }}</span>
</span>
</div>
<div class="how-many-to-buy">
<strong>{{ $t('howManyToBuy') }}</strong>
</div>
@@ -42,24 +58,25 @@
>
{{ item.addlNotes }}
</div>
<div class="box">
<input
v-model.number="selectedAmountToBuy"
class="form-control"
type="number"
min="0"
step="1"
>
<div>
<number-increment
@updateQuantity="selectedAmountToBuy = $event"
/>
</div>
<div class="total-row">
<span class="total-text">
{{ $t('sendTotal') }}
</span>
<span
class="svg-icon inline icon-20"
aria-hidden="true"
v-html="currencyIcon"
></span>
<span
class="total"
:class="priceType"
>{{ item.value * selectedAmountToBuy }}</span>
</div>
<span
class="svg-icon inline icon-32"
aria-hidden="true"
v-html="currencyIcon"
></span>
<span
class="value"
:class="priceType"
>{{ item.value }}</span>
</div>
<button
v-if="priceType === 'gems'
@@ -112,6 +129,40 @@
margin-top: 1rem;
}
.modal-body {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 0px;
}
.modal-footer {
height: 48px;
background-color: $gray-700;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
display: block;
margin-top: 24px;
padding: 16px 24px;
align-content: center;
.user-balance {
width: 150px;
height: 16px;
font-size: 0.75rem;
font-weight: bold;
line-height: 1.33;
color: $gray-100;
margin-bottom: 16px;
margin-top: -4px;
margin-left: -4px;
}
.currency-totals {
margin-right: -8px;
float: right;
}
}
.modal-dialog {
margin-top: 8%;
width: 448px !important;
@@ -129,8 +180,13 @@
margin: 33px auto auto;
}
.modal-body {
padding-bottom: 0px;
.item-notes {
height: 48px;
margin-top: 8px;
padding-left: 48.5px;
padding-right: 48.5px;
line-height: 1.71;
font-size: 0.875rem;
}
.questInfo {
@@ -152,16 +208,14 @@
height: 100%;
}
span.svg-icon.inline.icon-32 {
height: 32px;
width: 32px;
margin-right: 8px;
vertical-align: middle;
}
button.btn.btn-primary {
margin-top: 24px;
margin-bottom: 24px;
margin-top: 14px;
padding: 4px 16px;
height: 32px;
&:focus {
border: 2px solid black;
}
}
.balance {
@@ -173,19 +227,6 @@
color: $gray-200;
}
.modal-footer {
height: 48px;
background-color: $gray-700;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
display: block;
padding: 1rem 1.5rem;
&> * {
margin: 0;
}
}
.notEnough {
pointer-events: none;
opacity: 0.55;
@@ -198,30 +239,108 @@
margin-bottom: 16px;
}
.box {
display: inline-block;
width: 74px;
height: 40px;
border-radius: 2px;
background-color: #ffffff;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
margin-right: 24px;
input {
width: 100%;
border: none;
.item-cost {
padding-bottom: 16px;
}
input::-webkit-contacts-auto-fill-button {
visibility: hidden;
display: none !important;
pointer-events: none;
position: absolute;
right: 0;
}
.cost {
height: 40px;
font-size: 1.25rem;
font-weight: bold;
vertical-align: middle;
padding: 8px 20px 8px 20px;
&.gems {
color: $green-10;
background-color: rgba(36, 204, 143, 0.15);
line-height: 1.4;
margin: 0 0 0 -4px;
border-radius: 20px;
}
&.gold {
color: $yellow-5;
background-color: rgba(255, 190, 93, 0.15);
line-height: 1.4;
margin: 0 0 0 -4px;
border-radius: 20px;
}
&.hourglasses {
color: $hourglass-color;
background-color: rgba(41, 149, 205, 0.15);
line-height: 1.4;
margin: 0 0 0 -4px;
border-radius: 20px;
}
}
.total-row {
font-weight: bold;
font-size: 0.875rem;
margin-top: 16px;
}
.total {
font-weight: bold;
font-size: 0.875rem;
margin-top: 16px;
&.gems {
color: $green-10;
}
&.gold {
color: $yellow-5;
}
&.hourglasses {
color: $hourglass-color;
}
}
.total-text {
color: $gray-50;
font-weight: bold;
font-size: 0.875rem;
height: 24px;
line-height: 1.71;
padding-right: 4px;
&.gems {
color: $green-10;
}
&.gold {
color: $yellow-5;
}
&.hourglasses {
color: $hourglass-color;
}
}
span.svg-icon.inline.icon-20 {
height: 20px;
width: 20px;
margin-right: 4px;
vertical-align: middle;
}
span.svg-icon.inline.icon-24 {
height: 24px;
width: 24px;
margin-right: 8px;
vertical-align: middle;
}
span.svg-icon.inline.icon-32 {
height: 32px;
width: 32px;
margin-right: 8px;
vertical-align: middle;
}
@media only screen and (max-width: 1000px) {
.modal-dialog {
max-width: 80%;
@@ -234,9 +353,10 @@
}
}
}
}
</style>
<style lang="scss" scoped>
<!-- <style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.value {
@@ -260,7 +380,7 @@
color: $hourglass-color;
}
}
</style>
</style> -->
<script>
import moment from 'moment';
@@ -272,6 +392,8 @@ import svgExperience from '@/assets/svg/experience.svg';
import svgGem from '@/assets/svg/gem.svg';
import svgGold from '@/assets/svg/gold.svg';
import svgHourglasses from '@/assets/svg/hourglass.svg';
import svgPositive from '@/assets/svg/positive.svg';
import svgNegative from '@/assets/svg/negative.svg';
import BalanceInfo from '../balanceInfo.vue';
import currencyMixin from '../_currencyMixin';
@@ -280,6 +402,7 @@ import buyMixin from '@/mixins/buy';
import numberInvalid from '@/mixins/numberInvalid';
import PinBadge from '@/components/ui/pinBadge';
import CountdownBanner from '../countdownBanner';
import numberIncrement from '@/components/shared/numberIncrement';
import questDialogContent from './questDialogContent';
import QuestRewards from './questRewards';
@@ -293,6 +416,7 @@ export default {
PinBadge,
questDialogContent,
CountdownBanner,
numberIncrement,
},
mixins: [buyMixin, currencyMixin, notifications, numberInvalid],
props: {
@@ -301,6 +425,7 @@ export default {
},
priceType: {
type: String,
default: '',
},
withPin: {
type: Boolean,
@@ -312,9 +437,11 @@ export default {
clock: svgClock,
close: svgClose,
experience: svgExperience,
gem: svgGem,
gems: svgGem,
gold: svgGold,
hourglass: svgHourglasses,
hourglasses: svgHourglasses,
positive: svgPositive,
negative: svgNegative,
}),
isPinned: false,
@@ -339,8 +466,8 @@ export default {
},
currencyIcon () {
if (this.priceType === 'gold') return this.icons.gold;
if (this.priceType === 'hourglasses') return this.icons.hourglass;
return this.icons.gem;
if (this.priceType === 'hourglasses') return this.icons.hourglasses;
return this.icons.gems;
},
endDate () {
return moment(this.item.event.end);
@@ -33,17 +33,17 @@
h3 {
color: $gray-10;
margin-bottom: 0.25rem;
margin-bottom: 4pxrem;
}
.quest-image {
margin: 0 auto;
margin-bottom: 1em;
margin-top: 1.5em;
margin-bottom: 16px;
margin-top: 24px;
}
.text {
margin-bottom: 1rem;
margin: 16px 16px;
overflow-y: auto;
text-overflow: ellipsis;
}
@@ -54,10 +54,10 @@
line-height: 1.71;
color: $gray-50;
text-align: center;
margin-bottom: 0.5rem;
margin-bottom: 8px;
::v-deep .user-label {
font-size: 14px;
font-size: 0.875rem;
}
}
@@ -177,9 +177,6 @@ export default {
@import '~@/assets/scss/colors.scss';
.quest-rewards {
margin-left: -1rem;
margin-right: -1rem;
background-color: $gray-700;
}
+13 -6
View File
@@ -521,7 +521,12 @@ export default {
// Get Category Filter Labels
this.typeFilters = getFilterLabels(this.type, this.challenge);
// Set default filter for task column
this.activateFilter(this.type);
if (this.challenge) {
this.activateFilter(this.type);
} else {
this.activateFilter(this.type, this.user.preferences.tasks.activeFilter[this.type], true);
}
},
mounted () {
this.setColumnBackgroundVisibility();
@@ -661,7 +666,7 @@ export default {
taskSummary (task) {
this.$emit('taskSummary', task);
},
activateFilter (type, filter = '') {
activateFilter (type, filter = '', skipSave = false) {
// Needs a separate API call as this data may not reside in store
if (type === 'todo' && filter === 'complete2') {
if (this.group && this.group._id) {
@@ -677,14 +682,16 @@ export default {
// as default filter for daily
// and set the filter as 'due' only when the component first
// loads and not on subsequent reloads.
if (
type === 'daily' && filter === '' && !this.challenge
&& this.user.preferences.dailyDueDefaultView
) {
if (type === 'daily' && filter === '' && !this.challenge) {
filter = 'due'; // eslint-disable-line no-param-reassign
}
this.activeFilter = getActiveFilter(type, filter, this.challenge);
if (!skipSave && !this.challenge) {
const propertyToUpdate = `preferences.tasks.activeFilter.${type}`;
this.$store.dispatch('user:set', { [propertyToUpdate]: filter });
}
},
setColumnBackgroundVisibility () {
this.$nextTick(() => {
+10 -10
View File
@@ -241,10 +241,10 @@
>
<div
v-b-tooltip.hover.bottom="$t('dueDate')"
class="svg-icon calendar"
class="svg-icon calendar my-auto"
v-html="icons.calendar"
></div>
<span>{{ formatDueDate() }}</span>
<span>{{ $t('due') }} {{ formatDueDate() }}</span>
</div>
<div class="icons-right d-flex justify-content-end">
<div
@@ -701,7 +701,7 @@
.icons {
margin-top: 4px;
color: $gray-300;
color: $gray-100;
font-style: normal;
&-right {
@@ -760,7 +760,7 @@
}
.due-overdue {
color: $red-50;
color: $maroon-10;
}
.calendar.svg-icon {
@@ -899,7 +899,7 @@
}
</style>
<!-- eslint-enable max-len -->
<!-- eslint-disable-next-line vue/component-tags-order -->
<script>
import moment from 'moment';
import { v4 as uuid } from 'uuid';
@@ -1126,13 +1126,13 @@ export default {
return moment.duration(endOfDueDate.diff(endOfToday));
},
checkIfOverdue () {
return this.calculateTimeTillDue().asDays() <= 0;
return this.calculateTimeTillDue().asDays() < 0;
},
formatDueDate () {
const timeTillDue = this.calculateTimeTillDue();
const dueIn = timeTillDue.asDays() === 0 ? this.$t('today') : timeTillDue.humanize(true);
return this.task.date && this.$t('dueIn', { dueIn });
if (moment().isSame(this.task.date, 'day')) {
return this.$t('today');
}
return moment(this.task.date).format(this.user.preferences.dateFormat.toUpperCase());
},
edit (e, task) {
if (this.isRunningYesterdailies) return;
+3 -1
View File
@@ -1,7 +1,9 @@
export default {
computed: {
numberInvalid () {
return this.selectedAmountToBuy < 1 || !Number.isInteger(this.selectedAmountToBuy);
const inputNumber = Number(this.selectedAmountToBuy);
return inputNumber < 1
|| !Number.isInteger(inputNumber);
},
},
};
+1 -1
View File
@@ -114,7 +114,7 @@ export default {
this.castCancel();
// the selected member doesn't have the flags property which sets `cardReceived`
if (spell.pinType !== 'card') {
if (spell.pinType !== 'card' && spell.bulk !== true) {
try {
spell.cast(this.user, target, {});
} catch (e) {
@@ -21,6 +21,18 @@ describe('Task Column', () => {
getters: {
'tasks:getFilteredTaskList': () => [],
},
state: {
user: {
data: {
preferences: {
tasks: {
activeFilter: {},
},
},
},
},
},
},
mocks,
stubs,
@@ -76,7 +88,20 @@ describe('Task Column', () => {
'tasks:getFilteredTaskList': () => () => habits,
};
const store = new Store({ getters });
const store = new Store({
getters,
state: {
user: {
data: {
preferences: {
tasks: {
activeFilter: {},
},
},
},
},
},
});
wrapper = makeWrapper({ store });
});
@@ -1,88 +0,0 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import moment from 'moment';
import Task from '@/components/tasks/task.vue';
import Store from '@/libs/store';
const localVue = createLocalVue();
localVue.use(Store);
describe('Task', () => {
let wrapper;
function makeWrapper (additionalTaskData = {}, additionalUserData = {}) {
return shallowMount(Task, {
propsData: {
task: {
group: {},
...additionalTaskData,
},
},
store: {
state: {
user: {
data: {
preferences: {},
...additionalUserData,
},
},
},
getters: {
'tasks:getTaskClasses': () => ({}),
'tasks:canEdit': () => ({}),
'tasks:canDelete': () => ({}),
},
},
mocks: { $t: (key, params) => key + (params ? JSON.stringify(params) : '') },
directives: { 'b-tooltip': {} },
localVue,
});
}
it('returns a vue instance', () => {
wrapper = makeWrapper();
expect(wrapper.isVueInstance()).to.be.true;
});
describe('Due date calculation', () => {
let clock;
function setClockTo (time) {
const now = moment(time);
clock = sinon.useFakeTimers(now.toDate());
return now;
}
afterEach(() => {
clock.restore();
});
it('formats due date to today if due today', () => {
const now = setClockTo('2019-09-17T17:57:00+02:00');
wrapper = makeWrapper({ date: now });
expect(wrapper.vm.formatDueDate()).to.equal('dueIn{"dueIn":"today"}');
});
it('formats due date to tomorrow if due tomorrow', () => {
const now = setClockTo('2012-06-12T14:17:28Z');
wrapper = makeWrapper({ date: now.add(1, 'day') });
expect(wrapper.vm.formatDueDate()).to.equal('dueIn{"dueIn":"in a day"}');
});
it('formats due date to 5 days if due in 5 days', () => {
const now = setClockTo();
wrapper = makeWrapper({ date: now.add(5, 'days') });
expect(wrapper.vm.formatDueDate()).to.equal('dueIn{"dueIn":"in 5 days"}');
});
it('formats due date to tomorrow if today but before dayStart', () => {
const now = setClockTo('2019-06-12T04:23:37+02:00');
wrapper = makeWrapper({ date: now.add(8, 'hours') }, { preferences: { dayStart: 7 } });
expect(wrapper.vm.formatDueDate()).to.equal('dueIn{"dueIn":"in a day"}');
});
});
});
+1 -1
View File
@@ -132,5 +132,5 @@
"subscriptionBenefit6": "Earn Mystic Hourglasses for use in the Time Travelers' Shop!",
"purchaseAll": "Purchase Set",
"gemsRemaining": "gems remaining",
"notEnoughGemsToBuy": "You are unable to buy that amount of gems"
"notEnoughGemsToBuy": "You are unable to buy that amount of gems",
}
+4 -1
View File
@@ -147,5 +147,8 @@
"achievementPolarProModalText": "You collected all the Polar Pets!",
"achievementPlantParent": "Plant Parent",
"achievementPlantParentText": "Has hatched all standard colors of Plant pets: Cactus and Treeling!",
"achievementPlantParentModalText": "You collected all the Plant Pets!"
"achievementPlantParentModalText": "You collected all the Plant Pets!",
"achievementDinosaurDynasty": "Dinosaur Dynasty",
"achievementDinosaurDynastyText": "Has hatched all standard colors of bird and dinosaur pets: Falcon, Owl, Parrot, Peacock, Penguin, Rooster, Pterodactyl, T-Rex, Triceratops, and Velociraptor!",
"achievementDinosaurDynastyModalText": "You collected all the bird and dinosaur pets!"
}
+1 -1
View File
@@ -208,7 +208,7 @@
"userSentMessage": "<span class=\"notification-bold\"><%- user %></span> sent you a message",
"letsgo": "Let's Go!",
"selected": "Selected",
"howManyToBuy": "How many would you like to buy?",
"howManyToBuy": "How many would you like to purchase?",
"contactForm": "Contact the Moderation Team",
"loadEarlierMessages": "Load Earlier Messages",
"askQuestion": "Ask a Question",
+1 -1
View File
@@ -130,7 +130,7 @@
"sendGiftHeading": "Send Gift to <%= name %>",
"sendGiftGemsBalance": "From <%= number %> Gems",
"sendGiftCost": "Total: $<%= cost %> USD",
"sendGiftTotal": "Total:",
"sendTotal": "Total:",
"sendGiftFromBalance": "From Balance",
"sendGiftPurchase": "Purchase",
"sendGiftLabel": "Would you like to send a gift message?",
+1
View File
@@ -31,6 +31,7 @@
"howManyToSell": "How many would you like to sell?",
"yourBalance": "Your balance:",
"sell": "Sell",
"sellItems": "Sell Items",
"buyNow": "Buy Now",
"sortByNumber": "Number",
"featuredItems": "Featured Items!",
-2
View File
@@ -5,8 +5,6 @@
"helpWithTranslation": "Would you like to help with the translation of Habitica? Great! Then visit <a href=\"/groups/guild/7732f64c-33ee-4cce-873c-fc28f147a6f7\">the Aspiring Linguists Guild</a>!",
"stickyHeader": "Sticky header",
"newTaskEdit": "Open new tasks in edit mode",
"dailyDueDefaultView": "Set Dailies default to 'due' tab",
"dailyDueDefaultViewPop": "With this option set, the Dailies tasks will default to 'due' instead of 'all'",
"reverseChatOrder": "Show chat messages in reverse order",
"startAdvCollapsed": "Advanced Settings in tasks start collapsed",
"startAdvCollapsedPop": "With this option set, Advanced Settings will be hidden when you first open a task for editing.",
+4 -3
View File
@@ -194,8 +194,8 @@
"subscriptionBenefit5": "Receive the Royal Purple Jackalope pet when you become a new subscriber.",
"subscriptionBenefit6": "Earn Mystic Hourglasses to purchase items in the Time Travelers Shop!",
"purchaseAll": "Purchase Set",
"gemsRemaining": "Gems remaining",
"notEnoughGemsToBuy": "You are unable to buy that amount of Gems",
"gemsRemaining": "remaining",
"notEnoughGemsToBuy": "No more Gems available for purchase this month. More will become available within the first 3 days of each month.",
"subscribersReceiveBenefits": "Subscribers receive these useful benefits!",
"monthlyMysteryItems": "Monthly Mystery Items",
"doubleDropCap": "Double the Drops",
@@ -217,5 +217,6 @@
"haveNonRecurringSub": "You have a non-recurring gift subscription.",
"switchToRecurring": "Switch to a recurring subscription?",
"continueGiftSubBenefits": "Want to continue your benefits? You can start a new subscription before your gifted one runs out to keep your benefits active.",
"subscriptionCreditConversion": "Starting a new subscription will convert any remaining months to credit that will be used after the recurring subscription is canceled."
"subscriptionCreditConversion": "Starting a new subscription will convert any remaining months to credit that will be used after the recurring subscription is canceled.",
"monthlyGems": "Monthly Gems:"
}
@@ -178,6 +178,11 @@ const animalSetAchievs = {
titleKey: 'achievementBirdsOfAFeather',
textKey: 'achievementBirdsOfAFeatherText',
},
dinosaurDynasty: {
icon: 'achievement-dinosaurDynasty',
titleKey: 'achievementDinosaurDynasty',
textKey: 'achievementDinosaurDynastyText',
},
domesticated: {
icon: 'achievement-domesticated',
titleKey: 'achievementDomesticated',
+2 -2
View File
@@ -178,9 +178,9 @@ const bundles = {
'penguin',
'rooster',
],
event: EVENTS.bundle202109,
event: EVENTS.bundle202305,
canBuy () {
return moment().isBefore(EVENTS.bundle202109.end);
return moment().isBefore(EVENTS.bundle202305.end);
},
type: 'quests',
value: 7,
@@ -26,6 +26,23 @@ const ANIMAL_SET_ACHIEVEMENTS = {
achievementKey: 'birdsOfAFeather',
notificationType: 'ACHIEVEMENT_ANIMAL_SET',
},
dinosaurDynasty: {
type: 'pet',
species: [
'Falcon',
'Owl',
'Parrot',
'Peacock',
'Penguin',
'Rooster',
'Pterodactyl',
'TRex',
'Triceratops',
'Velociraptor',
],
achievementKey: 'dinosaurDynasty',
notificationType: 'ACHIEVEMENT_ANIMAL_SET',
},
domesticated: {
type: 'pet',
species: [
@@ -10,11 +10,19 @@ const gemsPromo = {
export const EVENTS = {
noEvent: {
start: '2023-05-01T23:59-04:00',
start: '2023-05-31T23:59-04:00',
end: '2023-06-22T08:00-04:00',
season: 'normal',
npcImageSuffix: '',
},
bundle202305: {
start:'2023-05-23T08:00-04:00',
end:'2023-05-31T23:59-04:00',
},
potions202305: {
start:'2023-05-16T08:00-04:00',
end:'2023-05-31T23:59-04:00',
},
aprilFools2023: {
start: '2023-04-01T08:00-04:00',
end: '2023-04-02T08:00-04:00',
@@ -88,26 +88,26 @@ const premium = {
value: 2,
text: t('hatchingPotionFairy'),
limited: true,
event: EVENTS.potions202105,
event: EVENTS.potions202305,
_addlNotes: t('eventAvailabilityReturning', {
availableDate: t('dateEndMay'),
previousDate: t('mayYYYY', { year: 2020 }),
previousDate: t('mayYYYY', { year: 2021 }),
}),
canBuy () {
return moment().isBefore(EVENTS.potions202105.end);
return moment().isBefore(EVENTS.potions202305.end);
},
},
Floral: {
value: 2,
text: t('hatchingPotionFloral'),
limited: true,
event: EVENTS.potions202205,
event: EVENTS.potions202305,
_addlNotes: t('eventAvailabilityReturning', {
availableDate: t('dateEndMay'),
previousDate: t('mayYYYY', { year: 2021 }),
previousDate: t('mayYYYY', { year: 2022 }),
}),
canBuy () {
return moment().isBefore(EVENTS.potions202205.end);
return moment().isBefore(EVENTS.potions202305.end);
},
},
Aquatic: {
@@ -5,7 +5,7 @@ import { EVENTS } from './constants';
// path: 'premiumHatchingPotions.Rainbow',
const featuredItems = {
market () {
if (moment().isBetween(EVENTS.spring2023.start, EVENTS.spring2023.end)) {
if (moment().isBetween(EVENTS.potions202305.start, EVENTS.potions202305.end)) {
return [
{
type: 'armoire',
@@ -13,15 +13,15 @@ const featuredItems = {
},
{
type: 'premiumHatchingPotion',
path: 'premiumHatchingPotions.PolkaDot',
path: 'premiumHatchingPotions.Fairy',
},
{
type: 'premiumHatchingPotion',
path: 'premiumHatchingPotions.BirchBark',
path: 'premiumHatchingPotions.Floral',
},
{
type: 'premiumHatchingPotion',
path: 'premiumHatchingPotions.Rainbow',
type: 'hatchingPotions',
path: 'hatchingPotions.Golden',
},
];
}
@@ -45,19 +45,19 @@ const featuredItems = {
];
},
quests () { // start date is 3/28
if (moment().isBetween(EVENTS.bundle202303.start, EVENTS.bundle202303.end)) {
if (moment().isBetween(EVENTS.bundle202305.start, EVENTS.bundle202305.end)) {
return [
{
type: 'bundles',
path: 'bundles.jungleBuddies',
path: 'bundles.birdBuddies',
},
{
type: 'quests',
path: 'quests.kraken',
path: 'quests.harpy',
},
{
type: 'quests',
path: 'quests.nudibranch',
path: 'quests.owl',
},
];
}
+25 -37
View File
@@ -77,13 +77,11 @@ spells.wizard = {
lvl: 12,
target: 'party',
notes: t('spellWizardMPHealNotes'),
cast (user, target) {
each(target, member => {
const bonus = statsComputed(user).int;
if (user._id !== member._id && member.stats.class !== 'wizard') {
member.stats.mp += Math.ceil(diminishingReturns(bonus, 25, 125));
}
});
bulk: true,
cast (user, data) {
const bonus = statsComputed(user).int;
data.query['stats.class'] = { $ne: 'wizard' };
data.update = { $inc: { 'stats.mp': Math.ceil(diminishingReturns(bonus, 25, 125)) } };
},
},
earth: { // Earthquake
@@ -92,12 +90,10 @@ spells.wizard = {
lvl: 13,
target: 'party',
notes: t('spellWizardEarthNotes'),
cast (user, target) {
each(target, member => {
const bonus = statsComputed(user).int - user.stats.buffs.int;
if (!member.stats.buffs.int) member.stats.buffs.int = 0;
member.stats.buffs.int += Math.ceil(diminishingReturns(bonus, 30, 200));
});
bulk: true,
cast (user, data) {
const bonus = statsComputed(user).int - user.stats.buffs.int;
data.update = { $inc: { 'stats.buffs.int': Math.ceil(diminishingReturns(bonus, 30, 200)) } };
},
},
frost: { // Chilling Frost
@@ -147,12 +143,10 @@ spells.warrior = {
lvl: 13,
target: 'party',
notes: t('spellWarriorValorousPresenceNotes'),
cast (user, target) {
each(target, member => {
const bonus = statsComputed(user).str - user.stats.buffs.str;
if (!member.stats.buffs.str) member.stats.buffs.str = 0;
member.stats.buffs.str += Math.ceil(diminishingReturns(bonus, 20, 200));
});
bulk: true,
cast (user, data) {
const bonus = statsComputed(user).str - user.stats.buffs.str;
data.update = { $inc: { 'stats.buffs.str': Math.ceil(diminishingReturns(bonus, 20, 200)) } };
},
},
intimidate: { // Intimidating Gaze
@@ -161,12 +155,10 @@ spells.warrior = {
lvl: 14,
target: 'party',
notes: t('spellWarriorIntimidateNotes'),
cast (user, target) {
each(target, member => {
const bonus = statsComputed(user).con - user.stats.buffs.con;
if (!member.stats.buffs.con) member.stats.buffs.con = 0;
member.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 24, 200));
});
bulk: true,
cast (user, data) {
const bonus = statsComputed(user).con - user.stats.buffs.con;
data.update = { $inc: { 'stats.buffs.con': Math.ceil(diminishingReturns(bonus, 24, 200)) } };
},
},
};
@@ -203,12 +195,10 @@ spells.rogue = {
lvl: 13,
target: 'party',
notes: t('spellRogueToolsOfTradeNotes'),
cast (user, target) {
each(target, member => {
const bonus = statsComputed(user).per - user.stats.buffs.per;
if (!member.stats.buffs.per) member.stats.buffs.per = 0;
member.stats.buffs.per += Math.ceil(diminishingReturns(bonus, 100, 50));
});
bulk: true,
cast (user, data) {
const bonus = statsComputed(user).per - user.stats.buffs.per;
data.update = { $inc: { 'stats.buffs.per': Math.ceil(diminishingReturns(bonus, 100, 50)) } };
},
},
stealth: { // Stealth
@@ -257,12 +247,10 @@ spells.healer = {
lvl: 13,
target: 'party',
notes: t('spellHealerProtectAuraNotes'),
cast (user, target) {
each(target, member => {
const bonus = statsComputed(user).con - user.stats.buffs.con;
if (!member.stats.buffs.con) member.stats.buffs.con = 0;
member.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 200, 200));
});
bulk: true,
cast (user, data) {
const bonus = statsComputed(user).con - user.stats.buffs.con;
data.update = { $inc: { 'stats.buffs.con': Math.ceil(diminishingReturns(bonus, 200, 200)) } };
},
},
healAll: { // Blessing
@@ -222,6 +222,7 @@ function _getBasicAchievements (user, language) {
_addSimple(result, user, { path: 'boneToPick', language });
_addSimple(result, user, { path: 'polarPro', language });
_addSimple(result, user, { path: 'plantParent', language });
_addSimple(result, user, { path: 'dinosaurDynasty', language });
_addSimpleWithMasterCount(result, user, { path: 'beastMaster', language });
_addSimpleWithMasterCount(result, user, { path: 'mountMaster', language });
@@ -282,6 +282,8 @@ api.postChat = {
analyticsObject.groupName = group.name;
}
res.analytics.track('group chat', analyticsObject);
if (chatUpdated) {
res.respond(200, { chat: chatRes.chat });
} else {
+6 -8
View File
@@ -93,7 +93,7 @@ api.inviteToQuest = {
user.party.quest.RSVPNeeded = false;
user.party.quest.key = questKey;
await User.update({
await User.updateMany({
'party._id': group._id,
_id: { $ne: user._id },
}, {
@@ -101,7 +101,7 @@ api.inviteToQuest = {
'party.quest.RSVPNeeded': true,
'party.quest.key': questKey,
},
}, { multi: true }).exec();
}).exec();
_.each(members, member => {
group.quest.members[member._id] = null;
@@ -409,10 +409,9 @@ api.cancelQuest = {
const [savedGroup] = await Promise.all([
group.save(),
newChatMessage.save(),
User.update(
User.updateMany(
{ 'party._id': groupId },
Group.cleanQuestParty(),
{ multi: true },
).exec(),
]);
@@ -467,12 +466,11 @@ api.abortQuest = {
});
await newChatMessage.save();
const memberUpdates = User.update({
const memberUpdates = User.updateMany({
'party._id': groupId,
}, Group.cleanQuestParty(),
{ multi: true }).exec();
}, Group.cleanQuestParty()).exec();
const questLeaderUpdate = User.update({
const questLeaderUpdate = User.updateOne({
_id: group.quest.leader,
}, {
$inc: {
+3 -3
View File
@@ -227,7 +227,7 @@ api.deleteTag = {
const tagFound = find(user.tags, tag => tag.id === req.params.tagId);
if (!tagFound) throw new NotFound(res.t('tagNotFound'));
await user.update({
await user.updateOne({
$pull: { tags: { id: tagFound.id } },
}).exec();
@@ -237,13 +237,13 @@ api.deleteTag = {
user._v += 1;
// Remove from all the tasks TODO test
await Tasks.Task.update({
await Tasks.Task.updateMany({
userId: user._id,
}, {
$pull: {
tags: tagFound.id,
},
}, { multi: true }).exec();
}).exec();
res.respond(200, {});
},
+3 -3
View File
@@ -840,7 +840,7 @@ api.moveTask = {
// Cannot send $pull and $push on same field in one single op
const pullQuery = { $pull: {} };
pullQuery.$pull[`tasksOrder.${task.type}s`] = task.id;
await owner.update(pullQuery).exec();
await owner.updateOne(pullQuery).exec();
let position = to;
if (to === -1) position = order.length - 1; // push to bottom
@@ -850,7 +850,7 @@ api.moveTask = {
$each: [task._id],
$position: position,
};
await owner.update(updateQuery).exec();
await owner.updateOne(updateQuery).exec();
// Update the user version field manually,
// it cannot be updated in the pre update hook
@@ -1434,7 +1434,7 @@ api.deleteTask = {
const pullQuery = { $pull: {} };
pullQuery.$pull[`tasksOrder.${task.type}s`] = task._id;
const taskOrderUpdate = (challenge || user).update(pullQuery).exec();
const taskOrderUpdate = (challenge || user).updateOne(pullQuery).exec();
// Update the user version field manually,
// it cannot be updated in the pre update hook
+9 -1
View File
@@ -37,7 +37,15 @@ export default function baseModel (schema, options = {}) {
});
schema.pre('update', function preUpdateModel () {
this.update({}, { $set: { updatedAt: new Date() } });
this.set({}, { $set: { updatedAt: new Date() } });
});
schema.pre('updateOne', function preUpdateModel () {
this.set({}, { $set: { updatedAt: new Date() } });
});
schema.pre('updateMany', function preUpdateModel () {
this.set({}, { $set: { updatedAt: new Date() } });
});
}
@@ -180,6 +180,7 @@ async function addSubToGroupUser (member, group) {
}
// save unused hourglass and mystery items
plan.perkMonthCount = memberPlan.perkMonthCount;
plan.consecutive.trinkets = memberPlan.consecutive.trinkets;
plan.mysteryItems = memberPlan.mysteryItems;
@@ -253,13 +253,18 @@ async function createSubscription (data) {
} = await prepareSubscriptionValues(data);
// Block sub perks
if (months > 0 && (!data.gift || !isNewSubscription)) {
if (months > 1 && (!data.gift || !isNewSubscription)) {
if (!data.gift && !groupId) {
plan.consecutive.offset = block.months;
}
} else if (months === 1) {
plan.consecutive.offset = 0;
}
if (months > 1 || data.gift) {
await plan.incrementPerkCounterAndReward(recipient._id, months);
} else {
// Make sure the perkMonthCount field is initialized.
await plan.incrementPerkCounterAndReward(recipient._id, 0);
}
if (recipient !== group) {
+1 -1
View File
@@ -21,7 +21,7 @@ const apnProvider = APN_ENABLED ? new apn.Provider({
}) : undefined;
function removePushDevice (user, pushDevice) {
return User.update({ _id: user._id }, {
return User.updateOne({ _id: user._id }, {
$pull: { pushDevices: { regId: pushDevice.regId } },
}).exec().catch(err => {
logger.error(err, `Error removing pushDevice ${pushDevice.regId} for user ${user._id}`);
+2 -1
View File
@@ -24,7 +24,7 @@ export function readController (router, controller, overrides = []) {
// If an authentication middleware is used run getUserLanguage after it, otherwise before
// for cron instead use it only if an authentication middleware is present
const authMiddlewareIndex = _.findIndex(middlewares, middleware => {
let authMiddlewareIndex = _.findIndex(middlewares, middleware => {
if (middleware.name.indexOf('authWith') === 0) { // authWith{Headers|Session|Url|...}
return true;
}
@@ -36,6 +36,7 @@ export function readController (router, controller, overrides = []) {
// disable caching for all routes with mandatory or optional authentication
if (authMiddlewareIndex !== -1) {
middlewares.unshift(disableCache);
authMiddlewareIndex += 1;
}
if (action.noLanguage !== true) { // unless getting the language is explictly disabled
+34 -15
View File
@@ -11,7 +11,7 @@ import {
} from '../models/group';
import apiError from './apiError';
const partyMembersFields = 'profile.name stats achievements items.special notifications flags pinnedItems';
const partyMembersFields = 'profile.name stats achievements items.special pinnedItems notifications flags';
// Excluding notifications and flags from the list of public fields to return.
const partyMembersPublicFields = 'profile.name stats achievements items.special';
@@ -74,12 +74,13 @@ async function castSelfSpell (req, user, spell, quantity = 1) {
await user.save();
}
async function castPartySpell (req, party, partyMembers, user, spell, quantity = 1) {
async function getPartyMembers (user, party) {
let partyMembers;
if (!party) {
// Act as solo party
partyMembers = [user]; // eslint-disable-line no-param-reassign
partyMembers = [user];
} else {
partyMembers = await User // eslint-disable-line no-param-reassign
partyMembers = await User
.find({
'party._id': party._id,
_id: { $ne: user._id }, // add separately
@@ -89,22 +90,40 @@ async function castPartySpell (req, party, partyMembers, user, spell, quantity =
partyMembers.unshift(user);
}
for (let i = 0; i < quantity; i += 1) {
spell.cast(user, partyMembers, req);
}
await Promise.all(partyMembers.map(m => m.save()));
return partyMembers;
}
async function castUserSpell (res, req, party, partyMembers, targetId, user, spell, quantity = 1) {
async function castPartySpell (req, party, user, spell, quantity = 1) {
let partyMembers;
if (spell.bulk) {
const data = { };
if (party) {
data.query = { 'party._id': party._id };
} else {
data.query = { _id: user._id };
}
spell.cast(user, data);
await User.updateMany(data.query, data.update);
await user.save();
partyMembers = await getPartyMembers(user, party);
} else {
partyMembers = await getPartyMembers(user, party);
for (let i = 0; i < quantity; i += 1) {
spell.cast(user, partyMembers, req);
}
await Promise.all(partyMembers.map(m => m.save()));
}
return partyMembers;
}
async function castUserSpell (res, req, party, targetId, user, spell, quantity = 1) {
let partyMembers;
if (!party && (!targetId || user._id === targetId)) {
partyMembers = user; // eslint-disable-line no-param-reassign
partyMembers = user;
} else {
if (!targetId) throw new BadRequest(res.t('targetIdUUID'));
if (!party) throw new NotFound(res.t('partyNotFound'));
partyMembers = await User // eslint-disable-line no-param-reassign
partyMembers = await User
.findOne({ _id: targetId, 'party._id': party._id })
.select(partyMembersFields)
.exec();
@@ -195,10 +214,10 @@ async function castSpell (req, res, { isV3 = false }) {
let partyMembers;
if (targetType === 'party') {
partyMembers = await castPartySpell(req, party, partyMembers, user, spell, quantity);
partyMembers = await castPartySpell(req, party, user, spell, quantity);
} else {
partyMembers = await castUserSpell(
res, req, party, partyMembers,
res, req, party,
targetId, user, spell, quantity,
);
}
+1 -1
View File
@@ -114,7 +114,7 @@ async function createTasks (req, res, options = {}) {
};
}
await owner.update(taskOrderUpdateQuery).exec();
await owner.updateOne(taskOrderUpdateQuery).exec();
// tasks with aliases need to be validated asynchronously
await validateTaskAlias(toSave, res);
+6 -3
View File
@@ -121,7 +121,7 @@ async function checkNewInputForProfanity (user, res, newValue) {
export async function update (req, res, { isV3 = false }) {
const { user } = res.locals;
const promisesForTagsRemoval = [];
let promisesForTagsRemoval = [];
if (req.body['party.seeking'] !== undefined && req.body['party.seeking'] !== null) {
user.invitations.party = {};
@@ -130,6 +130,7 @@ export async function update (req, res, { isV3 = false }) {
uuid: user._id,
hitType: 'event',
category: 'behavior',
headers: req.headers,
});
}
@@ -187,6 +188,7 @@ export async function update (req, res, { isV3 = false }) {
uuid: user._id,
hitType: 'event',
category: 'behavior',
headers: req.headers,
});
} else if (key === 'tags') {
if (!Array.isArray(val)) throw new BadRequest('Tag list must be an array.');
@@ -218,13 +220,13 @@ export async function update (req, res, { isV3 = false }) {
// Remove from all the tasks
// NOTE each tag to remove requires a query
promisesForTagsRemoval.push(removedTagsIds.map(tagId => Tasks.Task.update({
promisesForTagsRemoval = removedTagsIds.map(tagId => Tasks.Task.updateMany({
userId: user._id,
}, {
$pull: {
tags: tagId,
},
}, { multi: true }).exec()));
}).exec());
} else if (key === 'flags.newStuff' && val === false) {
// flags.newStuff was removed from the user schema and is only returned for compatibility
// reasons but we're keeping the ability to set it in API v3
@@ -274,6 +276,7 @@ export async function reset (req, res, { isV3 = false }) {
uuid: user._id,
hitType: 'event',
category: 'behavior',
headers: req.headers,
});
res.respond(200, ...resetRes);
+1 -1
View File
@@ -61,7 +61,7 @@ function sendWebhook (webhook, body, user) {
};
}
return User.update({
return User.updateOne({
_id: user._id,
'webhooks.id': webhook.id,
}, update).exec();
+5 -5
View File
@@ -16,7 +16,7 @@ async function checkForActiveCron (user, now) {
// To avoid double cron we first set _cronSignature
// and then check that it's not changed while processing
const userUpdateResult = await User.update({
const userUpdateResult = await User.updateOne({
_id: user._id,
$or: [ // Make sure last cron was successful or failed before cronRetryTime
{ _cronSignature: 'NOT_RUNNING' },
@@ -36,7 +36,7 @@ async function checkForActiveCron (user, now) {
}
async function updateLastCron (user, now) {
await User.update({
await User.updateOne({
_id: user._id,
}, {
lastCron: now, // setting lastCron now so we don't risk re-running parts of cron if it fails
@@ -44,7 +44,7 @@ async function updateLastCron (user, now) {
}
async function unlockUser (user) {
await User.update({
await User.updateOne({
_id: user._id,
}, {
_cronSignature: 'NOT_RUNNING',
@@ -125,7 +125,7 @@ async function cronAsync (req, res) {
await Group.processQuestProgress(user, progress);
// Set _cronSignature, lastCron and auth.timestamps.loggedin to signal end of cron
await User.update({
await User.updateOne({
_id: user._id,
}, {
$set: {
@@ -153,7 +153,7 @@ async function cronAsync (req, res) {
// For any other error make sure to reset _cronSignature
// so that it doesn't prevent cron from running
// at the next request
await User.update({
await User.updateOne({
_id: user._id,
}, {
_cronSignature: 'NOT_RUNNING',
+16 -15
View File
@@ -102,7 +102,7 @@ schema.methods.addToUser = async function addChallengeToUser (user) {
// Add challenge to users challenges atomically (with a condition that checks that it
// is not there already) to prevent multiple concurrent requests from passing through
// see https://github.com/HabitRPG/habitica/issues/11295
const result = await User.update(
const result = await User.updateOne(
{
_id: user._id,
challenges: { $nin: [this._id] },
@@ -249,7 +249,7 @@ async function _addTaskFn (challenge, tasks, memberId) {
},
};
const updateUserParams = { ...updateTasksOrderQ, ...addToChallengeTagSet };
toSave.unshift(User.update({ _id: memberId }, updateUserParams).exec());
toSave.unshift(User.updateOne({ _id: memberId }, updateUserParams).exec());
return Promise.all(toSave);
}
@@ -278,11 +278,11 @@ schema.methods.updateTask = async function challengeUpdateTask (task) {
const taskSchema = Tasks[task.type];
// Updating instead of loading and saving for performances,
// risks becoming a problem if we introduce more complexity in tasks
await taskSchema.update({
await taskSchema.updateMany({
userId: { $exists: true },
'challenge.id': challenge.id,
'challenge.taskId': task._id,
}, updateCmd, { multi: true }).exec();
}, updateCmd).exec();
};
// Remove a task from challenge members
@@ -290,13 +290,13 @@ schema.methods.removeTask = async function challengeRemoveTask (task) {
const challenge = this;
// Set the task as broken
await Tasks.Task.update({
await Tasks.Task.updateMany({
userId: { $exists: true },
'challenge.id': challenge.id,
'challenge.taskId': task._id,
}, {
$set: { 'challenge.broken': 'TASK_DELETED' },
}, { multi: true }).exec();
}).exec();
};
// Unlink challenges tasks (and the challenge itself) from user. TODO rename to 'leave'
@@ -311,9 +311,9 @@ schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep, sa
this.memberCount -= 1;
if (keep === 'keep-all') {
await Tasks.Task.update(findQuery, {
await Tasks.Task.updateMany(findQuery, {
$set: { challenge: {} },
}, { multi: true }).exec();
}).exec();
const promises = [this.save()];
@@ -356,11 +356,12 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
// Refund the leader if the challenge is deleted (no winner chosen)
if (brokenReason === 'CHALLENGE_DELETED') {
await User.update({ _id: challenge.leader }, { $inc: { balance: challenge.prize / 4 } }).exec();
await User.updateOne({ _id: challenge.leader }, { $inc: { balance: challenge.prize / 4 } })
.exec();
}
// Update the challengeCount on the group
await Group.update({ _id: challenge.group }, { $inc: { challengeCount: -1 } }).exec();
await Group.updateOne({ _id: challenge.group }, { $inc: { challengeCount: -1 } }).exec();
// Award prize to winner and notify
if (winner) {
@@ -370,7 +371,7 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
// reimburse the leader
const winnerCanGetGems = await winner.canGetGems();
if (!winnerCanGetGems) {
await User.update(
await User.updateOne(
{ _id: challenge.leader },
{ $inc: { balance: challenge.prize / 4 } },
).exec();
@@ -408,22 +409,22 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
Tasks.Task.remove({ 'challenge.id': challenge._id, userId: { $exists: false } }).exec(),
// Set the challenge tag to non-challenge status
// and remove the challenge from the user's challenges
User.update({
User.updateMany({
challenges: challenge._id,
'tags.id': challenge._id,
}, {
$set: { 'tags.$.challenge': false },
$pull: { challenges: challenge._id },
}, { multi: true }).exec(),
}).exec(),
// Break users' tasks
Tasks.Task.update({
Tasks.Task.updateMany({
'challenge.id': challenge._id,
}, {
$set: {
'challenge.broken': brokenReason,
'challenge.winner': winner && winner.profile.name,
},
}, { multi: true }).exec(),
}).exec(),
];
Promise.all(backgroundTasks);
+17 -15
View File
@@ -268,10 +268,13 @@ schema.statics.getGroup = async function getGroup (options = {}) {
if (groupId === user.party._id) {
// reset party object to default state
user.party = {};
await user.save();
} else {
removeFromArray(user.guilds, groupId);
const item = removeFromArray(user.guilds, groupId);
if (item) {
await user.save();
}
}
await user.save();
}
return group;
@@ -659,7 +662,7 @@ schema.methods.handleQuestInvitation = async function handleQuestInvitation (use
// to prevent multiple concurrent requests overriding updates
// see https://github.com/HabitRPG/habitica/issues/11398
const Group = this.constructor;
const result = await Group.update(
const result = await Group.updateOne(
{
_id: this._id,
[`quest.members.${user._id}`]: { $type: 10 }, // match BSON Type Null (type number 10)
@@ -707,7 +710,7 @@ schema.methods.startQuest = async function startQuest (user) {
// Persist quest.members early to avoid simultaneous handling of accept/reject
// while processing the rest of this script
await this.update({ $set: { 'quest.members': this.quest.members } }).exec();
await this.updateOne({ $set: { 'quest.members': this.quest.members } }).exec();
const nonUserQuestMembers = _.keys(this.quest.members);
removeFromArray(nonUserQuestMembers, user._id);
@@ -747,7 +750,7 @@ schema.methods.startQuest = async function startQuest (user) {
user.markModified('items.quests');
promises.push(user.save());
} else { // another user is starting the quest, update the leader separately
promises.push(User.update({ _id: this.quest.leader }, {
promises.push(User.updateOne({ _id: this.quest.leader }, {
$inc: {
[`items.quests.${this.quest.key}`]: -1,
},
@@ -755,7 +758,7 @@ schema.methods.startQuest = async function startQuest (user) {
}
// update the remaining users
promises.push(User.update({
promises.push(User.updateMany({
_id: { $in: nonUserQuestMembers },
}, {
$set: {
@@ -763,16 +766,15 @@ schema.methods.startQuest = async function startQuest (user) {
'party.quest.progress.down': 0,
'party.quest.completed': null,
},
}, { multi: true }).exec());
}).exec());
await Promise.all(promises);
// update the users who are not participating
// Do not block updates
User.update({
User.updateMany({
_id: { $in: nonMembers },
}, _cleanQuestParty(),
{ multi: true }).exec();
}, _cleanQuestParty()).exec();
const newMessage = this.sendChat({
message: `\`${shared.i18n.t('chatQuestStarted', { questName: quest.text('en') }, 'en')}\``,
@@ -903,7 +905,7 @@ function _getUserUpdateForQuestReward (itemToAward, allAwardedItems) {
async function _updateUserWithRetries (userId, updates, numTry = 1, query = {}) {
query._id = userId;
try {
return await User.update(query, updates).exec();
return await User.updateOne(query, updates).exec();
} catch (err) {
if (numTry < MAX_UPDATE_RETRIES) {
numTry += 1; // eslint-disable-line no-param-reassign
@@ -949,7 +951,7 @@ schema.methods.finishQuest = async function finishQuest (quest) {
this.markModified('quest');
if (this._id === TAVERN_ID) {
return User.update({}, updates, { multi: true }).exec();
return User.updateMany({}, updates).exec();
}
const promises = participants.map(userId => {
@@ -1389,10 +1391,10 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all', keepC
const userUpdate = { $pull: { 'preferences.tasks.mirrorGroupTasks': group._id } };
if (group.type === 'guild') {
userUpdate.$pull.guilds = group._id;
promises.push(User.update({ _id: user._id }, userUpdate).exec());
promises.push(User.updateOne({ _id: user._id }, userUpdate).exec());
} else {
userUpdate.$set = { party: {} };
promises.push(User.update({ _id: user._id }, userUpdate).exec());
promises.push(User.updateOne({ _id: user._id }, userUpdate).exec());
update.$unset = { [`quest.members.${user._id}`]: 1 };
}
@@ -1508,7 +1510,7 @@ schema.methods.unlinkTask = async function groupUnlinkTask (
const promises = [unlinkingTask.save()];
if (keep === 'keep-all') {
await Tasks.Task.update(findQuery, {
await Tasks.Task.updateOne(findQuery, {
$set: { group: {} },
}).exec();
+8 -1
View File
@@ -56,13 +56,20 @@ schema.methods.incrementPerkCounterAndReward = async function incrementPerkCount
if (typeof adding === 'string' || adding instanceof String) {
addingNumber = parseInt(adding, 10);
}
const isSingleMonthPlan = this.planId === 'basic_earned' || this.planId === 'group_plan_auto' || this.planId === 'group_monthly';
// if perkMonthCount wasn't used before, initialize it.
if (this.perkMonthCount === undefined || this.perkMonthCount === -1) {
if (this.planId === 'basic_earned') {
if (isSingleMonthPlan && this.consecutive.count > 0) {
this.perkMonthCount = (this.consecutive.count - 1) % SUBSCRIPTION_BASIC_BLOCK_LENGTH;
} else {
this.perkMonthCount = 0;
}
} else if (isSingleMonthPlan) {
const expectedPerkMonthCount = (this.consecutive.count - 1) % SUBSCRIPTION_BASIC_BLOCK_LENGTH;
if (this.perkMonthCount === (expectedPerkMonthCount - 1)) {
// User was affected by a bug that makes their perkMonthCount off by one
this.perkMonthCount += 1;
}
}
this.perkMonthCount += addingNumber;
+7
View File
@@ -392,6 +392,13 @@ schema.pre('update', function preUpdateUser () {
this.update({}, { $inc: { _v: 1 } });
});
schema.pre('updateOne', function preUpdateUser () {
this.updateOne({}, { $inc: { _v: 1 } });
});
schema.pre('updateMany', function preUpdateUser () {
this.updateMany({}, { $inc: { _v: 1 } });
});
schema.post('save', function postSaveUser () {
// Send a webhook notification when the user has leveled up
if (this._tmp && this._tmp.leveledUp && this._tmp.leveledUp.length > 0) {
+2 -4
View File
@@ -225,10 +225,9 @@ schema.statics.pushNotification = async function pushNotification (
throw validationResult;
}
await this.update(
await this.updateMany(
query,
{ $push: { notifications: newNotification.toObject() } },
{ multi: true },
).exec();
};
@@ -274,13 +273,12 @@ schema.statics.addAchievementUpdate = async function addAchievementUpdate (query
const validationResult = newNotification.validateSync();
if (validationResult) throw validationResult;
await this.update(
await this.updateMany(
query,
{
$push: { notifications: newNotification.toObject() },
$set: { [`achievements.${achievement}`]: true },
},
{ multi: true },
).exec();
};
+8
View File
@@ -154,6 +154,7 @@ export default new Schema({
boneToPick: Boolean,
polarPro: Boolean,
plantParent: Boolean,
dinosaurDynasty: Boolean,
// Onboarding Guide
createdTask: Boolean,
completedTask: Boolean,
@@ -534,6 +535,7 @@ export default new Schema({
stickyHeader: { $type: Boolean, default: true },
disableClasses: { $type: Boolean, default: false },
newTaskEdit: { $type: Boolean, default: false },
// not used anymore, now the current filter is saved in preferences.activeFilter
dailyDueDefaultView: { $type: Boolean, default: false },
advancedCollapsed: { $type: Boolean, default: false },
toolbarCollapsed: { $type: Boolean, default: false },
@@ -594,6 +596,12 @@ export default new Schema({
mirrorGroupTasks: [
{ $type: String, validate: [v => validator.isUUID(v), 'Invalid group UUID.'], ref: 'Group' },
],
activeFilter: {
habit: { $type: String, default: 'all' },
daily: { $type: String, default: 'all' },
todo: { $type: String, default: 'remaining' },
reward: { $type: String, default: 'all' },
},
},
improvementCategories: {
$type: Array,