Compare commits

...

65 Commits

Author SHA1 Message Date
Phillip Thelen
192d649ffa fix giving gear to contributors 2025-01-23 17:30:31 +01:00
Phillip Thelen
e7aae55eca Give contributors gear immediately 2025-01-21 11:10:29 +01:00
Phillip Thelen
36b03613e1 adjust contributor tests 2025-01-21 11:10:16 +01:00
Phillip Thelen
2de9a16a2c adjust gems per tier 2025-01-21 11:02:19 +01:00
negue
895241b7fa show date tooltip above system / skill messages 2025-01-20 21:08:55 +01:00
negue
2535fd7095 Combined Message Pages/Redesign (#15310)
* split component prepare new views / states

* extract empty and disabled state as components

* fix empty state mail icon

* first logic switching between modes, move page to /private-messages/index.vue

* extract autoCompleteHelper.js

* style header + start new message input

* style plus button + focus input

* state logic, types for sanity

* WIP PM new Message started

* add /members/username test

* first design changes to messageCard

* delete private message or chat - based on the mode

* copy as todo

* mention links to modal

* report chat or private message

* WIP likeButton

* likeButton styling

* hide like on private message cards

* fix unit test

* replace copy as todo - to just a copy to clipboard

* style changes

* menu position + like button width

* dropdown items background + like font

* fix like button padding

* move api endpoints and tests around to group inbox methods  + like for inbox private messages

* restyle system messages

* Dropdown Radius and Padding

* WIP system messages

* fix lint

* copy delta commit of allowing liking own private messages

* enable liking private messages

* fix menu non hovered item icon color

* fix import path

* ignore background on system messages

* requested changes + migration

* update migration to update the unique id to some messages and delete the duplicates

* migration based on users pagination

* fix(migration): use Promise.all

* change to bulkWrites per User, and all messages in one run (of a user)

* check for array

* use rest operator ...

* skip sorting to get the users

* remove migration, disable like for private messages without uniqueMessageId

* lean+bulkWrite for likes, add time checks for like and auth for further debugging

* add a limit 2 get the messages by uniqueId

* Adding a simple server start script

* remove pinned nodemon dep

* fix inbox controller/tests

* fix / requested style changes

* fix empty state padding /

* hide avatar weapons on messages - fix avatar spacing on messages

* Hourglass Simplification (#15323)

* begin removing obsolete tests

* begin refactoring

* update cron tests

* cleanup

* finish basic implementation of new logic

* add more subscription tests

* subscription test improvements

* return nextHourglassDate again

* fix gem limit

* fix(test): short circuit this.

* fix(admin): correct logic and style for shrimple subs

* WIP(frontend): draft of main subs page view

* fix hourglass count

* Fix hourglass logic for upgrades

* fix admin panel display

* WIP(subs): extant Stripe state

* fix admin panel strings

* fix missing transaction type

* add new field for cumulative subscription count

* show date for hourglass bonus if it was received

* fix test

* feat(subscription): max Gems progress readout

* fix(css): correct and refactor heights and selection states

* fix(subs): correct border-radius and redirect

* fix(stripe): correct redirect after success

* Admin panel display fixes

* don’t give additional HG for new sub if they already got one this month

* fix issue with promo hourglasses

* fix(subscription): update layout when gifting

* fix(subscriptions): more gift layout revisions

* fix(subscriptions): minor visual updates

* fix(subs): pass autoRenews through Stripe

* fix(subs): gifts DON't renew

* fix(lint): unnecessary ternary

* fix(lint): do negate object ig

* fix(subs): try again on gifts

* fix(subs): unhovery and un-12-monthy

* fix bug with incorrectly giving HG bonus

* remove only

* fix test

* fix test

* fix(subs): also redirect to subs after gift sub

* fix(subs): fix typeError

* fix(g1g1): don't try to find Gems promo during bogo

---------

Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Kalista Payne <sabe@habitica.com>

* chore(sprites): update subproject

* fix(layout): tighten cancellation note

* fix(subs): Google wording and HG escape

* chore(testing): fake g1g1 dates

* fix(subs): don't hide HG preview entirely

* fix(subs): center next hourglass message

* working validatedTextInput.vue within start-new-conversation-input-header.vue 🎉

* fix(git): remove changes from old develop

* Revert "fix(git): remove changes from old develop"

This reverts commit 0e30f7df00.

* fix(git): no actually just this file i guesss

* adding an empty loading state, hiding

* fought the avatar arch nemesis again

* fix chatMessages (party chat) message spacing

* move disabled text back to above the input area - re-enable input area

* show disabled private messages top panel

* fix font color

* fixing uiStates - removing disabled - moving the own user check to the last

* fix(lint): add missing prop defaults

* fix(lint): object default should be fn

* fix(chat): correct grammar in error

---------

Co-authored-by: SabreCat <sabe@habitica.com>
Co-authored-by: Kalista Payne <sabrecat@gmail.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
2025-01-16 16:52:24 -06:00
Kalista Payne
30f1820a49 5.32.5 2025-01-09 09:02:14 -06:00
Weblate
3bb6c391af Translated using Weblate (German)
Currently translated at 98.0% (3190 of 3255 strings)

Translated using Weblate (German)

Currently translated at 97.8% (3185 of 3255 strings)

Translated using Weblate (German)

Currently translated at 97.7% (3181 of 3255 strings)

Translated using Weblate (German)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (German)

Currently translated at 97.5% (3176 of 3255 strings)

Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translation: Habitica/Gear
Translation: Habitica/Subscriber
2025-01-09 14:46:54 +01:00
Kalista Payne
a0383c785a Squashed commit of the following:
commit 5d3713008dc3041f63b23b22196e1ed79fab45bd
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jan 8 14:39:27 2025 -0600

    fix(text): pet Pet

commit 3ff5d7afeb517ae3d0933d8e18045b24575bb90b
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jan 8 14:34:02 2025 -0600

    fix(links): remove unnecessary style and icon

commit 5023dd9258aa0f9416daa212ed1249db2e5d5fe4
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jan 8 09:43:17 2025 -0600

    fix(links): update task modal markdown help

commit 81ebb279cd881d1c560668c3cb03600d85b9f5b9
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jan 8 09:26:11 2025 -0600

    fix(misc): clean up some layout and one more link

commit 2fd216f01531052aa769738f8fe1956ca6943822
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Tue Jan 7 16:09:28 2025 -0600

    fix(links): remove/revise some outdated wiki links
2025-01-08 15:18:06 -06:00
Kalista Payne
99790c05f4 5.32.4 2025-01-06 16:22:26 -06:00
Weblate
fc5fec9bfe Merge branch 'origin/develop' into Weblate. 2025-01-06 23:18:24 +01:00
Phillip Thelen
9db5d4116d Fix availability of december background (#15378) 2025-01-06 15:57:57 -06:00
Weblate
6676e94ef6 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 95.7% (134 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Italian)

Currently translated at 9.4% (23 of 243 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (887 of 893 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (887 of 893 strings)

Translated using Weblate (Russian)

Currently translated at 91.4% (2978 of 3255 strings)

Translated using Weblate (Portuguese)

Currently translated at 97.2% (177 of 182 strings)

Translated using Weblate (French)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (French)

Currently translated at 99.2% (259 of 261 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3255 of 3255 strings)

Co-authored-by: Chase7-Diaphragm0-Jeeringly7-Smartly2-Drainer5 <linguists-commonwealth@silkylegs.aleeas.com>
Co-authored-by: Gabrielle Renoir <cococherierenoir@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Kesabirria <kebsebastian@gmail.com>
Co-authored-by: Oscar Rodríguez Díaz <alexoscarcrd@gmail.com>
Co-authored-by: Raquel Pantojo de Souza Bachour <raquel.pantojo@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Val <3qes0hnzh@mozmail.com>
Co-authored-by: Vitaliia Reinberg <vitalia.reynberg@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/es/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/es_419/
Translation: Habitica/Backgrounds
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2025-01-06 22:52:42 +01:00
Kalista Payne
723adceb25 chore(migrations): cute default and updateOne 2025-01-06 15:43:07 -06:00
Kalista Payne
440d06da4a 5.32.3 2025-01-03 12:51:57 -06:00
Weblate
0ea84668a8 Merge branch 'origin/develop' into Weblate. 2025-01-03 19:49:25 +01:00
Kalista Payne
5893d8b9bb fix(subs): revise benefit strings for clarity 2025-01-03 12:22:51 -06:00
Weblate
2c799b9c07 Translated using Weblate (Spanish)
Currently translated at 100.0% (3255 of 3255 strings)

Translated using Weblate (German)

Currently translated at 97.1% (3161 of 3255 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: Anna Tunger <anna.tunger@icloud.com>
Co-authored-by: Diego Alejandro Rios Vasquez <diegovasquezcolombia@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translation: Habitica/Backgrounds
Translation: Habitica/Gear
2025-01-03 04:41:47 +01:00
Kalista Payne
1550d9b4ee 5.32.2 2025-01-02 11:22:51 -06:00
Weblate
ade812b86d Merge branch 'origin/develop' into Weblate. 2025-01-02 18:19:49 +01:00
Phillip Thelen
62e6fbef61 Fix content end date if already in new year (#15376)
* Fix content end date if already in new year

* fix test
2025-01-02 11:15:04 -06:00
Weblate
67a0f8b65a Translated using Weblate (Korean)
Currently translated at 70.4% (629 of 893 strings)

Translated using Weblate (Korean)

Currently translated at 91.6% (153 of 167 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: widesky8 <widesky20@naver.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
2025-01-01 03:17:52 +01:00
Kalista Payne
aa432022d3 fix(script): replace deprecated update function 2024-12-30 14:58:52 -06:00
Kalista Payne
86fb3c1fd1 5.32.1 2024-12-30 09:23:08 -06:00
Weblate
ff2b4add8b Translated using Weblate (German)
Currently translated at 96.1% (3130 of 3255 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Korean)

Currently translated at 96.4% (110 of 114 strings)

Translated using Weblate (Korean)

Currently translated at 49.4% (90 of 182 strings)

Translated using Weblate (Korean)

Currently translated at 91.0% (152 of 167 strings)

Translated using Weblate (Korean)

Currently translated at 54.8% (1784 of 3255 strings)

Translated using Weblate (Korean)

Currently translated at 65.3% (170 of 260 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (German)

Currently translated at 95.8% (3119 of 3255 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Slovak)

Currently translated at 71.0% (577 of 812 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (394 of 394 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 79.3% (207 of 261 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 92.1% (398 of 432 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 4.9% (12 of 243 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 94.3% (182 of 193 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 95.4% (105 of 110 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 86.5% (773 of 893 strings)

Translated using Weblate (Russian)

Currently translated at 36.6% (89 of 243 strings)

Translated using Weblate (Russian)

Currently translated at 36.6% (89 of 243 strings)

Translated using Weblate (German)

Currently translated at 95.6% (3115 of 3255 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 77.7% (189 of 243 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

Translated using Weblate (German)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (German)

Currently translated at 95.1% (3096 of 3255 strings)

Translated using Weblate (French)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (German)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (French)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (German)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3255 of 3255 strings)

Translated using Weblate (German)

Currently translated at 93.8% (3056 of 3255 strings)

Translated using Weblate (French)

Currently translated at 100.0% (893 of 893 strings)

Translated using Weblate (German)

Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: Boni hahaha <chanrouber@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Hikari <sss840127@gmail.com>
Co-authored-by: Irina  Shcherbinina <cat3dcat007@gmail.com>
Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
Co-authored-by: Lancelot Liu <me@lancy.dev>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wyatt <1697570085@qq.com>
Co-authored-by: razil <boss.razmarin@gmail.com>
Co-authored-by: 리슈레이 <rishyurei@gmail.com>
Co-authored-by: 횬 <gkfpxldk0424@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/front/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hant/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-12-30 16:06:20 +01:00
Kalista Payne
4ba73dfbec fix(notifications): don't error on same page 2024-12-19 15:33:23 -06:00
dependabot[bot]
e675ea9bd1 Bump send and express in /website/client (#15324)
Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
2024-12-19 14:19:16 -05:00
dependabot[bot]
9c27d86ced Bump body-parser and express (#15319)
Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `body-parser` from 1.20.2 to 1.20.3
- [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.2...1.20.3)

Updates `express` from 4.19.2 to 4.20.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: direct:production
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
2024-12-19 14:19:04 -05:00
dependabot[bot]
58ee81adfc Bump serve-static and express (#15318)
Bumps [serve-static](https://github.com/expressjs/serve-static) to 1.16.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `serve-static` from 1.15.0 to 1.16.0
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...1.16.0)

Updates `express` from 4.19.2 to 4.20.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
2024-12-19 14:18:51 -05:00
dependabot[bot]
32c9904a6e Bump cookie and express (#15338)
Bumps [cookie](https://github.com/jshttp/cookie) to 0.7.1 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 13:43:21 -05:00
dependabot[bot]
b86e0a1549 Bump send and express (#15339)
Bumps [send](https://github.com/pillarjs/send) to 0.19.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 13:42:48 -05:00
dependabot[bot]
154ac9bb38 chore(deps): bump mongoose from 7.6.8 to 7.8.3 (#15374)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 7.6.8 to 7.8.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/7.8.3/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/7.6.8...7.8.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 13:28:20 -05:00
dependabot[bot]
a97060445a chore(deps): bump cookie and express in /website/client (#15375)
Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.19.2 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.2)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 13:28:01 -05:00
Kalista Payne
26b59de1de feat(migration): revised NYE script 2024-12-18 18:01:16 -06:00
Kalista Payne
21c8b00ef6 chore(subproj): update habitica-images 2024-12-18 17:33:34 -06:00
Kalista Payne
c25b7293bb 5.32.0 2024-12-17 15:00:55 -06:00
Weblate
15e078cb34 Translated using Weblate (German)
Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Polish)

Currently translated at 22.2% (54 of 243 strings)

Translated using Weblate (Polish)

Currently translated at 72.6% (189 of 260 strings)

Translated using Weblate (Bulgarian)

Currently translated at 64.0% (107 of 167 strings)

Co-authored-by: Jan Hesko-Kołodziński <janheski@gmail.com>
Co-authored-by: Matthieu <matthieu-ds@hotmail.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Дмитрий <dimaprohor570@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/bg/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/pl/
Translation: Habitica/Achievements
Translation: Habitica/Faq
Translation: Habitica/Limited
Translation: Habitica/Settings
2024-12-17 18:09:20 +01:00
Natalie
f7bb17202b January 2025 Content Build (#15371)
* chore: add spritesheet

* chore: update spritesheet

* chore: add January subscriber set

* chore: add January subscriber items

* chore: add January background and enchanted armoire gear

* chore: fixing dumb typos

* chore: fix another typo

* chore: and another dumb typo

* chore: fix release date for armoire items

* fix(grammar): articles

---------

Co-authored-by: Kalista Payne <sabrecat@gmail.com>
2024-12-16 11:54:09 -06:00
Kalista Payne
213b7696c5 5.31.2 2024-12-16 11:25:58 -06:00
Weblate
fe5c95316b Translated using Weblate (Turkish)
Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (German)

Currently translated at 94.0% (3054 of 3247 strings)

Translated using Weblate (German)

Currently translated at 93.6% (3041 of 3247 strings)

Translated using Weblate (German)

Currently translated at 93.6% (3041 of 3247 strings)

Translated using Weblate (German)

Currently translated at 93.4% (3034 of 3247 strings)

Translated using Weblate (German)

Currently translated at 93.3% (3032 of 3247 strings)

Translated using Weblate (German)

Currently translated at 100.0% (812 of 812 strings)

Translated using Weblate (German)

Currently translated at 93.3% (3030 of 3247 strings)

Translated using Weblate (German)

Currently translated at 98.8% (803 of 812 strings)

Translated using Weblate (German)

Currently translated at 93.2% (3028 of 3247 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (394 of 394 strings)

Translated using Weblate (German)

Currently translated at 93.1% (3026 of 3247 strings)

Translated using Weblate (German)

Currently translated at 93.1% (3024 of 3247 strings)

Translated using Weblate (German)

Currently translated at 92.9% (3019 of 3247 strings)

Translated using Weblate (German)

Currently translated at 92.9% (3019 of 3247 strings)

Translated using Weblate (German)

Currently translated at 97.7% (794 of 812 strings)

Translated using Weblate (Portuguese)

Currently translated at 98.2% (387 of 394 strings)

Translated using Weblate (Portuguese)

Currently translated at 68.8% (179 of 260 strings)

Translated using Weblate (German)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (German)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (German)

Currently translated at 97.2% (790 of 812 strings)

Translated using Weblate (Polish)

Currently translated at 58.2% (53 of 91 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (German)

Currently translated at 99.2% (258 of 260 strings)

Translated using Weblate (Turkish)

Currently translated at 59.8% (533 of 890 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 94.7% (3077 of 3247 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (German)

Currently translated at 99.1% (113 of 114 strings)

Translated using Weblate (German)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (German)

Currently translated at 92.6% (3009 of 3247 strings)

Translated using Weblate (German)

Currently translated at 98.8% (257 of 260 strings)

Translated using Weblate (German)

Currently translated at 98.6% (426 of 432 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3247 of 3247 strings)

Translated using Weblate (German)

Currently translated at 92.4% (3003 of 3247 strings)

Translated using Weblate (Hebrew)

Currently translated at 75.8% (182 of 240 strings)

Translated using Weblate (Dutch)

Currently translated at 66.1% (172 of 260 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (German)

Currently translated at 98.4% (256 of 260 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (3239 of 3247 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (394 of 394 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (890 of 890 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Spanish)

Currently translated at 97.6% (254 of 260 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3247 of 3247 strings)

Translated using Weblate (Turkish)

Currently translated at 96.2% (231 of 240 strings)

Translated using Weblate (German)

Currently translated at 92.2% (2996 of 3247 strings)

Translated using Weblate (Dutch)

Currently translated at 22.2% (54 of 243 strings)

Translated using Weblate (Dutch)

Currently translated at 53.8% (49 of 91 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (394 of 394 strings)

Translated using Weblate (Spanish)

Currently translated at 89.6% (233 of 260 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Spanish)

Currently translated at 99.6% (259 of 260 strings)

Translated using Weblate (Spanish)

Currently translated at 88.0% (229 of 260 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (Russian)

Currently translated at 35.8% (87 of 243 strings)

Translated using Weblate (Russian)

Currently translated at 35.8% (87 of 243 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (393 of 394 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.4% (188 of 193 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (890 of 890 strings)

Translated using Weblate (Hebrew)

Currently translated at 74.5% (179 of 240 strings)

Translated using Weblate (German)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (German)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (German)

Currently translated at 98.2% (112 of 114 strings)

Translated using Weblate (German)

Currently translated at 92.0% (2989 of 3247 strings)

Translated using Weblate (German)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (German)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Hebrew)

Currently translated at 75.4% (181 of 240 strings)

Translated using Weblate (French)

Currently translated at 99.9% (3246 of 3247 strings)

Translated using Weblate (French)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (French)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (French)

Currently translated at 99.3% (3225 of 3247 strings)

Translated using Weblate (French)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (French)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (French)

Currently translated at 100.0% (394 of 394 strings)

Translated using Weblate (Turkish)

Currently translated at 53.0% (138 of 260 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Turkish)

Currently translated at 68.5% (296 of 432 strings)

Translated using Weblate (Turkish)

Currently translated at 52.9% (1720 of 3247 strings)

Translated using Weblate (Turkish)

Currently translated at 83.3% (200 of 240 strings)

Translated using Weblate (Turkish)

Currently translated at 69.3% (563 of 812 strings)

Translated using Weblate (Turkish)

Currently translated at 65.9% (60 of 91 strings)

Translated using Weblate (Turkish)

Currently translated at 59.5% (530 of 890 strings)

Translated using Weblate (Turkish)

Currently translated at 79.2% (206 of 260 strings)

Translated using Weblate (German)

Currently translated at 91.7% (2980 of 3247 strings)

Translated using Weblate (Russian)

Currently translated at 29.2% (71 of 243 strings)

Translated using Weblate (German)

Currently translated at 100.0% (394 of 394 strings)

Translated using Weblate (Russian)

Currently translated at 99.4% (885 of 890 strings)

Translated using Weblate (German)

Currently translated at 100.0% (890 of 890 strings)

Translated using Weblate (German)

Currently translated at 91.7% (2979 of 3247 strings)

Translated using Weblate (German)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (German)

Currently translated at 100.0% (890 of 890 strings)

Translated using Weblate (German)

Currently translated at 100.0% (890 of 890 strings)

Translated using Weblate (Slovak)

Currently translated at 83.3% (95 of 114 strings)

Translated using Weblate (Slovak)

Currently translated at 99.7% (393 of 394 strings)

Translated using Weblate (Slovak)

Currently translated at 43.7% (73 of 167 strings)

Translated using Weblate (Spanish)

Currently translated at 87.6% (228 of 260 strings)

Co-authored-by: Alison Alex <spamkari@hotmail.com>
Co-authored-by: Antje Schubert <antje.schubert96@web.de>
Co-authored-by: Elizaveta <lizka4231@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Irina  Shcherbinina <cat3dcat007@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: João Santos Reis <joaosreis@outlook.pt>
Co-authored-by: Julia Bacik <bacikjulia@gmail.com>
Co-authored-by: Kasper van der Linden <kasper.van.der.linden@gmail.com>
Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
Co-authored-by: Kenvinn <kevinsavio514@gmail.com>
Co-authored-by: Marie Blosse--Gilbin <mbgil@hotmail.fr>
Co-authored-by: Maya <mayabaciu1@gmail.com>
Co-authored-by: Maya B <mayabaciu1@gmail.com>
Co-authored-by: Sand <weblate@sandhydraulik.de>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yaşar Efe Çelik <yasar.123.sevda@gmail.com>
Co-authored-by: YuyingLiang <standingfish.malina@gmail.com>
Co-authored-by: razil <boss.razmarin@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/character/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/es/
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/he/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/es/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/de/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/de/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-12-15 18:52:35 +01:00
Kalista Payne
54617f8583 Squashed commit of the following:
commit a1f44b855cff2b54992cdca81b9dd2f67c5de20a
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Dec 4 15:28:54 2024 -0600

    fix(g1g1): pass promo data thru profile flow

commit a9923f882f3294ba22e1dff9497e6f74b0d12eab
Merge: 05793922b2 75c9731ca4
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Tue Dec 3 13:53:28 2024 -0600

    Merge branch 'develop' into sabrecat/g1g1-success

commit 05793922b2a3c0f9dd206b61beefb927b00859e4
Merge: b0bbc10457 d6c47e7e81
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Tue Nov 19 12:21:42 2024 -0600

    Merge branch 'develop' into sabrecat/g1g1-success

commit b0bbc10457b0558faeaf02305210c8b0d5ed5839
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Mon Nov 4 16:26:30 2024 -0600

    feat(gifts): add dynamic G1G1 success text
    also clean out some more Amazon code
2024-12-12 12:45:14 -06:00
Phillip Thelen
75c9731ca4 filter stripe webhooks for correct server (#15320)
Co-authored-by: Kalista Payne <sabrecat@gmail.com>
2024-11-26 12:24:23 -06:00
Kalista Payne
31afc45744 5.31.1 2024-11-22 09:20:27 -06:00
Kalista Payne
f6466b161b Squashed commit of the following:
commit ea8512bc2eeda30c4d983fb43bfcb33ea39093e9
Merge: 2bb7683cfd a36114e904
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Nov 21 12:47:11 2024 -0600

    Merge branch 'develop' into ui-drawer-fix

commit 2bb7683cfd
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Nov 12 15:46:55 2024 -0500

    fix food centering and error centering at very small widths

commit fcabf03978
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Nov 1 14:44:16 2024 -0400

    grid size update

commit 47a531c1aa
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Oct 31 16:17:22 2024 -0400

    centered error messages on pet drawer

commit 14d2872392
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Oct 24 15:47:46 2024 -0400

    center no food/no saddle messages in drawer

commit 49d1d10a4f
Merge: 6a805bdc45 f1993db0fa
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Oct 24 14:04:16 2024 -0400

    Merge branch 'develop' into ui-drawer-fix

commit 6a805bdc45
Author: Alex <alex.kliger@gmail.com>
Date:   Sat Nov 19 09:41:21 2022 -0500

    fix(ui): correct crowding in title of drawer component that causes drawer toggle icon to become obstructed
2024-11-21 12:55:00 -06:00
Kalista Payne
a36114e904 Squashed commit of the following:
commit 4d88df1c381c4136cfe4780b6b5464d9c984bb31
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Oct 30 14:24:53 2024 -0400

    fixing active/focus states

commit 630f74db6365625af210de81d577d43945f62df4
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Oct 23 13:25:14 2024 -0400

    sign-up/login/debug menu updates

commit f85ef1c58e367219923c46aeebe77d1aa846b6f3
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Oct 15 13:56:36 2024 -0400

    more color standardization

commit cd2ee3350fc51016cc75fb799debeefa0b1d9b64
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Oct 15 13:39:02 2024 -0400

    updates per comments

commit e91371522bb9a6dae5c6d02eb6f16f0b632f7a24
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Oct 4 14:25:45 2024 -0400

    class selection active state

commit 4fcdca1eb07eb67aacd84feb1c9357d52fd6f23f
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Oct 3 14:31:00 2024 -0400

    fixing a few more buttons

commit 475e21aae7733cfa072f828c559cc0930f0ad878
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Oct 2 15:57:25 2024 -0400

    line-heights converted to unitless multipliers

commit c5dca257274a60b02e1f12e3c6daf103cec53096
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Sep 27 13:48:20 2024 -0400

    second pass

commit 685f4d4d64b8b234afbb702d1b8a52d4670d3825
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Sep 26 16:58:59 2024 -0400

    first pass
2024-11-21 12:40:05 -06:00
dependabot[bot]
529f856ab9 Bump cross-spawn from 7.0.3 to 7.0.5 (#15365)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.5.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.5)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 13:31:49 -05:00
Kalista Payne
9077e66973 fix(notifications): adjust z-index by @CuriousMagpie 2024-11-21 12:22:45 -06:00
Kalista Payne
a47a96b70d Squashed commit of the following:
commit 44583e224c771bab4b75758372e8b051a5346f72
Merge: 1436d8d5ef d6c47e7e81
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Tue Nov 19 12:33:34 2024 -0600

    Merge branch 'develop' into sabrecat/hourglass-afterward

commit 1436d8d5ef5b672425513e9c5886ca533594134f
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Tue Nov 19 10:44:53 2024 -0600

    fix(faq): update Hourglass explainer
2024-11-21 12:17:58 -06:00
Kalista Payne
8a94e88786 fix(migrations): correct linting errors 2024-11-21 12:07:51 -06:00
Kalista Payne
b3aa236d3d 5.31.0 2024-11-21 11:52:48 -06:00
Kalista Payne
4dd58ad89e fix(event): turkey migration update 2024-11-21 11:52:11 -06:00
Weblate
317f7ab598 Translated using Weblate (French)
Currently translated at 100.0% (890 of 890 strings)

Translated using Weblate (German)

Currently translated at 92.5% (2971 of 3211 strings)

Translated using Weblate (German)

Currently translated at 97.1% (789 of 812 strings)

Translated using Weblate (German)

Currently translated at 100.0% (887 of 887 strings)

Co-authored-by: Marie Blosse--Gilbin <mbgil@hotmail.fr>
Co-authored-by: Sand <weblate@sandhydraulik.de>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translation: Habitica/Backgrounds
Translation: Habitica/Gear
Translation: Habitica/Questscontent
2024-11-21 15:59:10 +01:00
Kalista Payne
d6c47e7e81 Squashed commit of the following:
commit 7de39d868b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Oct 14 13:05:03 2024 -0400

    gem text clickable
2024-11-19 12:09:39 -06:00
Kalista Payne
1ed61a3d3d December 2024 and Winter Wonderland 2025 build (#15358)
* feat(content): Winter 2025 build by @CuriousMagpie

* fix(content): add more WW

* fix(content): add more WW

* chore(subproj): update habitica-images

* fix(gala): December 2024 is Winter 2025

* fix(content): lint, background typo, 0 index month

* fix(content): add missing mystery set name

* fix(content): roll back erroneous month indexing

* fix(tests): no 13th month, consider releases in schedule test

* update gear strings

* fix(seasonal): show quest modal

* fix(seasonal): use category-item component

* chore(subproj): update habitica-images

---------

Co-authored-by: CuriousMagpie <eilatan@gmail.com>
2024-11-19 11:44:22 -06:00
Kalista Payne
5c734cfa00 chore(subproj): update images 2024-11-19 09:42:23 -06:00
Kalista Payne
07f485a654 chore(migrations): add subs migration 2024-11-19 09:38:05 -06:00
Kalista Payne
ae76271469 5.30.0 2024-11-19 09:11:17 -06:00
Weblate
c8a8ecbe1f Translated using Weblate (German)
Currently translated at 91.4% (2938 of 3211 strings)

Translated using Weblate (German)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (German)

Currently translated at 100.0% (887 of 887 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (French)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (133 of 133 strings)

Translated using Weblate (Turkish)

Currently translated at 28.8% (70 of 243 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (393 of 393 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Turkish)

Currently translated at 57.6% (511 of 887 strings)

Translated using Weblate (Indonesian)

Currently translated at 94.1% (835 of 887 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (French)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (French)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3211 of 3211 strings)

Translated using Weblate (French)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (French)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (French)

Currently translated at 100.0% (393 of 393 strings)

Translated using Weblate (French)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (French)

Currently translated at 98.7% (240 of 243 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (French)

Currently translated at 97.1% (236 of 243 strings)

Translated using Weblate (French)

Currently translated at 97.1% (236 of 243 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (French)

Currently translated at 100.0% (887 of 887 strings)

Translated using Weblate (Slovak)

Currently translated at 51.0% (122 of 239 strings)

Translated using Weblate (Slovak)

Currently translated at 55.2% (143 of 259 strings)

Translated using Weblate (Korean)

Currently translated at 70.9% (629 of 887 strings)

Translated using Weblate (Korean)

Currently translated at 89.8% (150 of 167 strings)

Translated using Weblate (Korean)

Currently translated at 89.8% (150 of 167 strings)

Translated using Weblate (Polish)

Currently translated at 77.1% (199 of 258 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 86.9% (771 of 887 strings)

Translated using Weblate (Ukrainian)

Currently translated at 60.4% (1940 of 3211 strings)

Co-authored-by: Binny <45uipcik@duck.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Jimly Asshiddiqy <j_mly@ymail.com>
Co-authored-by: Lapin <sirocuro01@gmail.com>
Co-authored-by: Marie Blosse--Gilbin <mbgil@hotmail.fr>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Sand <weblate@sandhydraulik.de>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yaşar Efe Çelik <yasar.123.sevda@gmail.com>
Co-authored-by: ZELIAL <nalfetic@gmail.com>
Co-authored-by: ana <coposmit@gmail.com>
Co-authored-by: 장은서 <eunseo5207@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/id/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/es/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/sk/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2024-11-19 06:46:04 +01:00
Kalista Payne
fbf69a4a34 Squashed commit of the following:
commit dd0a410fa6c3741dc0d6793283cf4df3c37790a5
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Mon Nov 4 14:24:30 2024 -0600

    fix(subs): center next hourglass message

commit 72d92ffd76bb43fee8ba2bbabd211e595afbd664
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Fri Nov 1 14:17:59 2024 -0500

    fix(subs): don't hide HG preview entirely

commit ea0ecb0c3d519ed3d5c42266367eaaa7283ac5de
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Fri Nov 1 13:01:06 2024 -0500

    fix(subs): Google wording and HG escape

commit 2bd2c69e18e37c8c8c7106c62f186c372d25c5d2
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Fri Nov 1 09:25:30 2024 -0500

    fix(layout): tighten cancellation note

commit eb2fc40d24
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 24 15:41:43 2024 -0500

    fix(g1g1): don't try to find Gems promo during bogo

commit d3eea86bd7
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 24 15:00:09 2024 -0500

    fix(subs): fix typeError

commit e3ae9a2d67
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 24 13:57:27 2024 -0500

    fix(subs): also redirect to subs after gift sub

commit 690163a0de
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Oct 23 16:42:38 2024 +0200

    fix test

commit 2ad7541fc0
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Oct 23 16:34:52 2024 +0200

    fix test

commit 7e337a9e59
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Oct 23 11:54:15 2024 +0200

    remove only

commit 7462b8a57f
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Oct 23 11:51:25 2024 +0200

    fix bug with incorrectly giving HG bonus

commit acd6183e95
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Mon Oct 21 17:22:26 2024 -0500

    fix(subs): unhovery and un-12-monthy

commit 935e9fd6ec
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Fri Oct 18 14:50:17 2024 -0500

    fix(subs): try again on gifts

commit 6e1fb7df38
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 17 18:19:20 2024 -0500

    fix(lint): do negate object ig

commit 71d434b94e
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 17 18:15:11 2024 -0500

    fix(lint): unnecessary ternary

commit b90b0bb9c3
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 17 17:34:24 2024 -0500

    fix(subs): gifts DON't renew

commit 19469304c5
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 17 17:13:29 2024 -0500

    fix(subs): pass autoRenews through Stripe

commit 6819e7b7e5
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 17 16:03:25 2024 -0500

    fix(subscriptions): minor visual updates

commit 74633b5e5e
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Oct 16 17:27:09 2024 -0500

    fix(subscriptions): more gift layout revisions

commit a90ccb89de
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Oct 16 15:37:50 2024 -0500

    fix(subscription): update layout when gifting

commit c24b2db8dc
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Oct 14 16:11:46 2024 +0200

    fix issue with promo hourglasses

commit 7a61c72b47
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Oct 14 15:59:40 2024 +0200

    don’t give additional HG for new sub if they already got one this month

commit f14cb09026
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Oct 14 10:38:01 2024 +0200

    Admin panel display fixes

commit f4cff698cf
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 3 17:58:59 2024 -0500

    fix(stripe): correct redirect after success

commit c468b58f3f
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Oct 3 17:35:37 2024 -0500

    fix(subs): correct border-radius and redirect

commit 78fb9e31d6
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Oct 2 17:41:49 2024 -0500

    fix(css): correct and refactor heights and selection states

commit e2babe8053
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Mon Sep 30 16:45:29 2024 -0500

    feat(subscription): max Gems progress readout

commit 61af8302a3
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Sep 27 15:11:22 2024 +0200

    fix test

commit ef8ff0ea9e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Sep 27 14:14:44 2024 +0200

    show date for hourglass bonus if it was received

commit 4bafafdc8d
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Sep 27 14:12:52 2024 +0200

    add new field for cumulative subscription count

commit 30096247b7
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Sep 27 13:39:49 2024 +0200

    fix missing transaction type

commit 70872651b0
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Sep 27 13:31:40 2024 +0200

    fix admin panel strings

commit f3398db65f
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Sep 26 23:11:16 2024 -0500

    WIP(subs): extant Stripe state

commit c6b2020109
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Sep 26 11:41:55 2024 +0200

    fix admin panel display

commit d9afc96d2d
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Sep 26 11:40:16 2024 +0200

    Fix hourglass logic for upgrades

commit 6e2c8eeb64
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Sep 25 17:48:54 2024 +0200

    fix hourglass count

commit cd752fbdce
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Fri Sep 20 12:24:21 2024 -0500

    WIP(frontend): draft of main subs page view

commit 0102b29d59
Author: Kalista Payne <sabe@habitica.com>
Date:   Wed Sep 18 15:29:08 2024 -0500

    fix(admin): correct logic and style for shrimple subs

commit 5469a5c5c3
Author: Kalista Payne <sabe@habitica.com>
Date:   Wed Sep 18 15:07:36 2024 -0500

    fix(test): short circuit this.

commit 526193ee6c
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Sep 18 14:42:06 2024 +0200

    fix gem limit

commit 19cf1636aa
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Aug 13 17:00:40 2024 +0200

    return nextHourglassDate again

commit eea36e3ed5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Aug 13 13:11:22 2024 +0200

    subscription test improvements

commit ca78e74330
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 12 15:46:15 2024 +0200

    add more subscription tests

commit f4c4f93a08
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 9 13:35:22 2024 +0200

    finish basic implementation of new logic

commit e036742048
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 9 11:37:44 2024 +0200

    cleanup

commit 6431865688
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Aug 7 05:41:18 2024 -0400

    update cron tests

commit 930d875ae9
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Aug 8 10:36:50 2024 +0200

    begin refactoring

commit 96623608d0
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Aug 6 16:28:16 2024 +0200

    begin removing obsolete tests
2024-11-14 12:31:57 -06:00
Kalista Payne
7f38ffe676 5.29.3 2024-11-12 11:52:41 -06:00
Kalista Payne
a0e0c392e9 chore(subproj): update habitica-images 2024-11-12 11:52:37 -06:00
Kalista Payne
573e472077 fix(faq): minor line edits 2024-11-07 14:42:05 -06:00
Kalista Payne
955d22278d Sabrecat/hourglass preview (#15355)
* feat(subscriptions): pre rollout announcement

* fix(faq): more h4 and p, less ul li

* fix(faq): a little less purple

* fix(subs): update change preview date
2024-11-07 12:14:29 -06:00
Kalista Payne
171ee93108 chore(subproj): update images 2024-11-07 11:36:57 -06:00
Kalista Payne
5fb0560f0b fix(migration): updateOne 2024-10-30 11:13:23 -05:00
259 changed files with 7838 additions and 4163 deletions

2
.gitignore vendored
View File

@@ -47,5 +47,5 @@ webpack.webstorm.config
# mongodb replica set for local dev
mongodb-*.tgz
/mongodb-data
/mongodb-data*
/.nyc_output

View File

@@ -0,0 +1,115 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20241119_gem_caps_hourglasses';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
const { consecutive, customerId, dateTerminated, planId } = user.purchased.plan;
const isRecurring = customerId !== 'Gift' && !dateTerminated;
const updateOp = {
$set: {
migration: MIGRATION_NAME,
'purchased.plan.consecutive.gemCapExtra': Math.max(2 * Math.ceil((consecutive.gemCapExtra + 1) / 2, 26)),
},
$inc: {},
};
let hourglassBonus = 0;
if (isRecurring) {
await user.updateBalance(
5,
'admin_update_balance',
'',
'Subscription Reward Migration',
);
updateOp.$inc.balance = 5;
switch (planId) {
case 'basic':
case 'basic_earned':
case 'group_plan_auto':
hourglassBonus = 2;
break;
case 'basic_3mo':
case 'basic_6mo':
case 'google_6mo':
hourglassBonus = 4;
break;
case 'basic_12mo':
hourglassBonus = 12;
updateOp.$set['purchased.plan.hourglassPromoReceived'] = new Date();
break;
default:
hourglassBonus = 0;
}
if (hourglassBonus) {
updateOp.$inc['purchased.plan.consecutive.trinkets'] = hourglassBonus;
await user.updateHourglasses(
hourglassBonus,
'admin_update_balance',
'',
'Subscription Reward Migration',
);
}
updateOp.$push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_subscriber_reward',
title: 'Thanks for being a subscriber!',
text: 'Enjoy these extra Mystic Hourglasses and Gems to celebrate our new benefits.',
},
seen: false,
},
};
}
return await User.updateOne(
{ _id: user._id },
updateOp,
).exec();
}
export default async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
'purchased.plan.customerId': { $exists: true },
$or: [
{ 'purchased.plan.dateTerminated': { $exists: false } },
{ 'purchased.plan.dateTerminated': null },
{ 'purchased.plan.dateTerminated': { $gt: new Date() } },
],
};
const fields = {
_id: 1,
purchased: 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)
.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],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -26,7 +26,7 @@ async function updateUser (user) {
[{ name: 'BASE_URL', content: BASE_URL }], // Add variables from template
);
return User.update({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
return User.updateOne({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
}
export default async function processUsers () {

View File

@@ -27,13 +27,13 @@ async function updateUser (user) {
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.update({ _id: user._id }, { $set: set }).exec();
return User.updateOne({ _id: user._id }, { $set: set }).exec();
}
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.local.lowerCaseUsername': 'olson1',
'auth.local.username': 'ExampleHabitican',
};
const fields = {

View File

@@ -57,7 +57,7 @@ async function updateUser (user) {
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.local.username': 'SabreTest',
'auth.local.username': 'ExampleHabitican',
};
const fields = {

View File

@@ -2,16 +2,15 @@
* Award Habitoween ladder items to participants in this month's Habitoween festivities
*/
/* eslint-disable no-console */
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20241030_habitoween_ladder'; // Update when running in future years
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
count += 1;
const set = { migration: MIGRATION_NAME };
const inc = {
@@ -26,7 +25,7 @@ async function updateUser (user) {
'items.food.Candy_Desert': 1,
'items.food.Candy_Red': 1,
};
let push = { notifications: { $each: [] }};
const push = { notifications: { $each: [] } };
if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-RoyalPurple']) {
push.notifications.$each.push({
@@ -138,13 +137,13 @@ async function updateUser (user) {
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$inc: inc, $push: push, $set: set}).exec();
return User.updateOne({ _id: user._id }, { $inc: inc, $push: push, $set: set }).exec();
}
export default async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2024-10-01')},
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2024-10-01') },
};
const fields = {
@@ -156,7 +155,7 @@ export default async function processUsers () {
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.sort({ _id: 1 })
.select(fields)
.lean()
.exec();
@@ -173,4 +172,4 @@ export default async function processUsers () {
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};
}

View File

@@ -0,0 +1,167 @@
/* eslint-disable no-console */
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20241120_harvest_feast';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const updateOp = {
$set: { migration: MIGRATION_NAME },
};
if (typeof user.items.gear.owned.head_special_turkeyHelmGilded !== 'undefined') {
updateOp.$inc = {
'items.food.Pie_Base': 1,
'items.food.Pie_CottonCandyBlue': 1,
'items.food.Pie_CottonCandyPink': 1,
'items.food.Pie_Desert': 1,
'items.food.Pie_Golden': 1,
'items.food.Pie_Red': 1,
'items.food.Pie_Shade': 1,
'items.food.Pie_Skeleton': 1,
'items.food.Pie_Zombie': 1,
'items.food.Pie_White': 1,
};
updateOp.$push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_harvestfeast_pie',
title: 'Happy Harvest Feast!',
text: 'Gobble gobble, you\'ve received an assortment of pie for your Pets!',
destination: '/inventory/stable',
},
seen: false,
},
};
} else if (typeof user.items.gear.owned.armor_special_turkeyArmorBase !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_turkeyHelmGilded'] = true;
updateOp.$set['items.gear.owned.armor_special_turkeyArmorGilded'] = true;
updateOp.$set['items.gear.owned.back_special_turkeyTailGilded'] = true;
updateOp.$push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_harvestfeast_gilded_set',
title: 'Happy Harvest Feast!',
text: 'Gobble gobble, you\'ve received the Gilded Turkey Armor, Helm, and Tail!',
destination: '/inventory/equipment',
},
seen: false,
},
};
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
updateOp.$set['items.gear.owned.head_special_turkeyHelmBase'] = true;
updateOp.$set['items.gear.owned.armor_special_turkeyArmorBase'] = true;
updateOp.$set['items.gear.owned.back_special_turkeyTailBase'] = true;
updateOp.$push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_harvestfeast_base_set',
title: 'Happy Harvest Feast!',
text: 'Gobble gobble, you\'ve received the Turkey Armor, Helm, and Tail!',
destination: '/inventory/equipment',
},
seen: false,
},
};
} else if (user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
updateOp.$set['items.mounts.Turkey-Gilded'] = true;
updateOp.$push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_harvestfeast_gilded_mount',
title: 'Happy Harvest Feast!',
text: 'Gobble gobble, you\'ve received the Gilded Turkey Mount!',
destination: '/inventory/stable',
},
seen: false,
},
};
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
updateOp.$set['items.pets.Turkey-Gilded'] = 5;
updateOp.$push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_harvestfeast_gilded_pet',
title: 'Happy Harvest Feast!',
text: 'Gobble gobble, you\'ve received the Gilded Turkey Pet!',
destination: '/inventory/stable',
},
seen: false,
},
};
} else if (user.items && user.items.pets && user.items.pets['Turkey-Base']) {
updateOp.$set['items.mounts.Turkey-Base'] = true;
updateOp.$push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_harvestfeast_base_mount',
title: 'Happy Harvest Feast!',
text: 'Gobble gobble, you\'ve received the Turkey Mount!',
destination: '/inventory/stable',
},
seen: false,
},
};
} else {
updateOp.$set['items.pets.Turkey-Base'] = 5;
updateOp.$push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_harvestfeast_base_pet',
title: 'Happy Harvest Feast!',
text: 'Gobble gobble, you\'ve received the Turkey Pet!',
destination: '/inventory/stable',
},
seen: false,
},
};
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.updateOne({ _id: user._id }, updateOp).exec();
}
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2024-10-20') },
};
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],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
}

125
migrations/users/nye.js Normal file
View File

@@ -0,0 +1,125 @@
/* eslint-disable no-console */
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20231228_nye';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const updateOp = {
$set: { migration: MIGRATION_NAME },
$push: { },
};
const data = {
title: 'Happy New Year!',
destination: '/inventory/equipment',
};
if (typeof user.items.gear.owned.head_special_nye2023 !== 'undefined') {
updateOp.$inc = {
'items.food.Candy_Skeleton': 1,
'items.food.Candy_Base': 1,
'items.food.Candy_CottonCandyBlue': 1,
'items.food.Candy_CottonCandyPink': 1,
'items.food.Candy_Shade': 1,
'items.food.Candy_White': 1,
'items.food.Candy_Golden': 1,
'items.food.Candy_Zombie': 1,
'items.food.Candy_Desert': 1,
'items.food.Candy_Red': 1,
};
data.icon = 'notif_candy_nye';
data.text = 'Youve received an assortment of candy to celebrate with your Pets!';
data.destination = '/inventory/stable';
} else if (typeof user.items.gear.owned.head_special_nye2022 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2023'] = true;
data.icon = 'notif_2023hat_nye';
data.text = 'Take on your resolutions with style in this Ludicrous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2022'] = true;
data.icon = 'notif_2022hat_nye';
data.text = 'Take on your resolutions with style in this Fabulous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2021'] = true;
data.icon = 'notif_2021hat_nye';
data.text = 'Take on your resolutions with style in this Preposterous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2020'] = true;
data.icon = 'notif_2020hat_nye';
data.text = 'Take on your resolutions with style in this Extravagant Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2019'] = true;
data.icon = 'notif_2019hat_nye';
data.text = 'Take on your resolutions with style in this Outrageous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2018'] = true;
data.icon = 'notif_2018hat_nye';
data.text = 'Take on your resolutions with style in this Outlandish Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2017'] = true;
data.icon = 'notif_2017hat_nye';
data.text = 'Take on your resolutions with style in this Fanciful Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2016'] = true;
data.icon = 'notif_2016hat_nye';
data.text = 'Take on your resolutions with style in this Whimsical Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2015'] = true;
data.icon = 'notif_2015hat_nye';
data.text = 'Take on your resolutions with style in this Ridiculous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2014'] = true;
data.icon = 'notif_2014hat_nye';
data.text = 'Take on your resolutions with style in this Silly Party Hat!';
} else {
updateOp.$set['items.gear.owned.head_special_nye'] = true;
data.icon = 'notif_2013hat_nye';
data.text = 'Take on your resolutions with style in this Absurd Party Hat!';
}
updateOp.$push.notifications = {
type: 'ITEM_RECEIVED',
data,
seen: false,
};
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.updateOne({ _id: user._id }, updateOp).exec();
}
export default async function processUsers () {
const query = {
'auth.timestamps.loggedin': { $gt: new Date('2023-12-01') },
migration: { $ne: MIGRATION_NAME },
};
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],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
}

210
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "habitica",
"version": "5.29.2",
"version": "5.32.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habitica",
"version": "5.29.2",
"version": "5.32.5",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "^7.22.10",
@@ -22,7 +22,7 @@
"apple-auth": "^1.0.9",
"babel-preset-env": "^1.7.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"body-parser": "^1.20.3",
"bootstrap": "^4.6.2",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
@@ -33,7 +33,7 @@
"eslint": "^8.55.0",
"eslint-config-habitrpg": "^6.2.3",
"eslint-plugin-mocha": "^5.0.0",
"express": "^4.19.2",
"express": "^4.21.1",
"express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0",
"firebase-admin": "^12.1.1",
@@ -56,7 +56,7 @@
"method-override": "^3.0.0",
"moment": "^2.29.4",
"moment-recur": "^1.0.7",
"mongoose": "^7.6.3",
"mongoose": "^7.8.3",
"morgan": "^1.10.0",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
@@ -92,7 +92,7 @@
"chai-as-promised": "^7.1.1",
"chai-moment": "^0.1.0",
"chalk": "^5.3.0",
"cross-spawn": "^7.0.3",
"cross-spawn": "^7.0.5",
"mocha": "^5.1.1",
"monk": "^7.3.4",
"nyc": "^15.1.0",
@@ -3044,9 +3044,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz",
"integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==",
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
"optional": true,
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -6244,9 +6244,9 @@
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -6256,7 +6256,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -6291,11 +6291,11 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/body-parser/node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -6577,14 +6577,15 @@
}
},
"node_modules/call-bind": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
"integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.3",
"set-function-length": "^1.2.0"
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
@@ -7369,9 +7370,9 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -7500,9 +7501,9 @@
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
"integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -8376,9 +8377,9 @@
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
@@ -8497,6 +8498,17 @@
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
@@ -9980,36 +9992,36 @@
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -10062,11 +10074,11 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/express/node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -10531,12 +10543,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -13348,10 +13360,27 @@
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
"integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA=="
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/ip-address/node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"node_modules/ip-address/node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
@@ -14961,9 +14990,12 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
@@ -15564,13 +15596,13 @@
}
},
"node_modules/mongoose": {
"version": "7.6.8",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.6.8.tgz",
"integrity": "sha512-q9zAySH+UtOK5yonWyNcLfq3PxrY6s4gdta4qNGKNOE2yTVoY9FP4hQtvWYnv4rkdk7T8QmQMC7bbhJjDxIunw==",
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.3.tgz",
"integrity": "sha512-eFnbkKgyVrICoHB6tVJ4uLanS7d5AIo/xHkEbQeOv6g2sD7gh/1biRwvFifsmbtkIddQVNr3ROqHik6gkknN3g==",
"dependencies": {
"bson": "^5.5.0",
"kareem": "2.5.1",
"mongodb": "5.9.1",
"mongodb": "5.9.2",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -15593,9 +15625,9 @@
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "5.9.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.1.tgz",
"integrity": "sha512-NBGA8AfJxGPeB12F73xXwozt8ZpeIPmCUeWRwl9xejozTXFes/3zaep9zhzs1B/nKKsw4P3I4iPfXl3K7s6g+Q==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz",
"integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==",
"dependencies": {
"bson": "^5.5.0",
"mongodb-connection-string-url": "^2.6.0",
@@ -17592,9 +17624,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
"node_modules/path-type": {
"version": "1.1.0",
@@ -19337,9 +19369,9 @@
}
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -19372,6 +19404,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -19386,14 +19426,14 @@
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -19519,11 +19559,11 @@
}
},
"node_modules/side-channel": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"dependencies": {
"call-bind": "^1.0.6",
"call-bind": "^1.0.7",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
@@ -19861,15 +19901,15 @@
}
},
"node_modules/socks": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
"integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"dependencies": {
"ip": "^2.0.0",
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.13.0",
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.29.2",
"version": "5.32.5",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -17,7 +17,7 @@
"apple-auth": "^1.0.9",
"babel-preset-env": "^1.7.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"body-parser": "^1.20.3",
"bootstrap": "^4.6.2",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
@@ -28,7 +28,7 @@
"eslint": "^8.55.0",
"eslint-config-habitrpg": "^6.2.3",
"eslint-plugin-mocha": "^5.0.0",
"express": "^4.19.2",
"express": "^4.21.1",
"express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0",
"firebase-admin": "^12.1.1",
@@ -51,7 +51,7 @@
"method-override": "^3.0.0",
"moment": "^2.29.4",
"moment-recur": "^1.0.7",
"mongoose": "^7.6.3",
"mongoose": "^7.8.3",
"morgan": "^1.10.0",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
@@ -110,6 +110,7 @@
"start:simple": "node ./website/server/index.js",
"debug": "gulp nodemon --inspect",
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
"mongo:test": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data-testing --number 1 --quiet",
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc",
"heroku-postbuild": ".heroku/report_deploy.sh"
@@ -120,7 +121,7 @@
"chai-as-promised": "^7.1.1",
"chai-moment": "^0.1.0",
"chalk": "^5.3.0",
"cross-spawn": "^7.0.3",
"cross-spawn": "^7.0.5",
"mocha": "^5.1.1",
"monk": "^7.3.4",
"nyc": "^15.1.0",

View File

@@ -44,7 +44,6 @@ describe('bug-report', () => {
USER_HOURGLASSES: 0,
USER_ID: userId,
USER_LEVEL: 1,
USER_OFFSET_MONTHS: 0,
USER_PAYMENT_PLATFORM: undefined,
USER_SUBSCRIPTION: undefined,
USER_TIMEZONE_OFFSET: 0,

View File

@@ -154,6 +154,14 @@ describe('cron', async () => {
expect(user.purchased.plan.consecutive.count).to.equal(1);
});
it('increments plan.cumulativeCount', async () => {
user.purchased.plan.cumulativeCount = 0;
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.cumulativeCount).to.equal(1);
});
it('increments plan.consecutive.count by more than 1 if user skipped months between logins', async () => {
user.purchased.plan.dateUpdated = moment().subtract(2, 'months').toDate();
user.purchased.plan.consecutive.count = 0;
@@ -163,12 +171,13 @@ describe('cron', async () => {
expect(user.purchased.plan.consecutive.count).to.equal(2);
});
it('decrements plan.consecutive.offset when offset is greater than 0', async () => {
user.purchased.plan.consecutive.offset = 2;
it('increments plan.cumulativeCount by more than 1 if user skipped months between logins', async () => {
user.purchased.plan.dateUpdated = moment().subtract(3, 'months').toDate();
user.purchased.plan.cumulativeCount = 0;
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.consecutive.offset).to.equal(1);
expect(user.purchased.plan.cumulativeCount).to.equal(3);
});
it('does not award unearned plan.consecutive.trinkets if subscription ended during an absence', async () => {
@@ -185,12 +194,12 @@ describe('cron', async () => {
});
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => {
user.purchased.plan.consecutive.gemCapExtra = 25;
user.purchased.plan.consecutive.gemCapExtra = 26;
user.purchased.plan.consecutive.count = 5;
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25);
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
});
it('does not reset plan stats if we are before the last day of the cancelled month', async () => {
@@ -205,16 +214,14 @@ describe('cron', async () => {
user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 });
user.purchased.plan.consecutive.gemCapExtra = 20;
user.purchased.plan.consecutive.count = 5;
user.purchased.plan.consecutive.offset = 1;
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.customerId).to.not.exist;
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0);
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(20);
expect(user.purchased.plan.consecutive.count).to.equal(0);
expect(user.purchased.plan.consecutive.offset).to.equal(0);
});
describe('for a 1-month recurring subscription', async () => {
@@ -236,13 +243,11 @@ describe('cron', async () => {
user1.purchased.plan.dateUpdated = moment().toDate();
user1.purchased.plan.planId = 'basic';
user1.purchased.plan.consecutive.count = 0;
user1.purchased.plan.perkMonthCount = 0;
user1.purchased.plan.consecutive.offset = 0;
user1.purchased.plan.consecutive.trinkets = 0;
user1.purchased.plan.consecutive.trinkets = 1;
user1.purchased.plan.consecutive.gemCapExtra = 0;
});
it('does not increment consecutive benefits after the first month', async () => {
it('increments consecutive benefits', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days')
.toDate());
@@ -253,75 +258,8 @@ describe('cron', async () => {
user: user1, tasksByType, daysMissed, analytics,
});
expect(user1.purchased.plan.consecutive.count).to.equal(1);
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
});
it('does not increment consecutive benefits after the second month', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days')
.toDate());
// Add 1 month to simulate what happens a month after the subscription was created.
// Add 2 days so that we're sure we're not affected by any start-of-month effects
// e.g., from time zone oddness.
await cron({
user: user1, tasksByType, daysMissed, analytics,
});
expect(user1.purchased.plan.consecutive.count).to.equal(2);
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
});
it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => {
user1.purchased.plan.perkMonthCount = 1;
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days')
.toDate());
// Add 1 month to simulate what happens a month after the subscription was created.
// Add 2 days so that we're sure we're not affected by any start-of-month effects
// e.g., from time zone oddness.
await cron({
user: user1, tasksByType, daysMissed, analytics,
});
expect(user1.purchased.plan.perkMonthCount).to.equal(0);
expect(user1.purchased.plan.consecutive.count).to.equal(2);
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
});
it('increments consecutive benefits after the third month', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days')
.toDate());
// Add 1 month to simulate what happens a month after the subscription was created.
// Add 2 days so that we're sure we're not affected by any start-of-month effects
// e.g., from time zone oddness.
await cron({
user: user1, tasksByType, daysMissed, analytics,
});
expect(user1.purchased.plan.consecutive.count).to.equal(3);
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
});
it('does not increment consecutive benefits after the fourth month', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
.add(2, 'days')
.toDate());
// Add 1 month to simulate what happens a month after the subscription was created.
// Add 2 days so that we're sure we're not affected by any start-of-month effects
// e.g., from time zone oddness.
await cron({
user: user1, tasksByType, daysMissed, analytics,
});
expect(user1.purchased.plan.consecutive.count).to.equal(4);
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(2);
});
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
@@ -332,33 +270,8 @@ describe('cron', async () => {
user: user1, tasksByType, daysMissed, analytics,
});
expect(user1.purchased.plan.consecutive.count).to.equal(10);
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
});
it('initializes plan.perkMonthCount if necessary', async () => {
user.purchased.plan.perkMonthCount = undefined;
clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated)
.utcOffset(0)
.startOf('month')
.add(1, 'months')
.add(2, 'days')
.toDate());
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.perkMonthCount).to.equal(1);
user.purchased.plan.perkMonthCount = undefined;
user.purchased.plan.consecutive.count = 8;
clock.restore();
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days')
.toDate());
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.perkMonthCount).to.equal(2);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(11);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
});
@@ -379,14 +292,12 @@ describe('cron', async () => {
user3.purchased.plan.customerId = 'subscribedId';
user3.purchased.plan.dateUpdated = moment().toDate();
user3.purchased.plan.planId = 'basic_3mo';
user3.purchased.plan.perkMonthCount = 0;
user3.purchased.plan.consecutive.count = 0;
user3.purchased.plan.consecutive.offset = 3;
user3.purchased.plan.consecutive.trinkets = 1;
user3.purchased.plan.consecutive.gemCapExtra = 5;
user3.purchased.plan.consecutive.gemCapExtra = 0;
});
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
it('increments consecutive benefits', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days')
.toDate());
@@ -394,102 +305,8 @@ describe('cron', async () => {
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(1);
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
});
it('does not increment consecutive benefits in the middle of the period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(2);
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
});
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(3);
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
});
it('increments consecutive benefits the month after the second paid period has started', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(4);
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
});
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
user3.purchased.plan.perkMonthCount = 2;
user3.purchased.plan.consecutive.trinkets = 1;
user3.purchased.plan.consecutive.gemCapExtra = 5;
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.perkMonthCount).to.equal(2);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
});
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(5);
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
});
it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(6);
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
});
it('increments consecutive benefits the month after the third paid period has started', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(7);
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(3);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(15);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(2);
});
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
@@ -500,8 +317,7 @@ describe('cron', async () => {
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(10);
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(4);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(11);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
});
@@ -523,14 +339,12 @@ describe('cron', async () => {
user6.purchased.plan.customerId = 'subscribedId';
user6.purchased.plan.dateUpdated = moment().toDate();
user6.purchased.plan.planId = 'google_6mo';
user6.purchased.plan.perkMonthCount = 0;
user6.purchased.plan.consecutive.count = 0;
user6.purchased.plan.consecutive.offset = 6;
user6.purchased.plan.consecutive.trinkets = 2;
user6.purchased.plan.consecutive.gemCapExtra = 10;
user6.purchased.plan.consecutive.trinkets = 1;
user6.purchased.plan.consecutive.gemCapExtra = 0;
});
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
it('increments benefits', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days')
.toDate());
@@ -538,74 +352,8 @@ describe('cron', async () => {
user: user6, tasksByType, daysMissed, analytics,
});
expect(user6.purchased.plan.consecutive.count).to.equal(1);
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
});
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6, tasksByType, daysMissed, analytics,
});
expect(user6.purchased.plan.consecutive.count).to.equal(6);
expect(user6.purchased.plan.consecutive.offset).to.equal(0);
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
});
it('increments consecutive benefits the month after the second paid period has started', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6, tasksByType, daysMissed, analytics,
});
expect(user6.purchased.plan.consecutive.count).to.equal(7);
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
user6.purchased.plan.perkMonthCount = 2;
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6, tasksByType, daysMissed, analytics,
});
expect(user6.purchased.plan.perkMonthCount).to.equal(2);
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
it('increments consecutive benefits the month after the third paid period has started', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6, tasksByType, daysMissed, analytics,
});
expect(user6.purchased.plan.consecutive.count).to.equal(13);
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
expect(user6.purchased.plan.consecutive.trinkets).to.equal(6);
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
});
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(19, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6, tasksByType, daysMissed, analytics,
});
expect(user6.purchased.plan.consecutive.count).to.equal(19);
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
expect(user6.purchased.plan.consecutive.trinkets).to.equal(8);
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(2);
});
});
@@ -626,11 +374,10 @@ describe('cron', async () => {
user12.purchased.plan.dateUpdated = moment().toDate();
user12.purchased.plan.planId = 'basic_12mo';
user12.purchased.plan.consecutive.count = 0;
user12.purchased.plan.consecutive.offset = 12;
user12.purchased.plan.consecutive.trinkets = 4;
user12.purchased.plan.consecutive.gemCapExtra = 20;
user12.purchased.plan.consecutive.trinkets = 1;
user12.purchased.plan.consecutive.gemCapExtra = 26;
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
it('increments consecutive benefits the month after the second paid period has started', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days')
.toDate());
@@ -638,61 +385,20 @@ describe('cron', async () => {
user: user12, tasksByType, daysMissed, analytics,
});
expect(user12.purchased.plan.consecutive.count).to.equal(1);
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(12, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user12, tasksByType, daysMissed, analytics,
});
expect(user12.purchased.plan.consecutive.count).to.equal(12);
expect(user12.purchased.plan.consecutive.offset).to.equal(0);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
it('increments consecutive benefits the month after the second paid period has started', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user12, tasksByType, daysMissed, analytics,
});
expect(user12.purchased.plan.consecutive.count).to.equal(13);
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(8);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
});
it('increments consecutive benefits the month after the third paid period has started', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(25, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user12, tasksByType, daysMissed, analytics,
});
expect(user12.purchased.plan.consecutive.count).to.equal(25);
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(12);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26);
});
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(37, 'months')
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(10, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user12, tasksByType, daysMissed, analytics,
});
expect(user12.purchased.plan.consecutive.count).to.equal(37);
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(16);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
expect(user12.purchased.plan.consecutive.count).to.equal(10);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(11);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26);
});
});
@@ -715,11 +421,11 @@ describe('cron', async () => {
.toDate();
user3g.purchased.plan.planId = null;
user3g.purchased.plan.consecutive.count = 0;
user3g.purchased.plan.consecutive.offset = 3;
user3g.purchased.plan.cumulativeCount = 0;
user3g.purchased.plan.consecutive.trinkets = 1;
user3g.purchased.plan.consecutive.gemCapExtra = 5;
user3g.purchased.plan.consecutive.gemCapExtra = 0;
it('does not increment consecutive benefits in the first month of the gift subscription', async () => {
it('increments benefits', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days')
.toDate());
@@ -727,35 +433,9 @@ describe('cron', async () => {
user: user3g, tasksByType, daysMissed, analytics,
});
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
expect(user3g.purchased.plan.consecutive.offset).to.equal(2);
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
});
it('does not increment consecutive benefits in the second month of the gift subscription', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3g, tasksByType, daysMissed, analytics,
});
expect(user3g.purchased.plan.consecutive.count).to.equal(2);
expect(user3g.purchased.plan.consecutive.offset).to.equal(1);
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
});
it('does not increment consecutive benefits in the third month of the gift subscription', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3g, tasksByType, daysMissed, analytics,
});
expect(user3g.purchased.plan.consecutive.count).to.equal(3);
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
expect(user3g.purchased.plan.cumulativeCount).to.equal(1);
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2);
});
it('does not increment consecutive benefits in the month after the gift subscription has ended', async () => {
@@ -767,84 +447,9 @@ describe('cron', async () => {
});
// subscription has been erased by now
expect(user3g.purchased.plan.consecutive.count).to.equal(0);
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(0); // erased
});
});
describe('for a 6-month recurring subscription where the user has incorrect consecutive month data from prior bugs', async () => {
const user6x = new User({
auth: {
local: {
username: 'username6x',
lowerCaseUsername: 'username6x',
email: 'email6x@example.com',
salt: 'salt',
hashed_password: 'hashed_password', // eslint-disable-line camelcase
},
},
});
// user6x has a 6-month recurring subscription starting 8 months in the past
// before issue #4819 was fixed
user6x.purchased.plan.customerId = 'subscribedId';
user6x.purchased.plan.dateUpdated = moment().toDate();
user6x.purchased.plan.planId = 'basic_6mo';
user6x.purchased.plan.consecutive.count = 8;
user6x.purchased.plan.consecutive.offset = 0;
user6x.purchased.plan.consecutive.trinkets = 3;
user6x.purchased.plan.consecutive.gemCapExtra = 15;
it('increments consecutive benefits in the first month since the fix for #4819 goes live', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6x, tasksByType, daysMissed, analytics,
});
expect(user6x.purchased.plan.consecutive.count).to.equal(9);
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
});
it('does not increment consecutive benefits in the second month after the fix goes live', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6x, tasksByType, daysMissed, analytics,
});
expect(user6x.purchased.plan.consecutive.count).to.equal(10);
expect(user6x.purchased.plan.consecutive.offset).to.equal(4);
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
});
it('does not increment consecutive benefits in the third month after the fix goes live', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6x, tasksByType, daysMissed, analytics,
});
expect(user6x.purchased.plan.consecutive.count).to.equal(11);
expect(user6x.purchased.plan.consecutive.offset).to.equal(3);
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
});
it('increments consecutive benefits in the seventh month after the fix goes live', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6x, tasksByType, daysMissed, analytics,
});
expect(user6x.purchased.plan.consecutive.count).to.equal(15);
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(7);
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2);
expect(user3g.purchased.plan.cumulativeCount).to.equal(1);
});
});
});
@@ -888,12 +493,12 @@ describe('cron', async () => {
expect(user.purchased.plan.consecutive.count).to.equal(0);
});
it('does not decrement plan.consecutive.offset when offset is greater than 0', async () => {
user.purchased.plan.consecutive.offset = 1;
it('does not increment plan.cumulativeCount', async () => {
user.purchased.plan.cumulativeCount = 0;
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.consecutive.offset).to.equal(1);
expect(user.purchased.plan.cumulativeCount).to.equal(0);
});
it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', async () => {
@@ -913,12 +518,12 @@ describe('cron', async () => {
});
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => {
user.purchased.plan.consecutive.gemCapExtra = 25;
user.purchased.plan.consecutive.gemCapExtra = 26;
user.purchased.plan.consecutive.count = 5;
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25);
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
});
it('does nothing to plan stats if we are before the last day of the cancelled month', async () => {
@@ -928,22 +533,6 @@ describe('cron', async () => {
});
expect(user.purchased.plan.customerId).to.not.exist;
});
xit('does nothing to plan stats when we are after the last day of the cancelled month', async () => {
user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 });
user.purchased.plan.consecutive.gemCapExtra = 20;
user.purchased.plan.consecutive.count = 5;
user.purchased.plan.consecutive.offset = 1;
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.customerId).to.exist;
expect(user.purchased.plan.consecutive.gemCapExtra).to.exist;
expect(user.purchased.plan.consecutive.count).to.exist;
expect(user.purchased.plan.consecutive.offset).to.exist;
});
});
describe('todos', async () => {

View File

@@ -715,7 +715,7 @@ describe('Purchasing a group plan for group', () => {
const mysteryItem = { title: 'item' };
const mysteryItems = [mysteryItem];
const consecutive = {
trinkets: 3,
trinkets: 4,
gemCapExtra: 20,
offset: 1,
count: 13,

View File

@@ -12,6 +12,7 @@ import {
} from '../../../../helpers/api-unit.helper';
import * as worldState from '../../../../../website/server/libs/worldState';
import { TransactionModel } from '../../../../../website/server/models/transaction';
import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events';
describe('payments/index', () => {
let user;
@@ -65,7 +66,6 @@ describe('payments/index', () => {
mysteryItems: [],
consecutive: {
trinkets: 0,
offset: 0,
gemCapExtra: 0,
},
};
@@ -108,14 +108,8 @@ describe('payments/index', () => {
});
it('add a transaction entry to the recipient', async () => {
recipient.purchased.plan = plan;
expect(recipient.purchased.plan.extraMonths).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.extraMonths).to.eql(3);
const transactions = await TransactionModel
.find({ userId: recipient._id })
.sort({ createdAt: -1 })
@@ -177,6 +171,45 @@ describe('payments/index', () => {
expect(recipient.purchased.plan.dateUpdated).to.exist;
});
it('does not reset gemCapExtra if they already had one', async () => {
recipient.purchased.plan.consecutive.gemCapExtra = 10;
await api.createSubscription(data);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('sets gemCapExtra to 0 if they receive a 3 month sub', async () => {
data.gift.subscription.key = 'basic_3mo';
data.gift.subscription.months = 3;
await api.createSubscription(data);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
});
it('sets gemCapExtra to max if they receive a 12 month sub', async () => {
recipient.purchased.plan.consecutive.gemCapExtra = 10;
data.gift.subscription.key = 'basic_12mo';
data.gift.subscription.months = 12;
await api.createSubscription(data);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
it('gives user 1 hourglass if they have no active subscription', async () => {
await api.createSubscription(data);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
});
it('does not give any hourglasses if they have an active subscription', async () => {
recipient.purchased.plan = plan;
await api.createSubscription(data);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(plan.consecutive.trinkets);
});
it('sets plan.dateUpdated if it did exist but the user has cancelled', async () => {
recipient.purchased.plan.dateUpdated = moment().subtract(1, 'days').toDate();
recipient.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
@@ -235,116 +268,6 @@ describe('payments/index', () => {
expect(recipient.purchased.plan.customerId).to.eql('customer-id');
});
it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 1;
recipient.purchased.plan.customerId = undefined;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
});
it('sets plan.perkMonthCount to 1 if field is not initialized', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = -1;
recipient.purchased.plan.customerId = undefined;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
});
it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 2;
recipient.purchased.plan.customerId = undefined;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
});
it('adds to plan.perkMonthCount if user is already subscribed', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 1;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
});
it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 2;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => {
recipient.purchased.plan.perkMonthCount = 0;
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => {
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('awards perks if plan.perkMonthCount goes over 3', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 2;
data.sub.key = 'basic_earned';
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('sets plan.customerId to "Gift" if it does not already exist', async () => {
expect(recipient.purchased.plan.customerId).to.not.exist;
@@ -421,8 +344,8 @@ describe('payments/index', () => {
context('Active Promotion', () => {
beforeEach(() => {
sinon.stub(worldState, 'getCurrentEventList').returns([{
...common.content.events.winter2021Promo,
event: 'winter2021',
...REPEATING_EVENTS.giftOneGetOne,
event: 'g1g1',
}]);
});
@@ -438,22 +361,30 @@ describe('payments/index', () => {
expect(user.purchased.plan.dateTerminated).to.exist;
expect(user.purchased.plan.dateUpdated).to.exist;
expect(user.purchased.plan.dateCreated).to.exist;
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(recipient.purchased.plan.customerId).to.eql('Gift');
expect(recipient.purchased.plan.dateTerminated).to.exist;
expect(recipient.purchased.plan.dateUpdated).to.exist;
expect(recipient.purchased.plan.dateCreated).to.exist;
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
});
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
user.purchased.plan = plan;
expect(user.purchased.plan.extraMonths).to.eql(0);
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(user.purchased.plan.extraMonths).to.eql(3);
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(recipient.purchased.plan.customerId).to.eql('Gift');
@@ -466,10 +397,12 @@ describe('payments/index', () => {
recipient.purchased.plan = plan;
expect(recipient.purchased.plan.extraMonths).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.extraMonths).to.eql(3);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(user.purchased.plan.customerId).to.eql('Gift');
@@ -484,11 +417,15 @@ describe('payments/index', () => {
expect(user.purchased.plan.extraMonths).to.eql(0);
expect(recipient.purchased.plan.extraMonths).to.eql(0);
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
await api.createSubscription(data);
expect(user.purchased.plan.extraMonths).to.eql(3);
expect(recipient.purchased.plan.extraMonths).to.eql(3);
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
});
it('sends a private message about the promotion', async () => {
@@ -511,7 +448,6 @@ describe('payments/index', () => {
expect(user.purchased.plan.customerId).to.eql('customer-id');
expect(user.purchased.plan.dateUpdated).to.exist;
expect(user.purchased.plan.gemsBought).to.eql(0);
expect(user.purchased.plan.perkMonthCount).to.eql(0);
expect(user.purchased.plan.paymentMethod).to.eql('Payment Method');
expect(user.purchased.plan.extraMonths).to.eql(0);
expect(user.purchased.plan.dateTerminated).to.eql(null);
@@ -549,33 +485,6 @@ describe('payments/index', () => {
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
});
it('keeps plan.perkMonthCount when changing subscription type', async () => {
await api.createSubscription(data);
user.purchased.plan.perkMonthCount = 2;
await api.createSubscription(data);
expect(user.purchased.plan.perkMonthCount).to.eql(2);
});
it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => {
user.purchased.plan.perkMonthCount = 2;
await api.createSubscription(data);
expect(user.purchased.plan.perkMonthCount).to.eql(0);
});
it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => {
user.purchased.plan.perkMonthCount = 2;
await api.createSubscription(data);
expect(user.purchased.plan.perkMonthCount).to.eql(0);
});
it('updates plan.consecutive.offset when changing subscription type', async () => {
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.offset).to.eql(3);
data.sub.key = 'basic_6mo';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.offset).to.eql(6);
});
it('awards the Royal Purple Jackalope pet', async () => {
await api.createSubscription(data);
@@ -694,6 +603,7 @@ describe('payments/index', () => {
expect(user.purchased.plan.dateCreated).to.eql(created);
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
expect(user.purchased.plan.customerId).to.eql('customer-id');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
});
@@ -741,55 +651,20 @@ describe('payments/index', () => {
});
context('Block subscription perks', () => {
it('adds block months to plan.consecutive.offset', async () => {
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.offset).to.eql(3);
});
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.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 () => {
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => {
data.sub.key = 'basic_6mo';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => {
it('adds 26 to plan.consecutive.gemCapExtra for 12 month block', async () => {
data.sub.key = 'basic_12mo';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
it('does not raise plan.consecutive.gemCapExtra higher than 25', async () => {
it('does not raise plan.consecutive.gemCapExtra higher than 26', async () => {
data.sub.key = 'basic_12mo';
await api.createSubscription(data);
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
it('adds a plan.consecutive.trinkets for 3 month block', async () => {
@@ -798,20 +673,29 @@ describe('payments/index', () => {
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
});
it('adds 2 plan.consecutive.trinkets for 6 month block', async () => {
it('adds 1 plan.consecutive.trinkets for 6 month block', async () => {
data.sub.key = 'basic_6mo';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
});
it('adds 4 plan.consecutive.trinkets for 12 month block', async () => {
it('adds 1 plan.consecutive.trinkets for 12 month block if they had promo', async () => {
user.purchased.plan.hourglassPromoReceived = new Date();
data.sub.key = 'basic_12mo';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
});
it('adds 12 plan.consecutive.trinkets for 12 month block', async () => {
data.sub.key = 'basic_12mo';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
context('Upgrades subscription', () => {
@@ -819,70 +703,38 @@ describe('payments/index', () => {
beforeEach(async () => {
data.updatedFrom = { logic: 'payDifference' };
});
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
@@ -894,7 +746,7 @@ describe('payments/index', () => {
data.updatedFrom.key = 'basic_3mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
});
@@ -902,70 +754,39 @@ describe('payments/index', () => {
beforeEach(async () => {
data.updatedFrom = { logic: 'payFull' };
});
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
@@ -977,7 +798,7 @@ describe('payments/index', () => {
data.updatedFrom.key = 'basic_3mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
});
@@ -988,30 +809,13 @@ describe('payments/index', () => {
data.updatedFrom = { logic: 'refundAndRepay' };
});
context('Upgrades within first half of subscription', () => {
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-01-10'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
@@ -1019,28 +823,10 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-02-05'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-01-08'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
@@ -1054,17 +840,17 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-01-31'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
@@ -1072,35 +858,17 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-01-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-01-08'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
it('2 plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
@@ -1108,10 +876,10 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-08-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
@@ -1125,11 +893,11 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-07-31'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
});
context('Upgrades within second half of subscription', () => {
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
it('Adds 0 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
@@ -1144,16 +912,16 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-01-20'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
});
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
@@ -1161,17 +929,17 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-02-24'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
it('Adds 0 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
@@ -1179,17 +947,17 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-01-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
@@ -1197,10 +965,10 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-05-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
@@ -1214,17 +982,17 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-03-03'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
it('Adds 0 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
@@ -1232,17 +1000,17 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2022-05-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
@@ -1250,10 +1018,10 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2023-05-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
@@ -1267,7 +1035,7 @@ describe('payments/index', () => {
clock = sinon.useFakeTimers(new Date('2023-09-03'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
});
afterEach(async () => {
@@ -1277,22 +1045,6 @@ describe('payments/index', () => {
});
context('Downgrades subscription', () => {
it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
data.sub.key = 'basic_earned';
data.updatedFrom = { key: 'basic_6mo' };
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
@@ -1300,28 +1052,12 @@ describe('payments/index', () => {
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
data.sub.key = 'basic_3mo';
data.updatedFrom = { key: 'basic_12mo' };
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
});
it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
data.sub.key = 'basic_earned';
data.updatedFrom = { key: 'basic_6mo' };
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
});
it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => {
@@ -1331,12 +1067,12 @@ describe('payments/index', () => {
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
data.sub.key = 'basic_3mo';
data.updatedFrom = { key: 'basic_12mo' };
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
});
});
});
@@ -1453,6 +1189,32 @@ describe('payments/index', () => {
expect(user.purchased.plan.extraMonths).to.eql(0);
});
it('does not reset gemCapExtra', async () => {
user.purchased.plan.consecutive.gemCapExtra = 12;
await api.cancelSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(12);
});
it('initializes gemCapExtra', async () => {
await api.cancelSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
});
it('initializes hourglasses', async () => {
await api.cancelSubscription(data);
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
});
it('does not reset owned hourglasses', async () => {
user.purchased.plan.consecutive.trinkets = 12;
await api.cancelSubscription(data);
expect(user.purchased.plan.consecutive.trinkets).to.eql(12);
});
it('sends an email', async () => {
await api.cancelSubscription(data);

View File

@@ -51,6 +51,7 @@ describe('Stripe - Checkout', () => {
gift: undefined,
sub: undefined,
gemsBlock: gemsBlockKey,
server_url: BASE_URL,
};
expect(gems.validateGiftMessage).to.not.be.called;
@@ -101,6 +102,7 @@ describe('Stripe - Checkout', () => {
gift: JSON.stringify(gift),
sub: undefined,
gemsBlock: undefined,
server_url: BASE_URL,
};
expect(gems.validateGiftMessage).to.be.calledOnce;
@@ -155,6 +157,7 @@ describe('Stripe - Checkout', () => {
gift: JSON.stringify(gift),
sub: undefined,
gemsBlock: undefined,
server_url: BASE_URL,
};
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
@@ -192,6 +195,7 @@ describe('Stripe - Checkout', () => {
userId: user._id,
gift: undefined,
sub: JSON.stringify(sub),
server_url: BASE_URL,
};
expect(subscriptions.checkSubData).to.be.calledOnce;
@@ -258,6 +262,7 @@ describe('Stripe - Checkout', () => {
userId: user._id,
gift: undefined,
sub: JSON.stringify(sub),
server_url: BASE_URL,
groupId,
};
@@ -328,8 +333,9 @@ describe('Stripe - Checkout', () => {
user.purchased.plan.customerId = customerId;
const metadata = {
userId: user._id,
type: 'edit-card-user',
userId: user._id,
server_url: BASE_URL,
};
const res = await createEditCardCheckoutSession({ user }, stripe);
@@ -418,6 +424,7 @@ describe('Stripe - Checkout', () => {
const metadata = {
userId: user._id,
type: 'edit-card-group',
server_url: BASE_URL,
groupId,
};
@@ -455,6 +462,7 @@ describe('Stripe - Checkout', () => {
userId: anotherUser._id,
type: 'edit-card-group',
groupId,
server_url: BASE_URL,
};
const res = await createEditCardCheckoutSession({ user: anotherUser, groupId }, stripe);

View File

@@ -308,6 +308,7 @@ describe('Stripe - One Time Payments', () => {
customerId,
paymentMethod: 'Gift',
gift,
autoRenews: false,
gemsBlock: undefined,
});
});

View File

@@ -173,6 +173,7 @@ describe('Stripe - Subscriptions', () => {
paymentMethod: 'Stripe',
sub: sinon.match({ ...sub }),
groupId: null,
autoRenews: true,
});
});
@@ -197,6 +198,7 @@ describe('Stripe - Subscriptions', () => {
paymentMethod: 'Stripe',
sub: sinon.match({ ...sub }),
groupId,
autoRenews: true,
});
});
@@ -231,6 +233,7 @@ describe('Stripe - Subscriptions', () => {
paymentMethod: 'Stripe',
sub: sinon.match({ ...sub }),
groupId,
autoRenews: true,
});
});
});

View File

@@ -16,6 +16,7 @@ import * as subscriptions from '../../../../../../website/server/libs/payments/s
const { i18n } = common;
describe('Stripe - Webhooks', () => {
const BASE_URL = nconf.get('BASE_URL');
const stripe = stripeModule('test');
const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET');
const headers = {};
@@ -284,7 +285,9 @@ describe('Stripe - Webhooks', () => {
const session = {};
beforeEach(() => {
session.metadata = {};
session.metadata = {
server_url: BASE_URL,
};
event = { type: eventType, data: { object: session } };
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
constructEventStub.returns(event);

View File

@@ -59,7 +59,7 @@ describe('POST /debug/jump-time', () => {
expect(resultDate.getDate()).to.eql(today.getDate());
expect(resultDate.getMonth()).to.eql(today.getMonth());
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 355 })).time);
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 365 })).time);
expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1);
});

View File

@@ -60,12 +60,12 @@ describe('PUT /heroes/:heroId', () => {
expect(heroRes.profile).to.have.all.keys(['name']);
// test response values
expect(heroRes.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
expect(heroRes.balance).to.equal(3 + 2.5); // 3+2.5 for first contrib level
expect(heroRes.contributor.level).to.equal(1);
expect(heroRes.purchased.ads).to.equal(true);
// test hero values
await hero.sync();
expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
expect(hero.balance).to.equal(3 + 2.5); // 3+2.5 for first contrib level
expect(hero.contributor.level).to.equal(1);
expect(hero.purchased.ads).to.equal(true);
expect(hero.auth.blocked).to.equal(prevBlockState);
@@ -136,12 +136,12 @@ describe('PUT /heroes/:heroId', () => {
expect(heroRes.profile).to.have.all.keys(['name']);
// test response values
expect(heroRes.balance).to.equal(1); // 0+1 for sixth contrib level
expect(heroRes.balance).to.equal(15); // 0+15 for sixth contrib level
expect(heroRes.contributor.level).to.equal(6);
expect(heroRes.items.pets['Dragon-Hydra']).to.equal(5);
// test hero values
await hero.sync();
expect(hero.balance).to.equal(1); // 0+1 for sixth contrib level
expect(hero.balance).to.equal(15); // 0+15 for sixth contrib level
expect(hero.contributor.level).to.equal(6);
expect(hero.items.pets['Dragon-Hydra']).to.equal(5);
});

View File

@@ -0,0 +1,56 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import common from '../../../../../website/common';
describe('GET /members/username/:username', () => {
let user;
before(async () => {
user = await generateUser();
});
it('validates req.params.username', async () => {
await expect(user.get('/members/username/')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns a member public data only', async () => {
// make sure user has all the fields that can be returned by the getMember call
const member = await generateUser({
contributor: { level: 1 },
backer: { tier: 3 },
preferences: {
costume: false,
background: 'volcano',
},
secret: {
text: 'Clark Kent',
},
});
const memberRes = await user.get(`/members/username/${member.auth.local.username}`);
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
]);
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
].sort());
expect(memberRes.stats.maxMP).to.exist;
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
expect(memberRes.inbox.optOut).to.exist;
expect(memberRes.inbox.canReceive).to.exist;
expect(memberRes.inbox.messages).to.not.exist;
expect(memberRes.secret).to.not.exist;
expect(memberRes.blocks).to.not.exist;
});
});

View File

@@ -31,7 +31,7 @@ describe('POST /user/buy-mystery-set/:key', () => {
expect(res.data).to.eql({
items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared
purchasedPlanConsecutive: user.purchased.plan.consecutive,
purchasedPlanConsecutive: JSON.parse(JSON.stringify(user.purchased.plan.consecutive)),
});
expect(res.message).to.equal(t('hourglassPurchaseSet'));
});

View File

@@ -0,0 +1,104 @@
import find from 'lodash/find';
import {
generateUser,
translate as t,
} from '../../../helpers/api-integration/v4';
/**
* Checks the messages array if the uniqueMessageId has the like flag
* @param {InboxMessage[]} messages
* @param {String} uniqueMessageId
* @param {String} userId
* @param {Boolean} likeStatus
*/
function expectMessagesLikeStatus (messages, uniqueMessageId, userId, likeStatus) {
const messageToCheck = find(messages, { uniqueMessageId });
expect(messageToCheck.likes[userId]).to.equal(likeStatus);
}
// eslint-disable-next-line mocha/no-exclusive-tests
describe('POST /inbox/like-private-message/:messageId', () => {
let userToSendMessage;
const getLikeUrl = messageId => `/inbox/like-private-message/${messageId}`;
before(async () => {
userToSendMessage = await generateUser();
});
it('Returns an error when private message is not found', async () => {
await expect(userToSendMessage.post(getLikeUrl('some-unknown-id')))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
it('Likes a message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[receiver._id]).to.equal(true);
const senderMessages = await userToSendMessage.get('/inbox/messages');
expectMessagesLikeStatus(senderMessages, uniqueMessageId, receiver._id, true);
const receiversMessages = await receiver.get('/inbox/messages');
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, receiver._id, true);
});
it('Allows to likes their own private message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await userToSendMessage.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[userToSendMessage._id]).to.equal(true);
const messages = await userToSendMessage.get('/inbox/messages');
expectMessagesLikeStatus(messages, uniqueMessageId, userToSendMessage._id, true);
const receiversMessages = await receiver.get('/inbox/messages');
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, userToSendMessage._id, true);
});
it('Unlikes a message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[receiver._id]).to.equal(true);
const unlikeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(unlikeResult.likes[receiver._id]).to.equal(false);
const messages = await userToSendMessage.get('/inbox/messages');
const messageToCheck = find(messages, { id: sentMessageResult.message.id });
expect(messageToCheck.likes[receiver._id]).to.equal(false);
});
});

View File

@@ -183,8 +183,6 @@ describe('cron utility functions', () => {
});
describe('getPlanContext', () => {
const now = new Date(2022, 5, 1);
function baseUserData (count, offset, planId) {
return {
purchased: {
@@ -192,7 +190,7 @@ describe('cron utility functions', () => {
consecutive: {
count,
offset,
gemCapExtra: 25,
gemCapExtra: 26,
trinkets: 19,
},
quantity: 1,
@@ -213,52 +211,19 @@ describe('cron utility functions', () => {
};
}
it('monthly plan, next date in 3 months', () => {
it('elapsedMonths is 0 if its the same month', () => {
const user = baseUserData(60, 0, 'group_plan_auto');
user.purchased.plan.perkMonthCount = 0;
const planContext = getPlanContext(user, now);
expect(planContext.nextHourglassDate)
.to.be.sameMoment('2022-08-10T02:00:00.144Z');
const planContext = getPlanContext(user, new Date(2022, 4, 20));
expect(planContext.elapsedMonths).to.equal(0);
});
it('monthly plan, next date in 1 month', () => {
const user = baseUserData(62, 0, 'group_plan_auto');
user.purchased.plan.perkMonthCount = 2;
it('elapsedMonths is 1 after one month', () => {
const user = baseUserData(60, 0, 'group_plan_auto');
const planContext = getPlanContext(user, now);
const planContext = getPlanContext(user, new Date(2022, 5, 11));
expect(planContext.nextHourglassDate)
.to.be.sameMoment('2022-06-10T02:00:00.144Z');
});
it('multi-month plan, no offset', () => {
const user = baseUserData(60, 0, 'basic_3mo');
const planContext = getPlanContext(user, now);
expect(planContext.nextHourglassDate)
.to.be.sameMoment('2022-06-10T02:00:00.144Z');
});
it('multi-month plan with offset', () => {
const user = baseUserData(60, 1, 'basic_3mo');
const planContext = getPlanContext(user, now);
expect(planContext.nextHourglassDate)
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
});
it('multi-month plan with perk count', () => {
const user = baseUserData(60, 1, 'basic_3mo');
user.purchased.plan.perkMonthCount = 2;
const planContext = getPlanContext(user, now);
expect(planContext.nextHourglassDate)
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
expect(planContext.elapsedMonths).to.equal(1);
});
});
});

View File

@@ -19,8 +19,8 @@ describe('releaseDates', () => {
});
describe('armoire', () => {
it('should only contain valid armoire names', () => {
const lastReleaseDate = maxBy(Object.values(ARMOIRE_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-20`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-20`));
const lastReleaseDate = maxBy(Object.values(ARMOIRE_RELEASE_DATES), value => new Date(`${value.year}-${value.month}-22`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month}-22`));
Object.keys(ARMOIRE_RELEASE_DATES).forEach(key => {
expect(find(armoire.all, { set: key }), `${key} is not a valid armoire set`).to.exist;
});
@@ -40,8 +40,8 @@ describe('releaseDates', () => {
describe('eggs', () => {
it('should only contain valid egg names', () => {
const lastReleaseDate = maxBy(Object.values(EGGS_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-${value.day}`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-${lastReleaseDate.day}`));
const lastReleaseDate = maxBy(Object.values(EGGS_RELEASE_DATES), value => new Date(`${value.year}-${value.month}-${value.day}`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month}-${lastReleaseDate.day + 1}`));
Object.keys(EGGS_RELEASE_DATES).forEach(key => {
expect(eggs.all[key], `${key} is not a valid egg name`).to.exist;
});
@@ -61,8 +61,8 @@ describe('releaseDates', () => {
describe('hatchingPotions', () => {
it('should only contain valid potion names', () => {
const lastReleaseDate = maxBy(Object.values(HATCHING_POTIONS_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-${value.day}`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-${lastReleaseDate.day}`));
const lastReleaseDate = maxBy(Object.values(HATCHING_POTIONS_RELEASE_DATES), value => new Date(`${value.year}-${value.month}-${value.day}`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month}-${lastReleaseDate.day + 1}`));
Object.keys(HATCHING_POTIONS_RELEASE_DATES).forEach(key => {
expect(hatchingPotions.all[key], `${key} is not a valid potion name`).to.exist;
});

View File

@@ -1,4 +1,5 @@
// eslint-disable-next-line max-len
import maxBy from 'lodash/maxBy';
import moment from 'moment';
import nconf from 'nconf';
import {
@@ -10,6 +11,7 @@ import QUEST_BUNDLES from '../../website/common/script/content/bundles';
import potions from '../../website/common/script/content/hatching-potions';
import SPELLS from '../../website/common/script/content/spells';
import QUEST_SEASONAL from '../../website/common/script/content/quests/seasonal';
import { HATCHING_POTIONS_RELEASE_DATES } from '../../website/common/script/content/constants/releaseDates';
function validateMatcher (matcher, checkedDate) {
expect(matcher.end).to.be.a('date');
@@ -142,6 +144,12 @@ describe('Content Schedule', () => {
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
});
it('sets the end date in new year for a winter gala', () => {
const date = new Date('2025-01-04');
const matchers = getAllScheduleMatchingGroups(date);
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
});
it('uses correct date for first hours of the month', () => {
// if the date is checked before CONTENT_SWITCHOVER_TIME_OFFSET,
// it should be considered the previous month
@@ -222,6 +230,8 @@ describe('Content Schedule', () => {
});
it('premium hatching potions', () => {
const lastReleaseDate = maxBy(Object.values(HATCHING_POTIONS_RELEASE_DATES), value => new Date(`${value.year}-${value.month}-${value.day}`));
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month}-${lastReleaseDate.day + 1}`));
const potionKeys = Object.keys(potions.premium);
Object.keys(MONTHLY_SCHEDULE).forEach(key => {
const monthlyPotions = MONTHLY_SCHEDULE[key][21].find(item => item.type === 'premiumHatchingPotions');
@@ -262,6 +272,21 @@ describe('Content Schedule', () => {
expect(matcher.match('backgroundkey072024')).to.be.true;
});
it('allows background matching the month for new backgrounds from multiple years', () => {
const date = new Date('2026-07-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey072024')).to.be.true;
expect(matcher.match('backgroundkey072025')).to.be.true;
expect(matcher.match('backgroundkey072026')).to.be.true;
});
it('allows background matching the previous month in the first week for new backgrounds', () => {
const date = new Date('2024-09-02');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey082024')).to.be.true;
expect(matcher.match('backgroundkey092024')).to.be.false;
});
it('disallows background in the future', () => {
const date = new Date('2024-07-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
@@ -281,19 +306,26 @@ describe('Content Schedule', () => {
expect(matcher.match('backgroundkey022021')).to.be.true;
});
it('allows background even yeared backgrounds in first half of year', () => {
it('allows even yeared backgrounds in first half of year', () => {
const date = new Date('2025-02-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey022024')).to.be.true;
expect(matcher.match('backgroundkey082022')).to.be.true;
});
it('allows background odd yeared backgrounds in second half of year', () => {
it('allows odd yeared backgrounds in second half of year', () => {
const date = new Date('2024-08-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey022023')).to.be.true;
expect(matcher.match('backgroundkey082021')).to.be.true;
});
it('allows odd yeared backgrounds in beginning of january', () => {
const date = new Date('2025-01-06');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey122024'), 'backgroundkey122024').to.be.true;
expect(matcher.match('backgroundkey062023'), 'backgroundkey062022').to.be.true;
});
});
describe('timeTravelers matcher', () => {

View File

@@ -7,7 +7,7 @@ module.exports = {
extends: [
'habitrpg/lib/vue',
],
ignorePatterns: ['dist/', 'node_modules/'],
ignorePatterns: ['dist/', 'node_modules/', '*.d.ts'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',

View File

@@ -3934,9 +3934,9 @@
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -3946,7 +3946,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -4140,6 +4140,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"get-intrinsic": "^1.2.6"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4616,9 +4643,9 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -5422,6 +5449,19 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/dunder-proto": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",
"integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -5464,9 +5504,9 @@
}
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
@@ -5588,12 +5628,9 @@
}
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"engines": {
"node": ">= 0.4"
}
@@ -5611,6 +5648,17 @@
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
},
"node_modules/es-object-atoms": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
@@ -6695,36 +6743,36 @@
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -6733,6 +6781,10 @@
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/array-flatten": {
@@ -6877,12 +6929,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -7125,15 +7177,20 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"dunder-proto": "^1.0.0",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.0.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
@@ -7253,11 +7310,11 @@
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -7353,9 +7410,9 @@
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"engines": {
"node": ">= 0.4"
},
@@ -7383,9 +7440,9 @@
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
},
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -8919,6 +8976,14 @@
"markdown-it": "bin/markdown-it.js"
}
},
"node_modules/math-intrinsics": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@@ -8958,9 +9023,12 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-source-map": {
"version": "1.1.0",
@@ -9862,9 +9930,12 @@
}
},
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -10310,9 +10381,9 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
"node_modules/path-type": {
"version": "4.0.0",
@@ -11129,11 +11200,11 @@
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -11736,9 +11807,9 @@
"dev": true
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -11771,6 +11842,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -11855,14 +11934,14 @@
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -11951,13 +12030,68 @@
}
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"

View File

@@ -1085,6 +1085,11 @@
width: 141px;
height: 147px;
}
.background_first_snow_forest {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_first_snow_forest.png');
width: 141px;
height: 147px;
}
.background_floating_islands {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_floating_islands.png');
width: 141px;
@@ -2391,6 +2396,11 @@
width: 141px;
height: 147px;
}
.background_winter_landscape_with_cabin {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_winter_landscape_with_cabin.png');
width: 141px;
height: 147px;
}
.background_winter_mountain_range {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_winter_mountain_range.png');
width: 141px;
@@ -29624,6 +29634,11 @@
width: 90px;
height: 90px;
}
.broad_armor_armoire_festiveHelperOveralls {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_festiveHelperOveralls.png');
width: 114px;
height: 90px;
}
.broad_armor_armoire_fiddlersCoat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_fiddlersCoat.png');
width: 114px;
@@ -29894,6 +29909,11 @@
width: 114px;
height: 90px;
}
.broad_armor_armoire_snowyFluffTrimmedCoat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_snowyFluffTrimmedCoat.png');
width: 114px;
height: 90px;
}
.broad_armor_armoire_softBlackSuit {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_softBlackSuit.png');
width: 114px;
@@ -30199,6 +30219,11 @@
width: 114px;
height: 90px;
}
.head_armoire_festiveHelperHat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_festiveHelperHat.png');
width: 114px;
height: 90px;
}
.head_armoire_fiddlersCap {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_fiddlersCap.png');
width: 114px;
@@ -30454,6 +30479,11 @@
width: 117px;
height: 120px;
}
.head_armoire_snowyTrapperHat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_snowyTrapperHat.png');
width: 114px;
height: 90px;
}
.head_armoire_stormKnightHelm {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_stormKnightHelm.png');
width: 114px;
@@ -31099,6 +31129,11 @@
width: 90px;
height: 90px;
}
.slim_armor_armoire_festiveHelperOveralls {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_festiveHelperOveralls.png');
width: 114px;
height: 90px;
}
.slim_armor_armoire_fiddlersCoat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_fiddlersCoat.png');
width: 114px;
@@ -31369,6 +31404,11 @@
width: 114px;
height: 90px;
}
.slim_armor_armoire_snowyFluffTrimmedCoat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_snowyFluffTrimmedCoat.png');
width: 114px;
height: 90px;
}
.slim_armor_armoire_softBlackSuit {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_softBlackSuit.png');
width: 114px;
@@ -33679,6 +33719,16 @@
width: 90px;
height: 90px;
}
.head_mystery_202501 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202501.png');
width: 114px;
height: 90px;
}
.shield_mystery_202501 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202501.png');
width: 114px;
height: 90px;
}
.back_mystery_201402 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_201402.png');
width: 90px;
@@ -35339,6 +35389,21 @@
width: 114px;
height: 90px;
}
.broad_armor_mystery_202412 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202412.png');
width: 114px;
height: 90px;
}
.head_mystery_202412 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202412.png');
width: 114px;
height: 90px;
}
.slim_armor_mystery_202412 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202412.png');
width: 114px;
height: 90px;
}
.broad_armor_mystery_301404 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png');
width: 90px;
@@ -37904,6 +37969,26 @@
width: 114px;
height: 90px;
}
.broad_armor_special_winter2025Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_winter2025Healer.png');
width: 114px;
height: 90px;
}
.broad_armor_special_winter2025Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_winter2025Mage.png');
width: 114px;
height: 90px;
}
.broad_armor_special_winter2025Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_winter2025Rogue.png');
width: 114px;
height: 90px;
}
.broad_armor_special_winter2025Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_winter2025Warrior.png');
width: 114px;
height: 90px;
}
.broad_armor_special_yeti {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_yeti.png');
width: 90px;
@@ -38179,6 +38264,26 @@
width: 114px;
height: 90px;
}
.head_special_winter2025Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_winter2025Healer.png');
width: 114px;
height: 90px;
}
.head_special_winter2025Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_winter2025Mage.png');
width: 114px;
height: 90px;
}
.head_special_winter2025Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_winter2025Rogue.png');
width: 114px;
height: 90px;
}
.head_special_winter2025Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_winter2025Warrior.png');
width: 114px;
height: 90px;
}
.head_special_yeti {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_yeti.png');
width: 90px;
@@ -38344,6 +38449,21 @@
width: 114px;
height: 90px;
}
.shield_special_winter2025Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_winter2025Healer.png');
width: 114px;
height: 90px;
}
.shield_special_winter2025Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_winter2025Rogue.png');
width: 114px;
height: 90px;
}
.shield_special_winter2025Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_winter2025Warrior.png');
width: 114px;
height: 90px;
}
.shield_special_yeti {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_yeti.png');
width: 90px;
@@ -38564,6 +38684,26 @@
width: 114px;
height: 90px;
}
.slim_armor_special_winter2025Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_winter2025Healer.png');
width: 114px;
height: 90px;
}
.slim_armor_special_winter2025Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_winter2025Mage.png');
width: 114px;
height: 90px;
}
.slim_armor_special_winter2025Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_winter2025Rogue.png');
width: 114px;
height: 90px;
}
.slim_armor_special_winter2025Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_winter2025Warrior.png');
width: 114px;
height: 90px;
}
.slim_armor_special_yeti {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_yeti.png');
width: 90px;
@@ -38784,6 +38924,26 @@
width: 114px;
height: 90px;
}
.weapon_special_winter2025Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_winter2025Healer.png');
width: 114px;
height: 90px;
}
.weapon_special_winter2025Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_winter2025Mage.png');
width: 114px;
height: 90px;
}
.weapon_special_winter2025Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_winter2025Rogue.png');
width: 114px;
height: 90px;
}
.weapon_special_winter2025Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_winter2025Warrior.png');
width: 114px;
height: 90px;
}
.weapon_special_yeti {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_yeti.png');
width: 90px;
@@ -39937,6 +40097,66 @@
width: 28px;
height: 28px;
}
.notif_2013hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2013hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2014hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2014hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2015hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2015hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2016hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2016hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2017hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2017hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2018hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2018hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2019hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2019hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2020hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2020hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2021hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2021hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2022hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2022hat_nye.png');
width: 28px;
height: 28px;
}
.notif_2023hat_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_2023hat_nye.png');
width: 28px;
height: 28px;
}
.notif_candy_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_candy_nye.png');
width: 28px;
height: 28px;
}
.notif_habitoween_base_mount {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_habitoween_base_mount.png');
width: 28px;
@@ -39982,6 +40202,41 @@
width: 28px;
height: 28px;
}
.notif_harvestfeast_base_mount {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_harvestfeast_base_mount.png');
width: 28px;
height: 28px;
}
.notif_harvestfeast_base_pet {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_harvestfeast_base_pet.png');
width: 28px;
height: 28px;
}
.notif_harvestfeast_base_set {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_harvestfeast_base_set.png');
width: 28px;
height: 28px;
}
.notif_harvestfeast_gilded_mount {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_harvestfeast_gilded_mount.png');
width: 28px;
height: 28px;
}
.notif_harvestfeast_gilded_pet {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_harvestfeast_gilded_pet.png');
width: 28px;
height: 28px;
}
.notif_harvestfeast_gilded_set {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_harvestfeast_gilded_set.png');
width: 28px;
height: 28px;
}
.notif_harvestfeast_pie {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_harvestfeast_pie.png');
width: 28px;
height: 28px;
}
.notif_head_special_nye {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_head_special_nye.png');
width: 28px;
@@ -40127,6 +40382,11 @@
width: 28px;
height: 28px;
}
.notif_subscriber_reward {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_subscriber_reward.png');
width: 28px;
height: 28px;
}
.npc_bailey {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/npc_bailey.png');
width: 60px;
@@ -41997,6 +42257,11 @@
width: 105px;
height: 105px;
}
.Mount_Body_BearCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_BearCub-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Body_BearCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_BearCub-Glass.png');
width: 105px;
@@ -42437,6 +42702,11 @@
width: 105px;
height: 105px;
}
.Mount_Body_Cactus-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cactus-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Body_Cactus-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cactus-Glass.png');
width: 105px;
@@ -43122,6 +43392,11 @@
width: 105px;
height: 105px;
}
.Mount_Body_Dragon-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Dragon-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Body_Dragon-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Dragon-Glass.png');
width: 105px;
@@ -43557,6 +43832,11 @@
width: 105px;
height: 105px;
}
.Mount_Body_FlyingPig-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_FlyingPig-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Body_FlyingPig-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_FlyingPig-Glass.png');
width: 105px;
@@ -43842,6 +44122,11 @@
width: 105px;
height: 105px;
}
.Mount_Body_Fox-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Fox-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Body_Fox-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Fox-Glass.png');
width: 105px;
@@ -44567,6 +44852,11 @@
width: 105px;
height: 105px;
}
.Mount_Body_LionCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_LionCub-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Body_LionCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_LionCub-Glass.png');
width: 105px;
@@ -45072,6 +45362,11 @@
width: 105px;
height: 105px;
}
.Mount_Body_PandaCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_PandaCub-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Body_PandaCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_PandaCub-Glass.png');
width: 105px;
@@ -46362,6 +46657,11 @@
width: 105px;
height: 105px;
}
.Mount_Body_TigerCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_TigerCub-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Body_TigerCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_TigerCub-Glass.png');
width: 105px;
@@ -46957,6 +47257,11 @@
width: 135px;
height: 135px;
}
.Mount_Body_Wolf-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Wolf-Gingerbread.png');
width: 135px;
height: 135px;
}
.Mount_Body_Wolf-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Wolf-Glass.png');
width: 135px;
@@ -47492,6 +47797,11 @@
width: 105px;
height: 105px;
}
.Mount_Head_BearCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_BearCub-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Head_BearCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_BearCub-Glass.png');
width: 105px;
@@ -47932,6 +48242,11 @@
width: 105px;
height: 105px;
}
.Mount_Head_Cactus-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cactus-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Head_Cactus-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cactus-Glass.png');
width: 105px;
@@ -48617,6 +48932,11 @@
width: 105px;
height: 105px;
}
.Mount_Head_Dragon-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Dragon-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Head_Dragon-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Dragon-Glass.png');
width: 105px;
@@ -49052,6 +49372,11 @@
width: 105px;
height: 105px;
}
.Mount_Head_FlyingPig-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_FlyingPig-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Head_FlyingPig-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_FlyingPig-Glass.png');
width: 105px;
@@ -49337,6 +49662,11 @@
width: 105px;
height: 105px;
}
.Mount_Head_Fox-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Fox-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Head_Fox-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Fox-Glass.png');
width: 105px;
@@ -50062,6 +50392,11 @@
width: 105px;
height: 105px;
}
.Mount_Head_LionCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_LionCub-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Head_LionCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_LionCub-Glass.png');
width: 105px;
@@ -50567,6 +50902,11 @@
width: 105px;
height: 105px;
}
.Mount_Head_PandaCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_PandaCub-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Head_PandaCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_PandaCub-Glass.png');
width: 105px;
@@ -51857,6 +52197,11 @@
width: 105px;
height: 105px;
}
.Mount_Head_TigerCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_TigerCub-Gingerbread.png');
width: 105px;
height: 105px;
}
.Mount_Head_TigerCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_TigerCub-Glass.png');
width: 105px;
@@ -52452,6 +52797,11 @@
width: 135px;
height: 135px;
}
.Mount_Head_Wolf-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Wolf-Gingerbread.png');
width: 135px;
height: 135px;
}
.Mount_Head_Wolf-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Wolf-Glass.png');
width: 135px;
@@ -53002,6 +53352,11 @@
width: 81px;
height: 99px;
}
.Pet-BearCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-BearCub-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-BearCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-BearCub-Glass.png');
width: 81px;
@@ -53467,6 +53822,11 @@
width: 81px;
height: 99px;
}
.Pet-Cactus-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-Cactus-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-Glass.png');
width: 81px;
@@ -54182,6 +54542,11 @@
width: 81px;
height: 99px;
}
.Pet-Dragon-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-Dragon-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-Glass.png');
width: 81px;
@@ -54652,6 +55017,11 @@
width: 81px;
height: 99px;
}
.Pet-FlyingPig-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-FlyingPig-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-FlyingPig-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-FlyingPig-Glass.png');
width: 81px;
@@ -54962,6 +55332,11 @@
width: 81px;
height: 99px;
}
.Pet-Fox-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Fox-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-Fox-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Fox-Glass.png');
width: 81px;
@@ -55717,6 +56092,11 @@
width: 81px;
height: 99px;
}
.Pet-LionCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-LionCub-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-LionCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-LionCub-Glass.png');
width: 81px;
@@ -56247,6 +56627,11 @@
width: 81px;
height: 99px;
}
.Pet-PandaCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-PandaCub-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-PandaCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-PandaCub-Glass.png');
width: 81px;
@@ -57567,6 +57952,11 @@
width: 81px;
height: 99px;
}
.Pet-TigerCub-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-TigerCub-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-TigerCub-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-TigerCub-Glass.png');
width: 81px;
@@ -58187,6 +58577,11 @@
width: 81px;
height: 99px;
}
.Pet-Wolf-Gingerbread {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Wolf-Gingerbread.png');
width: 78px;
height: 96px;
}
.Pet-Wolf-Glass {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Wolf-Glass.png');
width: 81px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -3,9 +3,9 @@
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: bold;
line-height: 1.71;
border: 1px solid transparent;
padding: 4px 12px;
line-height: 1.714;
border: 2px solid transparent;
padding: 2px 12px;
border-radius: 4px;
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
color: $white;
@@ -19,7 +19,7 @@
}
&:focus {
border-color: $purple-400;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
&:active, &.active:not(.btn-flat) {
@@ -30,9 +30,9 @@
cursor: default;
color: $gray-50;
opacity: 0.75;
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
box-shadow: none;
background-color: $gray-700;
border: 1px solid transparent;
border: 2px solid transparent;
.svg {
color: $gray-300;
@@ -40,7 +40,7 @@
}
&.with-icon {
height: 2rem; // otherwise would something set the height to 33px
height: 32px; // otherwise would something set the height to 33px
display: flex;
flex-direction: row;
align-items: center;
@@ -48,40 +48,47 @@
}
.btn-front {
border: none !important;
font-size: 16px;
line-height: 1.5;
padding: 7.5px 15.5px;
padding: 2px 17px;
&:hover {
border: none !important;
}
}
.btn-primary {
background: $purple-200;
border: 1px solid transparent;
border: 2px solid transparent;
line-height: 1.714;
--icon-color: #{$purple-500};
&:focus {
background: $purple-200;
border-color: $purple-400;
border: 2px solid $purple-400;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
--icon-color: #{$white};
}
&:not(:disabled):not(.disabled) {
&:hover {
background: #5d3b9c;
border: 1px solid transparent;
background: $purple-200;
border: 2px solid transparent;
--icon-color: #{$white};
}
&:active, &.active {
background: $purple-200;
border: 1px solid transparent;
border: 2px solid transparent;
box-shadow: none;
--icon-color: #{$white};
}
&:active:focus, &.active:focus {
box-shadow: none;
border-color: $purple-400;
border: 2px solid $purple-400;
}
}
@@ -94,42 +101,45 @@
.btn-secondary,
.dropdown > .btn-secondary.dropdown-toggle:not(.btn-success),
.show > .btn-secondary.dropdown-toggle:not(.btn-success)
{
.show > .btn-secondary.dropdown-toggle:not(.btn-success) {
background: $white;
border: 1px solid transparent;
border: 2px solid transparent;
color: $gray-50;
--icon-color: #{$gray-200};
&:focus, &:active {
color: $gray-50;
background: $white;
border-color: $purple-400;
border: 2px solid $purple-400;
color: $gray-50;
--icon-color: #{$purple-300};
}
&:not(:disabled):not(.disabled) {
&:active, &.active {
background: $white;
border: 2px solid $purple-400;
color: $purple-300;
--icon-color: #{$purple-300};
&:focus {
color: $purple-300;
box-shadow: none;
border-color: $purple-400;
border: 2px solid $purple-400;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
&:active {
box-shadow: none;
}
background: $white;
border: 1px solid transparent;
}
&:hover {
color: $purple-300;
background: $white !important;
border: 1px solid transparent;
border: 2px solid transparent;
--icon-color: #{$purple-300};
.svg {
@@ -151,91 +161,116 @@
.btn-danger {
background: $maroon-100;
border: 1px solid transparent;
border: 2px solid transparent;
box-shadow: 0 1px 3px 0 rgba($black, 0.16), 0 1px 3px 0 rgba($black, 0.24);
&:hover:not(:disabled):not(.disabled) {
background: #e14e4e;
border: 1px solid transparent;
background: $maroon-100;
border: 2px solid transparent;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
&:focus {
background: $maroon-100;
border-color: $purple-400;
border: 2px solid $purple-400;
}
&:not(:disabled):not(.disabled):active:focus, &:not(:disabled):not(.disabled).active:focus {
box-shadow: none;
border-color: $purple-400;
border: 2px solid $purple-400;
}
&:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active {
background: $maroon-100;
border: 1px solid transparent;
border: 2px solid $purple-400;
}
}
.btn-warning {
background: $orange-10;
box-shadow: 0 1px 3px 0 rgba($black, 0.16), 0 1px 3px 0 rgba($black, 0.24);
color: $white !important;
&:hover:not(:disabled):not(.disabled) {
background: $orange-100;
background: $orange-10;
color: $white;
}
&:focus {
background: $orange-10;
border-color: $purple-400;
border: 2px solid $purple-400;
}
&:not(:disabled):not(.disabled):active:focus, &:not(:disabled):not(.disabled).active:focus {
border: 2px solid $purple-400;
box-shadow: none;
border-color: $purple-400;
}
&:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active {
background: $orange-10;
box-shadow: none;
}
}
.btn-success {
background: $green-50;
border: 1px solid transparent;
border: 2px solid transparent;
&:hover:not(:disabled):not(.disabled) {
background: #32bd8a;
border: 1px solid transparent;
background: $green-50;
border: 2px solid transparent;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
&:focus {
background: $green-50;
border-color: $purple-400;
border: 2px solid $purple-400;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
&:not(:disabled):not(.disabled):active:focus, &:not(:disabled):not(.disabled).active:focus {
border: 2px solid $purple-400;
box-shadow: none;
border-color: $purple-400;
}
&:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active {
background: $green-50;
border: 1px solid transparent;
border: 2px solid $purple-400;
box-shadow: none;
}
}
.btn-info {
background: $blue-50;
border: 2px solid transparent;
box-shadow: 0 1px 3px 0 rgba($black, 0.16), 0 1px 3px 0 rgba($black, 0.24);
&:disabled {
background: $blue-50;
box-shadow: none;
}
&:hover {
border: 2px solid transparent;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
&:focus {
background: $blue-100;
border: 2px solid $purple-400;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
&:hover:not(:disabled):not(.disabled) {
background-color: $blue-100;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
&:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {
background: $blue-50;
background: $blue-100;
border: 2px solid $purple-400;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
}
@@ -244,7 +279,7 @@
width: 100%;
padding: 8px;
font-size: 14px;
line-height: 1.43;
line-height: 1.714;
font-weight: bold;
text-align: center;
background: $gray-500;
@@ -262,12 +297,22 @@
box-shadow: none;
}
.btn-flat,
.dropdown > .btn-flat.dropdown-toggle:not(.btn-success),
.show > .btn-flat.dropdown-toggle:not(.btn-success) {
&.with-icon {
.svg-icon.color {
color: var(--icon-color);
}
}
}
.btn-cancel {
color: $blue-10;
}
.btn-small {
font-size: 12px;
line-height: 1.33;
padding: 4px 8px;
line-height: 2;
padding: 2px 2px;
}

View File

@@ -78,15 +78,3 @@ $gold-color: #FFA624;
$hourglass-color: #2995CD;
$purple-task: #925cf3;
.gray-200 {
color: $gray-200 !important;
}
.purple-300 {
color: $purple-300 !important;
}
.white {
color: $white !important;
}

View File

@@ -1,8 +1,9 @@
.dropdown > .btn {
padding: 0.219rem 0.75rem;
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: normal;
line-height: 1.714;
padding: 2px 12px;
}
.dropdown-toggle:hover {
@@ -33,11 +34,16 @@
}
.dropdown-menu {
padding: 0px;
border: none;
border: transparent;
border-radius: 2px;
box-shadow: 0 3px 6px 0 rgba(26, 24, 29, 0.16), 0 3px 6px 0 rgba(26, 24, 29, 0.24);
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
padding: 0;
}
.no-min-width {
.dropdown-menu {
min-width: 0 !important;
}
}
// shared dropdown-item styles
@@ -53,6 +59,8 @@
color: $gray-50 !important;
cursor: pointer;
--dropdown-item-hover-icon-color: #{$gray-200};
&:focus {
outline: none;
background-color: inherit;
@@ -87,7 +95,7 @@
&:not(:hover) {
.with-icon .svg-icon {
color: $gray-200;
color: var(dropdown-item-hover-icon-color);
}
}
}
@@ -113,6 +121,10 @@
}
.dropdown-icon-item {
line-height: 1;
padding-top: 2px !important;
padding-bottom: 2px !important;
.svg-icon {
margin: 0px 16px 0px 0px;
vertical-align: middle;
@@ -128,7 +140,6 @@
.dropdown-toggle {
width: 100% !important;
height: 32px;
text-align: left;
}
@@ -147,7 +158,7 @@
// selectList.vue items sizing
.selectListItem .dropdown-item {
padding: 0.25rem 0.75rem;
padding: 0.25rem 1rem 0.25rem 0.75rem;
height: 32px;
&:active, &:hover, &:focus, &.active {

View File

@@ -44,6 +44,10 @@ ul {
color: $purple-200;
}
h4 {
color: $gray-50;
}
.body-text {
font-size: 1em;
color: $gray-10;

View File

@@ -86,3 +86,91 @@ h4 {
.opacity-75 {
opacity: 0.75;
}
.bg-gray-100 {
background-color: $gray-100 !important;
}
.bg-gray-300 {
background-color: $gray-300 !important;
}
.bg-gray-600 {
background-color: $gray-600 !important;
}
.bg-gray-700 {
background-color: $gray-700 !important;
}
.bg-green-10 {
background-color: $green-10 !important;
}
.bg-green-100 {
background-color: $green-100 !important;
}
.bg-purple-100 {
background-color: $purple-100 !important;
}
.bg-purple-300 {
background-color: $purple-300 !important;
}
.bg-white {
background-color: $white !important;
}
.gray-10 {
color: $gray-10 !important;
}
.gray-50 {
color: $gray-50 !important;
}
.gray-200 {
color: $gray-200 !important;
}
.gray-300 {
color: $gray-300 !important;
}
.green-10 {
color: $green-10 !important;
}
.maroon-50 {
color: $maroon-50 !important;
}
.purple-200 {
color: $purple-200 !important;
}
.purple-300 {
color: $purple-300 !important;
}
.purple-600 {
color: $purple-600 !important;
}
.teal-1 {
color: $teal-1 !important;
}
.teal-10 {
color: $teal-10 !important;
}
.yellow-10 {
color: $yellow-10 !important;
}
.white {
color: $white !important;
}

View File

@@ -0,0 +1,32 @@
<svg width="40" height="16" viewBox="0 0 40 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.3333 5.33333L12 8L17.3333 10.6667L20 16L22.6667 10.6667L28 8L22.6667 5.33333L20 0L17.3333 5.33333Z" fill="#BDA8FF"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M25 7.99984L21.6667 9.6665L20 7.99984L25 7.99984Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20 3L21.6666 6.33333L20 8V3Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M15 8L18.3333 6.33333L20 8L15 8Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20 13L18.3333 9.66667L20 8V13Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M15 7.99984L18.3333 9.6665L20 7.99984L15 7.99984Z" fill="#925CF3"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M20 3L18.3333 6.33333L20 8V3Z" fill="#925CF3"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M25 8L21.6667 6.33333L20 8L25 8Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M20 13L21.6666 9.66667L20 8V13Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M20.7999 7.20041L22.3333 8.00041L20.7999 8.80041L19.9999 10.3337L19.1999 8.80041L17.6666 8.00041L19.1999 7.20041L19.9999 5.66707L20.7999 7.20041Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.66667 6.66667L0 8L2.66667 9.33333L4 12L5.33333 9.33333L8 8L5.33333 6.66667L4 4L2.66667 6.66667Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M6.49976 7.99967L4.83309 8.83301L3.99976 7.99967H6.49976Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M3.99998 5.5L4.83331 7.16667L3.99998 8L3.99998 5.5Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M1.5 8L3.16667 7.16667L4 8H1.5Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M4 10.5L3.16667 8.83333L4 8V10.5Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M1.50018 7.99967L3.16685 8.83301L4.00018 7.99967H1.50018Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M4 5.5L3.16667 7.16667L4 8V5.5Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M6.5 8L4.83333 7.16667L4 8H6.5Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M3.99998 10.5L4.83331 8.83333L3.99998 8L3.99998 10.5Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M4.40002 7.59935L5.16669 7.99935L4.40002 8.39935L4.00002 9.16602L3.60002 8.39935L2.83335 7.99935L3.60002 7.59935L4.00002 6.83268L4.40002 7.59935Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.6667 6.66667L32 8L34.6667 9.33333L36 12L37.3333 9.33333L40 8L37.3333 6.66667L36 4L34.6667 6.66667Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M38.4998 7.99967L36.8331 8.83301L35.9998 7.99967H38.4998Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M36 5.5L36.8333 7.16667L36 8V5.5Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M33.5 8L35.1667 7.16667L36 8H33.5Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M36 10.5L35.1667 8.83333L36 8V10.5Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M33.5002 7.99967L35.1668 8.83301L36.0002 7.99967H33.5002Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M36 5.5L35.1667 7.16667L36 8V5.5Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M38.5 8L36.8333 7.16667L36 8H38.5Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M36 10.5L36.8333 8.83333L36 8V10.5Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M36.4 7.59935L37.1667 7.99935L36.4 8.39935L36 9.16602L35.6 8.39935L34.8334 7.99935L35.6 7.59935L36 6.83268L36.4 7.59935Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,7 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144 31">
<g fill="none" fill-rule="evenodd">
<path fill="#FFF" d="M120.876 24.007a2.27 2.27 0 0 0-3.183.41 4.595 4.595 0 0 1-3.663 1.804 4.62 4.62 0 0 1-4.613-4.335c-.005-.35-.009-2.864-.009-3.19a4.627 4.627 0 0 1 4.622-4.622c1.28 0 2.47.51 3.353 1.44a2.269 2.269 0 0 0 3.29-3.125 9.2 9.2 0 0 0-6.643-2.853c-5.05 0-9.16 4.109-9.16 9.16 0 .03.002 3.175.014 3.406a9.158 9.158 0 0 0 9.146 8.657 9.1 9.1 0 0 0 7.257-3.57 2.27 2.27 0 0 0-.411-3.182M134.373 26.221a4.62 4.62 0 0 1-4.613-4.333c-.005-.353-.008-2.877-.008-3.193a4.627 4.627 0 0 1 4.621-4.622 4.627 4.627 0 0 1 4.622 4.622c0 .328-.003 2.84-.009 3.189a4.618 4.618 0 0 1-4.613 4.337m6.891-17.078a2.264 2.264 0 0 0-2.19 1.706 9.095 9.095 0 0 0-4.7-1.313c-5.051 0-9.16 4.109-9.16 9.16 0 .031.001 3.173.013 3.406a9.158 9.158 0 0 0 9.146 8.657 9.118 9.118 0 0 0 4.81-1.37 2.268 2.268 0 0 0 4.35-.899V11.412a2.27 2.27 0 0 0-2.269-2.269M30.546 26.221a4.62 4.62 0 0 1-4.613-4.335c-.006-.35-.01-2.863-.01-3.19a4.627 4.627 0 0 1 4.623-4.623 4.627 4.627 0 0 1 4.622 4.622c0 .328-.004 2.84-.01 3.189a4.618 4.618 0 0 1-4.612 4.337m6.89-17.078a2.264 2.264 0 0 0-2.19 1.706 9.095 9.095 0 0 0-4.7-1.313c-5.052 0-9.16 4.109-9.16 9.16 0 .031 0 3.174.013 3.406a9.158 9.158 0 0 0 9.147 8.657 9.118 9.118 0 0 0 4.809-1.37 2.268 2.268 0 0 0 4.35-.899V11.412a2.27 2.27 0 0 0-2.269-2.269M70.84 9.143a2.27 2.27 0 0 0-2.27 2.27V28.49a2.27 2.27 0 0 0 4.539 0V11.412a2.27 2.27 0 0 0-2.27-2.269M97.563 9.143a2.27 2.27 0 0 0-2.27 2.27V28.49a2.27 2.27 0 0 0 4.538 0V11.412a2.27 2.27 0 0 0-2.268-2.269M59.066 21.888a4.62 4.62 0 0 1-4.613 4.333 4.62 4.62 0 0 1-4.613-4.338c-.006-.35-.009-2.86-.009-3.187a4.627 4.627 0 0 1 4.622-4.622 4.627 4.627 0 0 1 4.622 4.622c0 .315-.004 2.84-.009 3.192M54.453 9.536a9.089 9.089 0 0 0-4.622 1.265V2.33a2.27 2.27 0 0 0-4.537 0V28.49a2.269 2.269 0 0 0 4.35.9 9.117 9.117 0 0 0 4.81 1.37 9.16 9.16 0 0 0 9.146-8.666c.011-.224.013-3.367.013-3.398 0-5.052-4.11-9.16-9.16-9.16M8.92 9.536a9.143 9.143 0 0 0-4.382 1.11V2.33A2.27 2.27 0 0 0 0 2.33v26.16a2.269 2.269 0 1 0 4.538 0V16.763c.173-.147.333-.314.46-.516a4.601 4.601 0 0 1 3.921-2.173 4.627 4.627 0 0 1 4.622 4.622c0 .415-.004 9.233-.01 9.738a2.27 2.27 0 0 0 4.535.172c.01-.225.012-9.814.012-9.91 0-5.052-4.108-9.16-9.159-9.16M88.95 9.143h-2.648V2.33a2.27 2.27 0 0 0-4.538 0v6.813h-2.647a2.27 2.27 0 0 0 0 4.538h2.647V28.49a2.27 2.27 0 0 0 4.538 0V13.681h2.647a2.27 2.27 0 0 0 0-4.538"/>
<path fill="#FF6066" d="M73.025 2.33a2.27 2.27 0 1 1-4.538 0 2.27 2.27 0 0 1 4.538 0"/>
<path fill="#4FB5E8" d="M99.748 2.33a2.27 2.27 0 1 1-4.539 0 2.27 2.27 0 0 1 4.539 0"/>
</g>
<svg width="217" height="48" viewBox="0 0 217 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M108.785 0.0195312C106.343 0.0195312 104.355 1.99967 104.355 4.44184C104.355 6.88401 106.343 8.86415 108.785 8.86415C111.227 8.86415 113.215 6.87668 113.215 4.44184C113.215 2.007 111.227 0.0195312 108.785 0.0195312Z" fill="#FF6165"/>
<path d="M148.564 0.0195312C146.121 0.0195312 144.134 1.99967 144.134 4.44184C144.134 6.88401 146.121 8.86415 148.564 8.86415C151.006 8.86415 152.993 6.87668 152.993 4.44184C152.993 2.007 151.006 0.0195312 148.564 0.0195312Z" fill="#50B5E9"/>
<path d="M184.2 42.1989C181.332 45.8879 177.005 48 172.319 48C164.355 48 157.776 41.8176 157.344 33.9264C157.322 33.5303 157.322 28.8367 157.322 28.7927C157.322 20.5788 164.047 13.8976 172.319 13.8976C176.411 13.8976 180.379 15.5917 183.195 18.54C184.053 19.4347 184.515 20.6154 184.478 21.8548C184.449 23.0943 183.928 24.2457 183.019 25.0964C181.156 26.8565 178.201 26.7759 176.426 24.9277C175.341 23.7983 173.881 23.1749 172.312 23.1749C169.188 23.1749 166.65 25.6904 166.65 28.7853C166.65 29.2694 166.65 32.995 166.665 33.5083C166.841 36.4052 169.327 38.7154 172.312 38.7154C174.087 38.7154 175.722 37.916 176.8 36.5225C178.369 34.4984 181.31 34.1244 183.342 35.6865C184.332 36.4419 184.962 37.5346 185.124 38.7667C185.285 39.9988 184.948 41.2162 184.192 42.1989H184.2ZM216.82 18.4739V43.4164C216.82 45.9392 214.774 47.9927 212.258 47.9927C210.916 47.9927 209.669 47.3986 208.819 46.4159C206.787 47.45 204.543 47.9927 202.262 47.9927C194.533 47.9927 188.145 41.9129 187.727 34.1464C187.705 33.7577 187.705 29.152 187.705 29.1007C187.705 21.0261 194.239 14.4623 202.262 14.4623C204.419 14.4623 206.545 14.9537 208.503 15.8924C209.332 14.6677 210.726 13.8903 212.266 13.8903C214.781 13.8903 216.827 15.9438 216.827 18.4666L216.82 18.4739ZM207.689 33.721C207.697 33.1196 207.704 29.5774 207.704 29.108C207.704 26.0791 205.262 23.6223 202.262 23.6223C199.263 23.6223 196.821 26.0865 196.821 29.108C196.821 29.5701 196.821 33.2443 196.836 33.7577C197.004 36.5812 199.395 38.84 202.262 38.84C205.13 38.84 207.506 36.5959 207.689 33.721ZM63.3042 18.4739V43.4164C63.3042 45.9392 61.2581 47.9927 58.7426 47.9927C57.4006 47.9927 56.1539 47.3986 55.3032 46.4159C53.2717 47.45 51.0276 47.9927 48.7469 47.9927C41.0099 47.9927 34.6296 41.9129 34.2115 34.1464C34.1895 33.8017 34.1895 30.2008 34.1895 29.1007C34.1895 21.0261 40.7238 14.4623 48.7469 14.4623C50.903 14.4623 53.0371 14.9537 54.9878 15.8924C55.8165 14.6677 57.2099 13.8903 58.75 13.8903C61.2654 13.8903 63.3115 15.9438 63.3115 18.4666L63.3042 18.4739ZM48.7469 23.6223C45.7474 23.6223 43.3053 26.0865 43.3053 29.108C43.3053 29.5847 43.3053 33.237 43.32 33.7503C43.4886 36.5812 45.8794 38.84 48.7469 38.84C51.6143 38.84 53.9904 36.5959 54.1738 33.721C54.1811 33.1196 54.1884 29.5847 54.1884 29.108C54.1884 26.0791 51.7463 23.6223 48.7469 23.6223ZM108.78 14.1396C106.338 14.1396 104.351 16.1931 104.351 18.716V43.4164C104.351 45.9392 106.338 47.9927 108.78 47.9927C111.222 47.9927 113.21 45.9392 113.21 43.4164V18.716C113.21 16.1931 111.222 14.1396 108.78 14.1396ZM148.558 14.1396C146.116 14.1396 144.129 16.1931 144.129 18.716V43.4164C144.129 45.9392 146.116 47.9927 148.558 47.9927C151 47.9927 152.988 45.9392 152.988 43.4164V18.716C152.988 16.1931 151 14.1396 148.558 14.1396ZM98.7551 28.866C98.7551 28.91 98.7551 33.5817 98.7331 33.9704C98.3151 41.8396 91.9275 48 84.1978 48C81.917 48 79.6729 47.45 77.6415 46.4012C76.7908 47.3986 75.5441 48 74.1947 48C71.6792 48 69.6331 45.9245 69.6331 43.3797V4.62032C69.6331 2.07548 71.6792 0 74.1947 0C76.7101 0 78.7562 2.07548 78.7562 4.62032V15.1224C80.487 14.411 82.3351 14.037 84.1978 14.037C92.2282 14.037 98.7551 20.6888 98.7551 28.866ZM84.1978 23.285C81.1983 23.285 78.7562 25.7858 78.7562 28.866C78.7562 29.35 78.7562 33.0536 78.7709 33.5743C78.9469 36.4565 81.3303 38.752 84.1978 38.752C87.0653 38.752 89.434 36.4712 89.6247 33.5523C89.6247 32.929 89.6394 29.328 89.6394 28.866C89.6394 25.7858 87.1973 23.285 84.1978 23.285ZM14.3887 14.037C12.6139 14.037 10.8612 14.3597 9.21109 14.9757V4.62766C9.21109 2.08281 7.14299 0.00733401 4.60554 0.00733401C2.06809 0.00733401 0 2.08281 0 4.62766V43.3797C0 45.9319 2.06809 48 4.60554 48C7.14299 48 9.21109 45.9245 9.21109 43.3797V26.5412C9.40176 26.3358 11.1178 23.285 14.3887 23.285C17.4395 23.285 19.9182 25.7858 19.9182 28.866C19.9182 29.482 19.9182 42.529 19.9036 43.2623C19.8376 45.7852 21.759 47.868 24.2524 47.9927C24.3404 47.9927 24.4211 47.9927 24.5091 47.9927C26.9586 47.9927 28.9753 46.0639 29.1 43.607C29.122 43.2257 29.122 29.0053 29.122 28.866C29.122 20.6888 22.5144 14.037 14.3887 14.037ZM136.399 14.037H133.7V4.62032C133.7 2.07548 131.61 0 129.036 0C126.462 0 124.372 2.07548 124.372 4.62032V14.037H121.673C119.106 14.037 117.009 16.1125 117.009 18.6646C117.009 21.2168 119.099 23.285 121.673 23.285H124.372V43.3797C124.372 45.9319 126.462 48 129.036 48C131.61 48 133.7 45.9245 133.7 43.3797V23.285H136.399C138.973 23.285 141.063 21.2095 141.063 18.6646C141.063 16.1198 138.973 14.037 136.399 14.037Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,48 @@
<svg width="53" height="53" viewBox="0 0 53 53" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.4728 37.2881L31.0401 40.6634C30.8722 41.0588 30.533 41.3618 30.1106 41.4581C25.4964 42.5161 15.7591 38.3829 13.3131 34.3281C13.0907 33.958 13.073 33.5035 13.2408 33.108L14.6735 29.7328C15.9802 26.6545 18.829 24.3765 21.9907 23.6412C22.9143 23.4268 23.3484 22.4041 22.8611 21.5907C21.1937 18.8056 20.8534 15.174 22.16 12.0957L23.5927 8.72043C23.7606 8.32501 24.0998 8.02203 24.5199 7.92663C29.1358 6.86935 38.8731 11.0026 41.3173 15.0567C41.5422 15.4259 41.5599 15.8804 41.392 16.2758L39.9593 19.651C38.6527 22.7293 35.8039 25.0073 32.6422 25.7426C31.7185 25.957 31.2844 26.9797 31.7718 27.7931C33.4392 30.5782 33.7795 34.2099 32.4728 37.2881Z" fill="#A9DCF6" fill-opacity="0.8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.664 12.0938C29.7729 10.8666 27.3007 10.4316 25.8346 10.5102L24.7036 13.1747C23.7617 15.3938 23.9622 18.0859 25.2286 20.2007C25.9064 21.3331 26.0063 22.7223 25.4978 23.9203C24.9893 25.1182 23.9206 26.0114 22.6375 26.3096C20.2342 26.8686 18.1583 28.5945 17.2171 30.8118L16.0854 33.478C17.0473 34.5873 19.0775 36.0634 21.9686 37.2906C24.8596 38.5178 27.3318 38.9528 28.7979 38.8742L29.9297 36.208C30.8709 33.9906 30.6703 31.2985 29.4039 29.1837C28.7261 28.0513 28.6263 26.662 29.1348 25.4641C29.6433 24.2662 30.7119 23.3729 31.9957 23.0732C34.3983 22.5158 36.4742 20.7899 37.4162 18.5709L38.5472 15.9064C37.5853 14.7971 35.555 13.321 32.664 12.0938Z" fill="white" fill-opacity="0.9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.5684 13.8635C33.7371 14.784 35.9512 15.9582 35.7471 16.4997C35.5853 16.9371 35.2218 17.7653 33.0852 17.902C31.0091 18.0368 30.0196 19.4736 29.0902 19.0791C28.0092 18.6202 28.3314 17.6878 27.3948 16.2289C26.429 14.7261 26.743 13.3826 26.9685 12.8935C27.2828 12.2137 29.3996 12.9429 31.5684 13.8635Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.3942 16.2291C26.4285 14.7263 26.7424 13.3828 26.9679 12.8937C27.2164 12.3553 28.5959 12.7006 30.2369 13.3243C29.6862 13.3623 28.8925 13.5984 28.6315 14.6159C28.3585 15.6755 29.4738 17.4486 28.8447 18.2192C28.7229 18.3703 28.5806 18.4714 28.4271 18.5381C28.0907 18.0153 28.0509 17.2522 27.3942 16.2291Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.3429 26.9852C27.003 27.2653 26.6667 28.5305 27.3103 29.6779C28.1003 31.0844 28.7422 32.6761 28.3659 34.4146C28.0561 35.8466 27.757 35.9145 27.4109 35.8779C27.0665 35.842 24.5481 35.2396 22.4522 33.773C20.3546 32.3057 19.4609 30.8532 20.0783 30.0166C20.6666 29.2207 21.6339 28.7393 23.1921 28.369C24.6835 28.0155 25.6829 26.705 26.3429 26.9852Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.4114 35.8785C27.0653 35.8419 24.5469 35.2396 22.451 33.773C20.7841 32.6066 19.878 31.4501 19.8876 30.6036C19.8956 30.5893 19.9037 30.575 19.9144 30.5637C20.2509 30.1316 22.3027 30.658 24.5368 32.0612C26.6666 33.3964 28.1998 34.9294 27.8338 35.7684C27.6986 35.8902 27.5598 35.8942 27.4114 35.8785Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.80368 18.2264L8.2016e-05 20.1109L3.80368 21.9953L5.70549 25.7643L7.60729 21.9953L11.4109 20.1109L7.60729 18.2264L5.70549 14.4574L3.80368 18.2264Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M9.27148 20.1109L6.89423 21.2887L5.70561 20.1109H9.27148Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M5.70542 16.5775L6.89404 18.933L5.70542 20.1108V16.5775Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M2.13969 20.1108L4.51694 18.933L5.70557 20.1108H2.13969Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M5.70557 23.6443L4.51694 21.2887L5.70557 20.1109V23.6443Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M2.13969 20.1109L4.51694 21.2887L5.70557 20.1109H2.13969Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M5.70557 16.5775L4.51694 18.933L5.70557 20.1108V16.5775Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M9.27148 20.1108L6.89423 18.933L5.70561 20.1108H9.27148Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M5.70542 23.6443L6.89404 21.2887L5.70542 20.1109V23.6443Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M6.27609 19.5455L7.36963 20.1109L6.27609 20.6762L5.70555 21.7598L5.13501 20.6762L4.04148 20.1109L5.13501 19.5455L5.70555 18.4619L6.27609 19.5455Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8625 1.53112L11.3172 2.29668L12.8625 3.06225L13.6351 4.59338L14.4077 3.06225L15.9529 2.29668L14.4077 1.53112L13.6351 -1.81198e-05L12.8625 1.53112Z" fill="#BDA8FF"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M15.0837 2.29667L14.118 2.77515L13.6351 2.29667H15.0837Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M13.635 0.861193L14.1179 1.81815L13.635 2.29663V0.861193Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M12.1864 2.29663L13.1521 1.81815L13.635 2.29663H12.1864Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M13.635 3.73218L13.1521 2.77522L13.635 2.29674V3.73218Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M12.1864 2.29667L13.1521 2.77515L13.635 2.29667H12.1864Z" fill="#925CF3"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M13.635 0.861193L13.1521 1.81815L13.635 2.29663V0.861193Z" fill="#925CF3"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M15.0837 2.29663L14.118 1.81815L13.6351 2.29663H15.0837Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M13.635 3.73218L14.1179 2.77522L13.635 2.29674V3.73218Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M13.8668 2.06701L14.311 2.29668L13.8668 2.52635L13.635 2.96655L13.4032 2.52635L12.959 2.29668L13.4032 2.06701L13.635 1.62681L13.8668 2.06701Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.2455 29.7042L45.8682 30.882L48.2455 32.0598L49.4341 34.4154L50.6227 32.0598L53 30.882L50.6227 29.7042L49.4341 27.3486L48.2455 29.7042Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M51.6628 30.882L50.1771 31.6182L49.4342 30.882H51.6628Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M49.4341 28.6737L50.177 30.146L49.4341 30.8821V28.6737Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M47.2054 30.8821L48.6912 30.146L49.4341 30.8821H47.2054Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M49.4341 33.0903L48.6912 31.6181L49.4341 30.882V33.0903Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M47.2054 30.882L48.6912 31.6182L49.4341 30.882H47.2054Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M49.4341 28.6737L48.6912 30.146L49.4341 30.8821V28.6737Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M51.6628 30.8821L50.1771 30.146L49.4342 30.8821H51.6628Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M49.4341 33.0903L50.177 31.6181L49.4341 30.882V33.0903Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M49.7907 30.5287L50.4741 30.882L49.7907 31.2354L49.4341 31.9126L49.0775 31.2354L48.394 30.882L49.0775 30.5287L49.4341 29.8515L49.7907 30.5287Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.9456 43.5776L35.1911 45.9332L39.9456 48.2888L42.3228 53L44.7001 48.2888L49.4546 45.9332L44.7001 43.5776L42.3228 38.8665L39.9456 43.5776Z" fill="#6133B4"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.7803 45.9333L43.8087 47.4055L42.3229 45.9333H46.7803Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M42.3228 41.5165L43.8086 44.461L42.3228 45.9332V41.5165Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M37.8654 45.9332L40.837 44.461L42.3228 45.9332H37.8654Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M42.3228 50.35L40.837 47.4055L42.3228 45.9332V50.35Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M37.8654 45.9333L40.837 47.4055L42.3228 45.9333H37.8654Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M42.3228 41.5165L40.837 44.461L42.3228 45.9332V41.5165Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46.7803 45.9332L43.8087 44.461L42.3229 45.9332H46.7803Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M42.3228 50.35L43.8086 47.4055L42.3228 45.9332V50.35Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M43.0359 45.2266L44.4028 45.9332L43.0359 46.6399L42.3227 47.9944L41.6096 46.6399L40.2426 45.9332L41.6096 45.2266L42.3227 43.8721L43.0359 45.2266Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,48 @@
<svg width="53" height="53" viewBox="0 0 53 53" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.5272 37.2881L22.9599 40.6634C23.1278 41.0588 23.467 41.3618 23.8894 41.4581C28.5036 42.5161 38.2409 38.3829 40.6869 34.3281C40.9093 33.958 40.927 33.5035 40.7592 33.108L39.3265 29.7328C38.0198 26.6545 35.171 24.3765 32.0093 23.6412C31.0857 23.4268 30.6516 22.4041 31.1389 21.5907C32.8063 18.8056 33.1466 15.174 31.84 12.0957L30.4073 8.72043C30.2394 8.32501 29.9002 8.02203 29.4801 7.92663C24.8642 6.86935 15.1269 11.0026 12.6827 15.0567C12.4578 15.4259 12.4401 15.8804 12.608 16.2758L14.0407 19.651C15.3473 22.7293 18.1961 25.0073 21.3578 25.7426C22.2815 25.957 22.7156 26.9797 22.2282 27.7931C20.5608 30.5782 20.2205 34.2099 21.5272 37.2881Z" fill="#A9DCF6" fill-opacity="0.8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.336 12.0938C24.2271 10.8666 26.6993 10.4316 28.1654 10.5102L29.2964 13.1747C30.2383 15.3938 30.0378 18.0859 28.7714 20.2007C28.0936 21.3331 27.9937 22.7223 28.5022 23.9203C29.0107 25.1182 30.0794 26.0114 31.3625 26.3096C33.7658 26.8686 35.8417 28.5945 36.7829 30.8118L37.9146 33.478C36.9527 34.5873 34.9225 36.0634 32.0314 37.2906C29.1404 38.5178 26.6682 38.9528 25.2021 38.8742L24.0703 36.208C23.1291 33.9906 23.3297 31.2985 24.5961 29.1837C25.2739 28.0513 25.3737 26.662 24.8652 25.4641C24.3567 24.2662 23.2881 23.3729 22.0043 23.0732C19.6017 22.5158 17.5258 20.7899 16.5838 18.5709L15.4528 15.9064C16.4147 14.7971 18.445 13.321 21.336 12.0938Z" fill="white" fill-opacity="0.9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.4316 13.8635C20.2629 14.784 18.0488 15.9582 18.2529 16.4997C18.4147 16.9371 18.7782 17.7653 20.9148 17.902C22.9909 18.0368 23.9804 19.4736 24.9098 19.0791C25.9908 18.6202 25.6686 17.6878 26.6052 16.2289C27.571 14.7261 27.257 13.3826 27.0315 12.8935C26.7172 12.2137 24.6004 12.9429 22.4316 13.8635Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.6058 16.2291C27.5715 14.7263 27.2576 13.3828 27.0321 12.8937C26.7836 12.3553 25.4041 12.7006 23.7631 13.3243C24.3138 13.3623 25.1075 13.5984 25.3685 14.6159C25.6415 15.6755 24.5262 17.4486 25.1553 18.2192C25.2771 18.3703 25.4194 18.4714 25.5729 18.5381C25.9093 18.0153 25.9491 17.2522 26.6058 16.2291Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.6571 26.9852C26.997 27.2653 27.3333 28.5305 26.6897 29.6779C25.8997 31.0844 25.2578 32.6761 25.6341 34.4146C25.9439 35.8466 26.243 35.9145 26.5891 35.8779C26.9335 35.842 29.4519 35.2396 31.5478 33.773C33.6454 32.3057 34.5391 30.8532 33.9217 30.0166C33.3334 29.2207 32.3661 28.7393 30.8079 28.369C29.3165 28.0155 28.3171 26.705 27.6571 26.9852Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.5886 35.8785C26.9347 35.8419 29.4531 35.2396 31.549 33.773C33.2159 32.6066 34.122 31.4501 34.1124 30.6036C34.1044 30.5893 34.0963 30.575 34.0856 30.5637C33.7491 30.1316 31.6973 30.658 29.4632 32.0612C27.3334 33.3964 25.8002 34.9294 26.1662 35.7684C26.3014 35.8902 26.4402 35.8942 26.5886 35.8785Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 30L15 32.5L10 35L7.5 40L5 35L0 32.5L5 30L7.5 25L10 30Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M3 32L6.33333 34L8 32H3Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M8 27L6 30.3333L8 32L8 27Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M12 32L8.66667 30L7 32H12Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M7 37L9 33.6667L7 32L7 37Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M12 32L8.66667 34L7 32H12Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M7 27L9 30.3333L7 32L7 27Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M3 32L6.33333 30L8 32H3Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M8 37L6 33.6667L8 32L8 37Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M6.31429 31.3143L5 32L6.31429 32.6857L7 34L7.68571 32.6857L9 32L7.68571 31.3143L7 30L6.31429 31.3143Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.3333 47.6667L21 49L18.3333 50.3333L17 53L15.6667 50.3333L13 49L15.6667 47.6667L17 45L18.3333 47.6667Z" fill="#BDA8FF"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M14 49L16 50L17 49H14Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17 46L16 48L17 49V46Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20 49L18 48L17 49H20Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17 52L18 50L17 49V52Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20 49L18 50L17 49H20Z" fill="#925CF3"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17 46L18 48L17 49V46Z" fill="#925CF3"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M14 49L16 48L17 49H14Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17 52L16 50L17 49V52Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M16.9857 48.9857L16 49.5L16.9857 50.0143L17.5 51L18.0143 50.0143L19 49.5L18.0143 48.9857L17.5 48L16.9857 48.9857Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 2L42 3L40 4L39 6L38 4L36 3L38 2L39 0L40 2Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M37 3L38.3333 4L39 3H37Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M39 1L38 2.33333L39 3V1Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M41 3L39.6667 2L39 3L41 3Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M39 5L40 3.66667L39 3V5Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M41 3L39.6667 4L39 3H41Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M39 1L40 2.33333L39 3V1Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M37 3L38.3333 2L39 3L37 3Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M39 5L38 3.66667L39 3V5Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M38.6571 2.65714L38 3L38.6571 3.34286L39 4L39.3429 3.34286L40 3L39.3429 2.65714L39 2L38.6571 2.65714Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M49 21L53 23L49 25L47 29L45 25L41 23L45 21L47 17L49 21Z" fill="#6133B4"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M43 23L45.6667 24L47 23H43Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46 19L45 21.6667L46 23V19Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M50 23L47.3333 22L46 23H50Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M47 27L48 24.3333L47 23V27Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M50 23L47.3333 24L46 23H50Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M47 19L48 21.6667L47 23V19Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M43 23L45.6667 22L47 23H43Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46 27L45 24.3333L46 23V27Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46.3143 21.9857L45 22.5L46.3143 23.0143L47 24L47.6857 23.0143L49 22.5L47.6857 21.9857L47 21L46.3143 21.9857Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,9 @@
<svg width="38" height="44" viewBox="0 0 38 44" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="38" height="44" fill="url(#pattern0_1325_164)"/>
<defs>
<pattern id="pattern0_1325_164" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_1325_164" transform="scale(0.0263158 0.0227273)"/>
</pattern>
<image id="image0_1325_164" width="38" height="44" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAsCAYAAAAJpsrIAAAAAXNSR0IArs4c6QAAAn5JREFUWAnV1T1uFEEUBOAJwDEHICBFWomEE5BwDbiCAydOLRGSkRGQOOIYRGRIyEcgIbGFZIeN2tpvtBRqesx4lwbpqfb9Vm0xO56mzr9y87bUaI09fjCVGq2+em+ux+POjL2FHqFDvbkejzvT9fdXpcaLzXQbc2P7AdHp5qLUkPfmpunW3d8cxoM378y5AQtzY/uBkIMLuzifSg2CCE1Bn95flRrq5qH68dPPpYZ5zrlrPnnVZ8wBBxBxCpH6fGD7Qf3ehPkvJNDDiYiwFNwSZs4X4Zi7ePDmnTk3YMGBfy6MQgKhbw4JJ9geVDdnD7oL7XXRAnQQIiQgD6qbswfdhbm/OHcgiQjIQ+qE2HMn5/86dxABQgLysLo5e+7k/OrcYYiYEKhuDq4W0DqAABJAEFQ3B1t3V9cRv35+Xmp4gbbw7Nm3UsP7a7WA1oHhhKUgjnHi45vLshvqHIPqrS9+5/rBhSHsKTXHKe5w4MPJZdkNdXN3dgzhMMIIyj/SBOonbp48KjV23amfXx4d/RLZ56D7TUQ4rDAvQgI54lnyfpJDc+9OvpQ/BQOaDmXDwrDCfnx9WGpwwK8nnco8nbOfmIZ0c44NJ4xyAjkC0zlznIKezXwk5PbwLUaLBMHhhHEiBfoC+hyB6Zy6vcVOGbRICGI558zpI4Z7E4aQIEiQ3JwfDWGJ+vYZsRgtIiQA6svNIU5Bcn37iwUZtIgQEqIv14cEeH/J8xHAtxgRI4KE6Mv1ISH3Lsw3IAAiJkhdrg8J01/t2PDCUiAnOCBvObc3x4YXlgI5lMhJuHfHhhdGYCLnPHMc8ys8mHP/jTBCOZfY6qvvHVOQHLEcqvfwJ8mbnKIO4lmnAAAAAElFTkSuQmCC"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,52 @@
<svg width="41" height="61" viewBox="0 0 41 61" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.53642 4.26827L12.8046 6.40237L8.53642 8.53648L6.40232 12.8047L4.26821 8.53648L0 6.40237L4.26821 4.26827L6.40232 5.72205e-05L8.53642 4.26827Z" fill="#6133B4"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M2.40088 6.40239L5.06851 7.73621L6.40233 6.40239H2.40088Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M6.4023 2.4009L5.06848 5.06853L6.4023 6.40234V2.4009Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M10.4038 6.40234L7.73616 5.06853L6.40234 6.40234L10.4038 6.40234Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M6.40234 10.4038L7.73616 7.73618L6.40234 6.40236L6.40234 10.4038Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M10.4038 6.40239L7.73616 7.73621L6.40234 6.40239H10.4038Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M6.40234 2.4009L7.73616 5.06853L6.40234 6.40234L6.40234 2.4009Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M2.40088 6.40234L5.06851 5.06853L6.40233 6.40234L2.40088 6.40234Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M6.4023 10.4038L5.06848 7.73618L6.4023 6.40236V10.4038Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M5.76208 5.7622L4.53497 6.40243L5.76208 7.04267L6.40232 8.26978L7.04255 7.04267L8.26966 6.40243L7.04255 5.7622L6.40232 4.53509L5.76208 5.7622Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.5385 14.2663L39.8368 15.4154L37.5385 16.5645L36.3894 18.8628L35.2402 16.5645L32.942 15.4154L35.2402 14.2663L36.3894 11.968L37.5385 14.2663Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M34.2347 15.4153L35.6712 16.1335L36.3894 15.4153H34.2347Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M36.3894 13.2608L35.6711 14.6972L36.3894 15.4154V13.2608Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M38.544 15.4154L37.1076 14.6972L36.3893 15.4154H38.544Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M36.3893 17.5701L37.1076 16.1337L36.3893 15.4154V17.5701Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M38.544 15.4153L37.1076 16.1335L36.3893 15.4153H38.544Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M36.3893 13.2608L37.1076 14.6972L36.3893 15.4154V13.2608Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M34.2347 15.4154L35.6712 14.6972L36.3894 15.4154H34.2347Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M36.3894 17.5701L35.6711 16.1337L36.3894 15.4154V17.5701Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M36.0446 15.0707L35.3839 15.4154L36.0446 15.7601L36.3893 16.4209L36.7341 15.7601L37.3948 15.4154L36.7341 15.0707L36.3893 14.4099L36.0446 15.0707Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5354 28.2693L25.7885 30.8959L20.5354 33.5225L17.9088 38.7756L15.2822 33.5225L10.029 30.8959L15.2822 28.2693L17.9088 23.0161L20.5354 28.2693Z" fill="#BDA8FF"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M12.9839 30.8959L16.2671 32.5375L17.9087 30.8959H12.9839Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.9088 25.971L16.2672 29.2543L17.9088 30.8959V25.971Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M22.8336 30.8959L19.5504 29.2543L17.9088 30.8959H22.8336Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.9088 35.8207L19.5504 32.5374L17.9088 30.8958V35.8207Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M22.8336 30.8959L19.5504 32.5375L17.9088 30.8959H22.8336Z" fill="#925CF3"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.9088 25.971L19.5504 29.2543L17.9088 30.8959V25.971Z" fill="#925CF3"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M12.9839 30.8959L16.2671 29.2543L17.9087 30.8959H12.9839Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.9088 35.8207L16.2672 32.5374L17.9088 30.8958V35.8207Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.1208 30.1078L15.6105 30.8958L17.1208 31.6838L17.9087 33.1941L18.6967 31.6838L20.207 30.8958L18.6967 30.1078L17.9087 28.5976L17.1208 30.1078Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.53742 54.2673L10.8207 55.9089L7.53742 57.5505L5.8958 60.8337L4.25419 57.5505L0.970947 55.9089L4.25419 54.2673L5.8958 50.984L7.53742 54.2673Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M2.81775 55.9089L4.86977 56.9349L5.89579 55.9089H2.81775Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M5.89582 52.8309L4.86981 54.8829L5.89582 55.9089V52.8309Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M8.97385 55.9089L6.92183 54.8829L5.89581 55.9089H8.97385Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M5.89581 58.9869L6.92183 56.9349L5.89581 55.9089V58.9869Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M8.97385 55.9089L6.92183 56.9349L5.89581 55.9089H8.97385Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M5.89581 52.8309L6.92183 54.8829L5.89581 55.9089V52.8309Z" fill="#6133B4"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M2.81775 55.9089L4.86977 54.8829L5.89579 55.9089H2.81775Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M5.89582 58.9869L4.86981 56.9349L5.89582 55.9089V58.9869Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M5.40334 55.4164L4.45941 55.9089L5.40334 56.4014L5.89583 57.3453L6.38831 56.4014L7.33225 55.9089L6.38831 55.4164L5.89583 54.4725L5.40334 55.4164Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.5374 44.2673L40.8207 45.9089L37.5374 47.5505L35.8958 50.8337L34.2542 47.5505L30.9709 45.9089L34.2542 44.2673L35.8958 40.984L37.5374 44.2673Z" fill="#6133B4"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M32.8177 45.9089L34.8698 46.9349L35.8958 45.9089H32.8177Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M35.8958 42.8309L34.8698 44.8829L35.8958 45.9089V42.8309Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M38.9738 45.9089L36.9218 44.8829L35.8958 45.9089H38.9738Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M35.8958 48.9869L36.9218 46.9349L35.8958 45.9089V48.9869Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M38.9738 45.9089L36.9218 46.9349L35.8958 45.9089H38.9738Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M35.8958 42.8309L36.9218 44.8829L35.8958 45.9089V42.8309Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M32.8177 45.9089L34.8698 44.8829L35.8958 45.9089H32.8177Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M35.8958 48.9869L34.8698 46.9349L35.8958 45.9089V48.9869Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M35.4033 45.4164L34.4594 45.9089L35.4033 46.4014L35.8958 47.3453L36.3883 46.4014L37.3322 45.9089L36.3883 45.4164L35.8958 44.4725L35.4033 45.4164Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,19 @@
<svg width="60" height="58" viewBox="0 0 60 58" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="38" y="6" width="22" height="26" fill="url(#pattern0_1327_256)"/>
<rect width="26" height="26" fill="url(#pattern1_1327_256)"/>
<rect x="8" y="36" width="30" height="22" fill="url(#pattern2_1327_256)"/>
<defs>
<pattern id="pattern0_1327_256" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_1327_256" transform="scale(0.0454545 0.0384615)"/>
</pattern>
<pattern id="pattern1_1327_256" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image1_1327_256" transform="scale(0.0384615)"/>
</pattern>
<pattern id="pattern2_1327_256" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image2_1327_256" transform="scale(0.0333333 0.0454545)"/>
</pattern>
<image id="image0_1327_256" width="22" height="26" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAaCAYAAACzdqxAAAAAAXNSR0IArs4c6QAAAQlJREFUSA290k1qwlAUxfFsp+soXYdzoeAaWqFbcSx14LTQSbuAjruLVgdPnvArcsIjRlOFy8n9+t9jSNcN/B7u7stpDIyf3z6F1ufzN2MyQfvf91LjZ7s6RvZjvZ3m4tVgwN36q9TgkKrT7+ePUsNe06oBi4BUnQ6CE2iBAqXqU5w/5woWDVL1VH2K0wMbAHibfZYa6i01dzsw6y7m5+UfcMxhS3FwO4XJwICc5efFaeqg45uB05m85VCdwd679SqAUgFa2gQDWTQoT82+vOd4crALLtKszx8XpUarb76nFqgB+cVgoFTgp+VLqSHPudE50L+B16+bUsOh0Q5zAWhysEMOyCfTseADSSC3nu5iFacAAAAASUVORK5CYII="/>
<image id="image1_1327_256" width="26" height="26" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAASlJREFUSA3F1DFOw0AQBVDTRhSp6RHUaVLQcAWOECkNR6HiPtRcJl1KqBZtpEfxjbWrxA6WRl8znvl//q7lYYjnbnNb/gpt+e75fV1qqOtrooFEg1k/WygJ73c3pQYB+cUC/y6UTjh6225Ljd0wnMKi3ZhHNZsQYohYvv94LDXUOTm+vpQaXw9Pp1CfdIQQIpRfLIQQOnvEMOv6OWk6MwCTcHYhRwRTwAKJ7uT781BqcKj+e1ecEICzCyGmvFmtSg2bE7Rpbq7P+8m7upoQJ5AjZ5wb2xymg3Q8+ce4upCNc8OWA3Ow6Ujj4kLuiCBsCafjpqPFhaa+vl5hjvX7uPCOUIMB6Agh4sz14xkJZEEjdOaIWmgueUe5RriYUCoT7MWc7857BfQh/gFCaNQahl2u3gAAAABJRU5ErkJggg=="/>
<image id="image2_1327_256" width="30" height="22" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAWCAYAAADXYyzPAAAAAXNSR0IArs4c6QAAARRJREFUSA3F08FtAjEUBFCagAYSKd3QAsUgcUkDqSO31JBjLrnSBlJk5MOD1Wgt7Ro5IH2N/cee+bPsbjaDf68vb2Vag+3u8lPTur4zK1cpZJ8y+h+H71LLPs8t3hNITAF8tzEB+PlzKdP6Pf+VWvjE0/6r1Fo9QApNTet6uDHD4/u5zBXeoJm0OzHhOdPawz9sTIBgGnrEyevvtttSi05ivoy3vYMpbAAGyes/bEzIf8YItgYx+C3J0oWLw4wZJHpUkkksoX4inl4zqAOJ/2bsO4MSGkgyifBQ3/lmUoSDDCFB/HDjNGQMDQC9fHgoWBMdbCXFQ4aw29hEhA1gj4f6ifjVSOhpxgZYPXnvBYawV2fpvStxGFyuIXWsWQAAAABJRU5ErkJggg=="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,26 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 39 44">
<g fill="none" fill-rule="evenodd">
<path fill="#24CC8F" d="M2.782 35.927l1.867-8.397 12.887-5.47 7.338 4.49-5.967 16.655z"/>
<path fill="#FFF" d="M9.147 33.008L6.351 28.98l5.523-2.344zM18.352 29.1l-.955-4.808-5.523 2.344z" opacity=".25"/>
<path fill="#FFF" d="M9.147 33.008l2.727-6.372 6.478 2.465zM5.097 34.727L6.35 28.98l2.796 4.028z" opacity=".5"/>
<path fill="#1B996B" d="M22.402 27.382l-5.005-3.09.955 4.809zM5.097 34.727l4.05-1.719 8.627 7.528z" opacity=".35"/>
<path fill="#FFF" d="M22.402 27.382l-4.05 1.719-.578 11.435z" opacity=".5"/>
<path fill="#FFF" d="M9.147 33.008l9.205-3.907-.578 11.435z" opacity=".25"/>
<g>
<path fill="#24CC8F" d="M23.248 7.266l4.784-3.162 8.714 3.345 1.44 5.55-10.575 5.225z"/>
<path fill="#FFF" d="M27.653 8.814l.524-3.227 3.734 1.434zM33.876 11.203l1.77-2.749-3.735-1.433z" opacity=".25"/>
<path fill="#FFF" d="M27.653 8.814L31.91 7.02l1.965 4.182zM24.914 7.763l3.263-2.176-.524 3.227z" opacity=".5"/>
<path fill="#1B996B" d="M36.615 12.254l-.97-3.8-1.769 2.749zM24.914 7.763l2.739 1.05.65 7.606z" opacity=".35"/>
<path fill="#FFF" d="M36.615 12.254l-2.739-1.051-5.572 5.216z" opacity=".5"/>
<path fill="#FFF" d="M27.653 8.814l6.223 2.389-5.572 5.216z" opacity=".25"/>
</g>
<g>
<path fill="#24CC8F" d="M.815 5.996l1.58-4L9.185.301l3.273 2.791-4.25 7.758z"/>
<path fill="#FFF" d="M4.187 5.052L3.121 2.845l2.911-.726zM9.039 3.843l-.096-2.45-2.91.726z" opacity=".25"/>
<path fill="#FFF" d="M4.187 5.052L6.032 2.12 9.04 3.843zM2.053 5.585l1.068-2.74 1.066 2.207z" opacity=".5"/>
<path fill="#1B996B" d="M11.173 3.31l-2.23-1.917.096 2.45zM2.053 5.585l2.134-.533L7.86 9.445z" opacity=".35"/>
<path fill="#FFF" d="M11.173 3.31l-2.134.533-1.18 5.602z" opacity=".5"/>
<path fill="#FFF" d="M4.187 5.052l4.852-1.21-1.18 5.603z" opacity=".25"/>
</g>
</g>
<svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.89107 26.3989L14.4076 18.6441L33.6795 23.9114L37.9287 35.4286L16.5188 48.8092L4.89107 26.3989Z" fill="#24CC8F"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M14.6023 28.7572L15.0317 21.7734L23.2911 24.0309L14.6023 28.7572Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M28.368 32.5196L31.5505 26.2883L23.2911 24.0309L28.368 32.5196Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M14.6023 28.7573L23.2911 24.031L28.368 32.5197L14.6023 28.7573Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M8.54539 27.1018L15.0317 21.7735L14.6023 28.7572L8.54539 27.1018Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M34.4249 34.1751L31.5505 26.2883L28.368 32.5196L34.4249 34.1751Z" fill="#1B996B"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M8.54541 27.1018L14.6023 28.7572L17.6099 44.8171L8.54541 27.1018Z" fill="#1B996B"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M34.4249 34.175L28.368 32.5196L17.6099 44.8171L34.4249 34.175Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M14.6023 28.7573L28.368 32.5197L17.6099 44.8172L14.6023 28.7573Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2974 3.46554L14.2627 0.349942L21.1938 1.32978L23.1792 5.14526L16.3285 10.7414L11.2974 3.46554Z" fill="#24CC8F"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M14.7769 3.85639L14.6178 1.41003L17.5882 1.82996L14.7769 3.85639Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M19.7277 4.55627L20.5587 2.24989L17.5883 1.82996L19.7277 4.55627Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M14.7769 3.85647L17.5882 1.83004L19.7277 4.55636L14.7769 3.85647Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M12.5986 3.54849L14.6178 1.41008L14.7769 3.85644L12.5986 3.54849Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M21.906 4.86425L20.5587 2.24992L19.7277 4.5563L21.906 4.86425Z" fill="#1B996B"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M12.5986 3.54846L14.7769 3.85641L16.5314 9.30565L12.5986 3.54846Z" fill="#1B996B"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M21.906 4.86431L19.7277 4.55636L16.5314 9.30572L21.906 4.86431Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M14.7769 3.85645L19.7277 4.55633L16.5314 9.30568L14.7769 3.85645Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.8389 13.9788L33.0633 6.97062L44.4487 3.28717L50.3566 7.66432L44.0181 21.3937L30.8389 13.9788Z" fill="#24CC8F"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M36.4789 11.9744L34.4028 8.33398L39.2822 6.75536L36.4789 11.9744Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M44.6113 9.3434L44.1617 5.17675L39.2822 6.75537L44.6113 9.3434Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M36.4789 11.9745L39.2822 6.75541L44.6113 9.34344L36.4789 11.9745Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M32.9007 13.1321L34.4028 8.33396L36.4789 11.9744L32.9007 13.1321Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M48.1896 8.18571L44.1617 5.17672L44.6113 9.34337L48.1896 8.18571Z" fill="#1B996B"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M32.9007 13.1321L36.4789 11.9744L43.2551 19.0353L32.9007 13.1321Z" fill="#1B996B"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M48.1896 8.18576L44.6113 9.34342L43.2551 19.0353L48.1896 8.18576Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M36.4789 11.9745L44.6113 9.34345L43.2551 19.0353L36.4789 11.9745Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,26 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 47">
<g fill="none" fill-rule="evenodd">
<path fill="#A9DCF6" fill-opacity=".8" d="M25.955 43.329l-.999 2.352a.939.939 0 0 1-.644.555c-3.194.747-9.918-2.107-11.6-4.923a.941.941 0 0 1-.047-.85l.998-2.351c.911-2.146 2.886-3.738 5.074-4.257a.957.957 0 0 0 .607-1.429c-1.147-1.934-1.374-4.46-.463-6.606l.998-2.352a.937.937 0 0 1 .643-.555c3.195-.746 9.919 2.108 11.6 4.924a.935.935 0 0 1 .049.848l-.999 2.353c-.91 2.145-2.886 3.737-5.074 4.256a.957.957 0 0 0-.606 1.429c1.146 1.934 1.373 4.46.463 6.606"/>
<path fill="#FFF" fill-opacity=".9" d="M26.145 25.795c-1.997-.848-3.705-1.145-4.72-1.087l-.788 1.857c-.656 1.547-.524 3.42.347 4.889.466.786.532 1.753.178 2.588a2.881 2.881 0 0 1-1.983 1.669c-1.664.395-3.103 1.6-3.76 3.146l-.788 1.858c.663.77 2.063 1.792 4.06 2.64 1.996.847 3.705 1.144 4.719 1.086l.789-1.858c.656-1.545.523-3.418-.348-4.887a2.883 2.883 0 0 1-.178-2.588 2.886 2.886 0 0 1 1.984-1.67c1.663-.394 3.103-1.6 3.759-3.146l.788-1.857c-.662-.77-2.063-1.793-4.06-2.64"/>
<path fill="#9A62FF" d="M25.383 27.029c1.497.636 3.026 1.448 2.883 1.825-.112.305-.366.882-1.844.982-1.436.098-2.123 1.1-2.765.828-.746-.317-.521-.966-1.166-1.98-.664-1.043-.444-1.979-.287-2.32.219-.473 1.681.03 3.179.665"/>
<path fill="#4F2A93" d="M22.491 28.685c-.664-1.044-.444-1.98-.287-2.32.173-.376 1.126-.139 2.26.292-.382.027-.93.194-1.114.902-.19.738.576 1.97.14 2.507a.755.755 0 0 1-.29.223c-.231-.363-.257-.894-.709-1.604"/>
<path fill="#9A62FF" d="M21.74 36.173c.455.193.22 1.074.662 1.871.543.978.984 2.084.72 3.294-.218.998-.425 1.046-.664 1.021-.238-.024-1.979-.438-3.425-1.454-1.447-1.016-2.062-2.025-1.633-2.608.409-.556 1.079-.893 2.157-1.154 1.032-.25 1.727-1.164 2.182-.97"/>
<path fill="#4F2A93" d="M22.458 42.36c-.24-.025-1.98-.439-3.425-1.455-1.15-.807-1.774-1.61-1.766-2.2a.138.138 0 0 1 .019-.027c.233-.302 1.651.06 3.193 1.031 1.47.925 2.526 1.988 2.271 2.573-.093.085-.19.088-.292.077"/>
<g>
<path fill="#A9DCF6" fill-opacity=".8" d="M12.754 15.163l.61 1.59a.626.626 0 0 1-.052.565c-1.185 1.837-5.732 3.582-7.843 3.01a.627.627 0 0 1-.415-.385l-.61-1.59c-.558-1.45-.348-3.129.461-4.39a.638.638 0 0 0-.37-.967c-1.446-.397-2.725-1.503-3.282-2.954l-.61-1.59a.625.625 0 0 1 .05-.564c1.187-1.837 5.734-3.582 7.844-3.01a.623.623 0 0 1 .416.384l.61 1.59c.557 1.45.347 3.129-.462 4.39a.638.638 0 0 0 .371.967c1.446.397 2.725 1.503 3.282 2.954"/>
<path fill="#FFF" fill-opacity=".9" d="M4.725 6.666c-1.35.518-2.307 1.167-2.767 1.665l.482 1.255c.402 1.046 1.333 1.883 2.43 2.184a1.922 1.922 0 0 1 1.284 1.159 1.92 1.92 0 0 1-.178 1.719c-.615.96-.747 2.204-.346 3.249l.483 1.256c.674.062 1.82-.096 3.17-.614 1.349-.518 2.306-1.167 2.765-1.664l-.482-1.257c-.401-1.044-1.332-1.881-2.43-2.182a1.922 1.922 0 0 1-1.284-1.16 1.924 1.924 0 0 1 .178-1.72c.615-.958.747-2.203.346-3.248l-.482-1.256c-.674-.062-1.82.096-3.17.614"/>
<path fill="#9A62FF" d="M4.93 7.61c1.013-.388 2.122-.706 2.229-.46.087.199.233.593-.43 1.325-.643.713-.508 1.512-.942 1.678-.505.194-.698-.222-1.476-.41-.802-.192-1.13-.743-1.212-.979-.115-.328.82-.765 1.832-1.153"/>
<path fill="#4F2A93" d="M4.31 9.744c-.801-.193-1.129-.743-1.211-.98-.091-.26.476-.587 1.219-.906-.17.19-.357.524-.116.948.25.443 1.188.678 1.228 1.138a.503.503 0 0 1-.036.241c-.28-.067-.537-.31-1.083-.44"/>
<path fill="#9A62FF" d="M7.418 13.683c.308-.118.603.413 1.185.59.713.218 1.436.544 1.87 1.247.358.58.28.698.154.797-.125.099-1.15.706-2.315.889-1.165.183-1.926-.016-1.991-.495-.061-.455.104-.927.5-1.552.38-.598.289-1.358.597-1.476"/>
<path fill="#4F2A93" d="M10.628 16.317c-.126.1-1.152.707-2.316.89-.926.144-1.597.048-1.866-.238a.092.092 0 0 1-.004-.022c-.027-.253.82-.736 2.01-.984 1.132-.237 2.132-.217 2.28.182-.006.084-.05.13-.104.172"/>
</g>
<g>
<path fill="#A9DCF6" fill-opacity=".8" d="M32.605 10.804l-.31 1.24a.47.47 0 0 1-.275.324c-1.518.619-5.062-.265-6.113-1.524a.47.47 0 0 1-.09-.416l.31-1.24c.281-1.13 1.132-2.07 2.172-2.498a.479.479 0 0 0 .188-.753c-.718-.866-1.027-2.096-.745-3.226l.309-1.24a.469.469 0 0 1 .274-.324c1.519-.619 5.063.265 6.113 1.524.097.114.127.27.09.415l-.308 1.24c-.282 1.13-1.133 2.071-2.173 2.498a.479.479 0 0 0-.188.754c.718.865 1.027 2.095.746 3.226"/>
<path fill="#FFF" fill-opacity=".9" d="M31.327 2.13c-1.052-.262-1.92-.276-2.416-.168l-.244.98c-.203.814.009 1.729.554 2.386.292.352.4.824.29 1.264a1.44 1.44 0 0 1-.849.98 2.762 2.762 0 0 0-1.61 1.847l-.244.98c.387.328 1.16.723 2.211.986 1.052.262 1.92.275 2.416.167l.244-.98a2.761 2.761 0 0 0-.554-2.385 1.442 1.442 0 0 1-.29-1.265c.11-.44.427-.806.849-.98a2.76 2.76 0 0 0 1.61-1.847l.244-.979c-.387-.328-1.159-.724-2.211-.986"/>
<path fill="#9A62FF" d="M31.047 2.799c.79.197 1.608.478 1.567.676-.032.159-.112.464-.834.629-.701.16-.962.71-1.3.625-.394-.098-.334-.436-.731-.886-.41-.464-.375-.943-.324-1.124.071-.25.833-.117 1.622.08"/>
<path fill="#4F2A93" d="M29.749 3.843c-.41-.464-.375-.943-.324-1.124.056-.198.546-.156 1.139-.032-.186.044-.444.169-.48.533-.036.38.44.927.266 1.227a.378.378 0 0 1-.126.133c-.143-.161-.197-.422-.475-.737"/>
<path fill="#9A62FF" d="M29.963 7.6c.24.06.193.513.474.872.344.44.648.952.613 1.57-.03.51-.128.55-.248.556-.12.007-1.012-.061-1.805-.45-.794-.388-1.177-.838-1.01-1.16.158-.306.462-.525.974-.739.49-.204.762-.71 1.002-.65"/>
<path fill="#4F2A93" d="M30.802 10.599c-.12.006-1.012-.062-1.806-.45-.63-.31-1.002-.657-1.043-.949a.069.069 0 0 1 .007-.015c.091-.167.82-.1 1.657.26.798.341 1.403.784 1.323 1.092-.04.05-.087.059-.138.062"/>
</g>
</g>
<svg width="39" height="54" viewBox="0 0 39 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.2715 47.841L27.8997 51.0729C27.7389 51.4515 27.4157 51.7423 27.014 51.8357C22.6266 52.8619 13.3878 48.9402 11.0765 45.0707C10.8663 44.7175 10.8509 44.283 11.0117 43.9044L12.3835 40.6724C13.6347 37.7249 16.3485 35.5378 19.3549 34.8248C20.2332 34.6169 20.6488 33.6376 20.1883 32.8614C18.6128 30.2035 18.3009 26.732 19.552 23.7844L20.9239 20.5525C21.0846 20.1739 21.4079 19.8832 21.8073 19.7906C26.1963 18.7652 35.4351 22.6868 37.7447 26.5557C37.9572 26.9079 37.9726 27.3425 37.8119 27.7211L36.4401 30.953C35.1889 33.9005 32.475 36.0876 29.4687 36.8006C28.5904 37.0086 28.1747 37.9879 28.6352 38.7641C30.2107 41.4219 30.5227 44.8934 29.2715 47.841Z" fill="#A9DCF6" fill-opacity="0.8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.5322 23.7497C26.7892 22.5854 24.4416 22.1772 23.0484 22.257L21.9654 24.8083C21.0635 26.9331 21.2456 29.5067 22.4422 31.5248C23.0826 32.6056 23.1732 33.9336 22.6863 35.0807C22.1994 36.2277 21.1812 37.0852 19.9611 37.3743C17.6759 37.9164 15.6981 39.5731 14.7969 41.6963L13.7132 44.2492C14.6236 45.3069 16.548 46.712 19.2911 47.8764C22.0341 49.0407 24.3817 49.4489 25.7749 49.3691L26.8586 46.8162C27.7598 44.693 27.5777 42.1194 26.3811 40.1013C25.7407 39.0206 25.6501 37.6925 26.137 36.5454C26.6239 35.3984 27.6421 34.541 28.8628 34.2503C31.1474 33.7098 33.1252 32.053 34.0271 29.9282L35.1101 27.3769C34.1996 26.3192 32.2753 24.9141 29.5322 23.7497Z" fill="white" fill-opacity="0.9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.4856 25.4452C30.5433 26.3187 32.6433 27.4344 32.4477 27.9529C32.2926 28.3716 31.9446 29.1647 29.9141 29.3021C27.9411 29.4375 26.9965 30.8145 26.1146 30.4401C25.089 30.0048 25.3981 29.1122 24.5127 27.7201C23.5998 26.2862 23.9023 25.0005 24.1181 24.5322C24.4189 23.8812 26.4279 24.5718 28.4856 25.4452Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.5124 27.7203C23.5995 26.2864 23.902 25.0008 24.1178 24.5324C24.3555 24.0168 25.6652 24.3427 27.2224 24.9339C26.699 24.9719 25.9442 25.2001 25.693 26.1739C25.4303 27.188 26.4845 28.8798 25.8843 29.6187C25.7681 29.7636 25.6326 29.8607 25.4865 29.925C25.1685 29.4261 25.1331 28.6965 24.5124 27.7203Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.4797 38.0086C24.1059 38.2744 23.7824 39.4852 24.3904 40.5804C25.1366 41.9228 25.7414 43.4428 25.3785 45.1063C25.0797 46.4765 24.7953 46.5424 24.4665 46.5084C24.1394 46.4752 21.7484 45.9071 19.7617 44.5113C17.7733 43.1149 16.9287 41.7288 17.518 40.9269C18.0793 40.164 19 39.7007 20.4816 39.3417C21.8998 38.999 22.8534 37.7428 23.4797 38.0086Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.467 46.509C24.1383 46.4751 21.7473 45.907 19.7605 44.5113C18.1804 43.4012 17.3231 42.2982 17.3349 41.4887C17.3426 41.475 17.3503 41.4613 17.3605 41.4506C17.6815 41.0363 19.6294 41.5332 21.7477 42.8679C23.7671 44.138 25.2191 45.599 24.8686 46.4024C24.7398 46.5193 24.6079 46.5236 24.467 46.509Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8803 22.4748L13.0712 24.6344C13.0936 24.8874 13.0019 25.1397 12.8101 25.3073C10.7172 27.1406 4.54355 27.6865 2.16031 26.249C1.9432 26.1175 1.80861 25.8852 1.78624 25.6322L1.59529 23.4726C1.42114 21.5029 2.26494 19.5229 3.69621 18.2613C4.11444 17.893 4.05658 17.2386 3.5802 16.9493C1.94977 15.9584 0.771638 14.1572 0.597488 12.1876L0.406536 10.0279C0.384166 9.77492 0.475899 9.52258 0.666679 9.35616C2.76071 7.52275 8.93431 6.97689 11.3165 8.41452C11.5346 8.54479 11.6692 8.77712 11.6915 9.03012L11.8825 11.1898C12.0566 13.1594 11.2128 15.1394 9.78156 16.401C9.36333 16.7693 9.42119 17.4238 9.89756 17.713C11.528 18.7039 12.7061 20.5051 12.8803 22.4748Z" fill="#A9DCF6" fill-opacity="0.8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.02615 9.27039C4.19318 9.43245 2.79533 9.89173 2.05923 10.3398L2.20997 12.0447C2.33551 13.4645 3.18214 14.8141 4.4203 15.5665C5.08317 15.9694 5.51817 16.6668 5.58595 17.4333C5.65372 18.1998 5.34784 18.9628 4.76693 19.4746C3.67901 20.4337 3.08233 21.9109 3.20777 23.3297L3.35861 25.0356C4.16191 25.3476 5.61866 25.5545 7.45163 25.3924C9.2846 25.2303 10.6825 24.7711 11.4186 24.323L11.2677 22.617C11.1423 21.1983 10.2956 19.8487 9.05748 19.0963C8.39461 18.6934 7.95961 17.996 7.89184 17.2295C7.82406 16.463 8.12994 15.7 8.71076 15.1871C9.79877 14.2291 10.3955 12.7519 10.2699 11.332L10.1192 9.62718C9.31587 9.31519 7.85912 9.10832 6.02615 9.27039Z" fill="white" fill-opacity="0.9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.94818 10.4983C7.32318 10.3767 8.7916 10.3749 8.83559 10.7142C8.87266 10.9875 8.91336 11.5207 7.84673 12.1853C6.81082 12.832 6.69593 13.8567 6.10664 13.9088C5.42128 13.9694 5.33053 13.3933 4.44377 12.8918C3.52987 12.3754 3.32137 11.587 3.30294 11.2691C3.27784 10.8269 4.57319 10.6199 5.94818 10.4983Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.44368 12.892C3.52978 12.3757 3.32128 11.5872 3.30285 11.2693C3.28266 10.9193 4.09102 10.7165 5.1113 10.5865C4.83716 10.7592 4.49205 11.1028 4.63795 11.7064C4.78931 12.3353 5.8551 12.9512 5.74257 13.5281C5.72134 13.6408 5.6757 13.7331 5.61477 13.8105C5.29657 13.631 5.06544 13.2437 4.44368 12.892Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.86862 18.7985C7.2871 18.7615 7.46241 19.5152 8.11175 19.9355C8.90824 20.4504 9.67927 21.103 9.96455 22.115C10.1996 22.9484 10.0638 23.067 9.87478 23.1439C9.68684 23.2208 8.21892 23.6056 6.73087 23.4219C5.24175 23.2383 4.37897 22.7283 4.46722 22.1201C4.55161 21.5413 4.91878 21.0215 5.62194 20.3956C6.29524 19.797 6.45015 18.8355 6.86862 18.7985Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.87521 23.1441C9.68621 23.2211 8.21829 23.6059 6.73023 23.4222C5.54676 23.2762 4.75927 22.924 4.53061 22.4795C4.53083 22.4698 4.53104 22.4601 4.53349 22.4512C4.58813 22.1323 5.79395 21.8374 7.33593 21.9495C8.80526 22.0553 10.0208 22.4298 10.0631 22.9694C10.0269 23.0705 9.95622 23.1111 9.87521 23.1441Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.9597 9.01912L34.5713 10.2909C34.6429 10.4399 34.6449 10.6147 34.5665 10.7608C33.7119 12.3575 30.0762 14.1058 28.2947 13.7765C28.1323 13.7463 27.9971 13.6356 27.9254 13.4866L27.3139 12.2148C26.7561 11.0549 26.8161 9.65517 27.3987 8.5584C27.569 8.2381 27.3837 7.85272 27.0272 7.78575C25.8067 7.55599 24.676 6.72887 24.1182 5.56895L23.5066 4.29712C23.435 4.14812 23.433 3.97336 23.5111 3.82823C24.3663 2.23116 28.002 0.48293 29.7828 0.812446C29.9456 0.841791 30.0808 0.952458 30.1525 1.10146L30.764 2.37329C31.3218 3.53321 31.2618 4.93289 30.6792 6.02966C30.5089 6.34996 30.6942 6.73535 31.0507 6.80232C32.2712 7.03207 33.4019 7.85919 33.9597 9.01912Z" fill="#A9DCF6" fill-opacity="0.8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.7563 2.54696C25.6769 3.06602 24.9306 3.66635 24.585 4.10812L25.0678 5.11213C25.4698 5.94828 26.2951 6.5763 27.2218 6.75067C27.7181 6.84413 28.143 7.16925 28.36 7.62065C28.5771 8.07205 28.5657 8.60697 28.3292 9.05198C27.8864 9.8857 27.8617 10.9224 28.2634 11.758L28.7465 12.7626C29.3074 12.7685 30.2423 12.5604 31.3217 12.0414C32.4012 11.5223 33.1474 10.922 33.4931 10.4802L33.01 9.47556C32.6082 8.64004 31.783 8.01202 30.8562 7.83765C30.36 7.74419 29.9351 7.41907 29.718 6.96767C29.5009 6.51628 29.5123 5.98136 29.7485 5.53571C30.1917 4.70262 30.2164 3.66588 29.8143 2.82973L29.3315 1.82572C28.7706 1.81987 27.8358 2.0279 26.7563 2.54696Z" fill="white" fill-opacity="0.9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.9904 3.31291C27.8001 2.92355 28.6943 2.58571 28.7989 2.78233C28.8841 2.94028 29.0312 3.25581 28.5338 3.90522C28.051 4.53676 28.216 5.18735 27.869 5.35422C27.4654 5.5483 27.278 5.21811 26.6228 5.11596C25.9476 5.011 25.6398 4.57848 25.5557 4.38904C25.439 4.12546 26.1806 3.70228 26.9904 3.31291Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.6228 5.11609C25.9476 5.01113 25.6398 4.57861 25.5557 4.38917C25.4631 4.18057 25.9091 3.8717 26.5008 3.55853C26.3734 3.72661 26.242 4.01506 26.4692 4.3493C26.7057 4.69772 27.4962 4.82848 27.5599 5.20575C27.5728 5.27927 27.5662 5.34594 27.5468 5.40711C27.3118 5.37072 27.0822 5.18777 26.6228 5.11609Z" fill="#4F2A93"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.4545 8.15813C29.7009 8.03962 29.9806 8.4586 30.4725 8.56572C31.0758 8.69672 31.6951 8.91751 32.101 9.46854C32.4353 9.92237 32.3797 10.0257 32.2823 10.116C32.1854 10.2059 31.3794 10.7769 30.4308 11.0062C29.4815 11.2359 28.839 11.123 28.7533 10.7323C28.672 10.3604 28.7764 9.9595 29.0613 9.41694C29.3342 8.89791 29.208 8.27663 29.4545 8.15813Z" fill="#9A62FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.2826 10.1159C32.1851 10.2062 31.3791 10.7772 30.4305 11.0065C29.676 11.1889 29.1155 11.155 28.8743 10.9366C28.8722 10.9307 28.8701 10.9247 28.8696 10.9188C28.8297 10.7119 29.4967 10.2558 30.4617 9.97051C31.3811 9.69797 32.2074 9.64738 32.3569 9.96638C32.3581 10.0363 32.3243 10.0773 32.2826 10.1159Z" fill="#4F2A93"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -105,6 +105,11 @@
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.btn-primary:active {
border: 2px solid $purple-400 !important;
box-shadow: none !important;
}
.class-badge {
$badge-size: 32px;

View File

@@ -7,7 +7,7 @@
:class="{'open': expand}"
@click="expand = !expand"
>
Priviliges, Gem Balance
Privileges, Gem Balance
</h3>
</div>
<div

View File

@@ -91,11 +91,11 @@
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Consecutive months:
Cumulative months:
</label>
<div class="col-sm-9">
<input
v-model="hero.purchased.plan.consecutive.count"
v-model="hero.purchased.plan.cumulativeCount"
class="form-control"
type="number"
min="0"
@@ -105,39 +105,23 @@
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Perk offset months:
Received hourglass bonus:
</label>
<div class="col-sm-9">
<input
v-model="hero.purchased.plan.consecutive.offset"
class="form-control"
type="number"
min="0"
step="1"
>
<div class="input-group">
<input
v-model="hero.purchased.plan.hourglassPromoReceived"
class="form-control"
type="text"
>
<div class="input-group-append">
<strong class="input-group-text">
{{ dateFormat(hero.purchased.plan.hourglassPromoReceived) }}
</strong>
</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Perk month count:
</label>
<div class="col-sm-9">
<input
v-model="hero.purchased.plan.perkMonthCount"
class="form-control"
type="number"
min="0"
max="2"
step="1"
>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Next Mystic Hourglass:
</label>
<strong class="col-sm-9 col-form-label">{{ nextHourglassDate }}</strong>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Mystic Hourglasses:
@@ -162,8 +146,8 @@
class="form-control"
type="number"
min="0"
max="25"
step="5"
max="26"
step="2"
>
</div>
</div>
@@ -172,7 +156,7 @@
Total Gem cap:
</label>
<strong class="col-sm-9 col-form-label">
{{ Number(hero.purchased.plan.consecutive.gemCapExtra) + 25 }}
{{ Number(hero.purchased.plan.consecutive.gemCapExtra) + 24 }}
</strong>
</div>
<div class="form-group row">
@@ -185,7 +169,7 @@
class="form-control"
type="number"
min="0"
:max="hero.purchased.plan.consecutive.gemCapExtra + 25"
:max="hero.purchased.plan.consecutive.gemCapExtra + 24"
step="1"
>
</div>
@@ -268,6 +252,7 @@ export default {
nextHourglassDate () {
const currentPlanContext = getPlanContext(this.hero, new Date());
if (!currentPlanContext.nextHourglassDate) return 'N/A';
return currentPlanContext.nextHourglassDate.format('MMMM YYYY');
},
},

View File

@@ -131,13 +131,6 @@
>{{ $t('requestFeature') }}
</a>
</li>
<li>
<a
href="https://habitica.fandom.com/"
target="_blank"
>{{ $t('wiki') }}
</a>
</li>
</ul>
</div>
<!-- Developers -->
@@ -186,7 +179,7 @@
</div>
<div class="donate-button">
<button
class="button btn-contribute"
class="btn button btn-secondary btn-contribute"
@click="donate()"
>
<div class="text">
@@ -309,7 +302,7 @@
<div class="my-2">
Time Traveling! It is {{ new Date().toLocaleDateString() }}
<a
class="btn btn-warning btn-small"
class="btn btn-small"
@click="resetTime()"
>
Reset
@@ -341,7 +334,7 @@
</button>
<div
v-if="debugMenuShown"
class="debug-toggle debug-group"
class="btn debug-toggle debug-group"
>
<div class="debug-pop">
<a
@@ -512,7 +505,14 @@ li {
grid-area: debug-pop;
}
.time-travel { grid-area: time-travel;}
.time-travel {
grid-area: time-travel;
a:hover {
text-decoration: none !important;
}
}
footer {
background-color: $gray-500;
@@ -584,42 +584,65 @@ h3 {
}
.debug {
margin-top: 16px;
border: 2px solid transparent;
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
display: flex;
justify-content: center;
margin-top: 16px;
padding: 2px 12px;
&:hover {
box-shadow: 0 3px 6px 0 rgba($black, 0.12), 0 3px 6px 0 rgba($black, 0.24);
}
&:focus {
border: 2px solid $purple-400 !important;
box-shadow: 0 3px 6px 0 rgba($black, 0.12), 0 3px 6px 0 rgba($black, 0.24);
}
:active {
border: 2px solid $purple-600 !important;
box-shadow: none;
}
}
.debug-group {
border-radius: 4px;
padding: 16px;
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
font-weight: 700;
background-color: $gray-600;
border: 2px solid transparent;
border-radius: 4px;
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
font-weight: 700;
padding: 8px 16px;
.btn {
margin: 2px;
}
a:hover {
border: 2px solid transparent;
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
text-decoration: none !important;
}
}
.btn-small {
background-color: $maroon-100;
border: 2px solid transparent;
color: $white !important;
line-height: 18px;
&:hover {
background-color: $maroon-100;
text-decoration: none !important;
border: 2px solid $maroon-100;
}
}
.btn-secondary {
padding: 2px 12px;
}
.btn-secondary a:hover {
text-decoration: none !important;
}
.btn-contribute {
background: $white;
border-radius: 2px;
width: 175px;
height: 32px;
color: $gray-50;
text-align: center;
vertical-align: middle;
padding: 0;
margin: 0;
&:hover {
color:$purple-300;
box-shadow: 0 3px 6px 0 rgba(26, 24, 29, 0.16), 0 3px 6px 0 rgba(26, 24, 29, 0.24);
&:active:not(:disabled) {
color:$purple-300;
border: 1px solid $purple-400;
box-shadow: 0 3px 6px 0 rgba(26, 24, 29, 0.16), 0 3px 6px 0 rgba(26, 24, 29, 0.24);
}
}
border: 2px solid transparent;
a {
display: flex;

View File

@@ -1,93 +1,100 @@
<template>
<div
v-if="member.preferences"
class="avatar"
:style="{width, height, paddingTop}"
:class="backgroundClass"
@click.prevent="castEnd()"
>
<div class="avatar-wrapper">
<div
class="character-sprites"
:style="{margin: spritesMargin}"
v-if="member.preferences"
class="avatar"
:style="{width, height, paddingTop}"
:class="topLevelClassList"
@click.prevent="castEnd()"
>
<template v-if="!avatarOnly">
<!-- Mount Body-->
<span
v-if="member.items.currentMount"
:class="'Mount_Body_' + member.items.currentMount"
></span>
</template>
<!-- Buffs that cause visual changes to avatar: Snowman, Ghost, Flower, etc-->
<template v-for="(klass, item) in visualBuffs">
<span
v-if="member.stats.buffs[item] && showVisualBuffs"
:key="item"
:class="klass"
></span>
</template>
<!-- Show flower ALL THE TIME!!!-->
<!-- See https://github.com/HabitRPG/habitica/issues/7133-->
<span :class="'hair_flower_' + member.preferences.hair.flower"></span>
<!-- Show avatar only if not currently affected by visual buff-->
<template v-if="showAvatar()">
<span :class="['chair_' + member.preferences.chair, specialMountClass]"></span>
<span :class="[getGearClass('back'), specialMountClass]"></span>
<span :class="[skinClass, specialMountClass]"></span>
<!-- eslint-disable max-len-->
<span
:class="[shirtClass, specialMountClass]"
></span>
<!-- eslint-enable max-len-->
<span :class="['head_0', specialMountClass]"></span>
<!-- eslint-disable max-len-->
<span :class="[member.preferences.size + '_' + getGearClass('armor'), specialMountClass]"></span>
<!-- eslint-enable max-len-->
<span :class="[getGearClass('back_collar'), specialMountClass]"></span>
<template
v-for="type in ['bangs', 'base', 'mustache', 'beard']"
>
<div
class="character-sprites"
:style="{margin: spritesMargin}"
>
<template v-if="!avatarOnly">
<!-- Mount Body-->
<span
:key="type"
:class="[hairClass(type), specialMountClass]"
v-if="member.items.currentMount"
:class="'Mount_Body_' + member.items.currentMount"
></span>
</template>
<span :class="[getGearClass('body'), specialMountClass]"></span>
<span :class="[getGearClass('eyewear'), specialMountClass]"></span>
<span :class="[getGearClass('head'), specialMountClass]"></span>
<span :class="[getGearClass('headAccessory'), specialMountClass]"></span>
<span :class="['hair_flower_' + member.preferences.hair.flower, specialMountClass]"></span>
<!-- Buffs that cause visual changes to avatar: Snowman, Ghost, Flower, etc-->
<template v-for="(klass, item) in visualBuffs">
<span
v-if="member.stats.buffs[item] && showVisualBuffs"
:key="item"
:class="klass"
></span>
</template>
<!-- Show flower ALL THE TIME!!!-->
<!-- See https://github.com/HabitRPG/habitica/issues/7133-->
<span :class="'hair_flower_' + member.preferences.hair.flower"></span>
<!-- Show avatar only if not currently affected by visual buff-->
<template v-if="showAvatar()">
<span :class="['chair_' + member.preferences.chair, specialMountClass]"></span>
<span :class="[getGearClass('back'), specialMountClass]"></span>
<span :class="[skinClass, specialMountClass]"></span>
<!-- eslint-disable max-len-->
<span
:class="[shirtClass, specialMountClass]"
></span>
<!-- eslint-enable max-len-->
<span :class="['head_0', specialMountClass]"></span>
<!-- eslint-disable max-len-->
<span :class="[member.preferences.size + '_' + getGearClass('armor'), specialMountClass]"></span>
<!-- eslint-enable max-len-->
<span :class="[getGearClass('back_collar'), specialMountClass]"></span>
<template
v-for="type in ['bangs', 'base', 'mustache', 'beard']"
>
<span
:key="type"
:class="[hairClass(type), specialMountClass]"
></span>
</template>
<span :class="[getGearClass('body'), specialMountClass]"></span>
<span :class="[getGearClass('eyewear'), specialMountClass]"></span>
<span :class="[getGearClass('head'), specialMountClass]"></span>
<span :class="[getGearClass('headAccessory'), specialMountClass]"></span>
<span
:class="[
'hair_flower_' + member.preferences.hair.flower, specialMountClass
]"
></span>
<span
v-if="!hideGear('shield')"
:class="[getGearClass('shield'), specialMountClass]"
></span>
<span
v-if="!hideGear('weapon')"
:class="[getGearClass('weapon'), specialMountClass]"
class="weapon"
></span>
</template>
<!-- Resting-->
<span
v-if="!hideGear('shield')"
:class="[getGearClass('shield'), specialMountClass]"
v-if="member.preferences.sleep"
class="zzz"
></span>
<span
v-if="!hideGear('weapon')"
:class="[getGearClass('weapon'), specialMountClass]"
></span>
</template>
<!-- Resting-->
<span
v-if="member.preferences.sleep"
class="zzz"
></span>
<template v-if="!avatarOnly">
<!-- Mount Head-->
<span
v-if="member.items.currentMount"
:class="'Mount_Head_' + member.items.currentMount"
></span>
<!-- Pet-->
<span
class="current-pet"
:class="petClass"
></span>
</template>
<template v-if="!avatarOnly">
<!-- Mount Head-->
<span
v-if="member.items.currentMount"
:class="'Mount_Head_' + member.items.currentMount"
></span>
<!-- Pet-->
<span
class="current-pet"
:class="petClass"
></span>
</template>
</div>
<class-badge
v-if="hasClass && !hideClassBadge"
class="under-avatar"
:member-class="member.stats.class"
/>
</div>
<class-badge
v-if="hasClass && !hideClassBadge"
class="under-avatar"
:member-class="member.stats.class"
/>
</div>
</template>
@@ -96,15 +103,23 @@
.avatar {
width: 141px;
height: 147px;
image-rendering: pixelated;
position: relative;
cursor: pointer;
&.centered-avatar {
margin: 0 auto;
}
// resetting the additional padding
margin-bottom: -0.5rem !important;
}
.character-sprites {
width: 90px;
height: 90px;
display: inline-flex;
}
.character-sprites span {
@@ -123,6 +138,27 @@
.invert {
filter: invert(100%);
}
.weapon {
// the only one that is relative so that it fits into the parent div
position: relative !important;
}
.debug {
border: 1px solid red;
.character-sprites {
border: 1px solid blue;
}
.weapon {
border: 1px solid green;
}
span {
border: 1px solid yellow;
}
}
</style>
<script>
@@ -133,12 +169,24 @@ import foolPet from '../mixins/foolPet';
import ClassBadge from '@/components/members/classBadge';
/**
* TODO replace avatarOnly with multiple options like
* - showMount
* - showPet
* - showBackground
* - showWeapons
*/
export default {
components: {
ClassBadge,
},
mixins: [foolPet],
props: {
debugMode: {
type: Boolean,
default: false,
},
member: {
type: Object,
required: true,
@@ -156,14 +204,21 @@ export default {
},
overrideAvatarGear: {
type: Object,
default (data) {
return data;
},
},
width: {
type: Number,
default: 140,
type: String,
default: '140px',
},
height: {
type: Number,
default: 147,
type: String,
default: undefined,
},
centerAvatar: {
type: Boolean,
default: false,
},
spritesMargin: {
type: String,
@@ -171,11 +226,16 @@ export default {
},
overrideTopPadding: {
type: String,
default: null,
},
showVisualBuffs: {
type: Boolean,
default: true,
},
showWeapon: {
type: Boolean,
default: true,
},
},
computed: {
...mapState({
@@ -204,6 +264,19 @@ export default {
return val;
},
topLevelClassList () {
const classes = [this.backgroundClass];
if (this.debugMode) {
classes.push('debug');
}
if (this.centerAvatar) {
classes.push('centered-avatar');
}
return classes.join(' ');
},
backgroundClass () {
if (this.member) {
const { background } = this.member.preferences;
@@ -290,6 +363,10 @@ export default {
},
hideGear (gearType) {
if (!this.member) return true;
if (!this.showWeapon) {
return true;
}
if (gearType === 'weapon') {
const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];

View File

@@ -25,9 +25,13 @@
</b-modal>
</template>
<style scoped>
<style scoped lang="scss">
.modal-body {
padding-bottom: 2em;
.btn {
margin: 0px 2px;
}
}
</style>

View File

@@ -1,352 +0,0 @@
<template>
<div>
<div
v-if="isUserMentioned"
class="mentioned-icon"
></div>
<div
v-if="hasPermission(user, 'moderator') && msg.flagCount"
class="message-hidden"
>
{{ flagCountDescription }}
</div>
<div class="card-body">
<user-link
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p class="time">
<span
v-if="msg.username"
class="mr-1"
>@{{ msg.username }}</span>
<span
v-if="msg.username"
class="mr-1"
></span>
<span
v-b-tooltip.hover="messageDate"
>{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4">({{ msg.client }})</span>
</p>
<div
ref="markdownContainer"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
></div>
<hr>
<div
v-if="msg.id"
class="d-flex"
>
<div
class="action d-flex align-items-center"
@click="copyAsTodo(msg)"
>
<div
class="svg-icon"
v-html="icons.copy"
></div>
<div>{{ $t('copyAsTodo') }}</div>
</div>
<div
v-if="(user.flags.communityGuidelinesAccepted && msg.uuid !== 'system')
&& (!isMessageReported || hasPermission(user, 'moderator'))"
class="action d-flex align-items-center"
@click="report(msg)"
>
<div
v-once
class="svg-icon"
v-html="icons.report"
></div>
<div v-once>
{{ $t('report') }}
</div>
</div>
<div
v-if="msg.uuid === user._id || hasPermission(user, 'moderator')"
class="action d-flex align-items-center"
@click="remove()"
>
<div
v-once
class="svg-icon"
v-html="icons.delete"
></div>
<div v-once>
{{ $t('delete') }}
</div>
</div>
<div
v-b-tooltip="{title: likeTooltip(msg.likes[user._id])}"
class="ml-auto d-flex"
>
<div
v-if="likeCount > 0"
class="action d-flex align-items-center mr-0"
:class="{activeLike: msg.likes[user._id]}"
@click="like()"
>
<div
class="svg-icon"
:title="$t('liked')"
v-html="icons.liked"
></div>
+{{ likeCount }}
</div>
<div
v-if="likeCount === 0"
class="action d-flex align-items-center mr-0"
:class="{activeLike: msg.likes[user._id]}"
@click="like()"
>
<div
class="svg-icon"
:title="$t('like')"
v-html="icons.like"
></div>
</div>
</div>
<span v-if="!msg.likes[user._id]">{{ $t('like') }}</span>
</div>
</div>
</div>
</template>
<style lang="scss">
.at-highlight {
background-color: rgba(213, 200, 255, 0.32);
padding: 0.1rem;
}
.at-text {
color: #6133b4;
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #bda8ff;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
.message-hidden {
margin-left: 1.5em;
margin-top: 1em;
color: red;
}
hr {
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.card-body {
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
.time {
font-size: 12px;
color: #878190;
margin-bottom: 0.5rem;
}
.text {
font-size: 14px;
color: #4e4a57;
text-align: initial;
min-height: 0rem;
}
}
.action {
display: inline-block;
color: #878190;
margin-right: 1em;
font-size: 12px;
:hover {
cursor: pointer;
}
.svg-icon {
color: #A5A1AC;
margin-right: .2em;
width: 16px;
}
}
.activeLike {
color: $purple-300;
.svg-icon {
color: $purple-400;
}
}
</style>
<script>
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp';
import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants';
import renderWithMentions from '@/libs/renderWithMentions';
import { userStateMixin } from '../../mixins/userState';
import userLink from '../userLink';
import deleteIcon from '@/assets/svg/delete.svg';
import copyIcon from '@/assets/svg/copy.svg';
import likeIcon from '@/assets/svg/like.svg';
import likedIcon from '@/assets/svg/liked.svg';
import reportIcon from '@/assets/svg/report.svg';
export default {
components: { userLink },
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
},
mixins: [userStateMixin],
props: {
msg: {},
groupId: {},
},
data () {
return {
icons: Object.freeze({
like: likeIcon,
copy: copyIcon,
report: reportIcon,
delete: deleteIcon,
liked: likedIcon,
}),
reported: false,
};
},
computed: {
isUserMentioned () {
const message = this.msg;
if (message.highlight) return true;
const { user } = this;
const displayName = user.profile.name;
const { username } = user.auth.local;
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
message.highlight = new RegExp(pattern, 'i').test(message.text);
return message.highlight;
},
likeCount () {
const message = this.msg;
if (!message.likes) return 0;
let likeCount = 0;
for (const key of Object.keys(message.likes)) {
const like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
isMessageReported () {
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
},
flagCountDescription () {
if (!this.msg.flagCount) return '';
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) return 'Message flagged once, not hidden';
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) return 'Message hidden';
return 'Message hidden (shadow-muted)';
},
messageDate () {
const date = moment(this.msg.timestamp).toDate();
return date.toString();
},
},
mounted () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (let i = 0; i < links.length; i += 1) {
let link = links[i].pathname;
// Internet Explorer does not provide the leading slash character in the pathname
link = link.charAt(0) === '/' ? link : `/${link}`;
if (link.startsWith('/profile/')) {
links[i].onclick = ev => {
ev.preventDefault();
this.$router.push({ path: link });
};
}
}
this.CHAT_FLAG_LIMIT_FOR_HIDING = CHAT_FLAG_LIMIT_FOR_HIDING;
this.CHAT_FLAG_FROM_SHADOW_MUTE = CHAT_FLAG_FROM_SHADOW_MUTE;
this.$emit('chat-card-mounted', this.msg.id);
},
methods: {
async like () {
const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', {
groupId: this.groupId,
chatId: message.id,
});
message.likes[this.user._id] = !message.likes[this.user._id];
this.$emit('message-liked', message);
this.$root.$emit('bv::hide::tooltip');
},
likeTooltip (likedStatus) {
if (!likedStatus) return this.$t('like');
return null;
},
copyAsTodo (message) {
this.$root.$emit('habitica::copy-as-todo', message);
},
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
this.reported = true;
}
this.$root.$off('habitica:report-result');
});
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: this.groupId || 'privateMessage',
});
},
async remove () {
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
const message = this.msg;
this.$emit('message-removed', message);
await this.$store.dispatch('chat:deleteChat', {
groupId: this.groupId,
chatId: message.id,
});
},
parseMarkdown (text) {
return renderWithMentions(text, this.user);
},
},
};
</script>

View File

@@ -3,15 +3,6 @@
ref="container"
class="container-fluid"
>
<div class="row">
<div class="col-12">
<copy-as-todo-modal
:group-type="groupType"
:group-name="groupName"
:group-id="groupId"
/>
</div>
</div>
<div class="row loadmore">
<div v-if="canLoadMore">
<div class="loadmore-divider"></div>
@@ -33,6 +24,8 @@
<div
v-for="msg in messages.filter(m => chat && canViewFlag(m))"
:key="msg.id"
class="message-row"
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div class="d-flex">
<avatar
@@ -45,16 +38,14 @@
:override-top-padding="'14px'"
@click.native="showMemberModal(msg.uuid)"
/>
<div class="card">
<chat-card
:msg="msg"
:group-id="groupId"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@chat-card-mounted="itemWasMounted"
/>
</div>
<message-card
:msg="msg"
:group-id="groupId"
:user-sent-message="user._id === msg.uuid"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@message-card-mounted="itemWasMounted"
/>
<avatar
v-if="user._id === msg.uuid"
:class="{ invisible: avatarUnavailable(msg) }"
@@ -137,11 +128,27 @@
margin-bottom: .5em;
padding: 0rem;
width: 90%;
&.system-message {
width: 100%;
}
}
.message-scroll .d-flex {
min-width: 1px;
}
.message-row {
margin-left: 12px;
margin-right: 0;
margin-bottom: 1.2rem;
&:not(.margin-right) {
.d-flex {
justify-content: flex-end;
}
}
}
</style>
<script>
@@ -152,13 +159,13 @@ import findIndex from 'lodash/findIndex';
import { userStateMixin } from '../../mixins/userState';
import Avatar from '../avatar';
import copyAsTodoModal from './copyAsTodoModal';
import chatCard from './chatCard';
import MessageCard from '@/components/messages/messageCard.vue';
// TODO merge chatMessages.vue (party message list) with messageList.vue (private message list)
export default {
components: {
copyAsTodoModal,
chatCard,
MessageCard,
Avatar,
},
mixins: [userStateMixin],

View File

@@ -1,105 +0,0 @@
<template>
<b-modal
id="copyAsTodo"
:title="$t('copyMessageAsToDo')"
:hide-footer="true"
size="md"
>
<div class="form-group">
<input
v-model="task.text"
class="form-control"
type="text"
>
</div>
<div class="form-group">
<textarea
v-model="task.notes"
class="form-control"
rows="5"
focus-element="true"
></textarea>
</div>
<hr>
<task
v-if="task._id"
:is-user="isUser"
:task="task"
/>
<div class="modal-footer">
<button
class="btn btn-secondary"
@click="close()"
>
{{ $t('close') }}
</button>
<button
class="btn btn-primary"
@click="saveTodo()"
>
{{ $t('submit') }}
</button>
</div>
</b-modal>
</template>
<script>
import taskDefaults from '@/../../common/script/libs/taskDefaults';
import { mapActions } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import notificationsMixin from '@/mixins/notifications';
import Task from '@/components/tasks/task';
const baseUrl = 'https://habitica.com';
export default {
directives: {
markdown: markdownDirective,
},
components: {
Task,
},
mixins: [notificationsMixin],
props: ['copyingMessage', 'groupType', 'groupName', 'groupId'],
data () {
return {
isUser: true,
task: {},
};
},
mounted () {
this.$root.$on('habitica::copy-as-todo', message => {
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${this.groupPath()})`;
const newTask = {
text: message.text,
type: 'todo',
notes,
};
this.task = taskDefaults(newTask, this.$store.state.user.data);
this.$root.$emit('bv::show::modal', 'copyAsTodo');
});
},
beforeDestroy () {
this.$root.$off('habitica::copy-as-todo');
},
methods: {
...mapActions({
createTask: 'tasks:create',
}),
groupPath () {
if (this.groupType === 'party') {
return `${baseUrl}/party`;
}
return `${baseUrl}/groups/guild/${this.groupId}`;
},
close () {
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
saveTodo () {
this.createTask(this.task);
this.text(this.$t('messageAddedAsToDo'));
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
},
};
</script>

View File

@@ -27,13 +27,13 @@
</td>
</tr>
<tr
v-if="group.purchased.plan.consecutive.count || group.purchased.plan.consecutive.offset"
v-if="group.purchased.plan.consecutive.count"
>
<td>
<span class="glyphicon glyphicon-forward"></span>
{{ $t('consecutiveSubscription') }}
<ul class="list-unstyled">
<li>{{ $t('consecutiveMonths') }} {{ group.purchased.plan.consecutive.count + group.purchased.plan.consecutive.offset }}</li> <!-- eslint-disable-line max-len -->
<li>{{ $t('consecutiveMonths') }} {{ group.purchased.plan.consecutive.count }}</li> <!-- eslint-disable-line max-len -->
<li>{{ $t('gemCapExtra') }} {{ group.purchased.plan.consecutive.gemCapExtra }}</li>
<li>{{ $t('mysticHourglasses') }} {{ group.purchased.plan.consecutive.trinkets }}</li>
</ul>

View File

@@ -22,13 +22,13 @@
:placeholder="placeholder"
:class="{'user-entry': newMessage}"
:maxlength="MAX_MESSAGE_LENGTH"
@keydown="updateCarretPosition"
@keydown="autoCompleteMixinUpdateCarretPosition"
@keyup.ctrl.enter="sendMessageShortcut()"
@keydown.tab="handleTab($event)"
@keydown.up="selectPreviousAutocomplete($event)"
@keydown.down="selectNextAutocomplete($event)"
@keypress.enter="selectAutocomplete($event)"
@keydown.esc="handleEscape($event)"
@keydown.tab="autoCompleteMixinHandleTab($event)"
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
@keydown.esc="autoCompleteMixinHandleEscape($event)"
@paste="disableMessageSendShortcut()"
></textarea>
<span>{{ currentLength }} / {{ MAX_MESSAGE_LENGTH }}</span>
@@ -36,8 +36,8 @@
ref="autocomplete"
:text="newMessage"
:textbox="textbox"
:coords="coords"
:caret-position="caretPosition"
:coords="mixinData.autoComplete.coords"
:caret-position="mixinData.autoComplete.caretPosition"
:chat="group.chat"
@select="selectedAutocomplete"
/>
@@ -74,7 +74,7 @@
<slot name="additionRow"></slot>
<div class="row">
<div class="hr col-12"></div>
<chat-message
<chat-messages
:chat.sync="group.chat"
:group-type="group.type"
:group-id="group._id"
@@ -86,16 +86,15 @@
</template>
<script>
import debounce from 'lodash/debounce';
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import autocomplete from '../chat/autoComplete';
import communityGuidelines from './communityGuidelines';
import chatMessage from '../chat/chatMessages';
import chatMessages from '../chat/chatMessages';
import { mapState } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
export default {
directives: {
@@ -104,23 +103,18 @@ export default {
components: {
autocomplete,
communityGuidelines,
chatMessage,
chatMessages,
},
mixins: [externalLinks],
mixins: [externalLinks, autoCompleteHelperMixin],
props: ['label', 'group', 'placeholder'],
data () {
return {
newMessage: '',
sending: false,
caretPosition: 0,
chat: {
submitDisable: false,
submitTimeout: null,
},
coords: {
TOP: 0,
LEFT: 0,
},
textbox: null,
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
};
@@ -142,35 +136,6 @@ export default {
this.handleExternalLinks();
},
methods: {
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
getCoord (e, text) {
this.caretPosition = text.selectionEnd;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, this.caretPosition);
span.textContent = text.value.substr(this.caretPosition) || '.';
div.appendChild(span);
this.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this.getCoord(eventUpdate, text);
},
async sendMessageShortcut () {
// If the user recently pasted in the text field, don't submit
if (!this.chat.submitDisable) {
@@ -221,50 +186,6 @@ export default {
}, 500);
},
handleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
handleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
selectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
selectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
selectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
if (this.$refs.autocomplete.selected !== null) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
} else {
// no autocomplete selected, newline instead
this.$refs.autocomplete.cancel();
}
}
},
selectedAutocomplete (newText, newCaret) {
this.newMessage = newText;
// Wait for v-modal to update
@@ -273,7 +194,6 @@ export default {
this.textbox.focus();
});
},
fetchRecentMessages () {
this.$emit('fetchRecentMessages');
},
@@ -284,10 +204,7 @@ export default {
beforeRouteUpdate (to, from, next) {
// Reset chat
this.newMessage = '';
this.coords = {
TOP: 0,
LEFT: 0,
};
this.autoCompleteMixinResetCoordsPosition();
next();
},

View File

@@ -334,11 +334,6 @@
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
target="_blank"
>{{ $t('requestFeature') }}</a>
<a
class="topbar-dropdown-item dropdown-item"
href="https://habitica.fandom.com/wiki/Habitica_Wiki"
target="_blank"
>{{ $t('wiki') }}</a>
</div>
</li>
</b-navbar-nav>
@@ -354,13 +349,15 @@
></div>
<span>{{ userHourglasses }}</span>
</div>
<div class="item-with-icon gem">
<div
class="item-with-icon gem"
@click.prevent="showBuyGemsModal()"
>
<a
v-b-tooltip.hover.bottom="$t('gems')"
class="top-menu-icon svg-icon gem mr-2"
:aria-label="$t('gems')"
href="#buy-gems"
@click.prevent="showBuyGemsModal()"
v-html="icons.gem"
></a>
<span>{{ userGems }}</span>

View File

@@ -41,7 +41,8 @@ export default {
},
methods: {
action () {
if (!this.notification || !this.notification.data) {
if (!this.notification || !this.notification.data
|| this.notification.data.destination === this.$route.path) {
return;
}
if (this.notification.data.destination.indexOf('backgrounds') !== -1) {

View File

@@ -106,7 +106,7 @@
</div>
<div slot="drawer-header">
<div class="drawer-tab-container">
<div class="clearfix">
<div class="clearfix mb-2">
<toggle-switch
class="float-right align-with-tab"
:label="$t(costumeMode ? 'useCostume' : 'autoEquipBattleGear')"

View File

@@ -28,7 +28,6 @@
:name="member.profile.name"
:backer="member.backer"
:contributor="member.contributor"
:smaller-style="true"
/>
<inline-class-badge
v-if="member.stats"

View File

@@ -0,0 +1,110 @@
<template>
<div
class="d-inline-flex like-button"
@click="like()"
>
<div
v-b-tooltip="{title: likeTooltip(likeCount)}"
class="d-flex"
>
<div
v-if="likeCount > 0"
class="action d-flex align-items-center mr-0"
:class="{isLiked: true, currentUserLiked: likedByCurrentUser}"
>
<div
class="svg-icon mr-1"
:title="$t('liked')"
v-html="icons.liked"
></div>
+{{ likeCount }}
</div>
<div
v-if="likeCount === 0"
class="action d-flex align-items-center mr-1"
>
<div
class="svg-icon"
:title="$t('like')"
v-html="icons.like"
></div>
</div>
</div>
<span v-if="likeCount === 0">{{ $t('like') }}</span>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
.action {
display: inline-block;
margin-right: 1em;
.svg-icon {
color: $gray-100;
width: 16px;
}
&.isLiked {
color: $purple-200;
font-weight: bold;
.svg-icon {
color: $purple-300;
}
}
}
.like-button {
color: $gray-100;
font-size: 12px;
line-height: 16px;
&:hover {
cursor: pointer;
color: $purple-200;
.svg-icon {
color: $purple-300;
}
}
}
</style>
<script>
import likeIcon from '@/assets/svg/like.svg';
import likedIcon from '@/assets/svg/liked.svg';
export default {
props: {
likeCount: {
type: Number,
},
likedByCurrentUser: {
type: Boolean,
},
},
data () {
return {
icons: Object.freeze({
like: likeIcon,
liked: likedIcon,
}),
};
},
methods: {
async like () {
this.$emit('toggle-like');
},
likeTooltip (likedStatus) {
if (!likedStatus) return this.$t('like');
return null;
},
},
};
</script>

View File

@@ -1,73 +1,157 @@
<template>
<div class="card-body">
<user-link
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p class="time">
<span
v-if="msg.username"
class="mr-1"
>@{{ msg.username }}</span><span
v-if="msg.username"
class="mr-1"
></span>
<span
v-b-tooltip.hover="messageDate"
>{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4"> ({{ msg.client }})</span>
</p>
<div
class="card"
:class="{
'system-message': isSystemMessage
}"
>
<div
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
></div>
<div
v-if="isMessageReported"
class="reported"
>
<span v-once>{{ $t('reportedMessage') }}</span><br>
<span v-once>{{ $t('canDeleteNow') }}</span>
</div>
<hr>
<div
v-if="msg.id"
class="d-flex"
v-b-tooltip.hover="messageDateForSystemMessage"
class="message-card"
:class="{
'user-sent-message': userSentMessage,
'user-received-message': !userSentMessage && !isSystemMessage,
'system-message': isSystemMessage
}"
>
<div
v-if="!isMessageReported"
class="action d-flex align-items-center"
@click="report(msg)"
v-if="isUserMentioned"
class="mentioned-icon"
></div>
<div
v-if="userIsModerator && msg.flagCount"
class="message-hidden"
>
<div
v-once
class="svg-icon"
v-html="icons.report"
></div>
<div v-once>
{{ $t('report') }}
</div>
{{ flagCountDescription }}
</div>
<div
class="action d-flex align-items-center"
@click="remove()"
class="card-body"
>
<user-link
v-if="!isSystemMessage"
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p
v-if="!isSystemMessage"
class="time"
>
<span
v-if="msg.username"
class="mr-1"
>@{{ msg.username }}</span><span
v-if="msg.username"
class="mr-1"
></span>
<span v-b-tooltip.hover="messageDate">{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4">
({{ msg.client }})
</span>
</p>
<b-dropdown
v-if="!isSystemMessage"
right="right"
variant="flat"
toggle-class="with-icon"
class="card-menu no-min-width"
:no-caret="true"
>
<template #button-content>
<span
v-once
class="svg-icon inline menuIcon color"
v-html="icons.menuIcon"
>
</span>
</template>
<b-dropdown-item
class="selectListItem"
@click="copy(msg)"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.copy"
></span>
<span v-once>
{{ $t('copy') }}
</span>
</span>
</b-dropdown-item>
<b-dropdown-item
v-if="canReportMessage"
class="selectListItem custom-hover--red"
@click="report(msg)"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.report"
></span>
<span v-once>
{{ $t('report') }}
</span>
</span>
</b-dropdown-item>
<b-dropdown-item
v-if="canDeleteMessage"
class="selectListItem custom-hover--red"
@click="remove()"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.delete"
></span>
<span v-once>
{{ $t('delete') }}
</span>
</span>
</b-dropdown-item>
</b-dropdown>
<div
v-once
class="svg-icon"
v-html="icons.delete"
></div>
<div v-once>
{{ $t('delete') }}
v-if="isSystemMessage"
class="system-message-body"
>
{{ msg.unformattedText }}
</div>
<div
v-else
ref="markdownContainer"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
></div>
<div
v-if="isMessageReported"
class="reported"
>
<span v-once>{{ $t('reportedMessage') }}</span><br>
<span v-once>{{ $t('canDeleteNow') }}</span>
</div>
<like-button
v-if="canLikeMessage"
class="mt-75"
:liked-by-current-user="msg.likes[user._id]"
:like-count="likeCount"
@toggle-like="like()"
/>
</div>
</div>
</div>
</template>
<style lang="scss">
.message-card {
.at-highlight {
background-color: rgba(213, 200, 255, 0.32);
padding: 0.1rem;
@@ -76,43 +160,76 @@
.at-text {
color: #6133b4;
}
.card-menu button {
justify-content: center;
margin: 0;
padding: 0;
height: 1rem;
width: 1rem;
}
.markdown p:last-of-type {
margin-bottom: 0;
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
.action {
display: inline-block;
color: $gray-200;
margin-right: 1em;
font-size: 12px;
.card {
background: transparent !important;
margin-bottom: 0 !important;
}
:hover {
cursor: pointer;
}
.message-card:not(.system-message) {
background: white;
}
.svg-icon {
color: $gray-300;
margin-right: .2em;
width: 16px;
}
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: $purple-500;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
.message-hidden {
margin-left: 1.5em;
margin-top: 1em;
color: red;
}
.active {
color: $purple-300;
.svg-icon {
color: $purple-400;
}
}
.active {
color: $purple-300;
.message-card {
border-radius: 7px;
margin: 0;
padding: 1rem 0.75rem 0.5rem 1rem;
.svg-icon {
color: $purple-400;
}
&.system-message {
padding-top: 0.5rem;
}
.card-body {
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
position: relative;
padding: 0;
.time {
font-size: 12px;
color: $gray-200;
color: $gray-100;
margin-bottom: 0.5rem;
}
@@ -123,49 +240,173 @@
min-height: 0rem;
}
}
}
hr {
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.card-menu {
position: absolute;
top: 0;
right: 0;
.reported {
margin-top: 18px;
color: $red-50;
&:not(.show) {
display: none;
}
}
.card-body:hover {
.card-menu {
display: block;
}
}
hr {
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.reported {
margin-top: 18px;
color: $red-50;
}
.selectListItem:not(:hover) .svg-icon.icon-16.color {
color: #{$gray-100}
}
.custom-hover--red {
--hover-color: #{$maroon-50};
--hover-background: #{rgba($red-500, 0.25)};
}
.user-sent-message {
border: 1px solid $purple-400;
}
.system-message {
border: 1px solid $purple-400;
}
.user-received-message {
border: 1px solid $gray-500;
}
.card-menu {
// icon-color is the menu icon itself
--icon-color: #{$gray-100};
--dropdown-item-hover-icon-color: #{$gray-100};
&:hover {
--icon-color: #{$purple-300};
}
}
.menuIcon {
width: 4px;
height: 1rem;
object-fit: contain;
}
.system-message-body {
line-height: 1.71;
text-align: center;
color: $purple-300;
}
</style>
<script>
import axios from 'axios';
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp';
import { CHAT_FLAG_FROM_SHADOW_MUTE, CHAT_FLAG_LIMIT_FOR_HIDING } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import { CopyToClipboardMixin } from '@/mixins/copyToClipboard';
import renderWithMentions from '@/libs/renderWithMentions';
import { mapState } from '@/libs/store';
import userLink from '../userLink';
import deleteIcon from '@/assets/svg/delete.svg';
import reportIcon from '@/assets/svg/report.svg';
import menuIcon from '@/assets/svg/menu.svg';
import { userStateMixin } from '@/mixins/userState';
import copyIcon from '@/assets/svg/copy.svg';
import LikeButton from '@/components/messages/likeButton.vue';
const LikeLogicMixin = {
computed: {
likeCount () {
const message = this.msg;
if (!message.likes) return 0;
let likeCount = 0;
for (const key of Object.keys(message.likes)) {
const like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
},
methods: {
async like () {
const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', {
groupId: this.groupId,
chatMessageId: this.privateMessageMode ? message.uniqueMessageId : message.id,
});
message.likes[this.user._id] = !message.likes[this.user._id];
this.$emit('message-liked', message);
this.$root.$emit('bv::hide::tooltip');
},
},
};
export default {
components: {
LikeButton,
userLink,
},
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
},
mixins: [externalLinks],
mixins: [
externalLinks, userStateMixin, LikeLogicMixin,
CopyToClipboardMixin,
],
props: {
msg: {},
msg: {
type: Object,
},
groupId: {
type: String,
},
privateMessageMode: {
type: Boolean,
},
userSentMessage: {
type: Boolean,
},
},
data () {
return {
icons: Object.freeze({
delete: deleteIcon,
report: reportIcon,
copy: copyIcon,
menuIcon,
}),
reported: false,
};
@@ -175,19 +416,100 @@ export default {
isMessageReported () {
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
},
messageDateForSystemMessage () {
return this.isSystemMessage ? this.messageDate : '';
},
messageDate () {
const date = moment(this.msg.timestamp).toDate();
return date.toString();
},
userIsModerator () {
return this.hasPermission(this.user, 'moderator');
},
isSystemMessage () {
return this.msg.uuid === 'system';
},
canLikeMessage () {
if (this.isSystemMessage) {
return false;
}
if (this.privateMessageMode) {
return Boolean(this.msg.uniqueMessageId);
}
return this.msg.id;
},
canDeleteMessage () {
return this.privateMessageMode
|| this.msg.uuid === this.user._id
|| this.userIsModerator;
},
canReportMessage () {
if (this.privateMessageMode) {
return !this.isMessageReported;
}
return (this.user.flags.communityGuidelinesAccepted && this.msg.uuid !== 'system')
&& (!this.isMessageReported || this.userIsModerator);
},
isUserMentioned () {
const message = this.msg;
if (message.highlight) {
return true;
}
const { user } = this;
const displayName = user.profile.name;
const { username } = user.auth.local;
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
message.highlight = new RegExp(pattern, 'i').test(message.text);
return message.highlight;
},
flagCountDescription () {
if (!this.msg.flagCount) {
return '';
}
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) {
return 'Message flagged once, not hidden';
}
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) {
return 'Message hidden';
}
return 'Message hidden (shadow-muted)';
},
},
mounted () {
this.$emit('message-card-mounted');
this.handleExternalLinks();
this.mapProfileLinksToModal();
},
updated () {
this.handleExternalLinks();
this.mapProfileLinksToModal();
},
methods: {
mapProfileLinksToModal () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (let i = 0; i < links.length; i += 1) {
let link = links[i].pathname;
// Internet Explorer does not provide the leading slash character in the pathname
link = link.charAt(0) === '/' ? link : `/${link}`;
if (link.startsWith('/profile/')) {
links[i].onclick = ev => {
ev.preventDefault();
this.$router.push({ path: link });
};
}
}
},
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
@@ -199,16 +521,29 @@ export default {
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: 'privateMessage',
groupId: this.groupId,
});
},
async remove () {
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
// eslint-disable-next-line no-alert
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) {
return;
}
const message = this.msg;
this.$emit('message-removed', message);
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
if (this.privateMessageMode) {
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
} else {
await this.$store.dispatch('chat:deleteChat', {
groupId: this.groupId,
chatId: message.id,
});
}
},
copy (message) {
this.mixinCopyToClipboard(message.text, this.$t('messageCopiedToClipboard'));
},
parseMarkdown (text) {
return renderWithMentions(text, this.user);

View File

@@ -1,9 +1,9 @@
<template>
<div
ref="container"
class="container-fluid"
class="message-list"
>
<div class="row loadmore">
<div class="loadmore">
<div v-if="canLoadMore && !isLoading">
<div class="loadmore-divider-holder">
<div class="loadmore-divider"></div>
@@ -28,7 +28,7 @@
<div
v-for="(msg) in messages"
:key="msg.id"
class="row message-row"
class="message-row"
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div
@@ -39,28 +39,31 @@
class="avatar-left"
:member="conversationOpponentUser"
:avatar-only="true"
:override-top-padding="'14px'"
:show-weapon="false"
:debug-mode="false"
:override-top-padding="'0'"
:hide-class-badge="true"
@click.native="showMemberModal(msg.uuid)"
/>
<div
class="card"
:class="{'card-right': user._id !== msg.uuid, 'card-left': user._id === msg.uuid}"
>
<message-card
:msg="msg"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@message-card-mounted="itemWasMounted"
/>
</div>
<message-card
:msg="msg"
:user-sent-message="user._id === msg.uuid"
:group-id="'privateMessage'"
:private-message-mode="true"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@message-card-mounted="itemWasMounted"
/>
<avatar
v-if="user && user._id === msg.uuid"
class="avatar-right"
:member="user"
:avatar-only="true"
:show-weapon="false"
:debug-mode="false"
:hide-class-badge="true"
:override-top-padding="'14px'"
:override-top-padding="'0'"
@click.native="showMemberModal(msg.uuid)"
/>
</div>
@@ -69,121 +72,123 @@
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/colors.scss';
.avatar {
width: 170px;
min-width: 8rem;
height: 120px;
padding-top: 0 !important;
.avatar-left, .avatar-right {
align-self: center;
::v-deep .character-sprites {
margin-bottom: -5px !important;
padding-bottom: 0 !important;
margin-top: -1px !important;
}
.avatar-right {
margin-left: -1rem;
::v-deep .avatar {
margin-left: -1.75rem;
margin-right: -0.5rem;
}
}
::v-deep .character-sprites {
margin-right: 1rem !important;
.avatar-left {
margin-right: 1.5rem;
}
.avatar-right {
overflow: clip;
margin-left: 1.5rem;
::v-deep .character-sprites {
margin-right: 1rem !important;
}
}
.card {
border: 0px;
margin-bottom: 1rem;
padding: 0rem;
width: 684px;
}
.message-list {
width: 100%;
padding-right: 10px;
margin-right: 0 !important;
}
.message-row {
margin-left: 12px;
margin-right: 0;
margin-bottom: 1.2rem;
&:not(.margin-right) {
.d-flex {
justify-content: flex-end;
}
}
}
.card {
border: 0px;
margin-bottom: 1rem;
padding: 0rem;
width: 684px;
}
.message-row {
margin-left: 12px;
margin-right: 12px;
.hr {
width: 100%;
height: 20px;
border-bottom: 1px solid $gray-500;
text-align: center;
margin: 2em 0;
}
&:not(.margin-right) {
.d-flex {
justify-content: flex-end;
}
}
}
@media only screen and (max-width: 1200px) {
.card {
width: 100%;
}
}
.hr-middle {
font-size: 16px;
font-weight: bold;
font-family: 'Roboto Condensed';
line-height: 1.5;
text-align: center;
color: $gray-200;
background-color: $gray-700;
padding: .2em;
margin-top: .2em;
display: inline-block;
width: 100px;
}
@media only screen and (min-width: 1400px) {
.message-row {
margin-left: -15px;
margin-right: -30px;
}
}
.loadmore {
justify-content: center;
margin-right: 12px;
margin-top: 12px;
margin-bottom: 24px;
.card-left {
border: 1px solid $purple-500;
}
.card-right {
border: 1px solid $gray-500;
}
.hr {
> div {
display: flex;
width: 100%;
height: 20px;
border-bottom: 1px solid $gray-500;
text-align: center;
margin: 2em 0;
}
align-items: center;
.hr-middle {
font-size: 16px;
font-weight: bold;
font-family: 'Roboto Condensed';
line-height: 1.5;
text-align: center;
color: $gray-200;
background-color: $gray-700;
padding: .2em;
margin-top: .2em;
display: inline-block;
width: 100px;
}
.loadmore {
justify-content: center;
margin-right: 12px;
margin-top: 12px;
margin-bottom: 24px;
> div {
display: flex;
width: 100%;
align-items: center;
button {
text-align: center;
color: $gray-50;
}
button {
text-align: center;
color: $gray-50;
}
}
}
.loadmore-divider-holder {
flex: 1;
margin-left: 24px;
margin-right: 24px;
.loadmore-divider-holder {
flex: 1;
margin-left: 24px;
margin-right: 24px;
&:last-of-type {
margin-right: 0;
}
&:last-of-type {
margin-right: 0;
}
}
.loadmore-divider {
height: 1px;
border-top: 1px $gray-500 solid;
width: 100%;
.loadmore-divider {
height: 1px;
border-top: 1px $gray-500 solid;
width: 100%;
}
}
.loading {
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.loading {
padding-left: 1.5rem;
margin-bottom: 1rem;
}
</style>
@@ -280,6 +285,9 @@ export default {
// container.style.overflowY = 'scroll';
}
}, 50),
messageLiked (message) {
this.$emit('message-liked', message);
},
messageRemoved (message) {
this.$emit('message-removed', message);
},

View File

@@ -529,7 +529,7 @@ export default {
// List of prompts for user on changes.
// Sounds like we may need a refactor here, but it is clean for now
if (!this.user.flags.welcomed && !this.$route.name.includes('groupPlan')) {
if (!this.user.flags.welcomed && !this.$route?.name.includes('groupPlan')) {
if (this.$store.state.avatarEditorOptions) {
this.$store.state.avatarEditorOptions.editingUser = false;
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="payments-column mx-auto mt-auto">
<div class="payments-column mx-auto">
<h4>{{ $t('choosePaymentMethod') }}</h4>
<button
v-if="stripeAvailable"

View File

@@ -465,7 +465,7 @@ export default {
};
},
isGemsPromoActive () {
return Boolean(this.eventInfo);
return Boolean(this.eventInfo?.gemsPromo);
},
gemsBlocks () {
// We don't want to modify the original gems blocks when a promotion is running

View File

@@ -71,7 +71,6 @@
id="selectUser"
v-model="userSearchTerm"
:is-valid="foundUser._id"
:placeholder="$t('usernameOrUserId')"
:invalid-issues="userInputInvalidIssues"
/>
@@ -318,6 +317,7 @@ export default {
computed: {
...mapState({
currentEventList: 'worldState.data.currentEventList',
user: 'user.data',
}),
currentEvent () {
return find(this.currentEventList, event => Boolean(event.gemsPromo) || Boolean(event.promo));
@@ -399,6 +399,8 @@ export default {
this.foundUser = result;
}, 500),
selectUser () {
this.foundUser.g1g1 = this.currentEvent?.promo === 'g1g1'
&& this.foundUser._id !== this.user._id;
this.$root.$emit('habitica::send-gift', this.foundUser);
this.close();
},

View File

@@ -51,7 +51,7 @@
</div>
<!-- menu area -->
<div class="row">
<div class="row bg-gray-700">
<div class="col-md-8 offset-md-2 text-center nav">
<div
class="nav-link"
@@ -73,7 +73,7 @@
<!-- subscriber block -->
<subscription-options
v-show="selectedPage === 'subscription'"
class="subscribe-option"
class="bg-gray-700 py-3"
:user-receiving-gift="userReceivingGift"
:receiver-name="receiverName"
/>
@@ -248,6 +248,11 @@
<style lang="scss">
@import '~@/assets/scss/mixins.scss';
#send-gift {
#subscription-form {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
.modal-dialog {
max-width: 448px;
}
@@ -280,15 +285,7 @@
}
}
}
#subscription-form .subscribe-option {
background: #F9F9F9;
}
#subscription-form .selected {
background: rgba(213, 200, 255, 0.32);
// using rgba for transparency
}
}
}
</style>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
@@ -322,7 +319,6 @@
}
.row {
background-color: $gray-700;
margin: 0 0 0 0;
min-height: 32px;
}
@@ -336,19 +332,18 @@
}
.nav-link {
color: $gray-100;
color: $gray-50;
display: inline-block;
padding: 0px 8px 6px 8px;
&.active {
color: $purple-300;
border-bottom: 2px solid $purple-400;
color: $purple-300;
border-bottom: 2px solid $purple-400;
}
&:hover {
color: $purple-300;
border-bottom: 2px solid $purple-400;
cursor: pointer;
color: $purple-300;
cursor: pointer;
}
}

View File

@@ -59,6 +59,12 @@
<template v-if="paymentData.paymentType === 'gift-subscription'">
<div>
<span
v-if="paymentData.g1g1"
v-html="$t('paymentYouSentSubscriptionG1G1', {
name: paymentData.giftReceiver, months: paymentData.subscription.months})"
></span>
<span
v-else
v-html="$t('paymentYouSentSubscription', {
name: paymentData.giftReceiver, months: paymentData.subscription.months})"
></span>

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +1,175 @@
<template>
<div id="subscription-form">
<b-form-group class="mb-3 w-100 h-100">
<div
class="w-100 h-100"
:class="{'mb-2': userReceivingGift?._id}"
>
<strong
v-if="userReceivingGift?._id"
class="text-center d-block mb-3 mx-5"
>
{{ $t('giftSubscriptionLeadText') }}
</strong>
<!-- eslint-disable vue/no-use-v-if-with-v-for -->
<b-form-radio
<div
v-for="block in subscriptionBlocksOrdered"
v-if="block.target !== 'group' && block.canSubscribe === true"
:key="block.key"
v-model="subscription.key"
:value="block.key"
class="subscribe-option pt-2 pl-5 pb-3 mb-0"
:class="{selected: subscription.key === block.key}"
@click.native="updateSubscriptionData(block.key)"
class="subscribe-option d-flex"
:class="{
selected: subscription.key === block.key,
'mb-2': block.months !== 12,
final: block.months === 12,
'mx-4': userReceivingGift?._id,
}"
@click="updateSubscriptionData(block.key)"
>
<div v-if="subscription.key === block.key">
<div class="selected-corner"></div>
<div
class="svg svg-icon svg-check color m-2"
v-html="icons.check"
>
</div>
</div>
<div
v-if="block.months === 12"
class="ribbon mt-3 d-flex align-items-center"
>
<small class="bold teal-1"> {{ $t('popular') }} </small>
</div>
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
<div
v-if="userReceivingGift && userReceivingGift._id"
class="subscription-text ml-2 mb-1"
v-html="$t('giftSubscriptionRateText', {price: block.price, months: block.months})"
>
<div class="w-100">
<div
class="mx-5"
v-if="block.months < 12"
>
<h2
class="mt-3 mb-1"
> ${{ block.price }}.00 USD </h2>
<small
class="bold mb-2"
>
{{ recurrenceText(block.months) }}
</small>
<div class="d-flex flex-column mb-3">
<div class="d-flex align-items-center mb-1">
<div
class="svg svg-icon svg-plus color mr-1"
v-html="icons.plus"
>
</div>
<small
v-html="userReceivingGift?._id ? $t('unlockNGemsGift', { count: 24 })
: $t('unlockNGems', { count: 24 })"
></small>
</div>
<div class="d-flex align-items-center">
<div
class="svg svg-icon svg-plus color mr-1"
v-html="icons.plus"
>
</div>
<small
v-html="userReceivingGift?._id ? $t('earn2GemsGift') : $t('earn2Gems')"
></small>
</div>
</div>
</div>
<div v-else>
<div
class="bg-white py-3 pl-5"
:class="{ round: userReceivingGift?._id }"
>
<div class="d-flex align-items-center mb-1">
<h2 class="mr-2 my-auto"> ${{ block.price }}.00 USD</h2>
<strike class="gray-200">$60.00 USD</strike>
</div>
<small class="bold mb-2">
{{ recurrenceText(block.months) }}
</small>
<div class="d-flex flex-column">
<div class="d-flex align-items-center mb-1">
<div
class="svg svg-icon svg-plus color mr-1"
v-html="icons.plus"
>
</div>
<small
v-html="userReceivingGift?._id ? $t('unlockNGemsGift', { count: 50 })
: $t('unlockNGems', { count: 50 })"
></small>
</div>
<div class="d-flex align-items-center">
<div
class="svg svg-icon svg-plus color mr-1"
v-html="icons.plus"
>
</div>
<small v-html="userReceivingGift?._id ? $t('maxGemCapGift') : $t('maxGemCap')">
</small>
</div>
</div>
</div>
<div
class="gradient-banner text-center"
v-if="!userReceivingGift?._id && !user?.purchased?.plan?.hourglassPromoReceived"
>
<small class="my-3" v-html="$t('immediate12Hourglasses')"></small>
</div>
</div>
</div>
<div
v-else
class="subscription-text ml-2 mb-1"
v-html="$t('subscriptionRateText', {price: block.price, months: block.months})"
>
</div>
<div
class="ml-2"
v-html="subscriptionBubbles(block.key)"
>
</div>
</b-form-radio>
</b-form-group>
<div class="mx-4 mb-4 text-center">
</div>
<button
class="btn btn-primary"
:class="[canceled ? 'mt-4' : 'mt-3', userReceivingGift?._id ? 'mx-4' : 'w-100']"
@click="$root.$emit('bv::show::modal', 'buy-subscription')"
> {{ userReceivingGift?._id ? $t('selectPayment') : $t('subscribe') }} </button>
</div>
<div
v-if="note"
class="mx-4 my-3 text-center"
>
<small
v-if="note"
v-once
class="font-italic"
>
{{ $t(note) }}
</small>
</div>
<!-- payment buttons first is for gift subs and the second is for renewing subs -->
<payments-buttons
v-if="userReceivingGift && userReceivingGift._id"
:disabled="!subscription.key"
:stripe-fn="() => redirectToStripe({gift, uuid: userReceivingGift._id, receiverName})"
:paypal-fn="() => openPaypalGift({
gift: gift, giftedTo: userReceivingGift._id, receiverName,
})"
:amazon-data="{type: 'single', gift, giftedTo: userReceivingGift._id, receiverName}"
/>
<payments-buttons
v-else
:disabled="!subscription.key"
:stripe-fn="() => redirectToStripe({
subscription: subscription.key,
coupon: subscription.coupon,
})"
:paypal-fn="() => openPaypal({url: paypalPurchaseLink, type: 'subscription'})"
:amazon-data="{
type: 'subscription',
subscription: subscription.key,
coupon: subscription.coupon
}"
/>
<b-modal
id="buy-subscription"
size="md"
:hide-header="true"
:hide-footer="true"
>
<payments-buttons
v-if="userReceivingGift?._id"
:disabled="!subscription.key"
:stripe-fn="() => redirectToStripe({
gift,
uuid: userReceivingGift._id,
receiverName,
g1g1: userReceivingGift.g1g1,
})"
:paypal-fn="() => openPaypalGift({
gift: gift,
giftedTo: userReceivingGift._id,
receiverName,
g1g1: userReceivingGift.g1g1,
})"
/>
<payments-buttons
v-else
:disabled="!subscription.key"
:stripe-fn="() => redirectToStripe({
subscription: subscription.key,
coupon: subscription.coupon,
})"
:paypal-fn="() => openPaypal({url: paypalPurchaseLink, type: 'subscription'})"
/>
</b-modal>
</div>
</template>
@@ -77,9 +182,13 @@
margin-top: 0.75rem;
}
.selected {
background-color: rgba(213, 200, 255, 0.32);
.discount-bubble {
background-color: $green-10;
color: $white;
}
.selected {
outline: 2px solid $purple-300;
.subscription-bubble {
background-color: $purple-300;
color: $white;
@@ -102,9 +211,12 @@
color: $gray-200;
}
.discount-bubble {
background-color: $green-10;
color: $white;
.selected strong {
color: $yellow-5;
}
.selected .gradient-banner strong {
color: $teal-1;
}
}
</style>
@@ -112,20 +224,113 @@
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
small {
small, .small {
color: $gray-100;
display: inline-block;
font-size: 12px ;
font-weight: normal;
line-height: 16px;
&.bold {
font-weight: 700;
}
}
strike, strong {
line-height: 24px;
}
.btn-primary {
min-width: 400px;
}
.gradient-banner small {
color: $teal-1;
width: 61%;
}
.ribbon {
width: fit-content;
background: linear-gradient(90deg, rgba(119, 244, 199, 1), rgba(114, 207, 255, 1));
border-radius: 4px;
clip-path: polygon(0px 0px, calc(100% + 1px) 0px, calc(100% + 1px) calc(100% + 1px),
0px calc(100% + 3px), 4px 50%);
box-shadow: 0px 1px 3px 0px rgba(26, 24, 29, 0.12), 0px 1px 2px 0px rgba(26, 24, 29, 0.24);
position: absolute;
right: -4px;
line-height: 1.33;
padding: 4px 10px 4px 12px;
}
.selected-corner {
border-color: $purple-300 transparent transparent transparent;
border-style: solid;
border-width: 48px 48px 0 0;
border-top-left-radius: 4px;
position: absolute;
}
.subscribe-option {
background-color: $gray-700;
max-width: 448px;
border-radius: 8px;
box-shadow: 0px 1px 3px 0px rgba($black, 0.12), 0px 1px 2px 0px rgba($black, 0.24);
position: relative;
background-color: $white;
&:not(:last-of-type) {
border-bottom: 1px solid $gray-600;
.bg-white {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
&.round {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
}
&.final h2 {
color: $teal-10;
}
&:hover, &.selected {
box-shadow: 0px 3px 6px 0px rgba($black, 0.16), 0px 3px 6px 0px rgba($black, 0.24);
}
&.selected {
&.final {
small {
color: $teal-1;
}
}
&:not(.final) {
h2, small {
color: $purple-200;
}
}
.svg-plus {
color: $yellow-10;
}
}
&.final {
background: linear-gradient(90deg, rgba($green-500, 1), rgba(114, 207, 255, 1));
}
h2 {
font-family: 'Roboto', sans-serif;
line-height: 24px;
color: $gray-50;
}
}
.svg-check {
width: 16px;
position: absolute;
color: $white;
}
.svg-plus {
color: $gray-300;
min-width: 10px;
}
</style>
@@ -136,6 +341,8 @@ import sortBy from 'lodash/sortBy';
import subscriptionBlocks from '@/../../common/script/content/subscriptionBlocks';
import paymentsButtons from '@/components/payments/buttons/list';
import paymentsMixin from '../../mixins/payments';
import check from '@/assets/svg/check.svg';
import plus from '@/assets/svg/positive.svg';
export default {
components: {
@@ -157,15 +364,23 @@ export default {
type: String,
default: '',
},
canceled: {
type: Boolean,
default: false,
},
},
data () {
return {
subscription: {
key: 'basic_earned',
},
gift: {
type: 'subscription',
subscription: { key: 'basic_earned' },
subscription: { key: 'basic_12mo' },
},
icons: Object.freeze({
check,
plus,
}),
subscription: {
key: 'basic_12mo',
},
};
},
@@ -179,6 +394,18 @@ export default {
},
},
methods: {
recurrenceText (months) {
if (this.userReceivingGift?._id) {
if (months < 2) {
return this.$t('oneMonthGift');
}
return this.$t('nMonthsGift', { months });
}
if (months < 2) {
return this.$t('recurringMonthly');
}
return (this.$t('recurringNMonthly', { length: months }));
},
subscriptionBubbles (subscription) {
switch (subscription) {
case 'basic_3mo':
@@ -193,7 +420,7 @@ export default {
},
updateSubscriptionData (key) {
this.subscription.key = key;
if (this.userReceivingGift._id) this.gift.subscription.key = key;
if (this.userReceivingGift?._id) this.gift.subscription.key = key;
},
},
};

View File

@@ -27,27 +27,15 @@
@changedPosition="tabSelected($event)"
>
<div slot="right-item">
<div
<a
v-once
id="petLikeToEatMarket"
class="drawer-help-text"
href="/static/faq#pet-foods"
target="_blank"
>
<span>{{ $t('petLikeToEat') + ' ' }}</span>
<span
class="svg-icon inline icon-16"
v-html="icons.information"
></span>
</div>
<b-popover
target="petLikeToEatMarket"
:placement="'top'"
>
<div
v-once
class="popover-content-text"
v-html="$t('petLikeToEatText')"
></div>
</b-popover>
<span>{{ $t('petLikeToEat') }}</span>
</a>
</div>
</drawer-header-tabs>
</div>
@@ -80,7 +68,6 @@
import _filter from 'lodash/filter';
import { mapState } from '@/libs/store';
import inventoryUtils from '@/mixins/inventoryUtils';
import svgInformation from '@/assets/svg/information.svg';
import Drawer from '@/components/ui/drawer';
import DrawerSlider from '@/components/ui/drawerSlider';
@@ -127,10 +114,6 @@ export default {
},
],
selectedDrawerTab: this.defaultSelectedTab,
icons: Object.freeze({
information: svgInformation,
}),
};
},
computed: {

View File

@@ -378,7 +378,7 @@
height: 40px;
border-radius: 2px;
background-color: $white;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
margin-right: 24px;
input {
@@ -462,17 +462,17 @@
&.gems {
color: $green-10;
background-color: rgba(36, 204, 143, 0.15);
background-color: rgba($green-100, 0.15);
}
&.gold {
color: $yellow-5;
background-color: rgba(255, 190, 93, 0.15);
background-color: rgba($yellow-100, 0.15);
}
&.hourglasses {
color: $hourglass-color;
background-color: rgba(41, 149, 205, 0.15);
background-color: rgba($blue-10, 0.15);
}
}
@@ -516,11 +516,16 @@
button.btn.btn-primary {
margin-top: 16px;
padding: 4px 16px;
height: 32px;
padding: 2px 12px;
line-height: 1.714;
&:focus {
border: 2px solid black;
border: 2px solid $purple-400;
}
&:active {
border: 2px solid $purple-400 !important;
box-shadow:none;
}
}

View File

@@ -297,11 +297,16 @@
button.btn.btn-primary {
margin-top: 16px;
padding: 4px 16px;
height: 32px;
padding: 2px 12px;
line-height: 1.714;
&:focus {
border: 2px solid black;
border: 2px solid $purple-400;
}
&:active {
border: 2px solid $purple-400;
box-shadow: none;
}
.balance {

View File

@@ -198,7 +198,7 @@
top: 25px;
border-radius: 8px;
background-color: $gray-600;
box-shadow: 0 2px 16px 0 rgba(26, 24, 29, 0.32);
box-shadow: 0 2px 16px 0 rgba($black, 0.32);
display: flex;
align-items: center;
flex-direction: column;
@@ -208,12 +208,17 @@
}
button.btn.btn-primary {
margin-top: 14px;
padding: 4px 16px;
height: 32px;
margin-top: 16px;
padding: 2px 12px;
line-height: 1.714;
&:focus {
border: 2px solid black;
border: 2px solid $purple-400;
}
&:active {
border: 2px solid $purple-400;
box-shadow: none;
}
}
@@ -249,7 +254,7 @@
&.gems {
color: $green-10;
background-color: rgba(36, 204, 143, 0.15);
background-color: rgba($green-100, 0.15);
line-height: 1.4;
margin: 0 0 0 -4px;
border-radius: 20px;
@@ -257,7 +262,7 @@
&.gold {
color: $yellow-5;
background-color: rgba(255, 190, 93, 0.15);
background-color: rgba($yellow-100, 0.15);
line-height: 1.4;
margin: 0 0 0 -4px;
border-radius: 20px;
@@ -265,7 +270,7 @@
&.hourglasses {
color: $hourglass-color;
background-color: rgba(41, 149, 205, 0.15);
background-color: rgba($blue-10, 0.15);
line-height: 1.4;
margin: 0 0 0 -4px;
border-radius: 20px;

View File

@@ -163,14 +163,7 @@
slot="itemBadge"
slot-scope="ctx"
>
<span
class="badge-top"
@click.prevent.stop="togglePinned(ctx.item)"
>
<pin-badge
:pinned="ctx.item.pinned"
/>
</span>
<category-item :item="ctx.item" />
</template>
</shopItem>
</div>
@@ -178,6 +171,23 @@
</div>
</div>
</div>
<buy-quest-modal
:item="selectedItemToBuy || {}"
:price-type="selectedItemToBuy ? selectedItemToBuy.currency : ''"
:with-pin="true"
>
<template
slot="item"
slot-scope="ctx"
>
<item
class="flat"
:item="ctx.item"
:item-content-class="ctx.item.class"
:show-popover="false"
/>
</template>
</buy-quest-modal>
</div>
</template>
@@ -346,11 +356,17 @@ import svgWizard from '@/assets/svg/wizard.svg';
import svgRogue from '@/assets/svg/rogue.svg';
import svgHealer from '@/assets/svg/healer.svg';
import BuyQuestModal from '../quests/buyQuestModal.vue';
import CategoryItem from '../market/categoryItem';
import FilterGroup from '@/components/ui/filterGroup';
import FilterSidebar from '@/components/ui/filterSidebar';
import { worldStateMixin } from '@/mixins/worldState';
export default {
components: {
BuyQuestModal,
CategoryItem,
FilterGroup,
FilterSidebar,
Checkbox,
PinBadge,
@@ -386,6 +402,7 @@ export default {
featuredGearBought: false,
currentEvent: null,
backgroundUpdate: new Date(),
selectedItemToBuy: null,
imageURLs: {
background: '',
npc: '',
@@ -550,7 +567,12 @@ export default {
return false;
},
itemSelected (item) {
this.$root.$emit('buyModal::showItem', item);
if (item.type === 'quests') {
this.selectedItemToBuy = item;
this.$root.$emit('bv::show::modal', 'buy-quest-modal');
} else {
this.$root.$emit('buyModal::showItem', item);
}
},
},
};

View File

@@ -26,7 +26,7 @@
position: fixed;
right: 10px;
width: 350px;
z-index: 9999; // to keep it above modal overlays
z-index: 999; // to keep it above modal overlays
top: var(--current-scrollY);

View File

@@ -152,8 +152,11 @@
}
.btn-primary.pull-right {
height: 2.5em;
line-height: 2.25;
margin: auto 0px auto auto;
&:focus, :active {
border: 2px solid $purple-400;
}
}
nav.navbar {

View File

@@ -552,8 +552,16 @@
}
.sign-up {
border: 2px solid transparent;
box-shadow: 0 1px 3px 0 rgba($black, 0.16), 0 1px 3px 0 rgba($black, 0.24);
padding-top: 11px;
padding-bottom: 11px;
&:focus, &:active {
background-color: $blue-50;
border: 2px solid $purple-400;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
}
}
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
@@ -650,9 +658,9 @@
.btn-primary {
width: 411px;
height: 48px;
border-radius: 2px;
border-radius: 4px;
background-color: $purple-400;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.24), 0 1px 4px 0 rgba(26, 24, 29, 0.16);
box-shadow: 0 2px 2px 0 rgba($black, 0.24), 0 1px 4px 0 rgba($black, 0.16);
margin-bottom: 5em;
}
@@ -669,7 +677,7 @@
&:hover {
background-color: $purple-50;
box-shadow: 0 4px 4px 0 rgba(26, 24, 29, 0.16), 0 1px 8px 0 rgba(26, 24, 29, 0.12);
box-shadow: 0 4px 4px 0 rgba($black, 0.16), 0 1px 8px 0 rgba($black, 0.12);
}
}

View File

@@ -0,0 +1,111 @@
<template>
<div
v-once
class="top-container mx-auto"
>
<div class="main-text mr-4">
<div class="title-details">
<h1>{{ $t('subscriptionBenefitsFaqTitle') }}</h1>
</div>
<div class="body-text">
<p>{{ $t('subscriptionPara0') }}</p>
<p>{{ $t('contentFaqPara1') }}</p>
</div>
<h3>{{ $t('subscriptionHeading0') }}</h3>
<p>{{ $t('subscriptionDetail00') }}</p>
<ul>
<li>{{ $t('subscriptionDetail000') }}</li>
<li>{{ $t('subscriptionDetail001') }}</li>
<li>{{ $t('subscriptionDetail002') }}</li>
<li>{{ $t('subscriptionDetail003') }}</li>
</ul>
<p>{{ $t('subscriptionDetail01') }}</p>
<ul>
<li>{{ $t('subscriptionDetail010') }}</li>
<li>{{ $t('subscriptionDetail011') }}</li>
<li>{{ $t('subscriptionDetail012') }}</li>
</ul>
<h3>{{ $t('subscriptionHeading1') }}</h3>
<p>{{ $t('subscriptionDetail10') }}</p>
<ul>
<li>{{ $t('subscriptionDetail100') }}</li>
<li>{{ $t('subscriptionDetail101') }}</li>
<li>{{ $t('subscriptionDetail102') }}</li>
</ul>
<p>{{ $t('subscriptionDetail11') }}</p>
<ul>
<li>{{ $t('subscriptionDetail110') }}</li>
</ul>
<h3>{{ $t('subscriptionHeading2') }}</h3>
<ul>
<li>{{ $t('subscriptionDetail20') }}</li>
<li>{{ $t('subscriptionDetail21') }}</li>
<li>{{ $t('subscriptionDetail22') }}</li>
<li>{{ $t('subscriptionDetail23') }}</li>
<li>{{ $t('subscriptionDetail24') }}</li>
<li>{{ $t('subscriptionDetail25') }}</li>
</ul>
<h3>{{ $t('subscriptionHeading3') }}</h3>
<p>{{ $t('subscriptionPara1') }}</p>
<ul>
<li>{{ $t('subscriptionDetail30') }}</li>
<li>{{ $t('subscriptionDetail31') }}</li>
<li>{{ $t('subscriptionDetail32') }}</li>
<li>{{ $t('subscriptionDetail33') }}</li>
</ul>
<h3>{{ $t('commonQuestions') }}</h3>
<h4>{{ $t('subscriptionDetail40')}}</h4>
<p>{{ $t('subscriptionDetail400')}}</p>
<h4>{{ $t('subscriptionDetail41')}}</h4>
<p>{{ $t('subscriptionDetail410')}}</p>
<h4>{{ $t('subscriptionDetail42')}}</h4>
<p>{{ $t('subscriptionDetail420')}}</p>
<h4>{{ $t('subscriptionDetail43')}}</h4>
<p>{{ $t('subscriptionDetail430')}}</p>
<h4>{{ $t('subscriptionDetail44')}}</h4>
<p>{{ $t('subscriptionDetail440')}}</p>
<ul>
<li>{{ $t('subscriptionDetail4400', { initialNumber: 25, roundedNumber: 26 }) }}</li>
<li>{{ $t('subscriptionDetail4400', { initialNumber: 35, roundedNumber: 36 }) }}</li>
<li>{{ $t('subscriptionDetail4400', { initialNumber: 45, roundedNumber: 46 }) }}</li>
</ul>
<h4>{{ $t('subscriptionDetail45')}}</h4>
<p>{{ $t('subscriptionDetail450')}}</p>
<p>{{ $t('subscriptionDetail451')}}</p>
<h4>{{ $t('subscriptionDetail46')}}</h4>
<p>{{ $t('subscriptionDetail460')}}</p>
<h4>{{ $t('subscriptionDetail47')}}</h4>
<p>{{ $t('subscriptionDetail470')}}</p>
<h4>{{ $t('subscriptionDetail48')}}</h4>
<p>{{ $t('subscriptionDetail480')}}</p>
<p
v-html="$t('subscriptionPara2',
{ mailto: '<a href=mailto:admin@habitica.com>admin@habitica.com</a>'}
)"
></p>
<p>{{ $t('subscriptionPara3') }}</p>
</div>
<faq-sidebar />
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/faq.scss';
</style>
<script>
import FaqSidebar from '@/components/shared/faqSidebar';
export default {
components: {
FaqSidebar,
},
mounted () {
this.$store.dispatch('common:setTitle', {
section: this.$t('faq'),
subSection: this.$t('subscriptionBenefitsAdjustments'),
});
document.body.style.background = '#ffffff';
},
};
</script>

View File

@@ -88,7 +88,7 @@ $itemHeight: 2rem;
.inline-dropdown {
&.select-multi .dropdown-toggle {
height: auto;
line-height: 1.571;
padding-bottom: 0px;
}
}

View File

@@ -86,7 +86,7 @@
>
<a
target="_blank"
href="https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet"
href="https://github.com/HabitRPG/habitica/wiki/Markdown-in-Habitica"
:class="cssClass('headings')"
>{{ $t('markdownHelpLink') }}</a>
</small>
@@ -651,9 +651,9 @@
input, textarea {
transition-property: border-color, box-shadow, color, background;
background-color: rgba(255, 255, 255, 0.5);
background-color: rgba($white, 0.5);
&:focus:not(:disabled), &:active:not(:disabled), &:hover:not(:disabled) {
background-color: rgba(255, 255, 255, 0.75);
background-color: rgba($white, 0.75);
}
}
@@ -810,11 +810,7 @@
margin-right: 16px;
color: $blue-10;
}
.btn-footer {
height: 2rem;
}
}
}
.weekday-check {
margin-left: 0px;
@@ -884,6 +880,24 @@
margin-bottom: 0;
}
}
.disabled {
background-color: $white;
border: 2px solid transparent;
color: $gray-200;
line-height: 1.714;
box-shadow: 0px 1px 3px 0px rgba(26, 24, 29, 0.12), 0px 1px 2px 0px rgba(26, 24, 29, 0.24);
&:focus {
background-color: $white;
border: 2px solid $purple-400;
box-shadow: none;
}
&:active {
box-shadow: 0px 1px 3px 0px rgba(26, 24, 29, 0.12), 0px 1px 2px 0px rgba(26, 24, 29, 0.24);
}
}
}
@media only screen and (max-width: 768px) {

View File

@@ -61,6 +61,18 @@
left: calc((100% + 236px - 978px) / 2);
right: 0%;
}
@media screen and (max-width: 575px) {
max-width: none;
left: 3%;
right: 3%;
}
.message {
@media screen and (max-width: 450px) {
width: 100%;
}
}
}
.drawer-toggle-icon {
@@ -117,6 +129,12 @@
padding-top: 6px;
padding-left: 24px;
padding-right: 24px;
a {
line-height: 1.33;
color: $gray-500;
font-weight: normal;
}
}
.drawer-tab {
@@ -136,7 +154,7 @@
color: $white !important;
text-decoration: none !important;
border-bottom: 2px solid transparent;
padding: 0.5rem;
padding: 8px;
&-active, &:hover {
color: $white !important;
@@ -157,17 +175,19 @@
}
.drawer-slider {
padding: 12px 0 0 8px;
padding: 0;
white-space: nowrap;
position: relative;
width: 100%;
& .message {
display: flex;
align-items: center;
justify-content: center;
margin: auto;
top: calc(50% - 30px);
left: 24px;
left: 0;
right: 0;
position: absolute;

View File

@@ -51,7 +51,7 @@
.header-tabs {
display: grid;
grid-template-columns: 1fr auto 1fr;
grid-template-columns: max-content;
}
// MS Edge

View File

@@ -34,12 +34,6 @@
<template
v-for="item in showItems"
>
<div
v-if="shouldAddVerticalLine(item)"
:key="item.key"
class="vertical-divider"
:style="dividerMargins"
></div>
<slot
name="item"
:item="item"
@@ -56,6 +50,10 @@
$buttonAreaWidth: 60;
.items > div {
margin: 0 12px
}
.slider-root {
position: relative;
}
@@ -95,9 +93,11 @@
&.left-button {
left: 0;
padding-left: 0px;
}
&.right-button {
padding-left:20px;
right: 0;
}
}
@@ -108,15 +108,6 @@
justify-content: center;
align-items: center;
padding-top: 10px;
margin-left: $buttonAreaWidth+ px;
margin-right: $buttonAreaWidth+ px;
}
.vertical-divider {
height: 92px;
width: 1px;
background: #34313a;
margin-bottom: 8px;
}
</style>
@@ -199,9 +190,6 @@ export default {
itemsPerPage () {
return Math.floor(this.currentWidth / (this.itemWidth + this.itemMargin));
},
shouldAddVerticalLine (item) {
return this.items[this.itemsPerPage() - 1] === item && this.pointer !== 5;
},
scrollButtonsVisible () {
return this.items.length > this.itemsPerPage();
},

View File

@@ -12,7 +12,9 @@
@select="selectItem($event)"
>
<template #item="{ item }">
<span :class="{'dropdown-icon-item': withIcon}">
<span
:class="{'dropdown-icon-item': withIcon}"
>
<slot
name="item"
:item="item"
@@ -54,7 +56,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
</style>

View File

@@ -20,6 +20,7 @@
}"
>
<input
ref="textInput"
:value="value"
class="form-control"
:type="inputType"
@@ -29,19 +30,23 @@
}"
:readonly="readonly"
:aria-readonly="readonly"
autocomplete="off"
:placeholder="placeholder"
@keyup="handleChange"
@keyup.enter="$emit('enter')"
@blur="$emit('blur')"
>
</div>
<div
v-for="issue in invalidIssues"
:key="issue"
class="input-error"
>
{{ issue }} &nbsp;
</div>
<template v-if="!hideErrorLine">
<div
v-for="issue in invalidIssues"
:key="issue"
class="input-error"
>
{{ issue }} &nbsp;
</div>
</template>
</div>
</div>
</template>
@@ -85,6 +90,10 @@ export default {
type: Array,
default: () => [],
},
hideErrorLine: {
type: Boolean,
default: false,
},
},
data () {
return {
@@ -107,6 +116,9 @@ export default {
this.wasChanged = true;
this.$emit('update:value', value);
},
focus () {
this.$refs.textInput.focus();
},
},
};
</script>
@@ -128,4 +140,12 @@ export default {
margin-bottom: 0;
}
/* this removes safari "save username" UI, we only search for one, we dont want to save it */
input::-webkit-contacts-auto-fill-button,
input::-webkit-credentials-auto-fill-button {
visibility: hidden;
position: absolute;
right: 0;
}
</style>

View File

@@ -29,20 +29,12 @@
@import '~@/assets/scss/colors.scss';
.user-link { // this is the user name
font-family: 'Roboto Condensed', sans-serif;
font-weight: bold;
margin-bottom: 0;
cursor: pointer;
display: inline-block;
font-size: 16px;
// currently used in the member-details-new.vue
&.smaller {
font-family: Roboto;
font-size: 14px;
font-weight: bold;
line-height: 1.71;
}
font-size: 14px;
line-height: 1.71;
display: inline-flex !important;
&.no-tier {
color: $gray-50;
@@ -111,7 +103,6 @@ export default {
'backer',
'contributor',
'hideTooltip',
'smallerStyle',
'showBuffed',
'context',
],
@@ -173,7 +164,7 @@ export default {
return this.hideTooltip ? '' : achievementsLib.getContribText(this.contributor, this.isNPC) || '';
},
levelStyle () {
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)} ${this.smallerStyle ? 'smaller' : ''}`;
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)}`;
},
},
};

View File

@@ -979,6 +979,7 @@
import moment from 'moment';
import axios from 'axios';
import each from 'lodash/each';
import find from 'lodash/find';
import cloneDeep from 'lodash/cloneDeep';
import achievementsLib from '@/../../common/script/libs/achievements';
import Content from '@/../../common/script/content';
@@ -1062,8 +1063,12 @@ export default {
},
computed: {
...mapState({
currentEventList: 'worldState.data.currentEventList',
flatGear: 'content.gear.flat',
}),
currentEvent () {
return find(this.currentEventList, event => Boolean(event.promo));
},
userJoinedDate () {
return moment(this.user.auth.timestamps.created)
.format(this.userLoggedIn.preferences.dateFormat.toUpperCase());
@@ -1257,6 +1262,7 @@ export default {
},
openSendGemsModal () {
this.user.g1g1 = this.currentEvent?.promo === 'g1g1';
this.$store.state.giftModalOptions.startingPage = 'buyGems';
this.$root.$emit('habitica::send-gift', this.user);
},

View File

@@ -0,0 +1,102 @@
import debounce from 'lodash/debounce';
export const autoCompleteHelperMixin = {
data () {
return {
mixinData: {
autoComplete: {
caretPosition: 0,
coords: {
TOP: 0,
LEFT: 0,
},
},
},
};
},
methods: {
autoCompleteMixinHandleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
autoCompleteMixinHandleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
autoCompleteMixinSelectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
autoCompleteMixinSelectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
autoCompleteMixinSelectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
if (this.$refs.autocomplete.selected !== null) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
} else {
// no autocomplete selected, newline instead
this.$refs.autocomplete.cancel();
}
}
},
autoCompleteMixinUpdateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
autoCompleteMixinResetCoordsPosition () {
this.mixinData.autoComplete.coords = {
TOP: 0,
LEFT: 0,
};
},
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
_getCoord (e, text) {
const caretPosition = text.selectionEnd;
this.mixinData.autoComplete.caretPosition = caretPosition;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, caretPosition);
span.textContent = text.value.substr(caretPosition) || '.';
div.appendChild(span);
this.mixinData.autoComplete.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this._getCoord(eventUpdate, text);
},
},
};

View File

@@ -1,7 +1,7 @@
import notifications from './notifications';
import { NotificationMixins } from './notifications';
export default {
mixins: [notifications],
export const CopyToClipboardMixin = {
mixins: [NotificationMixins],
methods: {
async mixinCopyToClipboard (valueToCopy, notificationToShow = null) {
if (navigator.clipboard) {
@@ -21,3 +21,5 @@ export default {
},
},
};
export default CopyToClipboardMixin;

View File

@@ -71,6 +71,7 @@ export default {
giftData,
gemsBlock,
sku,
g1g1,
} = data;
let { url } = data;
@@ -80,6 +81,10 @@ export default {
paymentType: type,
};
if (type === 'gift-subscription') {
appState.g1g1 = g1g1;
}
if (type === 'subscription') {
appState.subscriptionKey = this.subscriptionPlan || this.subscription.key;
}
@@ -164,6 +169,9 @@ export default {
paymentCompleted: false,
paymentType,
};
if (paymentType === 'gift-subscription') {
appState.g1g1 = data.g1g1;
}
if (paymentType === 'subscription') {
appState.subscriptionKey = sub.key;
} else if (paymentType === 'groupPlan') {

View File

@@ -62,7 +62,7 @@
<script>
import moment from 'moment';
import userLabel from '../userLabel';
import userLabel from '../../components/userLabel.vue';
import dots from '@/assets/svg/dots.svg';
import block from '@/assets/svg/block.svg';
@@ -117,7 +117,7 @@ export default {
</script>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/colors';
.action-padding {
height: 24px !important;
@@ -153,7 +153,7 @@ export default {
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/colors';
.conversation {
padding: 1rem 1.5rem;

View File

@@ -0,0 +1,64 @@
<template>
<div
v-if="filtersConversations.length > 0"
class="conversations"
>
<conversation-item
v-for="conversation in filtersConversations"
:key="conversation.key"
:active-key="selectedConversation?.key"
:contributor="conversation.contributor"
:backer="conversation.backer"
:uuid="conversation.key"
:display-name="conversation.name"
:username="conversation.username"
:last-message-date="conversation.date"
:last-message-text="conversation.lastMessageText
? removeTags(parseMarkdown(conversation.lastMessageText)) : ''"
@click="selectConversation(conversation.key)"
/>
</div>
</template>
<style scoped lang="scss">
.conversations {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
}
</style>
<script>
import { defineComponent } from 'vue';
import habiticaMarkdown from 'habitica-markdown';
import conversationItem from '@/pages/private-messages/pm-conversation-item.vue';
export default defineComponent({
components: { conversationItem },
props: {
filtersConversations: {
type: Array,
default: () => [],
},
selectedConversation: {
type: Object,
default: null,
},
},
methods: {
removeTags (html) {
const tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
},
parseMarkdown (text) {
if (!text) return null;
return habiticaMarkdown.render(String(text));
},
selectConversation (conversationKey) {
this.$emit('selectConversation', conversationKey);
},
},
});
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div
class="pm-disabled-caption text-center"
>
<h4>{{ disabledTexts.title }}</h4>
<p>{{ disabledTexts.description }}</p>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.pm-disabled-caption {
padding-top: 1.5em;
z-index: 2;
h4, p {
color: $gray-200;
}
h4 {
margin-top: 0;
margin-bottom: 0.4em;
}
p {
font-size: 12px;
margin-bottom: 0;
}
}
</style>
<script>
export default {
props: ['disabledTexts'],
};
</script>

Some files were not shown because too many files have changed in this diff Show More