Compare commits

...

82 Commits

Author SHA1 Message Date
Kalista Payne
5f2c0f39a1 fix(snacks): reposition X in errors 2025-09-26 16:47:26 -05:00
Kalista Payne
e71d45a91f fix(snackbars): update toast styling 2025-09-26 16:29:09 -05:00
Kalista Payne
2029739a1b chore(cg): update staff info 2025-09-25 17:43:56 -05:00
Kalista Payne
5cef106ea5 chore(analytics): remove GA by @phillipthelen 2025-09-23 17:37:10 -05:00
Kalista Payne
e096d7ac42 5.41.3 2025-09-23 17:17:30 -05:00
Weblate
6db998e726 Merge branch 'origin/develop' into Weblate. 2025-09-24 00:04:32 +02:00
Kalista Payne
29c658b042 fix(faq): remove semifunctional links 2025-09-23 16:54:12 -05:00
Weblate
66710b8f38 Translated using Weblate (Japanese)
Currently translated at 93.4% (3217 of 3441 strings)

Translated using Weblate (Russian)

Currently translated at 98.3% (423 of 430 strings)

Translated using Weblate (Croatian)

Currently translated at 81.1% (220 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 93.3% (3211 of 3441 strings)

Translated using Weblate (German)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (244 of 244 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 93.1% (3206 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Japanese)

Currently translated at 93.1% (3205 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 97.5% (239 of 245 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (244 of 244 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Japanese)

Currently translated at 97.1% (238 of 245 strings)

Translated using Weblate (Japanese)

Currently translated at 93.1% (3204 of 3441 strings)

Co-authored-by: JohnnyDoor <kakimida.daredarone@gmail.com>
Co-authored-by: Pyak Denis Sergeevich <pyakdenis@mail.ru>
Co-authored-by: Summer_GUI <heyang94@163.com>
Co-authored-by: The_Blood_Orc <stefan.trbojevic188@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: いんこ <ayakabooker@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/de/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translation: Habitica/Challenge
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Settings
Translation: Habitica/Spells
Translation: Habitica/Subscriber
2025-09-23 22:30:01 +02:00
Kalista Payne
c77db3d625 chore(faq): update staff list 2025-09-23 10:18:50 -05:00
Fiz
c947fa97d9 Updates & Fixes: Fix Orb of Rebirth bug, update blocked player ToS message, Fix redundant disabled styling (#15494)
* add new frontend files

* Add UI for managing blockers

* correctly reset local data after creating blocker

* Tweak wording

* Add UI for managing blockers

* restructure admin pages

* add blocker to block emails from registration

* lint fixes

* Await genericPurchase completion before page reload to prevent request cancellation.

Also adds defensive check for undefined error.response in axios interceptor to prevent "t.response undefined" errors.

* Fix shop tabs overflow off screen at certain zoom levels
Fix quest cards get cut off on small screens
Fix pop-up windows extend past screen edges on mobile

* Update ToS error message

- Updated account suspension message from "This account, User ID..." to "Your account @[username] has been
  blocked..."
- Modified server auth middleware to pass username parameter when throwing account suspended error
-Modified auth utils loginRes function to include username in suspended account error
- Updated client bannedAccountModal component to pass username (empty string if unavailable)
- Updated login test to expect username in account suspended message

* lint fix

* Responsive Layout for Equipment Containers

- Added responsive CSS for mobile (<768px) and tablet (769px-1024px)
- Implemented flex-wrap layout that automatically stacks items in rows of 4 on smaller

* remove redundant disabled styles in task modals

The .disabled class conflicting with existing disabled state implementations

* Revert "Merge branch 'fiz/item-container-scaling' into qa/bat"

This reverts commit 4f28bfaad4, reversing
changes made to 477dd6328a.

* fix(blockers): duplicated code from rebase

* fix(admin): revert accidental change from rebase

* move !error.response to correct level

!error.response before any attempt to access error.response.status

* chore(github): split responsiveness to #15514

---------

Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Kalista Payne <kalista@habitica.com>
2025-09-22 11:12:09 -05:00
Kalista Payne
b2b9702797 5.41.2 2025-09-19 16:40:15 -05:00
Weblate
e92503f032 Translated using Weblate (Japanese)
Currently translated at 93.0% (3201 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (244 of 244 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (244 of 244 strings)

Translated using Weblate (German)

Currently translated at 100.0% (244 of 244 strings)

Translated using Weblate (French)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (French)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (French)

Currently translated at 100.0% (244 of 244 strings)

Translated using Weblate (Croatian)

Currently translated at 91.0% (840 of 923 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Croatian)

Currently translated at 87.8% (166 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 87.8% (166 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 87.8% (166 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 87.8% (166 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 87.8% (166 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 12.2% (30 of 245 strings)

Translated using Weblate (Croatian)

Currently translated at 49.6% (1707 of 3441 strings)

Translated using Weblate (Croatian)

Currently translated at 87.8% (166 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Croatian)

Currently translated at 86.3% (797 of 923 strings)

Translated using Weblate (Croatian)

Currently translated at 78.2% (212 of 271 strings)

Translated using Weblate (Croatian)

Currently translated at 83.8% (238 of 284 strings)

Translated using Weblate (Croatian)

Currently translated at 87.8% (166 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 95.3% (184 of 193 strings)

Translated using Weblate (Croatian)

Currently translated at 65.3% (177 of 271 strings)

Translated using Weblate (Croatian)

Currently translated at 91.0% (152 of 167 strings)

Translated using Weblate (Croatian)

Currently translated at 83.4% (237 of 284 strings)

Translated using Weblate (Croatian)

Currently translated at 92.9% (106 of 114 strings)

Translated using Weblate (Croatian)

Currently translated at 85.7% (162 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 11.4% (28 of 245 strings)

Translated using Weblate (Croatian)

Currently translated at 93.7% (181 of 193 strings)

Translated using Weblate (Croatian)

Currently translated at 59.7% (162 of 271 strings)

Translated using Weblate (Croatian)

Currently translated at 91.0% (152 of 167 strings)

Translated using Weblate (Croatian)

Currently translated at 83.4% (237 of 284 strings)

Translated using Weblate (Croatian)

Currently translated at 71.9% (82 of 114 strings)

Translated using Weblate (Croatian)

Currently translated at 84.6% (160 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 10.2% (25 of 245 strings)

Translated using Weblate (Croatian)

Currently translated at 93.7% (181 of 193 strings)

Translated using Weblate (Croatian)

Currently translated at 97.8% (137 of 140 strings)

Translated using Weblate (Croatian)

Currently translated at 47.2% (128 of 271 strings)

Translated using Weblate (Croatian)

Currently translated at 91.0% (152 of 167 strings)

Translated using Weblate (Croatian)

Currently translated at 83.4% (237 of 284 strings)

Translated using Weblate (Croatian)

Currently translated at 71.4% (135 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 6.5% (16 of 245 strings)

Translated using Weblate (Croatian)

Currently translated at 93.7% (181 of 193 strings)

Translated using Weblate (Croatian)

Currently translated at 97.8% (137 of 140 strings)

Translated using Weblate (Croatian)

Currently translated at 45.0% (122 of 271 strings)

Translated using Weblate (Croatian)

Currently translated at 91.0% (152 of 167 strings)

Translated using Weblate (Croatian)

Currently translated at 79.9% (195 of 244 strings)

Translated using Weblate (Croatian)

Currently translated at 4.0% (10 of 245 strings)

Translated using Weblate (Croatian)

Currently translated at 45.0% (122 of 271 strings)

Translated using Weblate (Croatian)

Currently translated at 70.8% (134 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 93.7% (181 of 193 strings)

Translated using Weblate (Croatian)

Currently translated at 45.0% (122 of 271 strings)

Translated using Weblate (Croatian)

Currently translated at 70.8% (134 of 189 strings)

Translated using Weblate (Croatian)

Currently translated at 93.7% (181 of 193 strings)

Translated using Weblate (Croatian)

Currently translated at 44.6% (121 of 271 strings)

Translated using Weblate (Croatian)

Currently translated at 50.1% (137 of 273 strings)

Translated using Weblate (Japanese)

Currently translated at 93.0% (3201 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 92.9% (3200 of 3441 strings)

Translated using Weblate (German)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (German)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (German)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Japanese)

Currently translated at 95.3% (822 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 96.7% (237 of 245 strings)

Translated using Weblate (Korean)

Currently translated at 79.3% (732 of 923 strings)

Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Laura Fleckenstein <fleckenstein_laura@web.de>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Stefan Trbojević <stefan.trbojevic188@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: いんこ <ayakabooker@gmail.com>
Co-authored-by: 최혜연 <serpia0326@naver.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/es/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/character/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
Translate-URL: https://translate.habitica.com/projects/habitica/front/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/es/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/es/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
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/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/hr/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/hr/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Spells
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2025-09-19 23:39:32 +02:00
Fiz
8faa5b0582 Updates & Fixes: Profile Modal Tab URLs, Chat Mention Case-Insensitive, G1G1 Updates, Challenge Participants, End Challenge Modal (#15493)
* Fix profile modal tab navigation URLs for both own and other users profiles

- Add routes for /user/profile, /user/stats, and /user/achievements
- Update selectPage() to properly update URLs when switching tabs
- Own profile uses /user/{tab} format
- Other users' profiles use /profile/{userId}#{tab} format
- Parse hash fragments when navigating to other users' profile tabs
- Ensure direct navigation to tab URLs opens correct tab

* Fix undefined userId

* Server now matches usernames case insensitively like client

- Preserves original capitalization in mention text
- Fixes profile links not working with wrong case mentions

* lint fixes

* g1g1 width auto sizing w/padding

* Challenge participants spacing & text sizing fix

* Fix inconsistent profile URL format between own and other users' profiles

- Update profile tab navigation to use consistent URL format for all users
- Redirect old /user/* routes to new format for backward compatibility
- Update all navigation points (dropdown menu, notifications) to use new URLs

* Update End Challenge modal

- Replace dropdown with searchable input (384x32px) for winner selection
- Add visual badge state with gems icons for challenge completion
- Update Delete Challenge flow with refund info and proper styling
- Add close button (X) with opacity hover effect
- Enhance Award Winner button with gem icon and dynamic prize display
- Apply conditional styling based on winner selection state
- Update text colors: Maroon/50 for delete warning, Gray/100 for "OR" text
- Add proper translations for gem/gems and refund description

* lint error fixes

* end challenge modal fixes

* lint fix

* Use existing closeX component, minor UI fixes to close challenge modal

* fix lint

* Delete icon color to match text on close challenge modal

use color field to set delete icon color

* Highlight username on close challenge modal color updates

- Background color on hover: purple-600
- Text color on hover: purple-300
- Changed transition from just background-color to all so both color changes animate smoothly

* Fix strings

* Refactor g1g1 notifications from database-driven to event-based system

Changed g1g1 (gift one get one) notifications to display automatically during event periods instead of requiring database storage. Notifications now appear based on event calendar dates and use sessionStorage for dismissal state.

- Display g1g1 notification when event is active in worldState
- Store dismissal state in sessionStorage with event-specific keys
- Remove dependency on user.notifications database array
- Maintain identical user experience and appearance

* Update prize card to match participants card on challenges

* End Challenge modal UI tweaks

* Prevent false mention highlights

Prevent false mention highlights when a user's display name matches another user's username. The purple mention indicator now only appears for actual @username mentions.

* lint fixes

* Remove mention highlight

* Mention highlighting to only highlight w/username mentions

* Update G1G1 Notification

- Updated text styling for title & description
- Updated button styling
- Updated close button

* lint fix

* Add updated G1G1 notification SVGs

* Don't highlight display name w/mention

* g1g1 UI updates

- Fix sizing of gift SVGs (96px tall)
- Update button to use button element and styles <button class="btn btn-secondary mx-auto">
- Fixed positioning, color, and hover state of close icon (default white 50% opacity, hover 75% opacity)

* Fix g1g1 close icon hover state

Fix hover state of close icon (default white 50% opacity, hover 75% opacity)

* g1g1 close hover state fix

* End challenge UI updates

- Fix modal title positioning
- Fix close icon positioning
- Fix spacing between title and gem graphic
- Fix spacing between label and input field
- Fix search icon position, change input hint to "@Username"
- Set search results text align start/left with 16px starting padding.
- Fix Award Button state

* remove trailing space

* Fix exit hover state on g1g1

* fix g1g1 close icon (directly render close icon)

* new line

* Update z-index of g1g1 close button

* add display name support for mention highlighting

mention highlights now trigger for both username and display name mentions.

* Override default close button color (gray -> white)

(Also revert the renderWithMentions change)

* Fix mention display name test (& fix lint)

* Revert display name mention, strictly only username

Mentions work w/username only (works w/case insensitive as well)

* Improved case-insensitive username matching

* add close-white.svg, replace close.svg on g1g1

* find mentions that match the current user's username (case-insensitive)

* fix lint errors

* end challenge modal UI updates

* Don't change gem color on update

* disabled state button match button.scss syling

* remove padding from g1g1 close

* Directly use button.scss on end challenge modal

* Update disabled state for button.scss

* explicitly set close challenge modal button disabled/enabled state

* fix trailing space

* Add font details (and fix text color) for button disabled state

* Update award winner button min-height & padding

* button.scss button disabled styling updates

* Remove redundant disabled override on award winner button

* lint

* Use single gifts svg, and apply transform to flip horizontally

Remove unneeded gifts_end.svg

* Replaced the hardcoded #1A1B1D color with the $black from colors.scss

* Removed the 0.5em padding w/p-2

* added v-once to the refund text element

* Converted the line-height values from pixel values to multipliers
2025-09-16 22:12:41 -05:00
Kalista Payne
95494c685b 5.41.1 2025-09-16 22:05:58 -05:00
Weblate
10978d46ab Translated using Weblate (Spanish)
Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 95.1% (820 of 862 strings)

Translated using Weblate (Polish)

Currently translated at 51.6% (1776 of 3441 strings)

Translated using Weblate (German)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Russian)

Currently translated at 99.4% (192 of 193 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 96.3% (236 of 245 strings)

Translated using Weblate (Japanese)

Currently translated at 95.1% (820 of 862 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 36.3% (89 of 245 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 35.1% (86 of 245 strings)

Translated using Weblate (Dutch)

Currently translated at 84.0% (776 of 923 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 94.8% (818 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 92.6% (3189 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 94.6% (232 of 245 strings)

Translated using Weblate (German)

Currently translated at 99.9% (3440 of 3441 strings)

Co-authored-by: Alexandre Le Mercier <couzinemile@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Liu leoyve <leoyve@gmail.com>
Co-authored-by: Ri Vargas <goldenhaitang@gmail.com>
Co-authored-by: Shchudrov Yaroslav Maksimovich <separatationally@mail.ru>
Co-authored-by: Sven Baumann <svenbaumann1996@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Uwe B <hbtca@tunixgut.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: nagase daichi <daihachi10sub@gmail.com>
Co-authored-by: いんこ <ayakabooker@gmail.com>
Co-authored-by: インコ <ayakabooker@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
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/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translation: Habitica/Backgrounds
Translation: Habitica/Character
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Subscriber
2025-09-17 02:09:45 +02:00
dependabot[bot]
447eb6a0c4 chore(deps): bump brace-expansion from 1.1.11 to 1.1.12 (#15498)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-11 14:58:24 -05:00
Kalista Payne
3dec49b72c GPC Message (#15508)
* feat(gpc): warn user about enabling analytics

* fix(gpc): style tweaks

* fix(privacy): local storage doesn't understand Boolean

* fix(gpc): do record if user has opted in

* fix(privacy): don't flip flop if no value changed
2025-09-11 14:58:10 -05:00
dependabot[bot]
472d03f276 chore(deps): bump vite from 6.3.5 to 6.3.6 in /website/client (#15507)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.3.5 to 6.3.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.6
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-11 14:53:43 -05:00
Kalista Payne
fd9a27c3ab 5.41.0 2025-09-11 14:43:53 -05:00
Weblate
a5c1423837 Translated using Weblate (Japanese)
Currently translated at 92.6% (3187 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 99.6% (270 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 93.4% (229 of 245 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.1% (906 of 923 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 91.7% (3157 of 3441 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 90.5% (3117 of 3441 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 90.5% (3117 of 3441 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (284 of 284 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Italian)

Currently translated at 89.6% (243 of 271 strings)

Translated using Weblate (German)

Currently translated at 99.9% (3439 of 3441 strings)

Translated using Weblate (German)

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (Japanese)

Currently translated at 92.5% (3186 of 3441 strings)

Co-authored-by: Bernardo Oliveira Abrão <bernardooliveiraabrao@gmail.com>
Co-authored-by: Deleted User <noreply+1161@weblate.org>
Co-authored-by: Karictre <karictre.git@gmail.com>
Co-authored-by: Lyam Santos Peres <kaka1213spaenrteoss@gmail.com>
Co-authored-by: Omar Bertolla <scaram@icloud.com>
Co-authored-by: Sven Baumann <svenbaumann1996@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: インコ <ayakabooker@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translation: Habitica/Backgrounds
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Limited
Translation: Habitica/Npc
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2025-09-11 21:43:25 +02:00
Phillip Thelen
e9829b8b60 Phillip/admin deleter (#15466)
* refactor sending jobs to worker server

* remove unused imports

* add delete button to adminpanel

* June 2025 content build (#15437)

* chore: June 2025 content build

* chore: typo fixing

* chore: corrections to summer 2025 mage armor, spritesheet

* fix(css): rebuild spritesmith-main

---------

Co-authored-by: Kalista Payne <sabrecat@gmail.com>

* fix(script): don't use extremely costly regex

* fix(logging): don't spam empty error events

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (280 of 280 strings)

Translated using Weblate (French)

Currently translated at 100.0% (280 of 280 strings)

Translated using Weblate (Spanish)

Currently translated at 99.6% (279 of 280 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.4% (840 of 862 strings)

Translated using Weblate (German)

Currently translated at 99.8% (907 of 908 strings)

Translated using Weblate (Dutch)

Currently translated at 79.3% (219 of 276 strings)

Translated using Weblate (Dutch)

Currently translated at 28.1% (69 of 245 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.4% (840 of 862 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.5% (402 of 412 strings)

Translated using Weblate (Dutch)

Currently translated at 91.5% (377 of 412 strings)

Translated using Weblate (Dutch)

Currently translated at 85.2% (774 of 908 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (908 of 908 strings)

Translated using Weblate (Slovak)

Currently translated at 63.4% (106 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (908 of 908 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (908 of 908 strings)

Translated using Weblate (Slovak)

Currently translated at 2.0% (5 of 245 strings)

Translated using Weblate (French)

Currently translated at 100.0% (908 of 908 strings)

Translated using Weblate (Russian)

Currently translated at 64.4% (158 of 245 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.0% (837 of 862 strings)

Translated using Weblate (German)

Currently translated at 97.9% (844 of 862 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.3% (401 of 412 strings)

Translated using Weblate (Portuguese)

Currently translated at 95.3% (393 of 412 strings)

Translated using Weblate (Slovak)

Currently translated at 45.6% (413 of 905 strings)

Translated using Weblate (Slovak)

Currently translated at 50.8% (85 of 167 strings)

Translated using Weblate (Russian)

Currently translated at 99.1% (113 of 114 strings)

Translated using Weblate (Russian)

Currently translated at 64.0% (157 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 64.0% (157 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 62.0% (152 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 62.0% (152 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 60.8% (149 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 60.8% (149 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 60.4% (148 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 60.4% (148 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 60.0% (147 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 60.0% (147 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 57.9% (142 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 57.9% (142 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 56.7% (139 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 56.7% (139 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 56.3% (138 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 56.3% (138 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 53.8% (132 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 53.8% (132 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 53.4% (131 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 53.4% (131 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 48.9% (120 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 48.9% (120 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 48.5% (119 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 48.5% (119 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 46.9% (115 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 45.3% (111 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 45.3% (111 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 45.3% (111 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 45.3% (111 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 44.4% (109 of 245 strings)

Translated using Weblate (German)

Currently translated at 99.9% (3324 of 3325 strings)

Translated using Weblate (Russian)

Currently translated at 44.4% (109 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 44.4% (109 of 245 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.8% (107 of 114 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (429 of 430 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.1% (820 of 862 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (902 of 905 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.1% (820 of 862 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.1% (820 of 862 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.1% (820 of 862 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.8% (107 of 114 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.6% (3114 of 3325 strings)

Translated using Weblate (Portuguese)

Currently translated at 53.9% (1793 of 3325 strings)

Translated using Weblate (Dutch)

Currently translated at 78.1% (2600 of 3325 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.5% (242 of 243 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.1% (820 of 862 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.6% (398 of 412 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (902 of 905 strings)

Translated using Weblate (Italian)

Currently translated at 99.1% (113 of 114 strings)

Translated using Weblate (Italian)

Currently translated at 87.3% (2903 of 3325 strings)

Translated using Weblate (Italian)

Currently translated at 17.1% (42 of 245 strings)

Translated using Weblate (Italian)

Currently translated at 99.0% (408 of 412 strings)

Translated using Weblate (Italian)

Currently translated at 92.7% (102 of 110 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.0% (3292 of 3325 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.7% (3285 of 3325 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.7% (3285 of 3325 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (412 of 412 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (905 of 905 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.1% (3262 of 3325 strings)

Co-authored-by: Andrea <goffopaguro@gmail.com>
Co-authored-by: Artem StolyROV <stolyarov11303@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: David Kaya <david@kaya.sk>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: FingerTiao <787170918@qq.com>
Co-authored-by: Irina  Shcherbinina <cat3dcat007@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Mencius <beautyalinap@gmail.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Nikita Maximov <ruvemaximus@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Summer_GUI <heyang94@163.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Tom <tompsognathus@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: V Aar <v.vanderaar@gmail.com>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: razil <boss.razmarin@gmail.com>
Co-authored-by: Волкозмей <klippiky@gmail.com>
Co-authored-by: Данила Мальцев <maltsev-danila@inbox.ru>
Co-authored-by: Татьяна Куклева <klippiky@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt_BR/
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/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/it/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/it/
Translate-URL: https://translate.habitica.com/projects/habitica/content/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
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/faq/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/
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/limited/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/it/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent

* 5.36.4

* chore(deps): bump serialize-javascript in /website/client (#15395)

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.1...v6.0.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
...

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

* chore(deps-dev): bump axios from 1.7.4 to 1.8.2 (#15401)

Bumps [axios](https://github.com/axios/axios) from 1.7.4 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.4...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

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

* chore(deps): bump prismjs from 1.29.0 to 1.30.0 (#15403)

Bumps [prismjs](https://github.com/PrismJS/prism) from 1.29.0 to 1.30.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.29.0...v1.30.0)

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

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

* chore(deps): bump @babel/runtime-corejs2 in /website/client (#15406)

Bumps [@babel/runtime-corejs2](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime-corejs2) from 7.23.6 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime-corejs2)

---
updated-dependencies:
- dependency-name: "@babel/runtime-corejs2"
  dependency-type: indirect
...

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

* chore(deps): bump @babel/helpers in /website/client (#15407)

Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) from 7.23.6 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-helpers)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-type: indirect
...

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

* chore(deps): bump @babel/runtime from 7.23.9 to 7.26.10 (#15410)

Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.23.9 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

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

* chore(deps): bump http-proxy-middleware in /website/client (#15427)

Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: indirect
...

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

* Optimize database access for some use cases (#15444)

* optimize query when listing challenge tasks

* Optimize query for checking if user is party leader

* correct worker call

* remove unused priority

* fix tests

* don’t use body with delete

* add detailed information about sub payment for google and apple

* Support paypal details for subscription in admin panel

* stripe payment details

* fix imports

* fix tests

* fix deleting account

* begin building group admin panel

* fix convertig sub to group plan

* improve sub status display

* fix lint

* fix long line

* fix sub state display

* lint fix

* fix

* delete amplitude data by default

* improve searching for email in admin panel

* correctly call method

* move delete button in admin panel

* fix(lint): whitespace

* fix(style): indent

* fix(typo): humand

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
Co-authored-by: Kalista Payne <sabrecat@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Andrea <goffopaguro@gmail.com>
Co-authored-by: Artem StolyROV <stolyarov11303@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: David Kaya <david@kaya.sk>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: FingerTiao <787170918@qq.com>
Co-authored-by: Irina  Shcherbinina <cat3dcat007@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Mencius <beautyalinap@gmail.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Nikita Maximov <ruvemaximus@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Summer_GUI <heyang94@163.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Tom <tompsognathus@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: V Aar <v.vanderaar@gmail.com>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: razil <boss.razmarin@gmail.com>
Co-authored-by: Волкозмей <klippiky@gmail.com>
Co-authored-by: Данила Мальцев <maltsev-danila@inbox.ru>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kalista Payne <kalista@habitica.com>
2025-09-09 16:41:03 -05:00
Kalista Payne
7ecb83dc7e 5.40.2 2025-09-08 15:40:45 -05:00
Weblate
e8ffe2286c Translated using Weblate (Japanese)
Currently translated at 92.5% (3183 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 99.6% (283 of 284 strings)

Translated using Weblate (German)

Currently translated at 99.2% (3415 of 3441 strings)

Translated using Weblate (German)

Currently translated at 98.7% (3397 of 3441 strings)

Translated using Weblate (Ukrainian)

Currently translated at 56.6% (1948 of 3441 strings)

Translated using Weblate (Ukrainian)

Currently translated at 88.1% (760 of 862 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (284 of 284 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.8% (278 of 284 strings)

Translated using Weblate (Japanese)

Currently translated at 92.2% (3174 of 3441 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (German)

Currently translated at 98.2% (3382 of 3441 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 85.7% (210 of 245 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 89.3% (825 of 923 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 87.8% (811 of 923 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 87.1% (804 of 923 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 30.2% (74 of 245 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 29.7% (73 of 245 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 83.0% (157 of 189 strings)

Translated using Weblate (Japanese)

Currently translated at 92.1% (3171 of 3441 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 82.6% (224 of 271 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 94.7% (108 of 114 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 82.0% (155 of 189 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.3% (917 of 923 strings)

Co-authored-by: FingerTiao <787170918@qq.com>
Co-authored-by: Laura Fleckenstein <fleckenstein_laura@web.de>
Co-authored-by: Luizo <t.czj2019@gmail.com>
Co-authored-by: Lyam Santos Peres <kaka1213spaenrteoss@gmail.com>
Co-authored-by: Mateus Scheper <mateus_scheper@hotmail.com>
Co-authored-by: Sara Olson <sara@habitica.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Музика Анастасія <ukrainianbimba25@gmail.com>
Co-authored-by: インコ <ayakabooker@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt_BR/
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/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/
Translation: Habitica/Backgrounds
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Limited
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Subscriber
2025-09-08 22:12:28 +02:00
Phillip Thelen
fe63436a57 Shorten missing auth headers error and include forwarder-for header 2025-09-05 11:10:20 +02:00
Phillip Thelen
5b93b9b37a fix crash on startup 2025-09-05 11:02:59 +02:00
Phillip Thelen
1d55027791 Trim logs sent to loggly (#15503)
* try making missing x-client header errors smaller

* add server emoji as tag

* always truncate headers
2025-09-04 14:36:23 -05:00
Kalista Payne
83f0984da1 5.40.1 2025-09-04 14:06:39 -05:00
Weblate
53d4f75cab Merge branch 'origin/develop' into Weblate. 2025-09-04 21:05:40 +02:00
Phillip Thelen
da45eb2adf Fix client side errors that happened on page load (#15502)
* fix momentjs error on page load

* use correct method to set moment locale

* fix vue draggable deprecation
2025-09-04 13:44:31 -05:00
Kalista Payne
3bf4af8d8b Set up analytics scripts on demand post user load (#15501)
* fix(analytics): can't get consented user during main,js load

* fix(race): don't let gtag load twice
also refactor to avoid unnecessary _getConsentedUser() calls

* fix(lint): need user ID for gtag config

* fix(analytics): adjust script loads and refs

* fix(vue): try moving plugin to most relevant file

* fix(amplitude): correct event fn

* fix(analytics): direct load gtag from uri

* fix(ga): use ga-gtag for loading google

* fix(lint): import order

* refactor(analytics): remove superfluous setUser fn

* fix(amplitude): return to Javascript SDK syntax

* refactor(misc): remove unneeded asyncs

* refactor(analytics): slim down if checks
2025-09-04 13:43:18 -05:00
Weblate
f030691fac Translated using Weblate (German)
Currently translated at 98.2% (3380 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 92.1% (3171 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (German)

Currently translated at 98.1% (3378 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Japanese)

Currently translated at 94.7% (817 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 93.4% (229 of 245 strings)

Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: インコ <ayakabooker@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translation: Habitica/Backgrounds
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Questscontent
2025-09-04 17:23:27 +02:00
Kalista Payne
1f94e51693 fix(migration): updateOne and minor usability refactor 2025-09-02 15:50:07 -05:00
Kalista Payne
86e7d7a72b 5.40.0 2025-09-02 09:01:10 -05:00
Weblate
140b852e03 Translated using Weblate (German)
Currently translated at 98.1% (3376 of 3441 strings)

Translated using Weblate (Russian)

Currently translated at 83.0% (225 of 271 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Russian)

Currently translated at 96.5% (415 of 430 strings)

Translated using Weblate (Russian)

Currently translated at 87.0% (2995 of 3441 strings)

Translated using Weblate (Russian)

Currently translated at 73.0% (179 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 98.9% (191 of 193 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (Russian)

Currently translated at 93.0% (254 of 273 strings)

Translated using Weblate (German)

Currently translated at 98.0% (3374 of 3441 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 80.4% (197 of 245 strings)

Translated using Weblate (Japanese)

Currently translated at 91.8% (3160 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 97.4% (264 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 96.3% (186 of 193 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 96.7% (893 of 923 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 88.9% (821 of 923 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 88.8% (820 of 923 strings)

Translated using Weblate (German)

Currently translated at 97.9% (3372 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Japanese)

Currently translated at 94.4% (814 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 94.4% (814 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 93.0% (228 of 245 strings)

Translated using Weblate (Japanese)

Currently translated at 91.4% (224 of 245 strings)

Translated using Weblate (Japanese)

Currently translated at 89.7% (220 of 245 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Polish)

Currently translated at 77.1% (219 of 284 strings)

Translated using Weblate (Polish)

Currently translated at 93.4% (402 of 430 strings)

Translated using Weblate (Polish)

Currently translated at 51.6% (1776 of 3441 strings)

Translated using Weblate (Polish)

Currently translated at 89.9% (170 of 189 strings)

Translated using Weblate (Polish)

Currently translated at 33.8% (83 of 245 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (Japanese)

Currently translated at 97.4% (264 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 99.6% (283 of 284 strings)

Translated using Weblate (Japanese)

Currently translated at 94.3% (813 of 862 strings)

Translated using Weblate (Polish)

Currently translated at 33.4% (82 of 245 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (271 of 271 strings)

Co-authored-by: Avoren5 <avoren@tuta.io>
Co-authored-by: Igor <777igor93@gmail.com>
Co-authored-by: Izzy <nineyellowgirl@gmail.com>
Co-authored-by: Kalista Payne <kalista@habitica.com>
Co-authored-by: TOMA Mitsuru <toma0001@gmail.com>
Co-authored-by: Thaíssa <zj8c8wkai@mozmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: インコ <ayakabooker@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/character/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/front/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ja/
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Npc
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2025-09-02 14:30:16 +02:00
Kalista Payne
8f949ce1cc fix(privacy): 18+ restriction 2025-08-29 16:12:12 -05:00
Weblate
5e21285370 Merge branch 'origin/develop' into Weblate. 2025-08-29 22:47:41 +02:00
Kalista Payne
7a65bc2d8d fix(privacy): wire up modal 2025-08-29 15:46:28 -05:00
Kalista Payne
a32fadbcbd fix(analytics): grab missing GA ID 2025-08-29 15:46:28 -05:00
Kalista Payne
305192ed1f fix(analytics): record user ID in GA if consented 2025-08-29 15:46:28 -05:00
Kalista Payne
7644e202c9 fix(lint): whitespace 2025-08-29 15:46:28 -05:00
Kalista Payne
d11c8442ef fix(settings): consistent layout with modal 2025-08-29 15:46:28 -05:00
Kalista Payne
d8b5391425 fix(terms): copy edits 2025-08-29 15:46:28 -05:00
Kalista Payne
dd287cd719 fix(signup): 18+ verbiage, refactor forms scss 2025-08-29 15:46:27 -05:00
Kalista Payne
e809d1f6e4 Privacy Controls (#15492)
* WIP(privacy): start of banner

* WIP(privacy): layout rough

* WIP(privacy): mobile layout, add modal

* fix(privacy): implement toggle disable and setting row fold

* fix(privacy): clean up a couple of styles

* fix(privacy): adjust banner width at mobile sizes

* WIP(privacy): remove Loggly echo of Amplitude data

* fix(banners): account for privacy in snackbar position

* WIP(privacy): dismiss banner

* chore(analytics): update to maintaned GA4 library

* fix(tests): lint, misuse of apiError

* fix(analytics): add debug mode

* fix(analytics): load new library on client

* WIP(privacy): gtag.js based implementation

* fix(analytics): lint issues

* fix(lint): one more unused

* fix(lint): client errors

* feat(privacy): draft workflows

* fix(analytics): linting, send needed user values

* fix(tests): use mock analytics service in test env

* fix(tests): restore previous logic for node env

* feat(intro): jump to page 2 onboarding

* WIP(auth): revisions to registration flow

* WIP(privacy): landing page and banner revisions

* WIP(signup): added new username, tos, privacy state

* fix(signup): revert debugging logic

* WIP(signup): add defaulting and checkbox

* wip(signup): move social auth behind username screen

* Squashed commit of the following:

commit ca0a238e5f008525ed154c5eaf12e44f2fc22b00
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed May 7 12:17:20 2025 +0200

    make emails lowercase

commit a2ce748558ce9134e6825208a7e66d78e720202e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 13:27:01 2025 +0200

    remove unused import

commit cc6ce6c388d9693cf192c4bea733931fc8c31c37
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 13:13:03 2025 +0200

    add tests for new api route

commit 0d40a6230b548625482aa9f6831c93ed9d62533a
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jun 18 15:50:22 2025 -0500

    update social tests

commit 79177d6754589b9e54682af8a531b63f60215dab
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 10:21:51 2025 +0200

    new api route to check if an email is available

commit 11df73fe07eeb730c2a95593e18e14a931f52429
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 10:21:39 2025 +0200

    Add field to not register social account when called

* Squashed commit of the following:

commit b8a2f0b8ee
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 20 17:18:30 2024 -0400

    update privacy policy

* fix(vite): import syntax

* feat(auth): precheck on defaulted username

* feat(auth): add store action for check-email

* feat(auth): check email before proceeding

* WIP(login): refactor username screen

* WIP(auth): complete login/reg flow

* fix(auth): filter out expected 404

* fix(login): use allowRegister with Apple
and add z-index to component

* fix(login): style corrections and email passthru

* Fix edgecase

Signed-off-by: Kalista Payne <sabrecat@gmail.com>

* fix(auth): correct error behaviors

* fix(auth): rewire Apple auth

* make check-email check for restricted domains

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(signup): all the style

* fix(express): return when responding

* fix(error): reduce specificity for restricted domain issue

* fix apple auth

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(signup): change from blur to 500ms debounce

* fix(login): add missing 200 response in Apple flow

* fix(signup): more reconciliation with @phillipthelen's work

* fix(signup): now using token not code

* fix(reg): don't bail on Apple if we're allowing reg

* fix(auth): more reconciliation with @phillipthelen code

* feat(copy): privacy policy updates

* fix(copy): replace placeholder

* fix(vue): use Vite syntax for scss import

* fix(static): corrections to copy and css

* chore(style): remove excess whitespace

* use correct error

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(layout): inputs, add privacy banner

* fix(login): button hover, more validation states

* fix(login): further layout and UX corrections

* fix(static): add back containing div for show/hide

* fix(apple): clean out Apple token

* fix(settings): only change preference on save

* fix(settings): correct save/cancel behavior

* fix(layout): consistent use of header/footer

* fix(layout): reposition mountains for reg/login/forgot

* fix(signup): partial rollback of /username route

* refactor(signup): move /username to page

* fix(apple): don't overwrite reg method

* fix(username): don't skip empty validation

* fix(input): don't show valid if no username

* fix(login): clean out Apple token if using another method

* fix(apple): possible race with token

* fix(tests): some housekeeping

* fix(config): copypasta

* fix(lint): various cleanup

* fix(lint): line squeeze

* fix(lint): one more v-for

* fix(groups): funnel invite flow to new username page

* Squashed commit of the following:

commit 3c5ba4bf24e4bb7996786520101f27ad66405bce
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 14:38:31 2025 -0500

    fix(privacy): update link ref

commit 9d216f623b
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 14:18:22 2025 -0500

    fix(privacy-tos): copy edits cont'd

commit d744f47140
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 13:43:22 2025 -0500

    fix(privacy): copy edits and ToC reflow

commit 2c3c3fc9ce
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:46:24 2025 +0200

    lint

commit cf363034d5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:34:54 2025 +0200

    fix link

commit 3afacd2c05
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:34:42 2025 +0200

    add updated terms

commit 258b722499
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 17:58:42 2025 +0200

    put back button to show/hide third party info

commit 2992e0299b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 17:58:32 2025 +0200

    minor edits

commit bb5e252299
Author: Kalista Payne <kalista@habitica.com>
Date:   Sun Aug 17 21:01:50 2025 -0500

    fix(privacy): update Section 3

commit c79af7baa8
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 15 17:28:49 2025 -0500

    fix(privacy): various copy edits

commit 100f2f4574
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 15 11:37:37 2025 +0200

    add newline

commit 11d1cfd0d9
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 15 11:10:01 2025 +0200

    update privacy policy

commit 59b99badf3
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 8 14:04:19 2025 -0500

    5.38.2

commit 78daeb4191
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 8 13:36:19 2025 -0500

    fix(apple): don't run auth middleware during redirect

commit 93f8d60903
Author: Weblate <noreply@weblate.org>
Date:   Fri Aug 8 10:12:25 2025 +0200

    Translated using Weblate (German)

    Currently translated at 99.4% (185 of 186 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (186 of 186 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (186 of 186 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (54 of 54 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (47 of 47 strings)

    Translated using Weblate (Dutch)

    Currently translated at 78.0% (2643 of 3385 strings)

    Translated using Weblate (Dutch)

    Currently translated at 40.8% (100 of 245 strings)

    Translated using Weblate (Polish)

    Currently translated at 89.9% (233 of 259 strings)

    Translated using Weblate (Dutch)

    Currently translated at 67.5% (175 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (110 of 110 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 20.8% (51 of 245 strings)

    Translated using Weblate (Turkish)

    Currently translated at 65.9% (60 of 91 strings)

    Translated using Weblate (Turkish)

    Currently translated at 65.9% (60 of 91 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 17.9% (44 of 245 strings)

    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Linsey Dunya Pastoor <sekai.creations@gmail.com>
    Co-authored-by: Mete Olmez <metezori27@gmail.com>
    Co-authored-by: Sefa Uğurlu <ugurlusefa2@gmail.com>
    Co-authored-by: Summer_GUI <heyang94@163.com>
    Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: innnko <ayakabooker@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/tr/
    Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/pl/
    Translation: Habitica/Backgrounds
    Translation: Habitica/Challenge
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Contrib
    Translation: Habitica/Death
    Translation: Habitica/Defaulttasks
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Settings

commit eb16fec41e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Aug 6 22:08:07 2025 +0200

    Add interface to block ip-addresses or clients due to abuse (#15484)

    * Read IP blocks from database

    * begin building general blocking solution

    * add new frontend files

    * Add UI for managing blockers

    * correctly reset local data after creating blocker

    * Tweak wording

    * Add UI for managing blockers

    * restructure admin pages

    * improve test coverage

    * Improve blocker UI

    * add blocker to block emails from registration

    * lint fix

    * fix

    * lint fixes

    * fix import

    * add new permission for managing blockers

    * improve permission check

    * fix managing permissions from admin

    * improve navbar display for non fullAccess admin

    * update block error strings

    * lint fix

    * add option to errorHandler to skip logging

    * validate blocker value during input

    * improve blocker form display

    * chore(subproj): reconcile habitica-images

    * fix(scripts): use same Mongo version for dev/test

    * fix(whitespace): eof

    * documentation improvements

    * remove nconf import

    * remove old test

    ---------

    Co-authored-by: Kalista Payne <kalista@habitica.com>
    Co-authored-by: Kalista Payne <sabrecat@gmail.com>

commit 47d832bf12
Author: Fiz <34069775+Hafizzle@users.noreply.github.com>
Date:   Tue Aug 5 15:12:44 2025 -0500

    Add backend support for Hydra mount (#15482)

    * chore: update time travelers shop to display seasonal backgrounds

    * chore: update time travelers banner (note CSS borken rn)

    * chore: fix borken CSS and update logic in shop

    * chore: added isSubscribed function, not working

    * chore: isSubscribed working but no bg for subscribers

    * chore: logic and css updates

    * chore: update habitica-images

    * chore: add check for trinket

    * chore: more time traveler shop logicking

    * Add backend support for Hydra mount

    - Add Dragon-Hydra to special mounts in stable.js
      - Configure as contributor level 7 reward with canFind: true
      - Add GIF format support for mount sprites
      - Enable admin panel granting capability

    * Fix Vue template errors in timeTravelers component

    * Fix duplicate template block in timeTravelers component

    * add CSS for Hydra mount GIF sprites

    Added CSS rules for Mount_Head_Dragon-Hydra and Mount_Body_Dragon-Hydra GIF sprites

    * Remove the separate Hydra mount dimension declaration

    ---------

    Co-authored-by: CuriousMagpie <eilatan@gmail.com>

commit c03ab9855f
Author: Kalista Payne <kalista@habitica.com>
Date:   Tue Aug 5 14:31:05 2025 -0500

    5.38.1

commit 8f96b7b7fd
Author: Weblate <noreply@weblate.org>
Date:   Tue Aug 5 13:02:45 2025 +0200

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 17.1% (42 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 16.7% (41 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 16.3% (40 of 245 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.8% (425 of 430 strings)

    Translated using Weblate (French)

    Currently translated at 99.4% (184 of 185 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.9% (39 of 245 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.8% (3379 of 3385 strings)

    Translated using Weblate (Polish)

    Currently translated at 95.5% (128 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.7% (254 of 268 strings)

    Translated using Weblate (Polish)

    Currently translated at 94.0% (126 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.6% (424 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.3% (423 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (798 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.4% (797 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 90.6% (781 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.9% (3112 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.9% (3111 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.0% (174 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (8 of 8 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.5% (38 of 245 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.6% (3104 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 93.5% (173 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 99.6% (279 of 280 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Japanese)

    Currently translated at 89.2% (769 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.4% (253 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.8% (170 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 97.9% (421 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.6% (3104 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 93.6% (251 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 90.8% (168 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 82.4% (202 of 245 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.1% (37 of 245 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.3% (3092 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (248 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (248 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Croatian)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Korean)

    Currently translated at 22.8% (56 of 245 strings)

    Translated using Weblate (Korean)

    Currently translated at 47.7% (128 of 268 strings)

    Translated using Weblate (Croatian)

    Currently translated at 45.1% (121 of 268 strings)

    Translated using Weblate (Korean)

    Currently translated at 71.9% (620 of 862 strings)

    Translated using Weblate (Croatian)

    Currently translated at 70.6% (609 of 862 strings)

    Translated using Weblate (Croatian)

    Currently translated at 75.0% (6 of 8 strings)

    Translated using Weblate (Korean)

    Currently translated at 67.6% (291 of 430 strings)

    Translated using Weblate (Korean)

    Currently translated at 52.8% (1788 of 3385 strings)

    Translated using Weblate (Croatian)

    Currently translated at 50.3% (1706 of 3385 strings)

    Translated using Weblate (Croatian)

    Currently translated at 51.7% (134 of 259 strings)

    Translated using Weblate (Czech)

    Currently translated at 92.8% (130 of 140 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 86.9% (233 of 268 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (Danish)

    Currently translated at 92.1% (105 of 114 strings)

    Translated using Weblate (Czech)

    Currently translated at 89.4% (102 of 114 strings)

    Translated using Weblate (Czech)

    Currently translated at 83.5% (112 of 134 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 71.6% (308 of 430 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 100.0% (245 of 245 strings)

    Translated using Weblate (Serbian)

    Currently translated at 84.4% (49 of 58 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 51.4% (144 of 280 strings)

    Translated using Weblate (Swedish)

    Currently translated at 66.5% (286 of 430 strings)

    Translated using Weblate (Serbian)

    Currently translated at 65.5% (282 of 430 strings)

    Translated using Weblate (Slovak)

    Currently translated at 65.5% (282 of 430 strings)

    Translated using Weblate (Romanian)

    Currently translated at 66.7% (287 of 430 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (430 of 430 strings)

    Translated using Weblate (Danish)

    Currently translated at 66.0% (284 of 430 strings)

    Translated using Weblate (Czech)

    Currently translated at 69.7% (300 of 430 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.7% (3377 of 3385 strings)

    Translated using Weblate (Swedish)

    Currently translated at 54.1% (1834 of 3385 strings)

    Translated using Weblate (Serbian)

    Currently translated at 50.6% (1714 of 3385 strings)

    Translated using Weblate (Slovak)

    Currently translated at 50.0% (1695 of 3385 strings)

    Translated using Weblate (Romanian)

    Currently translated at 60.5% (2050 of 3385 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 38.4% (1301 of 3385 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Danish)

    Currently translated at 54.0% (1829 of 3385 strings)

    Translated using Weblate (Czech)

    Currently translated at 59.6% (2020 of 3385 strings)

    Translated using Weblate (Swedish)

    Currently translated at 75.6% (140 of 185 strings)

    Translated using Weblate (Serbian)

    Currently translated at 73.5% (136 of 185 strings)

    Translated using Weblate (Slovak)

    Currently translated at 84.8% (157 of 185 strings)

    Translated using Weblate (Romanian)

    Currently translated at 78.9% (146 of 185 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 82.1% (152 of 185 strings)

    Translated using Weblate (Italian)

    Currently translated at 91.8% (170 of 185 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (Danish)

    Currently translated at 77.2% (143 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.7% (242 of 245 strings)

    Translated using Weblate (Czech)

    Currently translated at 75.1% (139 of 185 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 74.5% (138 of 185 strings)

    Translated using Weblate (Czech)

    Currently translated at 8.1% (20 of 245 strings)

    Translated using Weblate (Swedish)

    Currently translated at 72.0% (621 of 862 strings)

    Translated using Weblate (Serbian)

    Currently translated at 65.1% (562 of 862 strings)

    Translated using Weblate (Slovak)

    Currently translated at 66.9% (577 of 862 strings)

    Translated using Weblate (Romanian)

    Currently translated at 77.7% (670 of 862 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 70.0% (604 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Italian)

    Currently translated at 86.8% (749 of 862 strings)

    Translated using Weblate (Indonesian)

    Currently translated at 86.0% (742 of 862 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 66.1% (570 of 862 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.0% (845 of 862 strings)

    Translated using Weblate (Danish)

    Currently translated at 69.9% (603 of 862 strings)

    Translated using Weblate (Czech)

    Currently translated at 69.7% (601 of 862 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 66.3% (572 of 862 strings)

    Translated using Weblate (Serbian)

    Currently translated at 74.0% (305 of 412 strings)

    Translated using Weblate (Turkish)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Danish)

    Currently translated at 90.0% (371 of 412 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Swedish)

    Currently translated at 53.6% (139 of 259 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Danish)

    Currently translated at 62.1% (161 of 259 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 54.0% (140 of 259 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 82.8% (222 of 268 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 99.4% (184 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.3% (241 of 245 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.3% (3092 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 88.4% (237 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (134 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (Japanese)

    Currently translated at 82.4% (202 of 245 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 87.3% (234 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 86.4% (160 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 99.8% (913 of 914 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (Russian)

    Currently translated at 88.5% (248 of 280 strings)

    Translated using Weblate (Spanish)

    Currently translated at 99.8% (3379 of 3385 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (862 of 862 strings)

    Co-authored-by: Ayaka Booker <ayakabooker@gmail.com>
    Co-authored-by: Chaotic Lawful <habitica@eusebius.fr>
    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Lio Zam <zerofux@web.de>
    Co-authored-by: Mika <isekai.chr@gmail.com>
    Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
    Co-authored-by: Summer_GUI <heyang94@163.com>
    Co-authored-by: Vera <verasmolinap@gmail.com>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Zhi Hao Li <zhihaoli000@gmail.com>
    Co-authored-by: Zuz Q <zuzannakunik@gmail.com>
    Co-authored-by: innnko <ayakabooker@gmail.com>
    Co-authored-by: 吳昀錡 <J1120241@gm.fdhs.tyc.edu.tw>
    Co-authored-by: 潘致翰 <happyq0908@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/
    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/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/it/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/da/
    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/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/
    Translate-URL: https://translate.habitica.com/projects/habitica/messages/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/id/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/
    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/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/tasks/cs/
    Translation: Habitica/Achievements
    Translation: Habitica/Backgrounds
    Translation: Habitica/Character
    Translation: Habitica/Content
    Translation: Habitica/Death
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Groups
    Translation: Habitica/Limited
    Translation: Habitica/Messages
    Translation: Habitica/Npc
    Translation: Habitica/Overview
    Translation: Habitica/Pets
    Translation: Habitica/Quests
    Translation: Habitica/Questscontent
    Translation: Habitica/Settings
    Translation: Habitica/Subscriber
    Translation: Habitica/Tasks

commit 1dde2674f6
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Mon Jun 16 16:43:56 2025 -0500

    fix(content): don't filter out the thing we want

commit 76122a8889
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jun 4 14:28:27 2025 -0500

    fix(mobile): provide Challenge categories via API

commit 9e309a875e
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Jul 28 14:15:00 2025 -0500

    5.38.0

commit 09e3a394b8
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Jul 28 14:06:45 2025 -0500

    5.37.3

commit eba263360f
Author: Weblate <noreply@weblate.org>
Date:   Mon Jul 28 21:03:17 2025 +0200

    Translated using Weblate (German)

    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 (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 98.6% (850 of 862 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.4% (3360 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (245 of 245 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (47 of 47 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 14.2% (35 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 13.8% (34 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 13.0% (32 of 245 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 2.0% (5 of 245 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 66.1% (570 of 862 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 54.1% (1830 of 3377 strings)

    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
    Co-authored-by: Jonathan Niessen <37.friedrich@gmail.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
    Co-authored-by: Laura Fleckenstein <fleckenstein_laura@web.de>
    Co-authored-by: Omer I.S <omeritzicschwartz@gmail.com>
    Co-authored-by: Remigiusz Haziak <haziakremigiusz@gmail.com>
    Co-authored-by: Uwe B <hbtca@tunixgut.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Wellinton Cardoso <wmcardoso1@hotmail.com>
    Co-authored-by: cloudzzy <truskawka412@gmail.com>
    Co-authored-by: 吳昀錡 <J1120241@gm.fdhs.tyc.edu.tw>
    Translate-URL: https://translate.habitica.com/projects/habitica/character/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/contrib/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/
    Translation: Habitica/Character
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Contrib
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Npc
    Translation: Habitica/Questscontent

commit 9550eec718
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jul 28 16:50:38 2025 +0200

    Fix 500 when deleting a very old group plan account (#15481)

commit f267eb67e9
Author: Kalista Payne <kalista@habitica.com>
Date:   Tue Jul 29 14:12:35 2025 -0500

    fix(static): add back missing div for show/hide

commit 28251f42ab
Author: Kalista Payne <kalista@habitica.com>
Date:   Thu Jul 24 22:59:01 2025 -0500

    feat(privacy): preview page

* feat(privacy): respect Global Privacy Control

* fix(lint): remove unused component

* fix(test): test user opts in to tracking

* fix(test): add user pref to more contexts

* fix(test): final spot in api-unit

* fix(tests): update integrations

* chore(privacy): add paragraph to s1, retire separate preview pages

* fix(build): route copypasta

* fix(router): lingering dead import

---------

Signed-off-by: Kalista Payne <sabrecat@gmail.com>
Signed-off-by: Kalista Payne <kalista@habitica.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: CuriousMagpie <eilatan@gmail.com>
2025-08-29 15:46:00 -05:00
innnko
da90fa6aaf Translated using Weblate (Japanese)
Currently translated at 100.0% (114 of 114 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ja/
2025-08-29 15:44:21 -05:00
Weblate
77392db25a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (German)

Currently translated at 97.9% (3370 of 3441 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (857 of 862 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (412 of 412 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (412 of 412 strings)

Translated using Weblate (Portuguese)

Currently translated at 37.4% (91 of 243 strings)

Translated using Weblate (Japanese)

Currently translated at 98.9% (270 of 273 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Portuguese)

Currently translated at 35.8% (87 of 243 strings)

Translated using Weblate (Russian)

Currently translated at 96.2% (414 of 430 strings)

Translated using Weblate (Russian)

Currently translated at 99.0% (914 of 923 strings)

Translated using Weblate (French)

Currently translated at 99.5% (3425 of 3441 strings)

Translated using Weblate (French)

Currently translated at 98.5% (3391 of 3441 strings)

Translated using Weblate (French)

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (French)

Currently translated at 98.5% (269 of 273 strings)

Translated using Weblate (French)

Currently translated at 98.1% (268 of 273 strings)

Translated using Weblate (French)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (French)

Currently translated at 97.8% (267 of 273 strings)

Translated using Weblate (Japanese)

Currently translated at 98.4% (186 of 189 strings)

Translated using Weblate (Indonesian)

Currently translated at 70.4% (2425 of 3441 strings)

Translated using Weblate (Indonesian)

Currently translated at 86.1% (743 of 862 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (French)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Portuguese)

Currently translated at 53.4% (1838 of 3441 strings)

Translated using Weblate (French)

Currently translated at 96.2% (182 of 189 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 28.9% (71 of 245 strings)

Translated using Weblate (German)

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (German)

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Portuguese)

Currently translated at 53.2% (1834 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (185 of 189 strings)

Translated using Weblate (German)

Currently translated at 99.4% (188 of 189 strings)

Translated using Weblate (German)

Currently translated at 99.6% (920 of 923 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (German)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (German)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Russian)

Currently translated at 98.9% (191 of 193 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (185 of 189 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 27.7% (68 of 245 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (Japanese)

Currently translated at 97.3% (184 of 189 strings)

Translated using Weblate (Japanese)

Currently translated at 98.1% (268 of 273 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (German)

Currently translated at 100.0% (284 of 284 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Japanese)

Currently translated at 94.3% (813 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (Japanese)

Currently translated at 86.5% (212 of 245 strings)

Translated using Weblate (Japanese)

Currently translated at 93.7% (808 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 91.8% (3160 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 86.5% (212 of 245 strings)

Translated using Weblate (Spanish)

Currently translated at 99.3% (3420 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 91.7% (3158 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 99.7% (921 of 923 strings)

Co-authored-by: FingerTiao <787170918@qq.com>
Co-authored-by: Hanayuri Kuchiki <chocochili.lover@gmail.com>
Co-authored-by: Igor <777igor93@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
Co-authored-by: Kubo Mizuki <m.kubo.0916@gmail.com>
Co-authored-by: Lyam Santos Peres <kaka1213spaenrteoss@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Summer_GUI <heyang94@163.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wellinton Cardoso <wmcardoso1@hotmail.com>
Co-authored-by: innnko <ayakabooker@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/front/
Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
Translate-URL: https://translate.habitica.com/projects/habitica/front/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
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/id/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
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/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/id/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
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/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translation: Habitica/Backgrounds
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/Limited
Translation: Habitica/Npc
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2025-08-29 15:44:21 -05:00
Kalista Payne
1bc1bf0621 fix(signup): two style corrections 2025-08-29 15:44:21 -05:00
Kalista Payne
635a258d62 fix(signup): 18+ verbiage, refactor forms scss 2025-08-29 15:44:21 -05:00
Kalista Payne
384fb505c1 Privacy Controls (#15492)
* WIP(privacy): start of banner

* WIP(privacy): layout rough

* WIP(privacy): mobile layout, add modal

* fix(privacy): implement toggle disable and setting row fold

* fix(privacy): clean up a couple of styles

* fix(privacy): adjust banner width at mobile sizes

* WIP(privacy): remove Loggly echo of Amplitude data

* fix(banners): account for privacy in snackbar position

* WIP(privacy): dismiss banner

* chore(analytics): update to maintaned GA4 library

* fix(tests): lint, misuse of apiError

* fix(analytics): add debug mode

* fix(analytics): load new library on client

* WIP(privacy): gtag.js based implementation

* fix(analytics): lint issues

* fix(lint): one more unused

* fix(lint): client errors

* feat(privacy): draft workflows

* fix(analytics): linting, send needed user values

* fix(tests): use mock analytics service in test env

* fix(tests): restore previous logic for node env

* feat(intro): jump to page 2 onboarding

* WIP(auth): revisions to registration flow

* WIP(privacy): landing page and banner revisions

* WIP(signup): added new username, tos, privacy state

* fix(signup): revert debugging logic

* WIP(signup): add defaulting and checkbox

* wip(signup): move social auth behind username screen

* Squashed commit of the following:

commit ca0a238e5f008525ed154c5eaf12e44f2fc22b00
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed May 7 12:17:20 2025 +0200

    make emails lowercase

commit a2ce748558ce9134e6825208a7e66d78e720202e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 13:27:01 2025 +0200

    remove unused import

commit cc6ce6c388d9693cf192c4bea733931fc8c31c37
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 13:13:03 2025 +0200

    add tests for new api route

commit 0d40a6230b548625482aa9f6831c93ed9d62533a
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jun 18 15:50:22 2025 -0500

    update social tests

commit 79177d6754589b9e54682af8a531b63f60215dab
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 10:21:51 2025 +0200

    new api route to check if an email is available

commit 11df73fe07eeb730c2a95593e18e14a931f52429
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 10:21:39 2025 +0200

    Add field to not register social account when called

* Squashed commit of the following:

commit b8a2f0b8ee
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 20 17:18:30 2024 -0400

    update privacy policy

* fix(vite): import syntax

* feat(auth): precheck on defaulted username

* feat(auth): add store action for check-email

* feat(auth): check email before proceeding

* WIP(login): refactor username screen

* WIP(auth): complete login/reg flow

* fix(auth): filter out expected 404

* fix(login): use allowRegister with Apple
and add z-index to component

* fix(login): style corrections and email passthru

* Fix edgecase

Signed-off-by: Kalista Payne <sabrecat@gmail.com>

* fix(auth): correct error behaviors

* fix(auth): rewire Apple auth

* make check-email check for restricted domains

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(signup): all the style

* fix(express): return when responding

* fix(error): reduce specificity for restricted domain issue

* fix apple auth

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(signup): change from blur to 500ms debounce

* fix(login): add missing 200 response in Apple flow

* fix(signup): more reconciliation with @phillipthelen's work

* fix(signup): now using token not code

* fix(reg): don't bail on Apple if we're allowing reg

* fix(auth): more reconciliation with @phillipthelen code

* feat(copy): privacy policy updates

* fix(copy): replace placeholder

* fix(vue): use Vite syntax for scss import

* fix(static): corrections to copy and css

* chore(style): remove excess whitespace

* use correct error

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(layout): inputs, add privacy banner

* fix(login): button hover, more validation states

* fix(login): further layout and UX corrections

* fix(static): add back containing div for show/hide

* fix(apple): clean out Apple token

* fix(settings): only change preference on save

* fix(settings): correct save/cancel behavior

* fix(layout): consistent use of header/footer

* fix(layout): reposition mountains for reg/login/forgot

* fix(signup): partial rollback of /username route

* refactor(signup): move /username to page

* fix(apple): don't overwrite reg method

* fix(username): don't skip empty validation

* fix(input): don't show valid if no username

* fix(login): clean out Apple token if using another method

* fix(apple): possible race with token

* fix(tests): some housekeeping

* fix(config): copypasta

* fix(lint): various cleanup

* fix(lint): line squeeze

* fix(lint): one more v-for

* fix(groups): funnel invite flow to new username page

* Squashed commit of the following:

commit 3c5ba4bf24e4bb7996786520101f27ad66405bce
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 14:38:31 2025 -0500

    fix(privacy): update link ref

commit 9d216f623b
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 14:18:22 2025 -0500

    fix(privacy-tos): copy edits cont'd

commit d744f47140
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 13:43:22 2025 -0500

    fix(privacy): copy edits and ToC reflow

commit 2c3c3fc9ce
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:46:24 2025 +0200

    lint

commit cf363034d5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:34:54 2025 +0200

    fix link

commit 3afacd2c05
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:34:42 2025 +0200

    add updated terms

commit 258b722499
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 17:58:42 2025 +0200

    put back button to show/hide third party info

commit 2992e0299b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 17:58:32 2025 +0200

    minor edits

commit bb5e252299
Author: Kalista Payne <kalista@habitica.com>
Date:   Sun Aug 17 21:01:50 2025 -0500

    fix(privacy): update Section 3

commit c79af7baa8
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 15 17:28:49 2025 -0500

    fix(privacy): various copy edits

commit 100f2f4574
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 15 11:37:37 2025 +0200

    add newline

commit 11d1cfd0d9
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 15 11:10:01 2025 +0200

    update privacy policy

commit 59b99badf3
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 8 14:04:19 2025 -0500

    5.38.2

commit 78daeb4191
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 8 13:36:19 2025 -0500

    fix(apple): don't run auth middleware during redirect

commit 93f8d60903
Author: Weblate <noreply@weblate.org>
Date:   Fri Aug 8 10:12:25 2025 +0200

    Translated using Weblate (German)

    Currently translated at 99.4% (185 of 186 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (186 of 186 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (186 of 186 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (54 of 54 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (47 of 47 strings)

    Translated using Weblate (Dutch)

    Currently translated at 78.0% (2643 of 3385 strings)

    Translated using Weblate (Dutch)

    Currently translated at 40.8% (100 of 245 strings)

    Translated using Weblate (Polish)

    Currently translated at 89.9% (233 of 259 strings)

    Translated using Weblate (Dutch)

    Currently translated at 67.5% (175 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (110 of 110 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 20.8% (51 of 245 strings)

    Translated using Weblate (Turkish)

    Currently translated at 65.9% (60 of 91 strings)

    Translated using Weblate (Turkish)

    Currently translated at 65.9% (60 of 91 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 17.9% (44 of 245 strings)

    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Linsey Dunya Pastoor <sekai.creations@gmail.com>
    Co-authored-by: Mete Olmez <metezori27@gmail.com>
    Co-authored-by: Sefa Uğurlu <ugurlusefa2@gmail.com>
    Co-authored-by: Summer_GUI <heyang94@163.com>
    Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: innnko <ayakabooker@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/tr/
    Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/pl/
    Translation: Habitica/Backgrounds
    Translation: Habitica/Challenge
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Contrib
    Translation: Habitica/Death
    Translation: Habitica/Defaulttasks
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Settings

commit eb16fec41e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Aug 6 22:08:07 2025 +0200

    Add interface to block ip-addresses or clients due to abuse (#15484)

    * Read IP blocks from database

    * begin building general blocking solution

    * add new frontend files

    * Add UI for managing blockers

    * correctly reset local data after creating blocker

    * Tweak wording

    * Add UI for managing blockers

    * restructure admin pages

    * improve test coverage

    * Improve blocker UI

    * add blocker to block emails from registration

    * lint fix

    * fix

    * lint fixes

    * fix import

    * add new permission for managing blockers

    * improve permission check

    * fix managing permissions from admin

    * improve navbar display for non fullAccess admin

    * update block error strings

    * lint fix

    * add option to errorHandler to skip logging

    * validate blocker value during input

    * improve blocker form display

    * chore(subproj): reconcile habitica-images

    * fix(scripts): use same Mongo version for dev/test

    * fix(whitespace): eof

    * documentation improvements

    * remove nconf import

    * remove old test

    ---------

    Co-authored-by: Kalista Payne <kalista@habitica.com>
    Co-authored-by: Kalista Payne <sabrecat@gmail.com>

commit 47d832bf12
Author: Fiz <34069775+Hafizzle@users.noreply.github.com>
Date:   Tue Aug 5 15:12:44 2025 -0500

    Add backend support for Hydra mount (#15482)

    * chore: update time travelers shop to display seasonal backgrounds

    * chore: update time travelers banner (note CSS borken rn)

    * chore: fix borken CSS and update logic in shop

    * chore: added isSubscribed function, not working

    * chore: isSubscribed working but no bg for subscribers

    * chore: logic and css updates

    * chore: update habitica-images

    * chore: add check for trinket

    * chore: more time traveler shop logicking

    * Add backend support for Hydra mount

    - Add Dragon-Hydra to special mounts in stable.js
      - Configure as contributor level 7 reward with canFind: true
      - Add GIF format support for mount sprites
      - Enable admin panel granting capability

    * Fix Vue template errors in timeTravelers component

    * Fix duplicate template block in timeTravelers component

    * add CSS for Hydra mount GIF sprites

    Added CSS rules for Mount_Head_Dragon-Hydra and Mount_Body_Dragon-Hydra GIF sprites

    * Remove the separate Hydra mount dimension declaration

    ---------

    Co-authored-by: CuriousMagpie <eilatan@gmail.com>

commit c03ab9855f
Author: Kalista Payne <kalista@habitica.com>
Date:   Tue Aug 5 14:31:05 2025 -0500

    5.38.1

commit 8f96b7b7fd
Author: Weblate <noreply@weblate.org>
Date:   Tue Aug 5 13:02:45 2025 +0200

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 17.1% (42 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 16.7% (41 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 16.3% (40 of 245 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.8% (425 of 430 strings)

    Translated using Weblate (French)

    Currently translated at 99.4% (184 of 185 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.9% (39 of 245 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.8% (3379 of 3385 strings)

    Translated using Weblate (Polish)

    Currently translated at 95.5% (128 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.7% (254 of 268 strings)

    Translated using Weblate (Polish)

    Currently translated at 94.0% (126 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.6% (424 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.3% (423 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (798 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.4% (797 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 90.6% (781 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.9% (3112 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.9% (3111 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.0% (174 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (8 of 8 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.5% (38 of 245 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.6% (3104 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 93.5% (173 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 99.6% (279 of 280 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Japanese)

    Currently translated at 89.2% (769 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.4% (253 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.8% (170 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 97.9% (421 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.6% (3104 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 93.6% (251 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 90.8% (168 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 82.4% (202 of 245 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.1% (37 of 245 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.3% (3092 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (248 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (248 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Croatian)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Korean)

    Currently translated at 22.8% (56 of 245 strings)

    Translated using Weblate (Korean)

    Currently translated at 47.7% (128 of 268 strings)

    Translated using Weblate (Croatian)

    Currently translated at 45.1% (121 of 268 strings)

    Translated using Weblate (Korean)

    Currently translated at 71.9% (620 of 862 strings)

    Translated using Weblate (Croatian)

    Currently translated at 70.6% (609 of 862 strings)

    Translated using Weblate (Croatian)

    Currently translated at 75.0% (6 of 8 strings)

    Translated using Weblate (Korean)

    Currently translated at 67.6% (291 of 430 strings)

    Translated using Weblate (Korean)

    Currently translated at 52.8% (1788 of 3385 strings)

    Translated using Weblate (Croatian)

    Currently translated at 50.3% (1706 of 3385 strings)

    Translated using Weblate (Croatian)

    Currently translated at 51.7% (134 of 259 strings)

    Translated using Weblate (Czech)

    Currently translated at 92.8% (130 of 140 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 86.9% (233 of 268 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (Danish)

    Currently translated at 92.1% (105 of 114 strings)

    Translated using Weblate (Czech)

    Currently translated at 89.4% (102 of 114 strings)

    Translated using Weblate (Czech)

    Currently translated at 83.5% (112 of 134 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 71.6% (308 of 430 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 100.0% (245 of 245 strings)

    Translated using Weblate (Serbian)

    Currently translated at 84.4% (49 of 58 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 51.4% (144 of 280 strings)

    Translated using Weblate (Swedish)

    Currently translated at 66.5% (286 of 430 strings)

    Translated using Weblate (Serbian)

    Currently translated at 65.5% (282 of 430 strings)

    Translated using Weblate (Slovak)

    Currently translated at 65.5% (282 of 430 strings)

    Translated using Weblate (Romanian)

    Currently translated at 66.7% (287 of 430 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (430 of 430 strings)

    Translated using Weblate (Danish)

    Currently translated at 66.0% (284 of 430 strings)

    Translated using Weblate (Czech)

    Currently translated at 69.7% (300 of 430 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.7% (3377 of 3385 strings)

    Translated using Weblate (Swedish)

    Currently translated at 54.1% (1834 of 3385 strings)

    Translated using Weblate (Serbian)

    Currently translated at 50.6% (1714 of 3385 strings)

    Translated using Weblate (Slovak)

    Currently translated at 50.0% (1695 of 3385 strings)

    Translated using Weblate (Romanian)

    Currently translated at 60.5% (2050 of 3385 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 38.4% (1301 of 3385 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Danish)

    Currently translated at 54.0% (1829 of 3385 strings)

    Translated using Weblate (Czech)

    Currently translated at 59.6% (2020 of 3385 strings)

    Translated using Weblate (Swedish)

    Currently translated at 75.6% (140 of 185 strings)

    Translated using Weblate (Serbian)

    Currently translated at 73.5% (136 of 185 strings)

    Translated using Weblate (Slovak)

    Currently translated at 84.8% (157 of 185 strings)

    Translated using Weblate (Romanian)

    Currently translated at 78.9% (146 of 185 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 82.1% (152 of 185 strings)

    Translated using Weblate (Italian)

    Currently translated at 91.8% (170 of 185 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (Danish)

    Currently translated at 77.2% (143 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.7% (242 of 245 strings)

    Translated using Weblate (Czech)

    Currently translated at 75.1% (139 of 185 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 74.5% (138 of 185 strings)

    Translated using Weblate (Czech)

    Currently translated at 8.1% (20 of 245 strings)

    Translated using Weblate (Swedish)

    Currently translated at 72.0% (621 of 862 strings)

    Translated using Weblate (Serbian)

    Currently translated at 65.1% (562 of 862 strings)

    Translated using Weblate (Slovak)

    Currently translated at 66.9% (577 of 862 strings)

    Translated using Weblate (Romanian)

    Currently translated at 77.7% (670 of 862 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 70.0% (604 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Italian)

    Currently translated at 86.8% (749 of 862 strings)

    Translated using Weblate (Indonesian)

    Currently translated at 86.0% (742 of 862 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 66.1% (570 of 862 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.0% (845 of 862 strings)

    Translated using Weblate (Danish)

    Currently translated at 69.9% (603 of 862 strings)

    Translated using Weblate (Czech)

    Currently translated at 69.7% (601 of 862 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 66.3% (572 of 862 strings)

    Translated using Weblate (Serbian)

    Currently translated at 74.0% (305 of 412 strings)

    Translated using Weblate (Turkish)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Danish)

    Currently translated at 90.0% (371 of 412 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Swedish)

    Currently translated at 53.6% (139 of 259 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Danish)

    Currently translated at 62.1% (161 of 259 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 54.0% (140 of 259 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 82.8% (222 of 268 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 99.4% (184 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.3% (241 of 245 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.3% (3092 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 88.4% (237 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (134 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (Japanese)

    Currently translated at 82.4% (202 of 245 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 87.3% (234 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 86.4% (160 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 99.8% (913 of 914 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (Russian)

    Currently translated at 88.5% (248 of 280 strings)

    Translated using Weblate (Spanish)

    Currently translated at 99.8% (3379 of 3385 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (862 of 862 strings)

    Co-authored-by: Ayaka Booker <ayakabooker@gmail.com>
    Co-authored-by: Chaotic Lawful <habitica@eusebius.fr>
    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Lio Zam <zerofux@web.de>
    Co-authored-by: Mika <isekai.chr@gmail.com>
    Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
    Co-authored-by: Summer_GUI <heyang94@163.com>
    Co-authored-by: Vera <verasmolinap@gmail.com>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Zhi Hao Li <zhihaoli000@gmail.com>
    Co-authored-by: Zuz Q <zuzannakunik@gmail.com>
    Co-authored-by: innnko <ayakabooker@gmail.com>
    Co-authored-by: 吳昀錡 <J1120241@gm.fdhs.tyc.edu.tw>
    Co-authored-by: 潘致翰 <happyq0908@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/
    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/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/it/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/da/
    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/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/
    Translate-URL: https://translate.habitica.com/projects/habitica/messages/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/id/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/
    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/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/tasks/cs/
    Translation: Habitica/Achievements
    Translation: Habitica/Backgrounds
    Translation: Habitica/Character
    Translation: Habitica/Content
    Translation: Habitica/Death
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Groups
    Translation: Habitica/Limited
    Translation: Habitica/Messages
    Translation: Habitica/Npc
    Translation: Habitica/Overview
    Translation: Habitica/Pets
    Translation: Habitica/Quests
    Translation: Habitica/Questscontent
    Translation: Habitica/Settings
    Translation: Habitica/Subscriber
    Translation: Habitica/Tasks

commit 1dde2674f6
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Mon Jun 16 16:43:56 2025 -0500

    fix(content): don't filter out the thing we want

commit 76122a8889
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jun 4 14:28:27 2025 -0500

    fix(mobile): provide Challenge categories via API

commit 9e309a875e
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Jul 28 14:15:00 2025 -0500

    5.38.0

commit 09e3a394b8
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Jul 28 14:06:45 2025 -0500

    5.37.3

commit eba263360f
Author: Weblate <noreply@weblate.org>
Date:   Mon Jul 28 21:03:17 2025 +0200

    Translated using Weblate (German)

    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 (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 98.6% (850 of 862 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.4% (3360 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (245 of 245 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (47 of 47 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 14.2% (35 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 13.8% (34 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 13.0% (32 of 245 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 2.0% (5 of 245 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 66.1% (570 of 862 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 54.1% (1830 of 3377 strings)

    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
    Co-authored-by: Jonathan Niessen <37.friedrich@gmail.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
    Co-authored-by: Laura Fleckenstein <fleckenstein_laura@web.de>
    Co-authored-by: Omer I.S <omeritzicschwartz@gmail.com>
    Co-authored-by: Remigiusz Haziak <haziakremigiusz@gmail.com>
    Co-authored-by: Uwe B <hbtca@tunixgut.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Wellinton Cardoso <wmcardoso1@hotmail.com>
    Co-authored-by: cloudzzy <truskawka412@gmail.com>
    Co-authored-by: 吳昀錡 <J1120241@gm.fdhs.tyc.edu.tw>
    Translate-URL: https://translate.habitica.com/projects/habitica/character/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/contrib/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/
    Translation: Habitica/Character
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Contrib
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Npc
    Translation: Habitica/Questscontent

commit 9550eec718
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jul 28 16:50:38 2025 +0200

    Fix 500 when deleting a very old group plan account (#15481)

commit f267eb67e9
Author: Kalista Payne <kalista@habitica.com>
Date:   Tue Jul 29 14:12:35 2025 -0500

    fix(static): add back missing div for show/hide

commit 28251f42ab
Author: Kalista Payne <kalista@habitica.com>
Date:   Thu Jul 24 22:59:01 2025 -0500

    feat(privacy): preview page

* feat(privacy): respect Global Privacy Control

* fix(lint): remove unused component

* fix(test): test user opts in to tracking

* fix(test): add user pref to more contexts

* fix(test): final spot in api-unit

* fix(tests): update integrations

* chore(privacy): add paragraph to s1, retire separate preview pages

* fix(build): route copypasta

* fix(router): lingering dead import

---------

Signed-off-by: Kalista Payne <sabrecat@gmail.com>
Signed-off-by: Kalista Payne <kalista@habitica.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: CuriousMagpie <eilatan@gmail.com>
2025-08-29 15:44:19 -05:00
dependabot[bot]
3e0bc36373 chore(deps): bump tar-fs from 2.1.1 to 2.1.3 (#15446)
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.1 to 2.1.3.
- [Commits](https://github.com/mafintosh/tar-fs/commits)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 15:43:46 -05:00
dependabot[bot]
0a431afaaf chore(deps): bump brace-expansion in /website/client (#15461)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 15:43:46 -05:00
dependabot[bot]
8c911bcd41 chore(deps): bump on-headers, compression, cookie-session and morgan (#15477)
Bumps [on-headers](https://github.com/jshttp/on-headers), [compression](https://github.com/expressjs/compression), [cookie-session](https://github.com/expressjs/cookie-session) and [morgan](https://github.com/expressjs/morgan). These dependencies needed to be updated together.

Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `compression` from 1.7.4 to 1.8.1
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/1.7.4...v1.8.1)

Updates `cookie-session` from 2.0.0 to 2.1.1
- [Release notes](https://github.com/expressjs/cookie-session/releases)
- [Changelog](https://github.com/expressjs/cookie-session/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/cookie-session/compare/v2.0.0...v2.1.1)

Updates `morgan` from 1.10.0 to 1.10.1
- [Release notes](https://github.com/expressjs/morgan/releases)
- [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/morgan/compare/1.10.0...1.10.1)

---
updated-dependencies:
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: direct:production
- dependency-name: compression
  dependency-version: 1.8.1
  dependency-type: direct:production
- dependency-name: cookie-session
  dependency-version: 2.1.1
  dependency-type: direct:production
- dependency-name: morgan
  dependency-version: 1.10.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 15:43:46 -05:00
dependabot[bot]
dcb7ac5955 chore(deps): bump form-data from 4.0.0 to 4.0.4 in /website/client (#15478)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.0 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.0...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 15:43:46 -05:00
Fiz
fb730942a0 Respect user language preference in content endpoint (#15485)
* Respect user language preference in content endpoint

Content API now returns data in user's preferred language when authenticated without language parameter. No breaking changes - existing clients unaffected.

* lint fix
2025-08-29 15:43:46 -05:00
Kalista Payne
9c92bf73f5 5.39.1 2025-08-29 15:43:30 -05:00
Weblate
58f195fdb7 Merge branch 'develop' of github.com:HabitRPG/habitica into develop 2025-08-29 22:39:07 +02:00
Kalista Payne
4b86c9c8a7 fix(privacy): update section 7 for new age limit 2025-08-29 15:28:01 -05:00
Kalista Payne
4cc689ec63 fix(privacy): wire up modal 2025-08-28 17:41:38 -05:00
innnko
8690484f5e Translated using Weblate (Japanese)
Currently translated at 100.0% (114 of 114 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ja/
2025-08-28 11:40:25 +02:00
Kalista Payne
1f3e5b7a76 fix(analytics): grab missing GA ID 2025-08-27 14:25:21 -05:00
Kalista Payne
61c790f291 fix(analytics): record user ID in GA if consented 2025-08-27 13:59:15 -05:00
Weblate
b3440fa3a8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (German)

Currently translated at 97.9% (3370 of 3441 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (857 of 862 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (412 of 412 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (412 of 412 strings)

Translated using Weblate (Portuguese)

Currently translated at 37.4% (91 of 243 strings)

Translated using Weblate (Japanese)

Currently translated at 98.9% (270 of 273 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Portuguese)

Currently translated at 35.8% (87 of 243 strings)

Translated using Weblate (Russian)

Currently translated at 96.2% (414 of 430 strings)

Translated using Weblate (Russian)

Currently translated at 99.0% (914 of 923 strings)

Translated using Weblate (French)

Currently translated at 99.5% (3425 of 3441 strings)

Translated using Weblate (French)

Currently translated at 98.5% (3391 of 3441 strings)

Translated using Weblate (French)

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (French)

Currently translated at 98.5% (269 of 273 strings)

Translated using Weblate (French)

Currently translated at 98.1% (268 of 273 strings)

Translated using Weblate (French)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (French)

Currently translated at 97.8% (267 of 273 strings)

Translated using Weblate (Japanese)

Currently translated at 98.4% (186 of 189 strings)

Translated using Weblate (Indonesian)

Currently translated at 70.4% (2425 of 3441 strings)

Translated using Weblate (Indonesian)

Currently translated at 86.1% (743 of 862 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (French)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Portuguese)

Currently translated at 53.4% (1838 of 3441 strings)

Translated using Weblate (French)

Currently translated at 96.2% (182 of 189 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 28.9% (71 of 245 strings)

Translated using Weblate (German)

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (German)

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Portuguese)

Currently translated at 53.2% (1834 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (185 of 189 strings)

Translated using Weblate (German)

Currently translated at 99.4% (188 of 189 strings)

Translated using Weblate (German)

Currently translated at 99.6% (920 of 923 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (German)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (German)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Russian)

Currently translated at 98.9% (191 of 193 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (185 of 189 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3441 of 3441 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 27.7% (68 of 245 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (273 of 273 strings)

Translated using Weblate (Japanese)

Currently translated at 97.3% (184 of 189 strings)

Translated using Weblate (Japanese)

Currently translated at 98.1% (268 of 273 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (132 of 132 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (189 of 189 strings)

Translated using Weblate (German)

Currently translated at 100.0% (284 of 284 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Japanese)

Currently translated at 94.3% (813 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (923 of 923 strings)

Translated using Weblate (Japanese)

Currently translated at 86.5% (212 of 245 strings)

Translated using Weblate (Japanese)

Currently translated at 93.7% (808 of 862 strings)

Translated using Weblate (Japanese)

Currently translated at 91.8% (3160 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 86.5% (212 of 245 strings)

Translated using Weblate (Spanish)

Currently translated at 99.3% (3420 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 91.7% (3158 of 3441 strings)

Translated using Weblate (Japanese)

Currently translated at 99.7% (921 of 923 strings)

Co-authored-by: FingerTiao <787170918@qq.com>
Co-authored-by: Hanayuri Kuchiki <chocochili.lover@gmail.com>
Co-authored-by: Igor <777igor93@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
Co-authored-by: Kubo Mizuki <m.kubo.0916@gmail.com>
Co-authored-by: Lyam Santos Peres <kaka1213spaenrteoss@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Summer_GUI <heyang94@163.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wellinton Cardoso <wmcardoso1@hotmail.com>
Co-authored-by: innnko <ayakabooker@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/front/
Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
Translate-URL: https://translate.habitica.com/projects/habitica/front/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
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/id/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
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/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/id/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
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/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translation: Habitica/Backgrounds
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/Limited
Translation: Habitica/Npc
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2025-08-27 20:08:43 +02:00
Kalista Payne
a3f1835d1d fix(lint): whitespace 2025-08-27 12:25:28 -05:00
Kalista Payne
9226f6f70e fix(settings): consistent layout with modal 2025-08-27 12:21:08 -05:00
Kalista Payne
1130f9957f fix(terms): copy edits 2025-08-27 11:40:00 -05:00
Kalista Payne
ad1fd03aad fix(signup): two style corrections 2025-08-27 11:35:17 -05:00
Kalista Payne
6c93033ad2 fix(signup): 18+ verbiage, refactor forms scss 2025-08-27 11:35:17 -05:00
Kalista Payne
dd97b11b60 Privacy Controls (#15492)
* WIP(privacy): start of banner

* WIP(privacy): layout rough

* WIP(privacy): mobile layout, add modal

* fix(privacy): implement toggle disable and setting row fold

* fix(privacy): clean up a couple of styles

* fix(privacy): adjust banner width at mobile sizes

* WIP(privacy): remove Loggly echo of Amplitude data

* fix(banners): account for privacy in snackbar position

* WIP(privacy): dismiss banner

* chore(analytics): update to maintaned GA4 library

* fix(tests): lint, misuse of apiError

* fix(analytics): add debug mode

* fix(analytics): load new library on client

* WIP(privacy): gtag.js based implementation

* fix(analytics): lint issues

* fix(lint): one more unused

* fix(lint): client errors

* feat(privacy): draft workflows

* fix(analytics): linting, send needed user values

* fix(tests): use mock analytics service in test env

* fix(tests): restore previous logic for node env

* feat(intro): jump to page 2 onboarding

* WIP(auth): revisions to registration flow

* WIP(privacy): landing page and banner revisions

* WIP(signup): added new username, tos, privacy state

* fix(signup): revert debugging logic

* WIP(signup): add defaulting and checkbox

* wip(signup): move social auth behind username screen

* Squashed commit of the following:

commit ca0a238e5f008525ed154c5eaf12e44f2fc22b00
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed May 7 12:17:20 2025 +0200

    make emails lowercase

commit a2ce748558ce9134e6825208a7e66d78e720202e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 13:27:01 2025 +0200

    remove unused import

commit cc6ce6c388d9693cf192c4bea733931fc8c31c37
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 13:13:03 2025 +0200

    add tests for new api route

commit 0d40a6230b548625482aa9f6831c93ed9d62533a
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jun 18 15:50:22 2025 -0500

    update social tests

commit 79177d6754589b9e54682af8a531b63f60215dab
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 10:21:51 2025 +0200

    new api route to check if an email is available

commit 11df73fe07eeb730c2a95593e18e14a931f52429
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 10:21:39 2025 +0200

    Add field to not register social account when called

* Squashed commit of the following:

commit b8a2f0b8ee
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 20 17:18:30 2024 -0400

    update privacy policy

* fix(vite): import syntax

* feat(auth): precheck on defaulted username

* feat(auth): add store action for check-email

* feat(auth): check email before proceeding

* WIP(login): refactor username screen

* WIP(auth): complete login/reg flow

* fix(auth): filter out expected 404

* fix(login): use allowRegister with Apple
and add z-index to component

* fix(login): style corrections and email passthru

* Fix edgecase

Signed-off-by: Kalista Payne <sabrecat@gmail.com>

* fix(auth): correct error behaviors

* fix(auth): rewire Apple auth

* make check-email check for restricted domains

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(signup): all the style

* fix(express): return when responding

* fix(error): reduce specificity for restricted domain issue

* fix apple auth

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(signup): change from blur to 500ms debounce

* fix(login): add missing 200 response in Apple flow

* fix(signup): more reconciliation with @phillipthelen's work

* fix(signup): now using token not code

* fix(reg): don't bail on Apple if we're allowing reg

* fix(auth): more reconciliation with @phillipthelen code

* feat(copy): privacy policy updates

* fix(copy): replace placeholder

* fix(vue): use Vite syntax for scss import

* fix(static): corrections to copy and css

* chore(style): remove excess whitespace

* use correct error

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(layout): inputs, add privacy banner

* fix(login): button hover, more validation states

* fix(login): further layout and UX corrections

* fix(static): add back containing div for show/hide

* fix(apple): clean out Apple token

* fix(settings): only change preference on save

* fix(settings): correct save/cancel behavior

* fix(layout): consistent use of header/footer

* fix(layout): reposition mountains for reg/login/forgot

* fix(signup): partial rollback of /username route

* refactor(signup): move /username to page

* fix(apple): don't overwrite reg method

* fix(username): don't skip empty validation

* fix(input): don't show valid if no username

* fix(login): clean out Apple token if using another method

* fix(apple): possible race with token

* fix(tests): some housekeeping

* fix(config): copypasta

* fix(lint): various cleanup

* fix(lint): line squeeze

* fix(lint): one more v-for

* fix(groups): funnel invite flow to new username page

* Squashed commit of the following:

commit 3c5ba4bf24e4bb7996786520101f27ad66405bce
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 14:38:31 2025 -0500

    fix(privacy): update link ref

commit 9d216f623b
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 14:18:22 2025 -0500

    fix(privacy-tos): copy edits cont'd

commit d744f47140
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 13:43:22 2025 -0500

    fix(privacy): copy edits and ToC reflow

commit 2c3c3fc9ce
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:46:24 2025 +0200

    lint

commit cf363034d5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:34:54 2025 +0200

    fix link

commit 3afacd2c05
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:34:42 2025 +0200

    add updated terms

commit 258b722499
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 17:58:42 2025 +0200

    put back button to show/hide third party info

commit 2992e0299b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 17:58:32 2025 +0200

    minor edits

commit bb5e252299
Author: Kalista Payne <kalista@habitica.com>
Date:   Sun Aug 17 21:01:50 2025 -0500

    fix(privacy): update Section 3

commit c79af7baa8
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 15 17:28:49 2025 -0500

    fix(privacy): various copy edits

commit 100f2f4574
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 15 11:37:37 2025 +0200

    add newline

commit 11d1cfd0d9
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 15 11:10:01 2025 +0200

    update privacy policy

commit 59b99badf3
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 8 14:04:19 2025 -0500

    5.38.2

commit 78daeb4191
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 8 13:36:19 2025 -0500

    fix(apple): don't run auth middleware during redirect

commit 93f8d60903
Author: Weblate <noreply@weblate.org>
Date:   Fri Aug 8 10:12:25 2025 +0200

    Translated using Weblate (German)

    Currently translated at 99.4% (185 of 186 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (186 of 186 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (186 of 186 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (54 of 54 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (47 of 47 strings)

    Translated using Weblate (Dutch)

    Currently translated at 78.0% (2643 of 3385 strings)

    Translated using Weblate (Dutch)

    Currently translated at 40.8% (100 of 245 strings)

    Translated using Weblate (Polish)

    Currently translated at 89.9% (233 of 259 strings)

    Translated using Weblate (Dutch)

    Currently translated at 67.5% (175 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (110 of 110 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 20.8% (51 of 245 strings)

    Translated using Weblate (Turkish)

    Currently translated at 65.9% (60 of 91 strings)

    Translated using Weblate (Turkish)

    Currently translated at 65.9% (60 of 91 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 17.9% (44 of 245 strings)

    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Linsey Dunya Pastoor <sekai.creations@gmail.com>
    Co-authored-by: Mete Olmez <metezori27@gmail.com>
    Co-authored-by: Sefa Uğurlu <ugurlusefa2@gmail.com>
    Co-authored-by: Summer_GUI <heyang94@163.com>
    Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: innnko <ayakabooker@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/tr/
    Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/pl/
    Translation: Habitica/Backgrounds
    Translation: Habitica/Challenge
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Contrib
    Translation: Habitica/Death
    Translation: Habitica/Defaulttasks
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Settings

commit eb16fec41e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Aug 6 22:08:07 2025 +0200

    Add interface to block ip-addresses or clients due to abuse (#15484)

    * Read IP blocks from database

    * begin building general blocking solution

    * add new frontend files

    * Add UI for managing blockers

    * correctly reset local data after creating blocker

    * Tweak wording

    * Add UI for managing blockers

    * restructure admin pages

    * improve test coverage

    * Improve blocker UI

    * add blocker to block emails from registration

    * lint fix

    * fix

    * lint fixes

    * fix import

    * add new permission for managing blockers

    * improve permission check

    * fix managing permissions from admin

    * improve navbar display for non fullAccess admin

    * update block error strings

    * lint fix

    * add option to errorHandler to skip logging

    * validate blocker value during input

    * improve blocker form display

    * chore(subproj): reconcile habitica-images

    * fix(scripts): use same Mongo version for dev/test

    * fix(whitespace): eof

    * documentation improvements

    * remove nconf import

    * remove old test

    ---------

    Co-authored-by: Kalista Payne <kalista@habitica.com>
    Co-authored-by: Kalista Payne <sabrecat@gmail.com>

commit 47d832bf12
Author: Fiz <34069775+Hafizzle@users.noreply.github.com>
Date:   Tue Aug 5 15:12:44 2025 -0500

    Add backend support for Hydra mount (#15482)

    * chore: update time travelers shop to display seasonal backgrounds

    * chore: update time travelers banner (note CSS borken rn)

    * chore: fix borken CSS and update logic in shop

    * chore: added isSubscribed function, not working

    * chore: isSubscribed working but no bg for subscribers

    * chore: logic and css updates

    * chore: update habitica-images

    * chore: add check for trinket

    * chore: more time traveler shop logicking

    * Add backend support for Hydra mount

    - Add Dragon-Hydra to special mounts in stable.js
      - Configure as contributor level 7 reward with canFind: true
      - Add GIF format support for mount sprites
      - Enable admin panel granting capability

    * Fix Vue template errors in timeTravelers component

    * Fix duplicate template block in timeTravelers component

    * add CSS for Hydra mount GIF sprites

    Added CSS rules for Mount_Head_Dragon-Hydra and Mount_Body_Dragon-Hydra GIF sprites

    * Remove the separate Hydra mount dimension declaration

    ---------

    Co-authored-by: CuriousMagpie <eilatan@gmail.com>

commit c03ab9855f
Author: Kalista Payne <kalista@habitica.com>
Date:   Tue Aug 5 14:31:05 2025 -0500

    5.38.1

commit 8f96b7b7fd
Author: Weblate <noreply@weblate.org>
Date:   Tue Aug 5 13:02:45 2025 +0200

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 17.1% (42 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 16.7% (41 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 16.3% (40 of 245 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.8% (425 of 430 strings)

    Translated using Weblate (French)

    Currently translated at 99.4% (184 of 185 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.9% (39 of 245 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.8% (3379 of 3385 strings)

    Translated using Weblate (Polish)

    Currently translated at 95.5% (128 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.7% (254 of 268 strings)

    Translated using Weblate (Polish)

    Currently translated at 94.0% (126 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.6% (424 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.3% (423 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (798 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.4% (797 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 90.6% (781 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.9% (3112 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.9% (3111 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.0% (174 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (8 of 8 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.5% (38 of 245 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.6% (3104 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 93.5% (173 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 99.6% (279 of 280 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Japanese)

    Currently translated at 89.2% (769 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.4% (253 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.8% (170 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 97.9% (421 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.6% (3104 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 93.6% (251 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 90.8% (168 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 82.4% (202 of 245 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.1% (37 of 245 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.3% (3092 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (248 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (248 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Croatian)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Korean)

    Currently translated at 22.8% (56 of 245 strings)

    Translated using Weblate (Korean)

    Currently translated at 47.7% (128 of 268 strings)

    Translated using Weblate (Croatian)

    Currently translated at 45.1% (121 of 268 strings)

    Translated using Weblate (Korean)

    Currently translated at 71.9% (620 of 862 strings)

    Translated using Weblate (Croatian)

    Currently translated at 70.6% (609 of 862 strings)

    Translated using Weblate (Croatian)

    Currently translated at 75.0% (6 of 8 strings)

    Translated using Weblate (Korean)

    Currently translated at 67.6% (291 of 430 strings)

    Translated using Weblate (Korean)

    Currently translated at 52.8% (1788 of 3385 strings)

    Translated using Weblate (Croatian)

    Currently translated at 50.3% (1706 of 3385 strings)

    Translated using Weblate (Croatian)

    Currently translated at 51.7% (134 of 259 strings)

    Translated using Weblate (Czech)

    Currently translated at 92.8% (130 of 140 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 86.9% (233 of 268 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (Danish)

    Currently translated at 92.1% (105 of 114 strings)

    Translated using Weblate (Czech)

    Currently translated at 89.4% (102 of 114 strings)

    Translated using Weblate (Czech)

    Currently translated at 83.5% (112 of 134 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 71.6% (308 of 430 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 100.0% (245 of 245 strings)

    Translated using Weblate (Serbian)

    Currently translated at 84.4% (49 of 58 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 51.4% (144 of 280 strings)

    Translated using Weblate (Swedish)

    Currently translated at 66.5% (286 of 430 strings)

    Translated using Weblate (Serbian)

    Currently translated at 65.5% (282 of 430 strings)

    Translated using Weblate (Slovak)

    Currently translated at 65.5% (282 of 430 strings)

    Translated using Weblate (Romanian)

    Currently translated at 66.7% (287 of 430 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (430 of 430 strings)

    Translated using Weblate (Danish)

    Currently translated at 66.0% (284 of 430 strings)

    Translated using Weblate (Czech)

    Currently translated at 69.7% (300 of 430 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.7% (3377 of 3385 strings)

    Translated using Weblate (Swedish)

    Currently translated at 54.1% (1834 of 3385 strings)

    Translated using Weblate (Serbian)

    Currently translated at 50.6% (1714 of 3385 strings)

    Translated using Weblate (Slovak)

    Currently translated at 50.0% (1695 of 3385 strings)

    Translated using Weblate (Romanian)

    Currently translated at 60.5% (2050 of 3385 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 38.4% (1301 of 3385 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Danish)

    Currently translated at 54.0% (1829 of 3385 strings)

    Translated using Weblate (Czech)

    Currently translated at 59.6% (2020 of 3385 strings)

    Translated using Weblate (Swedish)

    Currently translated at 75.6% (140 of 185 strings)

    Translated using Weblate (Serbian)

    Currently translated at 73.5% (136 of 185 strings)

    Translated using Weblate (Slovak)

    Currently translated at 84.8% (157 of 185 strings)

    Translated using Weblate (Romanian)

    Currently translated at 78.9% (146 of 185 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 82.1% (152 of 185 strings)

    Translated using Weblate (Italian)

    Currently translated at 91.8% (170 of 185 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (Danish)

    Currently translated at 77.2% (143 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.7% (242 of 245 strings)

    Translated using Weblate (Czech)

    Currently translated at 75.1% (139 of 185 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 74.5% (138 of 185 strings)

    Translated using Weblate (Czech)

    Currently translated at 8.1% (20 of 245 strings)

    Translated using Weblate (Swedish)

    Currently translated at 72.0% (621 of 862 strings)

    Translated using Weblate (Serbian)

    Currently translated at 65.1% (562 of 862 strings)

    Translated using Weblate (Slovak)

    Currently translated at 66.9% (577 of 862 strings)

    Translated using Weblate (Romanian)

    Currently translated at 77.7% (670 of 862 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 70.0% (604 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Italian)

    Currently translated at 86.8% (749 of 862 strings)

    Translated using Weblate (Indonesian)

    Currently translated at 86.0% (742 of 862 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 66.1% (570 of 862 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.0% (845 of 862 strings)

    Translated using Weblate (Danish)

    Currently translated at 69.9% (603 of 862 strings)

    Translated using Weblate (Czech)

    Currently translated at 69.7% (601 of 862 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 66.3% (572 of 862 strings)

    Translated using Weblate (Serbian)

    Currently translated at 74.0% (305 of 412 strings)

    Translated using Weblate (Turkish)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Danish)

    Currently translated at 90.0% (371 of 412 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Swedish)

    Currently translated at 53.6% (139 of 259 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Danish)

    Currently translated at 62.1% (161 of 259 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 54.0% (140 of 259 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 82.8% (222 of 268 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 99.4% (184 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.3% (241 of 245 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.3% (3092 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 88.4% (237 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (134 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (Japanese)

    Currently translated at 82.4% (202 of 245 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 87.3% (234 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 86.4% (160 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 99.8% (913 of 914 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (Russian)

    Currently translated at 88.5% (248 of 280 strings)

    Translated using Weblate (Spanish)

    Currently translated at 99.8% (3379 of 3385 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (862 of 862 strings)

    Co-authored-by: Ayaka Booker <ayakabooker@gmail.com>
    Co-authored-by: Chaotic Lawful <habitica@eusebius.fr>
    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Lio Zam <zerofux@web.de>
    Co-authored-by: Mika <isekai.chr@gmail.com>
    Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
    Co-authored-by: Summer_GUI <heyang94@163.com>
    Co-authored-by: Vera <verasmolinap@gmail.com>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Zhi Hao Li <zhihaoli000@gmail.com>
    Co-authored-by: Zuz Q <zuzannakunik@gmail.com>
    Co-authored-by: innnko <ayakabooker@gmail.com>
    Co-authored-by: 吳昀錡 <J1120241@gm.fdhs.tyc.edu.tw>
    Co-authored-by: 潘致翰 <happyq0908@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/
    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/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/it/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/da/
    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/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/
    Translate-URL: https://translate.habitica.com/projects/habitica/messages/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/id/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/
    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/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/tasks/cs/
    Translation: Habitica/Achievements
    Translation: Habitica/Backgrounds
    Translation: Habitica/Character
    Translation: Habitica/Content
    Translation: Habitica/Death
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Groups
    Translation: Habitica/Limited
    Translation: Habitica/Messages
    Translation: Habitica/Npc
    Translation: Habitica/Overview
    Translation: Habitica/Pets
    Translation: Habitica/Quests
    Translation: Habitica/Questscontent
    Translation: Habitica/Settings
    Translation: Habitica/Subscriber
    Translation: Habitica/Tasks

commit 1dde2674f6
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Mon Jun 16 16:43:56 2025 -0500

    fix(content): don't filter out the thing we want

commit 76122a8889
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jun 4 14:28:27 2025 -0500

    fix(mobile): provide Challenge categories via API

commit 9e309a875e
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Jul 28 14:15:00 2025 -0500

    5.38.0

commit 09e3a394b8
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Jul 28 14:06:45 2025 -0500

    5.37.3

commit eba263360f
Author: Weblate <noreply@weblate.org>
Date:   Mon Jul 28 21:03:17 2025 +0200

    Translated using Weblate (German)

    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 (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 98.6% (850 of 862 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.4% (3360 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (245 of 245 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (47 of 47 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 14.2% (35 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 13.8% (34 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 13.0% (32 of 245 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 2.0% (5 of 245 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 66.1% (570 of 862 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 54.1% (1830 of 3377 strings)

    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
    Co-authored-by: Jonathan Niessen <37.friedrich@gmail.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
    Co-authored-by: Laura Fleckenstein <fleckenstein_laura@web.de>
    Co-authored-by: Omer I.S <omeritzicschwartz@gmail.com>
    Co-authored-by: Remigiusz Haziak <haziakremigiusz@gmail.com>
    Co-authored-by: Uwe B <hbtca@tunixgut.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Wellinton Cardoso <wmcardoso1@hotmail.com>
    Co-authored-by: cloudzzy <truskawka412@gmail.com>
    Co-authored-by: 吳昀錡 <J1120241@gm.fdhs.tyc.edu.tw>
    Translate-URL: https://translate.habitica.com/projects/habitica/character/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/contrib/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/
    Translation: Habitica/Character
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Contrib
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Npc
    Translation: Habitica/Questscontent

commit 9550eec718
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jul 28 16:50:38 2025 +0200

    Fix 500 when deleting a very old group plan account (#15481)

commit f267eb67e9
Author: Kalista Payne <kalista@habitica.com>
Date:   Tue Jul 29 14:12:35 2025 -0500

    fix(static): add back missing div for show/hide

commit 28251f42ab
Author: Kalista Payne <kalista@habitica.com>
Date:   Thu Jul 24 22:59:01 2025 -0500

    feat(privacy): preview page

* feat(privacy): respect Global Privacy Control

* fix(lint): remove unused component

* fix(test): test user opts in to tracking

* fix(test): add user pref to more contexts

* fix(test): final spot in api-unit

* fix(tests): update integrations

* chore(privacy): add paragraph to s1, retire separate preview pages

* fix(build): route copypasta

* fix(router): lingering dead import

---------

Signed-off-by: Kalista Payne <sabrecat@gmail.com>
Signed-off-by: Kalista Payne <kalista@habitica.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: CuriousMagpie <eilatan@gmail.com>
2025-08-27 11:35:17 -05:00
dependabot[bot]
59ba07d4f3 chore(deps): bump tar-fs from 2.1.1 to 2.1.3 (#15446)
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.1 to 2.1.3.
- [Commits](https://github.com/mafintosh/tar-fs/commits)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 11:34:46 -05:00
dependabot[bot]
d2bfd1e3a9 chore(deps): bump brace-expansion in /website/client (#15461)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 11:34:46 -05:00
dependabot[bot]
a8264bf526 chore(deps): bump on-headers, compression, cookie-session and morgan (#15477)
Bumps [on-headers](https://github.com/jshttp/on-headers), [compression](https://github.com/expressjs/compression), [cookie-session](https://github.com/expressjs/cookie-session) and [morgan](https://github.com/expressjs/morgan). These dependencies needed to be updated together.

Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `compression` from 1.7.4 to 1.8.1
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/1.7.4...v1.8.1)

Updates `cookie-session` from 2.0.0 to 2.1.1
- [Release notes](https://github.com/expressjs/cookie-session/releases)
- [Changelog](https://github.com/expressjs/cookie-session/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/cookie-session/compare/v2.0.0...v2.1.1)

Updates `morgan` from 1.10.0 to 1.10.1
- [Release notes](https://github.com/expressjs/morgan/releases)
- [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/morgan/compare/1.10.0...1.10.1)

---
updated-dependencies:
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: direct:production
- dependency-name: compression
  dependency-version: 1.8.1
  dependency-type: direct:production
- dependency-name: cookie-session
  dependency-version: 2.1.1
  dependency-type: direct:production
- dependency-name: morgan
  dependency-version: 1.10.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 11:34:46 -05:00
dependabot[bot]
f202f2b3d3 chore(deps): bump form-data from 4.0.0 to 4.0.4 in /website/client (#15478)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.0 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.0...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 11:34:46 -05:00
Fiz
4ea9f8282e Respect user language preference in content endpoint (#15485)
* Respect user language preference in content endpoint

Content API now returns data in user's preferred language when authenticated without language parameter. No breaking changes - existing clients unaffected.

* lint fix
2025-08-27 11:34:46 -05:00
Kalista Payne
205d84a111 fix(terms): copy edits 2025-08-27 11:34:32 -05:00
Kalista Payne
5810853cc2 fix(signup): two style corrections 2025-08-25 15:00:08 -05:00
Kalista Payne
4547204bd8 fix(signup): 18+ verbiage, refactor forms scss 2025-08-22 17:38:44 -05:00
Kalista Payne
f17a0c91a3 Privacy Controls (#15492)
* WIP(privacy): start of banner

* WIP(privacy): layout rough

* WIP(privacy): mobile layout, add modal

* fix(privacy): implement toggle disable and setting row fold

* fix(privacy): clean up a couple of styles

* fix(privacy): adjust banner width at mobile sizes

* WIP(privacy): remove Loggly echo of Amplitude data

* fix(banners): account for privacy in snackbar position

* WIP(privacy): dismiss banner

* chore(analytics): update to maintaned GA4 library

* fix(tests): lint, misuse of apiError

* fix(analytics): add debug mode

* fix(analytics): load new library on client

* WIP(privacy): gtag.js based implementation

* fix(analytics): lint issues

* fix(lint): one more unused

* fix(lint): client errors

* feat(privacy): draft workflows

* fix(analytics): linting, send needed user values

* fix(tests): use mock analytics service in test env

* fix(tests): restore previous logic for node env

* feat(intro): jump to page 2 onboarding

* WIP(auth): revisions to registration flow

* WIP(privacy): landing page and banner revisions

* WIP(signup): added new username, tos, privacy state

* fix(signup): revert debugging logic

* WIP(signup): add defaulting and checkbox

* wip(signup): move social auth behind username screen

* Squashed commit of the following:

commit ca0a238e5f008525ed154c5eaf12e44f2fc22b00
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed May 7 12:17:20 2025 +0200

    make emails lowercase

commit a2ce748558ce9134e6825208a7e66d78e720202e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 13:27:01 2025 +0200

    remove unused import

commit cc6ce6c388d9693cf192c4bea733931fc8c31c37
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 13:13:03 2025 +0200

    add tests for new api route

commit 0d40a6230b548625482aa9f6831c93ed9d62533a
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jun 18 15:50:22 2025 -0500

    update social tests

commit 79177d6754589b9e54682af8a531b63f60215dab
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 10:21:51 2025 +0200

    new api route to check if an email is available

commit 11df73fe07eeb730c2a95593e18e14a931f52429
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Apr 9 10:21:39 2025 +0200

    Add field to not register social account when called

* Squashed commit of the following:

commit b8a2f0b8ee
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 20 17:18:30 2024 -0400

    update privacy policy

* fix(vite): import syntax

* feat(auth): precheck on defaulted username

* feat(auth): add store action for check-email

* feat(auth): check email before proceeding

* WIP(login): refactor username screen

* WIP(auth): complete login/reg flow

* fix(auth): filter out expected 404

* fix(login): use allowRegister with Apple
and add z-index to component

* fix(login): style corrections and email passthru

* Fix edgecase

Signed-off-by: Kalista Payne <sabrecat@gmail.com>

* fix(auth): correct error behaviors

* fix(auth): rewire Apple auth

* make check-email check for restricted domains

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(signup): all the style

* fix(express): return when responding

* fix(error): reduce specificity for restricted domain issue

* fix apple auth

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(signup): change from blur to 500ms debounce

* fix(login): add missing 200 response in Apple flow

* fix(signup): more reconciliation with @phillipthelen's work

* fix(signup): now using token not code

* fix(reg): don't bail on Apple if we're allowing reg

* fix(auth): more reconciliation with @phillipthelen code

* feat(copy): privacy policy updates

* fix(copy): replace placeholder

* fix(vue): use Vite syntax for scss import

* fix(static): corrections to copy and css

* chore(style): remove excess whitespace

* use correct error

Signed-off-by: Kalista Payne <kalista@habitica.com>

* fix(layout): inputs, add privacy banner

* fix(login): button hover, more validation states

* fix(login): further layout and UX corrections

* fix(static): add back containing div for show/hide

* fix(apple): clean out Apple token

* fix(settings): only change preference on save

* fix(settings): correct save/cancel behavior

* fix(layout): consistent use of header/footer

* fix(layout): reposition mountains for reg/login/forgot

* fix(signup): partial rollback of /username route

* refactor(signup): move /username to page

* fix(apple): don't overwrite reg method

* fix(username): don't skip empty validation

* fix(input): don't show valid if no username

* fix(login): clean out Apple token if using another method

* fix(apple): possible race with token

* fix(tests): some housekeeping

* fix(config): copypasta

* fix(lint): various cleanup

* fix(lint): line squeeze

* fix(lint): one more v-for

* fix(groups): funnel invite flow to new username page

* Squashed commit of the following:

commit 3c5ba4bf24e4bb7996786520101f27ad66405bce
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 14:38:31 2025 -0500

    fix(privacy): update link ref

commit 9d216f623b
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 14:18:22 2025 -0500

    fix(privacy-tos): copy edits cont'd

commit d744f47140
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Aug 18 13:43:22 2025 -0500

    fix(privacy): copy edits and ToC reflow

commit 2c3c3fc9ce
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:46:24 2025 +0200

    lint

commit cf363034d5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:34:54 2025 +0200

    fix link

commit 3afacd2c05
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 18:34:42 2025 +0200

    add updated terms

commit 258b722499
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 17:58:42 2025 +0200

    put back button to show/hide third party info

commit 2992e0299b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Aug 18 17:58:32 2025 +0200

    minor edits

commit bb5e252299
Author: Kalista Payne <kalista@habitica.com>
Date:   Sun Aug 17 21:01:50 2025 -0500

    fix(privacy): update Section 3

commit c79af7baa8
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 15 17:28:49 2025 -0500

    fix(privacy): various copy edits

commit 100f2f4574
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 15 11:37:37 2025 +0200

    add newline

commit 11d1cfd0d9
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Aug 15 11:10:01 2025 +0200

    update privacy policy

commit 59b99badf3
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 8 14:04:19 2025 -0500

    5.38.2

commit 78daeb4191
Author: Kalista Payne <kalista@habitica.com>
Date:   Fri Aug 8 13:36:19 2025 -0500

    fix(apple): don't run auth middleware during redirect

commit 93f8d60903
Author: Weblate <noreply@weblate.org>
Date:   Fri Aug 8 10:12:25 2025 +0200

    Translated using Weblate (German)

    Currently translated at 99.4% (185 of 186 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (186 of 186 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (186 of 186 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (54 of 54 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (47 of 47 strings)

    Translated using Weblate (Dutch)

    Currently translated at 78.0% (2643 of 3385 strings)

    Translated using Weblate (Dutch)

    Currently translated at 40.8% (100 of 245 strings)

    Translated using Weblate (Polish)

    Currently translated at 89.9% (233 of 259 strings)

    Translated using Weblate (Dutch)

    Currently translated at 67.5% (175 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (110 of 110 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 20.8% (51 of 245 strings)

    Translated using Weblate (Turkish)

    Currently translated at 65.9% (60 of 91 strings)

    Translated using Weblate (Turkish)

    Currently translated at 65.9% (60 of 91 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 17.9% (44 of 245 strings)

    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Linsey Dunya Pastoor <sekai.creations@gmail.com>
    Co-authored-by: Mete Olmez <metezori27@gmail.com>
    Co-authored-by: Sefa Uğurlu <ugurlusefa2@gmail.com>
    Co-authored-by: Summer_GUI <heyang94@163.com>
    Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: innnko <ayakabooker@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/tr/
    Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/nl/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/pl/
    Translation: Habitica/Backgrounds
    Translation: Habitica/Challenge
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Contrib
    Translation: Habitica/Death
    Translation: Habitica/Defaulttasks
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Settings

commit eb16fec41e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Aug 6 22:08:07 2025 +0200

    Add interface to block ip-addresses or clients due to abuse (#15484)

    * Read IP blocks from database

    * begin building general blocking solution

    * add new frontend files

    * Add UI for managing blockers

    * correctly reset local data after creating blocker

    * Tweak wording

    * Add UI for managing blockers

    * restructure admin pages

    * improve test coverage

    * Improve blocker UI

    * add blocker to block emails from registration

    * lint fix

    * fix

    * lint fixes

    * fix import

    * add new permission for managing blockers

    * improve permission check

    * fix managing permissions from admin

    * improve navbar display for non fullAccess admin

    * update block error strings

    * lint fix

    * add option to errorHandler to skip logging

    * validate blocker value during input

    * improve blocker form display

    * chore(subproj): reconcile habitica-images

    * fix(scripts): use same Mongo version for dev/test

    * fix(whitespace): eof

    * documentation improvements

    * remove nconf import

    * remove old test

    ---------

    Co-authored-by: Kalista Payne <kalista@habitica.com>
    Co-authored-by: Kalista Payne <sabrecat@gmail.com>

commit 47d832bf12
Author: Fiz <34069775+Hafizzle@users.noreply.github.com>
Date:   Tue Aug 5 15:12:44 2025 -0500

    Add backend support for Hydra mount (#15482)

    * chore: update time travelers shop to display seasonal backgrounds

    * chore: update time travelers banner (note CSS borken rn)

    * chore: fix borken CSS and update logic in shop

    * chore: added isSubscribed function, not working

    * chore: isSubscribed working but no bg for subscribers

    * chore: logic and css updates

    * chore: update habitica-images

    * chore: add check for trinket

    * chore: more time traveler shop logicking

    * Add backend support for Hydra mount

    - Add Dragon-Hydra to special mounts in stable.js
      - Configure as contributor level 7 reward with canFind: true
      - Add GIF format support for mount sprites
      - Enable admin panel granting capability

    * Fix Vue template errors in timeTravelers component

    * Fix duplicate template block in timeTravelers component

    * add CSS for Hydra mount GIF sprites

    Added CSS rules for Mount_Head_Dragon-Hydra and Mount_Body_Dragon-Hydra GIF sprites

    * Remove the separate Hydra mount dimension declaration

    ---------

    Co-authored-by: CuriousMagpie <eilatan@gmail.com>

commit c03ab9855f
Author: Kalista Payne <kalista@habitica.com>
Date:   Tue Aug 5 14:31:05 2025 -0500

    5.38.1

commit 8f96b7b7fd
Author: Weblate <noreply@weblate.org>
Date:   Tue Aug 5 13:02:45 2025 +0200

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 17.1% (42 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 16.7% (41 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 16.3% (40 of 245 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.8% (425 of 430 strings)

    Translated using Weblate (French)

    Currently translated at 99.4% (184 of 185 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.9% (39 of 245 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.8% (3379 of 3385 strings)

    Translated using Weblate (Polish)

    Currently translated at 95.5% (128 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.7% (254 of 268 strings)

    Translated using Weblate (Polish)

    Currently translated at 94.0% (126 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.6% (424 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 98.3% (423 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (798 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.4% (797 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 90.6% (781 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.9% (3112 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.9% (3111 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.0% (174 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (8 of 8 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.5% (38 of 245 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.6% (3104 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 93.5% (173 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 99.6% (279 of 280 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Japanese)

    Currently translated at 89.2% (769 of 862 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 94.4% (253 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.8% (170 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 97.9% (421 of 430 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.6% (3104 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 93.6% (251 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 90.8% (168 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 82.4% (202 of 245 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 15.1% (37 of 245 strings)

    Translated using Weblate (French)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.3% (3092 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (248 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 92.5% (248 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Croatian)

    Currently translated at 100.0% (15 of 15 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (167 of 167 strings)

    Translated using Weblate (Korean)

    Currently translated at 22.8% (56 of 245 strings)

    Translated using Weblate (Korean)

    Currently translated at 47.7% (128 of 268 strings)

    Translated using Weblate (Croatian)

    Currently translated at 45.1% (121 of 268 strings)

    Translated using Weblate (Korean)

    Currently translated at 71.9% (620 of 862 strings)

    Translated using Weblate (Croatian)

    Currently translated at 70.6% (609 of 862 strings)

    Translated using Weblate (Croatian)

    Currently translated at 75.0% (6 of 8 strings)

    Translated using Weblate (Korean)

    Currently translated at 67.6% (291 of 430 strings)

    Translated using Weblate (Korean)

    Currently translated at 52.8% (1788 of 3385 strings)

    Translated using Weblate (Croatian)

    Currently translated at 50.3% (1706 of 3385 strings)

    Translated using Weblate (Croatian)

    Currently translated at 51.7% (134 of 259 strings)

    Translated using Weblate (Czech)

    Currently translated at 92.8% (130 of 140 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 86.9% (233 of 268 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (94 of 94 strings)

    Translated using Weblate (Danish)

    Currently translated at 92.1% (105 of 114 strings)

    Translated using Weblate (Czech)

    Currently translated at 89.4% (102 of 114 strings)

    Translated using Weblate (Czech)

    Currently translated at 83.5% (112 of 134 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 71.6% (308 of 430 strings)

    Translated using Weblate (Spanish (Latin America))

    Currently translated at 100.0% (245 of 245 strings)

    Translated using Weblate (Serbian)

    Currently translated at 84.4% (49 of 58 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 51.4% (144 of 280 strings)

    Translated using Weblate (Swedish)

    Currently translated at 66.5% (286 of 430 strings)

    Translated using Weblate (Serbian)

    Currently translated at 65.5% (282 of 430 strings)

    Translated using Weblate (Slovak)

    Currently translated at 65.5% (282 of 430 strings)

    Translated using Weblate (Romanian)

    Currently translated at 66.7% (287 of 430 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (430 of 430 strings)

    Translated using Weblate (Danish)

    Currently translated at 66.0% (284 of 430 strings)

    Translated using Weblate (Czech)

    Currently translated at 69.7% (300 of 430 strings)

    Translated using Weblate (Chinese (Simplified))

    Currently translated at 99.7% (3377 of 3385 strings)

    Translated using Weblate (Swedish)

    Currently translated at 54.1% (1834 of 3385 strings)

    Translated using Weblate (Serbian)

    Currently translated at 50.6% (1714 of 3385 strings)

    Translated using Weblate (Slovak)

    Currently translated at 50.0% (1695 of 3385 strings)

    Translated using Weblate (Romanian)

    Currently translated at 60.5% (2050 of 3385 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 38.4% (1301 of 3385 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Danish)

    Currently translated at 54.0% (1829 of 3385 strings)

    Translated using Weblate (Czech)

    Currently translated at 59.6% (2020 of 3385 strings)

    Translated using Weblate (Swedish)

    Currently translated at 75.6% (140 of 185 strings)

    Translated using Weblate (Serbian)

    Currently translated at 73.5% (136 of 185 strings)

    Translated using Weblate (Slovak)

    Currently translated at 84.8% (157 of 185 strings)

    Translated using Weblate (Romanian)

    Currently translated at 78.9% (146 of 185 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 82.1% (152 of 185 strings)

    Translated using Weblate (Italian)

    Currently translated at 91.8% (170 of 185 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (Danish)

    Currently translated at 77.2% (143 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.7% (242 of 245 strings)

    Translated using Weblate (Czech)

    Currently translated at 75.1% (139 of 185 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 74.5% (138 of 185 strings)

    Translated using Weblate (Czech)

    Currently translated at 8.1% (20 of 245 strings)

    Translated using Weblate (Swedish)

    Currently translated at 72.0% (621 of 862 strings)

    Translated using Weblate (Serbian)

    Currently translated at 65.1% (562 of 862 strings)

    Translated using Weblate (Slovak)

    Currently translated at 66.9% (577 of 862 strings)

    Translated using Weblate (Romanian)

    Currently translated at 77.7% (670 of 862 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 70.0% (604 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Italian)

    Currently translated at 86.8% (749 of 862 strings)

    Translated using Weblate (Indonesian)

    Currently translated at 86.0% (742 of 862 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 66.1% (570 of 862 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.0% (845 of 862 strings)

    Translated using Weblate (Danish)

    Currently translated at 69.9% (603 of 862 strings)

    Translated using Weblate (Czech)

    Currently translated at 69.7% (601 of 862 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 66.3% (572 of 862 strings)

    Translated using Weblate (Serbian)

    Currently translated at 74.0% (305 of 412 strings)

    Translated using Weblate (Turkish)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Danish)

    Currently translated at 90.0% (371 of 412 strings)

    Translated using Weblate (Ukrainian)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Swedish)

    Currently translated at 53.6% (139 of 259 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Danish)

    Currently translated at 62.1% (161 of 259 strings)

    Translated using Weblate (Bulgarian)

    Currently translated at 54.0% (140 of 259 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 82.8% (222 of 268 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 99.4% (184 of 185 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 98.3% (241 of 245 strings)

    Translated using Weblate (Japanese)

    Currently translated at 91.3% (3092 of 3385 strings)

    Translated using Weblate (Japanese)

    Currently translated at 88.4% (237 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (134 of 134 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Japanese)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (Japanese)

    Currently translated at 82.4% (202 of 245 strings)

    Translated using Weblate (English (United Kingdom))

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Japanese)

    Currently translated at 87.3% (234 of 268 strings)

    Translated using Weblate (Japanese)

    Currently translated at 86.4% (160 of 185 strings)

    Translated using Weblate (Japanese)

    Currently translated at 99.8% (913 of 914 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (259 of 259 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (3385 of 3385 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (914 of 914 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (268 of 268 strings)

    Translated using Weblate (Russian)

    Currently translated at 88.5% (248 of 280 strings)

    Translated using Weblate (Spanish)

    Currently translated at 99.8% (3379 of 3385 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (862 of 862 strings)

    Co-authored-by: Ayaka Booker <ayakabooker@gmail.com>
    Co-authored-by: Chaotic Lawful <habitica@eusebius.fr>
    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Lio Zam <zerofux@web.de>
    Co-authored-by: Mika <isekai.chr@gmail.com>
    Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
    Co-authored-by: Summer_GUI <heyang94@163.com>
    Co-authored-by: Vera <verasmolinap@gmail.com>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Zhi Hao Li <zhihaoli000@gmail.com>
    Co-authored-by: Zuz Q <zuzannakunik@gmail.com>
    Co-authored-by: innnko <ayakabooker@gmail.com>
    Co-authored-by: 吳昀錡 <J1120241@gm.fdhs.tyc.edu.tw>
    Co-authored-by: 潘致翰 <happyq0908@gmail.com>
    Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/
    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/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/content/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/death/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/fr/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/it/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/da/
    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/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/es_419/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/groups/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/
    Translate-URL: https://translate.habitica.com/projects/habitica/messages/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/overview/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/pets/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/quests/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/cs/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/id/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ro/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sk/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sr/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/bg/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/da/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/sv/
    Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/
    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/hr/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ko/
    Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
    Translate-URL: https://translate.habitica.com/projects/habitica/tasks/cs/
    Translation: Habitica/Achievements
    Translation: Habitica/Backgrounds
    Translation: Habitica/Character
    Translation: Habitica/Content
    Translation: Habitica/Death
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Groups
    Translation: Habitica/Limited
    Translation: Habitica/Messages
    Translation: Habitica/Npc
    Translation: Habitica/Overview
    Translation: Habitica/Pets
    Translation: Habitica/Quests
    Translation: Habitica/Questscontent
    Translation: Habitica/Settings
    Translation: Habitica/Subscriber
    Translation: Habitica/Tasks

commit 1dde2674f6
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Mon Jun 16 16:43:56 2025 -0500

    fix(content): don't filter out the thing we want

commit 76122a8889
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jun 4 14:28:27 2025 -0500

    fix(mobile): provide Challenge categories via API

commit 9e309a875e
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Jul 28 14:15:00 2025 -0500

    5.38.0

commit 09e3a394b8
Author: Kalista Payne <kalista@habitica.com>
Date:   Mon Jul 28 14:06:45 2025 -0500

    5.37.3

commit eba263360f
Author: Weblate <noreply@weblate.org>
Date:   Mon Jul 28 21:03:17 2025 +0200

    Translated using Weblate (German)

    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 (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (3377 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (243 of 243 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 98.6% (850 of 862 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.8% (3373 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.5% (3361 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 99.4% (3360 of 3377 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (185 of 185 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 67.1% (579 of 862 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (Polish)

    Currently translated at 100.0% (91 of 91 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (184 of 184 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (245 of 245 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (47 of 47 strings)

    Translated using Weblate (German)

    Currently translated at 100.0% (193 of 193 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 14.2% (35 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 13.8% (34 of 245 strings)

    Translated using Weblate (Chinese (Traditional))

    Currently translated at 13.0% (32 of 245 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 2.0% (5 of 245 strings)

    Translated using Weblate (Hebrew)

    Currently translated at 66.1% (570 of 862 strings)

    Translated using Weblate (Portuguese)

    Currently translated at 54.1% (1830 of 3377 strings)

    Co-authored-by: FingerTiao <787170918@qq.com>
    Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
    Co-authored-by: Jan Freihöfer <jan.stauch.is@gmail.com>
    Co-authored-by: Jonathan Niessen <37.friedrich@gmail.com>
    Co-authored-by: Karmelkowy <kicimeow.karmelio@gmail.com>
    Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
    Co-authored-by: Laura Fleckenstein <fleckenstein_laura@web.de>
    Co-authored-by: Omer I.S <omeritzicschwartz@gmail.com>
    Co-authored-by: Remigiusz Haziak <haziakremigiusz@gmail.com>
    Co-authored-by: Uwe B <hbtca@tunixgut.de>
    Co-authored-by: Weblate <noreply@weblate.org>
    Co-authored-by: Wellinton Cardoso <wmcardoso1@hotmail.com>
    Co-authored-by: cloudzzy <truskawka412@gmail.com>
    Co-authored-by: 吳昀錡 <J1120241@gm.fdhs.tyc.edu.tw>
    Translate-URL: https://translate.habitica.com/projects/habitica/character/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pl/
    Translate-URL: https://translate.habitica.com/projects/habitica/contrib/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
    Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
    Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/
    Translation: Habitica/Character
    Translation: Habitica/Communityguidelines
    Translation: Habitica/Contrib
    Translation: Habitica/Faq
    Translation: Habitica/Front
    Translation: Habitica/Gear
    Translation: Habitica/Generic
    Translation: Habitica/Npc
    Translation: Habitica/Questscontent

commit 9550eec718
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jul 28 16:50:38 2025 +0200

    Fix 500 when deleting a very old group plan account (#15481)

commit f267eb67e9
Author: Kalista Payne <kalista@habitica.com>
Date:   Tue Jul 29 14:12:35 2025 -0500

    fix(static): add back missing div for show/hide

commit 28251f42ab
Author: Kalista Payne <kalista@habitica.com>
Date:   Thu Jul 24 22:59:01 2025 -0500

    feat(privacy): preview page

* feat(privacy): respect Global Privacy Control

* fix(lint): remove unused component

* fix(test): test user opts in to tracking

* fix(test): add user pref to more contexts

* fix(test): final spot in api-unit

* fix(tests): update integrations

* chore(privacy): add paragraph to s1, retire separate preview pages

* fix(build): route copypasta

* fix(router): lingering dead import

---------

Signed-off-by: Kalista Payne <sabrecat@gmail.com>
Signed-off-by: Kalista Payne <kalista@habitica.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: CuriousMagpie <eilatan@gmail.com>
2025-08-22 10:08:33 -05:00
dependabot[bot]
16e1523b08 chore(deps): bump tar-fs from 2.1.1 to 2.1.3 (#15446)
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.1 to 2.1.3.
- [Commits](https://github.com/mafintosh/tar-fs/commits)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-21 17:09:23 -05:00
dependabot[bot]
0f06ec1ab8 chore(deps): bump brace-expansion in /website/client (#15461)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-21 17:09:13 -05:00
dependabot[bot]
641266122a chore(deps): bump on-headers, compression, cookie-session and morgan (#15477)
Bumps [on-headers](https://github.com/jshttp/on-headers), [compression](https://github.com/expressjs/compression), [cookie-session](https://github.com/expressjs/cookie-session) and [morgan](https://github.com/expressjs/morgan). These dependencies needed to be updated together.

Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `compression` from 1.7.4 to 1.8.1
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/1.7.4...v1.8.1)

Updates `cookie-session` from 2.0.0 to 2.1.1
- [Release notes](https://github.com/expressjs/cookie-session/releases)
- [Changelog](https://github.com/expressjs/cookie-session/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/cookie-session/compare/v2.0.0...v2.1.1)

Updates `morgan` from 1.10.0 to 1.10.1
- [Release notes](https://github.com/expressjs/morgan/releases)
- [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/morgan/compare/1.10.0...1.10.1)

---
updated-dependencies:
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: direct:production
- dependency-name: compression
  dependency-version: 1.8.1
  dependency-type: direct:production
- dependency-name: cookie-session
  dependency-version: 2.1.1
  dependency-type: direct:production
- dependency-name: morgan
  dependency-version: 1.10.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-21 16:45:01 -05:00
dependabot[bot]
5ba939ee9c chore(deps): bump form-data from 4.0.0 to 4.0.4 in /website/client (#15478)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.0 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.0...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-21 16:44:41 -05:00
Fiz
c979e568f1 Respect user language preference in content endpoint (#15485)
* Respect user language preference in content endpoint

Content API now returns data in user's preferred language when authenticated without language parameter. No breaking changes - existing clients unaffected.

* lint fix
2025-08-21 16:44:15 -05:00
315 changed files with 7105 additions and 4192 deletions

View File

@@ -8,18 +8,26 @@
"AMAZON_PAYMENTS_SELLER_ID": "SELLER_ID",
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
"AMPLITUDE_SECRET": "AMPLITUDE_SECRET",
"APPLE_AUTH_CLIENT_ID": "",
"APPLE_AUTH_KEY_ID": "",
"APPLE_AUTH_PRIVATE_KEY": "",
"APPLE_TEAM_ID": "",
"BASE_URL": "http://localhost:3000",
"BLOCKED_IPS": "",
"CONTENT_SWITCHOVER_TIME_OFFSET": 8,
"CRON_SAFE_MODE": "false",
"CRON_SEMI_SAFE_MODE": "false",
"DEBUG_ENABLED": "false",
"DISABLE_REQUEST_LOGGING": "true",
"EMAILS_COMMUNITY_MANAGER_EMAIL": "admin@habitica.com",
"EMAILS_PRESS_ENQUIRY_EMAIL": "admin@habitica.com",
"EMAILS_TECH_ASSISTANCE_EMAIL": "admin@habitica.com",
"EMAIL_SERVER_AUTH_PASSWORD": "password",
"EMAIL_SERVER_AUTH_USER": "user",
"EMAIL_SERVER_URL": "http://example.com",
"EMAILS_COMMUNITY_MANAGER_EMAIL": "admin@habitica.com",
"EMAILS_PRESS_ENQUIRY_EMAIL": "admin@habitica.com",
"EMAILS_TECH_ASSISTANCE_EMAIL": "admin@habitica.com",
"ENABLE_CONSOLE_LOGS_IN_PROD": "false",
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
"ENABLE_STACKDRIVER_TRACING": "false",
"FACEBOOK_KEY": "123456789012345",
"FACEBOOK_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"FLAG_REPORT_EMAIL": "email@example.com, email2@example.com",
@@ -29,15 +37,16 @@
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
"IGNORE_REDIRECT": "true",
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"LIVELINESS_PROBE_KEY": "",
"LOG_AMPLITUDE_EVENTS": "false",
"LOG_REQUESTS_EXCESSIVE_MODE": "false",
"LOGGLY_CLIENT_TOKEN": "token",
"LOGGLY_SUBDOMAIN": "example-subdomain",
"LOGGLY_TOKEN": "example-token",
"LOG_REQUESTS_EXCESSIVE_MODE": "false",
"MAINTENANCE_MODE": "false",
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
"MONGODB_POOL_SIZE": "10",
"MONGODB_SOCKET_TIMEOUT": "20000",
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
"NODE_ENV": "development",
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
@@ -55,44 +64,34 @@
"PLAY_API_REFRESH_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
"PORT": 3000,
"PUSH_CONFIGS_APN_ENABLED": "false",
"PUSH_CONFIGS_APN_KEY": "xxxxxxxxxx",
"PUSH_CONFIGS_APN_KEY_ID": "xxxxxxxxxx",
"PUSH_CONFIGS_APN_KEY": "xxxxxxxxxx",
"PUSH_CONFIGS_APN_TEAM_ID": "aaabbbcccd",
"PUSH_CONFIGS_FCM_SERVER_API_KEY": "aaabbbcccd",
"RATE_LIMITER_ENABLED": "false",
"REDIS_HOST": "aaabbbcccdddeeefff",
"REDIS_PASSWORD": "12345678",
"REDIS_PORT": "1234",
"S3_ACCESS_KEY_ID": "accessKeyId",
"S3_BUCKET": "bucket",
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
"SESSION_SECRET": "YOUR SECRET HERE",
"SESSION_SECRET_IV": "12345678912345678912345678912345",
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
"SESSION_SECRET": "YOUR SECRET HERE",
"SITE_HTTP_AUTH_ENABLED": "false",
"SITE_HTTP_AUTH_PASSWORDS": "password,wordpass,passkey",
"SITE_HTTP_AUTH_USERNAMES": "admin,tester,contributor",
"SKIP_SSL_CHECK_KEY": "key",
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
"SLACK_URL": "https://hooks.slack.com/services/some-url",
"SLOW_REQUEST_THRESHOLD": 1000,
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
"STRIPE_WEBHOOKS_ENDPOINT_SECRET": "111111",
"TRANSIFEX_SLACK_CHANNEL": "transifex",
"WEB_CONCURRENCY": 1,
"SKIP_SSL_CHECK_KEY": "key",
"ENABLE_STACKDRIVER_TRACING": "false",
"APPLE_AUTH_PRIVATE_KEY": "",
"APPLE_TEAM_ID": "",
"APPLE_AUTH_CLIENT_ID": "",
"APPLE_AUTH_KEY_ID": "",
"BLOCKED_IPS": "",
"LOG_AMPLITUDE_EVENTS": "false",
"RATE_LIMITER_ENABLED": "false",
"LIVELINESS_PROBE_KEY": "",
"REDIS_HOST": "aaabbbcccdddeeefff",
"REDIS_PORT": "1234",
"REDIS_PASSWORD": "12345678",
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
"TIME_TRAVEL_ENABLED": "false",
"DEBUG_ENABLED": "false",
"CONTENT_SWITCHOVER_TIME_OFFSET": 8,
"SLOW_REQUEST_THRESHOLD": 1000
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
"WEB_CONCURRENCY": 1
}

View File

@@ -3,7 +3,8 @@ import { v4 as uuid } from 'uuid';
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20181203_take_this';
const MIGRATION_NAME = 'YYYYMMDD_take_this';
const CHALLENGE_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
const progressCount = 1000;
let count = 0;
@@ -41,15 +42,15 @@ async function updateUser (user) {
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (push) {
return User.update({ _id: user._id }, { $set: set, $push: push }).exec();
return User.updateOne({ _id: user._id }, { $set: set, $push: push }).exec();
}
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 },
challenges: '00708425-d477-41a5-bf27-6270466e7976',
challenges: CHALLENGE_ID,
};
const fields = {

144
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "habitica",
"version": "5.39.0",
"version": "5.41.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habitica",
"version": "5.39.0",
"version": "5.41.3",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "^7.22.10",
@@ -24,8 +24,8 @@
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"bootstrap": "^4.6.2",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
"compression": "^1.8.1",
"cookie-session": "^2.1.1",
"coupon-code": "^0.4.5",
"csv-stringify": "^5.6.5",
"cwait": "^1.1.1",
@@ -56,10 +56,10 @@
"moment": "^2.29.4",
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
"mongoose": "^8.9.5",
"morgan": "^1.10.0",
"morgan": "^1.10.1",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
"on-headers": "^1.0.2",
"on-headers": "^1.1.0",
"passport": "^0.5.3",
"passport-facebook": "^3.0.0",
"passport-google-oauth2": "^0.2.0",
@@ -75,7 +75,6 @@
"sinon": "^15.2.0",
"stripe": "^12.18.0",
"superagent": "^8.1.2",
"universal-analytics": "^0.5.3",
"useragent": "^2.1.9",
"uuid": "^9.0.0",
"validator": "^13.11.0",
@@ -6087,9 +6086,10 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -6906,30 +6906,23 @@
}
},
"node_modules/compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
"compressible": "~2.0.16",
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/compression/node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/compression/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -6943,6 +6936,35 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/compression/node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -7113,13 +7135,14 @@
}
},
"node_modules/cookie-session": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.0.0.tgz",
"integrity": "sha512-hKvgoThbw00zQOleSlUr2qpvuNweoqBtxrmx0UFosx6AGi9lYtLoA+RbsvknrEX8Pr6MDbdWAb2j6SnMn+lPsg==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.1.1.tgz",
"integrity": "sha512-ji3kym/XZaFVew1+tIZk5ZLp9Z/fLv9rK1aZmpug0FsgE7Cu3ZDrUdRo7FT9vFjMYfNimrrUHJzywDwT7XEFlg==",
"license": "MIT",
"dependencies": {
"cookies": "0.8.0",
"cookies": "0.9.1",
"debug": "3.2.7",
"on-headers": "~1.0.2",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1"
},
"engines": {
@@ -7164,9 +7187,10 @@
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="
},
"node_modules/cookies": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz",
"integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==",
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
"integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"keygrip": "~1.1.0"
@@ -11425,9 +11449,10 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -14005,6 +14030,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
"integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
"license": "MIT",
"dependencies": {
"tsscmp": "1.0.6"
},
@@ -15519,15 +15545,16 @@
"dev": true
},
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
"integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
"license": "MIT",
"dependencies": {
"basic-auth": "~2.0.1",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-finished": "~2.3.0",
"on-headers": "~1.0.2"
"on-headers": "~1.1.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -16856,9 +16883,10 @@
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -20501,9 +20529,10 @@
}
},
"node_modules/tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
@@ -21043,6 +21072,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
"license": "MIT",
"engines": {
"node": ">=0.6.x"
}
@@ -21368,26 +21398,6 @@
"through2-filter": "^3.0.0"
}
},
"node_modules/universal-analytics": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.5.3.tgz",
"integrity": "sha512-HXSMyIcf2XTvwZ6ZZQLfxfViRm/yTGoRgDeTbojtq6rezeyKB0sTBcKH2fhddnteAHRcHiKgr/ACpbgjGOC6RQ==",
"dependencies": {
"debug": "^4.3.1",
"uuid": "^8.0.0"
},
"engines": {
"node": ">=12.18.2"
}
},
"node_modules/universal-analytics/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.39.0",
"version": "5.41.3",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -19,8 +19,8 @@
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"bootstrap": "^4.6.2",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
"compression": "^1.8.1",
"cookie-session": "^2.1.1",
"coupon-code": "^0.4.5",
"csv-stringify": "^5.6.5",
"cwait": "^1.1.1",
@@ -51,10 +51,10 @@
"moment": "^2.29.4",
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
"mongoose": "^8.9.5",
"morgan": "^1.10.0",
"morgan": "^1.10.1",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
"on-headers": "^1.0.2",
"on-headers": "^1.1.0",
"passport": "^0.5.3",
"passport-facebook": "^3.0.0",
"passport-google-oauth2": "^0.2.0",
@@ -70,7 +70,6 @@
"sinon": "^15.2.0",
"stripe": "^12.18.0",
"superagent": "^8.1.2",
"universal-analytics": "^0.5.3",
"useragent": "^2.1.9",
"uuid": "^9.0.0",
"validator": "^13.11.0",
@@ -106,8 +105,8 @@
"start": "node --watch ./website/server/index.js",
"start:simple": "node ./website/server/index.js",
"debug": "node --watch --inspect ./website/server/index.js",
"mongo:dev": "run-rs -v 7.0.23 -l ubuntu2404 --keep --dbpath mongodb-data --number 1 --quiet",
"mongo:test": "run-rs -v 7.0.23 -l ubuntu2404 --keep --dbpath mongodb-data-testing --number 1 --quiet",
"mongo:dev": "run-rs -v 7.0.23 -l ubuntu2204 --keep --dbpath mongodb-data --number 1 --quiet",
"mongo:test": "run-rs -v 7.0.23 -l ubuntu2204 --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"

View File

@@ -1,15 +1,11 @@
/* eslint-disable camelcase */
import nconf from 'nconf';
import Amplitude from 'amplitude';
import { Visitor } from 'universal-analytics';
import * as analyticsService from '../../../../website/server/libs/analyticsService';
describe('analyticsService', () => {
beforeEach(() => {
sandbox.stub(Amplitude.prototype, 'track').returns(Promise.resolve());
sandbox.stub(Visitor.prototype, 'event');
sandbox.stub(Visitor.prototype, 'transaction');
});
afterEach(() => {
@@ -37,8 +33,6 @@ describe('analyticsService', () => {
data;
beforeEach(() => {
Visitor.prototype.event.yields();
eventType = 'Cron';
data = {
category: 'behavior',
@@ -49,6 +43,11 @@ describe('analyticsService', () => {
'x-client': 'habitica-web',
'user-agent': '',
},
user: {
preferences: {
analyticsConsent: true,
},
},
};
});
@@ -295,6 +294,9 @@ describe('analyticsService', () => {
rewards: [{ _id: 'reward' }],
balance: 12,
loginIncentives: 1,
preferences: {
analyticsConsent: true,
},
};
data.user = user;
@@ -326,37 +328,12 @@ describe('analyticsService', () => {
});
});
});
context('GA', () => {
it('calls out to GA', () => analyticsService.track(eventType, data)
.then(() => {
expect(Visitor.prototype.event).to.be.calledOnce;
}));
it('sends details about event', () => analyticsService.track(eventType, data)
.then(() => {
expect(Visitor.prototype.event).to.be.calledWith({
ea: 'Cron',
ec: 'behavior',
});
}));
});
});
describe('#trackPurchase', () => {
let data; let
itemSpy;
let data;
beforeEach(() => {
Visitor.prototype.event.yields();
itemSpy = sandbox.stub().returnsThis();
Visitor.prototype.transaction.returns({
item: itemSpy,
send: sandbox.stub().yields(),
});
data = {
uuid: 'user-id',
sku: 'paypal-checkout',
@@ -370,6 +347,11 @@ describe('analyticsService', () => {
'x-client': 'habitica-web',
'user-agent': '',
},
user: {
preferences: {
analyticsConsent: true,
},
},
};
});
@@ -533,6 +515,9 @@ describe('analyticsService', () => {
dailys: [{ _id: 'daily' }],
todos: [{ _id: 'todo' }],
rewards: [{ _id: 'reward' }],
preferences: {
analyticsConsent: true,
},
};
data.user = user;
@@ -561,26 +546,6 @@ describe('analyticsService', () => {
});
});
});
context('GA', () => {
it('calls out to GA', () => analyticsService.trackPurchase(data)
.then(() => {
expect(Visitor.prototype.event).to.be.calledOnce;
expect(Visitor.prototype.transaction).to.be.calledOnce;
}));
it('sends details about purchase', () => analyticsService.trackPurchase(data)
.then(() => {
expect(Visitor.prototype.event).to.be.calledWith({
ea: 'checkout',
ec: 'commerce',
el: 'PayPal',
ev: 8,
});
expect(Visitor.prototype.transaction).to.be.calledWith('user-id', 8);
expect(itemSpy).to.be.calledWith(8, 1, 'paypal-checkout', 'Gems', 'checkout');
}));
});
});
describe('mockAnalyticsService', () => {

View File

@@ -34,6 +34,7 @@ describe('bug-report', () => {
emailData: {
BROWSER_UA: userAgent,
REPORT_MSG: userMessage,
USER_ANALYTICS: undefined,
USER_CLASS: 'warrior',
USER_CONSECUTIVE_MONTHS: 0,
USER_COSTUME: 'false',

View File

@@ -150,7 +150,7 @@ describe('emails', () => {
sendTxn(mailingInfo, emailType);
expect(got.post).to.be.called;
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
expect(got.post).to.be.calledWith('http://example.com/job', sinon.match({
json: {
data: {
emailType: sinon.match.same(emailType),
@@ -234,7 +234,7 @@ describe('emails', () => {
sendTxn(mailingInfo, emailType);
expect(got.post).to.be.called;
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
expect(got.post).to.be.calledWith('http://example.com/job', sinon.match({
json: {
data: {
emailType: sinon.match.same(emailType),
@@ -254,7 +254,7 @@ describe('emails', () => {
sendTxn(mailingInfo, emailType, variables);
expect(got.post).to.be.called;
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
expect(got.post).to.be.calledWith('http://example.com/job', sinon.match({
json: {
data: {
variables: sinon.match(value => value[0].name === 'BASE_URL', 'matches variables'),

View File

@@ -47,6 +47,12 @@ describe('highlightMentions', () => {
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
});
it('highlights users with case-insensitive matching', async () => {
const text = '@USER: message @User2 @USER3';
const result = await highlightMentions(text);
expect(result[0]).to.equal('[@USER](/profile/111): message [@User2](/profile/222) [@USER3](/profile/333)');
});
it('doesn\'t highlight nonexisting users', async () => {
const text = '@nouser message';
const result = await highlightMentions(text);

View File

@@ -12,11 +12,33 @@ const { i18n } = common;
describe('Apple Payments', () => {
const subKey = 'basic_3mo';
let iapSetupStub;
let iapValidateStub;
let iapIsValidatedStub;
let iapIsCanceledStub;
let iapIsExpiredStub;
let paymentBuySkuStub;
let iapGetPurchaseDataStub;
let validateGiftMessageStub;
let paymentsCreateSubscritionStub;
beforeEach(() => {
iapSetupStub = sinon.stub(iap, 'setup').resolves();
iapValidateStub = sinon.stub(iap, 'validate').resolves({});
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
iap.isExpired.restore();
iap.isCanceled.restore();
iap.getPurchaseData.restore();
});
describe('verifyPurchase', () => {
let sku; let user; let token; let receipt; let
headers;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuySkuStub; let
iapGetPurchaseDataStub; let validateGiftMessageStub;
beforeEach(() => {
token = 'testToken';
@@ -25,13 +47,9 @@ describe('Apple Payments', () => {
receipt = `{"token": "${token}", "productId": "${sku}"}`;
headers = {};
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub = sinon.stub(iap, 'validate')
.resolves({});
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
sinon.stub(iap, 'isExpired').returns(false);
sinon.stub(iap, 'isCanceled').returns(false);
iapIsCanceledStub = sinon.stub(iap, 'isCanceled').returns(false);
iapIsExpiredStub = sinon.stub(iap, 'isExpired').returns(false);
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
productId: 'com.habitrpg.ios.Habitica.21gems',
@@ -42,12 +60,6 @@ describe('Apple Payments', () => {
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
iap.isExpired.restore();
iap.isCanceled.restore();
iap.getPurchaseData.restore();
payments.buySkuItem.restore();
gems.validateGiftMessage.restore();
});
@@ -209,9 +221,6 @@ describe('Apple Payments', () => {
describe('subscribe', () => {
let sub; let sku; let user; let token; let receipt; let headers; let
nextPaymentProcessing;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub;
let paymentsCreateSubscritionStub; let
iapGetPurchaseDataStub;
beforeEach(() => {
sub = common.content.subscriptionBlocks[subKey];
@@ -223,12 +232,10 @@ describe('Apple Payments', () => {
nextPaymentProcessing = moment.utc().add({ days: 2 });
user = new User();
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub = sinon.stub(iap, 'validate')
.resolves({});
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true);
iapIsCanceledStub = sinon.stub(iap, 'isCanceled').returns(false);
iapIsExpiredStub = sinon.stub(iap, 'isExpired').returns(false);
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
@@ -250,10 +257,6 @@ describe('Apple Payments', () => {
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
iap.getPurchaseData.restore();
if (payments.createSubscription.restore) payments.createSubscription.restore();
});
@@ -270,6 +273,29 @@ describe('Apple Payments', () => {
});
});
it('should throw an error if no active subscription is found', async () => {
iap.isCanceled.restore();
iapIsCanceledStub = sinon.stub(iap, 'isCanceled')
.returns(true);
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().add({ day: -2 }).toDate(),
purchaseDate: new Date(),
productId: 'subscription1month',
transactionId: token,
originalTransactionId: token,
}]);
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: applePayments.constants.RESPONSE_NO_ITEM_PURCHASED,
});
});
const subOptions = [
{
sku: 'subscription1month',
@@ -574,8 +600,7 @@ describe('Apple Payments', () => {
describe('cancelSubscribe ', () => {
let user; let token; let receipt; let headers; let customerId; let
expirationDate;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let iapGetPurchaseDataStub; let
paymentCancelSubscriptionSpy;
let paymentCancelSubscriptionSpy;
beforeEach(async () => {
token = 'test-token';
@@ -584,8 +609,7 @@ describe('Apple Payments', () => {
customerId = 'test-customerId';
expirationDate = moment.utc();
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub.restore();
iapValidateStub = sinon.stub(iap, 'validate')
.resolves({
expirationDate,
@@ -593,8 +617,8 @@ describe('Apple Payments', () => {
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ expirationDate: expirationDate.toDate() }]);
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
sinon.stub(iap, 'isCanceled').returns(false);
sinon.stub(iap, 'isExpired').returns(true);
iapIsCanceledStub = sinon.stub(iap, 'isCanceled').returns(false);
iapIsExpiredStub = sinon.stub(iap, 'isExpired').returns(true);
user = new User();
user.profile.name = 'sender';
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
@@ -606,13 +630,7 @@ describe('Apple Payments', () => {
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
iap.isExpired.restore();
iap.isCanceled.restore();
iap.getPurchaseData.restore();
payments.cancelSubscription.restore();
paymentCancelSubscriptionSpy.restore();
});
it('should throw an error if we are missing a subscription', async () => {
@@ -695,6 +713,8 @@ describe('Apple Payments', () => {
expect(iapIsValidatedStub).to.be.calledWith({
expirationDate,
});
expect(iapIsCanceledStub).to.be.calledOnce;
expect(iapIsExpiredStub).to.be.calledOnce;
expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(paymentCancelSubscriptionSpy).to.be.calledOnce;

View File

@@ -11,12 +11,36 @@ const { i18n } = common;
describe('Google Payments', () => {
const subKey = 'basic_3mo';
let iapSetupStub;
let iapValidateStub;
let iapIsValidatedStub;
let paymentBuySkuStub;
let validateGiftMessageStub;
beforeEach(() => {
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true);
sinon.stub(iap, 'isCanceled').returns(false);
sinon.stub(iap, 'isExpired').returns(false);
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
iap.isCanceled.restore();
iap.isExpired.restore();
payments.buySkuItem.restore();
gems.validateGiftMessage.restore();
});
describe('verifyPurchase', () => {
let sku; let user; let token; let receipt; let signature; let
headers;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
paymentBuySkuStub; let validateGiftMessageStub;
beforeEach(() => {
sku = 'com.habitrpg.android.habitica.iap.21gems';
@@ -25,21 +49,7 @@ describe('Google Payments', () => {
signature = '';
headers = {};
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub = sinon.stub(iap, 'validate').resolves({ productId: sku });
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true);
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
payments.buySkuItem.restore();
gems.validateGiftMessage.restore();
});
it('should throw an error if receipt is invalid', async () => {
@@ -160,8 +170,7 @@ describe('Google Payments', () => {
describe('subscribe', () => {
let sub; let sku; let user; let token; let receipt; let signature; let headers; let
nextPaymentProcessing;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
paymentsCreateSubscritionStub;
let paymentsCreateSubscritionStub;
beforeEach(() => {
sub = common.content.subscriptionBlocks[subKey];
@@ -173,19 +182,12 @@ describe('Google Payments', () => {
signature = '';
nextPaymentProcessing = moment.utc().add({ days: 2 });
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub = sinon.stub(iap, 'validate')
.resolves({});
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true);
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
payments.createSubscription.restore();
});
@@ -243,7 +245,7 @@ describe('Google Payments', () => {
describe('cancelSubscribe ', () => {
let user; let token; let receipt; let signature; let headers; let customerId; let
expirationDate;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let iapGetPurchaseDataStub; let
let iapGetPurchaseDataStub; let
paymentCancelSubscriptionSpy;
beforeEach(async () => {
@@ -253,17 +255,12 @@ describe('Google Payments', () => {
signature = '';
customerId = 'test-customerId';
expirationDate = moment.utc();
iapSetupStub = sinon.stub(iap, 'setup')
.resolves();
iapValidateStub = sinon.stub(iap, 'validate')
.resolves({
expirationDate,
});
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ expirationDate: expirationDate.toDate(), autoRenewing: false }]);
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true);
user = new User();
user.profile.name = 'sender';
@@ -276,9 +273,6 @@ describe('Google Payments', () => {
});
afterEach(() => {
iap.setup.restore();
iap.validate.restore();
iap.isValidated.restore();
iap.getPurchaseData.restore();
payments.cancelSubscription.restore();
});
@@ -308,6 +302,8 @@ describe('Google Payments', () => {
});
it('should cancel a user subscription', async () => {
iap.isCanceled.restore();
iap.isCanceled = sinon.stub(iap, 'isCanceled').returns(true);
await googlePayments.cancelSubscribe(user, headers);
expect(iapSetupStub).to.be.calledOnce;
@@ -332,11 +328,20 @@ describe('Google Payments', () => {
});
it('should cancel a user subscription with multiple inactive subscriptions', async () => {
iap.isCanceled.restore();
iap.isCanceled = sinon.stub(iap, 'isCanceled').returns(true);
const laterDate = moment.utc().add(7, 'days');
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ expirationDate, autoRenewing: false },
{ expirationDate: laterDate, autoRenewing: false },
.returns([{
startTimeMillis: expirationDate.valueOf(),
expirationDate,
autoRenewing: false,
}, {
startTimeMillis: laterDate.valueOf(),
expirationDate: laterDate,
autoRenewing: false,
},
]);
await googlePayments.cancelSubscribe(user, headers);
@@ -365,7 +370,12 @@ describe('Google Payments', () => {
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ autoRenewing: true }]);
await googlePayments.cancelSubscribe(user, headers);
await expect(googlePayments.cancelSubscribe(user, headers))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: googlePayments.constants.RESPONSE_STILL_VALID,
});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
@@ -388,8 +398,12 @@ describe('Google Payments', () => {
.returns([{ expirationDate, autoRenewing: false },
{ autoRenewing: true },
{ expirationDate, autoRenewing: false }]);
await googlePayments.cancelSubscribe(user, headers);
await expect(googlePayments.cancelSubscribe(user, headers))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: googlePayments.constants.RESPONSE_STILL_VALID,
});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {

View File

@@ -238,6 +238,18 @@ describe('POST /chat', () => {
expect(groupMessages[0].id).to.exist;
});
it('creates a chat with case-insensitive mentions', async () => {
const originalUsername = member.auth.local.username;
const uppercaseUsername = originalUsername.toUpperCase();
const messageWithMentions = `hi @${uppercaseUsername}`;
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: messageWithMentions });
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
expect(newMessage.message.id).to.exist;
expect(newMessage.message.text).to.include(`[@${uppercaseUsername}](/profile/${member._id})`);
expect(groupMessages[0].id).to.exist;
});
it('creates a chat with a max length of 3000 chars', async () => {
const veryLongMessage = `
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.

View File

@@ -1,6 +1,7 @@
import {
requester,
translate as t,
generateUser,
} from '../../../../helpers/api-integration/v3';
import i18n from '../../../../../website/common/script/i18n';
@@ -56,4 +57,28 @@ describe('GET /content', () => {
const res = await requester().get('/content?filter=backgroundsFlat,invalid');
expect(res).to.not.have.property('backgroundsFlat');
});
describe('authenticated user', () => {
let user;
it('returns content in user\'s preferred language when no language parameter is provided', async () => {
user = await generateUser({ 'preferences.language': 'de' });
const res = await user.get('/content');
expect(res).to.have.nested.property('backgrounds.backgrounds062014.beach');
expect(res.backgrounds.backgrounds062014.beach.text).to.equal(i18n.t('backgroundBeachText', 'de'));
});
it('respects language parameter over user\'s preferred language', async () => {
user = await generateUser({ 'preferences.language': 'de' });
const res = await user.get('/content?language=fr');
expect(res).to.have.nested.property('backgrounds.backgrounds062014.beach');
expect(res.backgrounds.backgrounds062014.beach.text).to.equal(i18n.t('backgroundBeachText', 'fr'));
});
it('falls back to English if user\'s preferred language is invalid', async () => {
user = await generateUser({ 'preferences.language': 'invalid_lang' });
const res = await user.get('/content');
expect(res).to.have.nested.property('backgrounds.backgrounds062014.beach');
expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText'));
});
});
});

View File

@@ -25,7 +25,7 @@ describe('GET /user/auth/apple', () => {
});
it('registers a new user', async () => {
const response = await api.get(appleEndpoint);
const response = await api.get(`${appleEndpoint}?allowRegister=true`);
expect(response.apiToken).to.exist;
expect(response.id).to.exist;
@@ -35,7 +35,7 @@ describe('GET /user/auth/apple', () => {
});
it('logs an existing user in', async () => {
const registerResponse = await api.get(appleEndpoint);
const registerResponse = await api.get(`${appleEndpoint}?allowRegister=true`);
const response = await api.get(appleEndpoint);

View File

@@ -44,7 +44,7 @@ describe('POST /user/auth/local/login', () => {
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id, username: user.auth.local.username }),
});
});

View File

@@ -6,6 +6,7 @@ import {
translate as t,
getProperty,
} from '../../../../../helpers/api-integration/v3';
import apiErrorMessages from '../../../../../../website/common/script/errors/apiErrorMessages';
describe('POST /user/auth/social', () => {
let api;
@@ -64,6 +65,18 @@ describe('POST /user/auth/social', () => {
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
});
it('fails if allowRegister is false and user does not exist', async () => {
await expect(api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
network,
allowRegister: false,
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: `${apiErrorMessages.socialFlowUserNotFound} ${user.auth.local.username}+google@example.com`,
});
});
it('logs an existing user in', async () => {
const registerResponse = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
@@ -131,6 +144,36 @@ describe('POST /user/auth/social', () => {
expect(response.newUser).to.be.false;
});
it('logs an existing user into their social account if allowRegister is false', async () => {
const registerResponse = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
network,
});
expect(registerResponse.newUser).to.be.true;
// This is important for existing accounts before the new social handling
passport._strategies.google.userProfile.restore();
const expectedResult = {
id: randomGoogleId,
displayName: 'a google user',
emails: [
{ value: user.auth.local.email },
],
};
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
const response = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
network,
allowRegister: false,
});
expect(response.apiToken).to.eql(registerResponse.apiToken);
expect(response.id).to.eql(registerResponse.id);
expect(response.apiToken).not.to.eql(user.apiToken);
expect(response.id).not.to.eql(user._id);
expect(response.newUser).to.be.false;
});
it('add social auth to an existing user', async () => {
const response = await user.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase

View File

@@ -0,0 +1,66 @@
import {
translate as t,
requester,
generateUser,
} from '../../../../helpers/api-integration/v4';
const ENDPOINT = '/user/auth/check-email';
describe('POST /user/auth/check-email', () => {
const email = 'SOmE-nEw-emAIl_2@example.net';
let api;
beforeEach(async () => {
api = requester();
});
it('returns email if it is not used yet', async () => {
const response = await api.post(ENDPOINT, {
email,
});
expect(response.email).to.eql(email);
expect(response.valid).to.be.true;
});
it('rejects if email is not provided', async () => {
await expect(api.post(ENDPOINT, {
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid request parameters.',
});
});
it('rejects if email is already taken', async () => {
const user = await generateUser();
const response = await api.post(ENDPOINT, {
email: user.auth.local.email,
});
expect(response).to.eql({
valid: false,
email: user.auth.local.email,
error: t('cannotFulfillReq'),
});
});
it('rejects if casing is different', async () => {
const user = await generateUser();
const response = await api.post(ENDPOINT, {
email: user.auth.local.email.toUpperCase(),
});
expect(response).to.eql({
valid: false,
email: user.auth.local.email.toUpperCase(),
error: t('cannotFulfillReq'),
});
});
it('rejects if email uses restricted domain', async () => {
const response = await api.post(ENDPOINT, {
email: 'fake@habitica.com',
});
expect(response.valid).to.be.false;
});
});

View File

@@ -37,7 +37,7 @@
"timers-browserify": "^2.0.12",
"uuid": "^9.0.1",
"validator": "^13.9.0",
"vite": "^6.0.0",
"vite": "^6.3.6",
"vite-plugin-compression2": "^1.3.3",
"vue": "^2.7.10",
"vue-fragment": "^1.6.0",
@@ -3393,9 +3393,10 @@
"integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -4024,13 +4025,15 @@
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
"integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.2",
"has-tostringtag": "^1.0.0",
"hasown": "^2.0.0"
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -5081,12 +5084,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -5116,6 +5122,20 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -5364,11 +5384,12 @@
}
},
"node_modules/has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.2"
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
@@ -7113,6 +7134,21 @@
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
@@ -8515,9 +8551,10 @@
}
},
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",

View File

@@ -41,7 +41,7 @@
"timers-browserify": "^2.0.12",
"uuid": "^9.0.1",
"validator": "^13.9.0",
"vite": "^6.0.0",
"vite": "^6.3.6",
"vite-plugin-compression2": "^1.3.3",
"vue": "^2.7.10",
"vue-fragment": "^1.6.0",

View File

@@ -108,7 +108,6 @@
<script>
import axios from 'axios';
import * as Analytics from '@/libs/analytics';
import { mapState } from '@/libs/store';
import snackbars from '@/components/snackbars/notifications';
import { LOCALSTORAGE_AUTH_KEY } from '@/libs/auth';
@@ -150,10 +149,6 @@ export default {
this.hideLoadingScreen();
}
});
this.$nextTick(() => {
// Load external scripts after the app has been rendered
Analytics.load();
});
axios.interceptors.response.use(response => { // Set up Response interceptors
// Verify that the user was not updated from another browser/app/client
@@ -208,17 +203,32 @@ export default {
return response;
}, error => { // Set up Error interceptors
if (!error.response) {
return Promise.reject(error);
}
if (error.response.status >= 400) {
const isBanned = this.checkForBannedUser(error);
if (isBanned === true) return null; // eslint-disable-line consistent-return
// Don't show errors from getting user details. These users have delete their account,
// Don't show errors from getting user details. These users have deleted their account,
// but their chat message still exists.
const configExists = Boolean(error.response) && Boolean(error.response.config);
if (configExists && error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v4/members/') !== -1) {
// @TODO: We resolve the promise because we need our caching to cache this user as tried
// Chat paging should help this, but maybe we can also find another solution..
return Promise.resolve(error);
if (configExists) {
if (error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v4/members/') !== -1) {
// @TODO: We resolve the promise because we need our caching to cache this user as tried
// Chat paging should help this, but maybe we can also find another solution..
return Promise.resolve(error);
}
// Also, a 404 occurs during routine attempt to log in with social,
// when we check for account already existing.
if (error.response.config.method === 'post' && (error.response.config.url.indexOf('/api/v4/user/auth/social') !== -1
|| error.response.config.url.indexOf('/api/v4/user/auth/apple') !== -1)) {
const socialEmail = error.response.data.message.split(': ')[1];
if (socialEmail) {
window.sessionStorage.setItem('social-email', socialEmail);
}
return Promise.resolve(error);
}
}
const errorData = error.response.data;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,9 @@
<svg width="378" height="176" viewBox="0 0 378 176" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0H378V174C378 175.105 377.105 176 376 176H1.99999C0.895423 176 0 175.105 0 174V0Z" fill="url(#paint0_linear_2257_239)"/>
<defs>
<linearGradient id="paint0_linear_2257_239" x1="378" y1="0" x2="0" y2="0" gradientUnits="userSpaceOnUse">
<stop stop-color="#925CF3"/>
<stop offset="1" stop-color="#34B5C1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 448 B

View File

@@ -0,0 +1,37 @@
<svg width="48" height="96" viewBox="0 0 48 96" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.10104 12.0483C-2.82088 9.43721 -3.53422 6.57214 -5.6115 5.24584C-7.68877 3.91954 -9.89543 4.92709 -10.1422 6.808C-10.3891 8.68891 -9.06061 9.83066 -4.97737 13.9337C-3.81821 15.0985 -3.3812 14.6594 -3.10104 12.0483Z" stroke="#FFA624" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.34089 15.2054C4.45116 13.6561 7.27707 12.8443 9.45877 13.9889C11.6405 15.1334 11.8754 17.5575 10.3778 18.7127C8.88016 19.868 7.23193 19.2828 1.65411 17.781C0.0706697 17.3546 0.230624 16.7548 2.34089 15.2054Z" stroke="#FFBE5D" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.549002 12.0098C-3.61871 9.59194 -3.87667 15.8322 -2.20457 16.8023C-0.532473 17.7724 4.71671 14.4277 0.549002 12.0098Z" fill="#EE9109"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-1.76917 16.0445L13.637 24.9825L9.18965 32.7229L-6.21656 23.785L-1.76917 16.0445Z" fill="#F8F9F9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-6.90457 13.0652L3.36623 19.0238L-1.08116 26.7643L-11.352 20.8057L-6.90457 13.0652Z" fill="#FFBE5D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-1.76917 16.0445L3.36623 19.0238L1.88377 21.604L-3.25163 18.6247L-1.76917 16.0445Z" fill="#FFA624"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-6.21656 23.785L6.62195 31.2333L-3.75529 49.2944L-16.5938 41.8461L-6.21656 23.785Z" fill="#F8F9F9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.64886 25.2747L6.62195 31.2333L5.13948 33.8134L-5.13132 27.8548L-3.64886 25.2747Z" fill="#DDF3F3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.401307 24.1842L10.6721 30.1428L9.18965 32.7229L-1.08116 26.7643L0.401307 24.1842Z" fill="#DDF3F3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.7924 38.4607L17.9387 42.0519L21.31 40.5834L24.8838 41.4413L23.4225 38.0537L24.2762 34.4625L20.9049 35.9309L17.3311 35.0731L18.7924 38.4607Z" fill="white" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.93867 71.2331L-4.79238 74.8243L-1.42111 73.3559L2.15271 74.2137L0.691383 70.8261L1.54509 67.2349L-1.82618 68.7033L-5.4 67.8455L-3.93867 71.2331Z" fill="white" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.8949 25.3807L35.0583 29.8802L37.9424 26.2452L42.4202 25.0761L38.8028 22.178L37.6393 17.6786L34.7552 21.3135L30.2775 22.4826L33.8949 25.3807Z" fill="white" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.2596 71.999L40.579 68.1435L45.9507 88.2881L31.6312 92.1436L26.2596 71.999Z" fill="#F8F9F9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9401 75.8545L26.2589 71.9966L31.6273 92.1421L17.3084 96L11.9401 75.8545Z" fill="#DDF3F3"/>
<rect width="2.96589" height="20.8485" transform="matrix(0.965611 -0.25999 0.257652 0.966238 23.3957 72.7701)" fill="#FFA624"/>
<rect width="2.96589" height="20.8485" transform="matrix(0.965611 -0.25999 0.257652 0.966238 26.2596 71.999)" fill="#FFBE5D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.9999 90.0369L30.8638 89.2658L31.6312 92.1436L28.7673 92.9147L27.9999 90.0369Z" fill="#EE9109"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3957 72.7701L26.2596 71.999L27.0269 74.8768L24.163 75.6479L23.3957 72.7701Z" fill="#EE9109"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9401 75.8545L23.3951 72.7682L24.162 75.6461L12.707 78.7325L11.9401 75.8545Z" fill="#C1E9E9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5443 93.1213L27.9999 90.0369L28.7673 92.9147L17.3117 95.9991L16.5443 93.1213Z" fill="#C1E9E9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.1235 71.2279L40.579 68.1435L41.3464 71.0213L29.8908 74.1057L29.1235 71.2279Z" fill="#DDF3F3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.7277 88.4947L45.1833 85.4103L45.9507 88.2881L34.4951 91.3725L33.7277 88.4947Z" fill="#DDF3F3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.8638 89.2658L33.7277 88.4947L34.4951 91.3725L31.6312 92.1436L30.8638 89.2658Z" fill="#FFA624"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.2596 71.999L29.1235 71.2279L29.8908 74.1057L27.0269 74.8768L26.2596 71.999Z" fill="#FFA624"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.5224 56.3076C25.8087 53.7812 24.0792 51.3933 21.6588 50.9455C19.2383 50.4977 17.5679 52.2625 18.0403 54.0994C18.5126 55.9363 20.17 56.4948 25.4855 58.7621C26.9945 59.4057 27.236 58.834 26.5224 56.3076Z" stroke="#FFA624" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.745 57.1864C34.124 54.9555 36.4415 53.1391 38.8911 53.3791C41.3406 53.6191 42.4621 55.7782 41.5042 57.413C40.5463 59.0479 38.7999 59.1258 33.0684 59.8329C31.4413 60.0337 31.366 59.4173 32.745 57.1864Z" stroke="#FFBE5D" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.8923 54.898C25.1267 54.225 27.2139 60.108 29.1258 60.378C31.0378 60.648 34.6579 55.571 29.8923 54.898Z" fill="#EE9109"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.247 59.5115L46.8635 61.9994L45.6255 70.8503L28.0091 68.3625L29.247 59.5115Z" fill="#F8F9F9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.6306 57.0236L29.247 59.5114L28.0091 68.3624L10.3927 65.8745L11.6306 57.0236Z" fill="#DDF3F3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L35.1192 60.3408L33.8813 69.1917L22.137 67.5332L23.3749 58.6822Z" fill="#FFBE5D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L29.247 59.5115L28.0091 68.3625L22.137 67.5332L23.3749 58.6822Z" fill="#FFA624"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.247 59.5115L35.1192 60.3408L34.7065 63.2911L28.8344 62.4618L29.247 59.5115Z" fill="#FFA624"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L29.247 59.5115L28.8344 62.4618L22.9622 61.6326L23.3749 58.6822Z" fill="#EE9109"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8053 62.9241L22.5496 64.5827L22.137 67.533L10.3927 65.8745L10.8053 62.9241Z" fill="#C1E9E9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.2939 66.2414L46.0382 67.9L45.6255 70.8503L33.8813 69.1917L34.2939 66.2414Z" fill="#DDF3F3"/>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -10,7 +10,7 @@
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
color: $white;
&:hover, &:focus {
&:hover:not(:disabled):not(.disabled), &:focus {
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
&.btn-flat {
@@ -28,14 +28,25 @@
&:disabled, &.disabled {
cursor: default;
color: $gray-50;
opacity: 0.75;
box-shadow: none;
background-color: $gray-700;
color: $gray-200;
opacity: 1;
background-color: transparent;
border: 2px solid transparent;
box-shadow:
0 1px 3px 0 rgba($black, 0.12),
0 1px 2px 0 rgba($black, 0.24);
font-family: 'Roboto', sans-serif;
font-weight: 700;
font-size: 14px;
line-height: 24px;
padding: 4px 12px;
min-height: 32px;
max-height: 32px;
gap: 8px;
border-radius: 4px;
.svg {
color: $gray-300;
color: $gray-200;
}
}
@@ -164,7 +175,6 @@
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: $maroon-100;
border: 2px solid transparent;
@@ -242,29 +252,32 @@
}
.btn-info {
background: $blue-50;
background-color: $blue-100;
color: $black;
font-weight: 700;
border: 2px solid transparent;
box-shadow: 0 1px 3px 0 rgba($black, 0.16), 0 1px 3px 0 rgba($black, 0.24);
box-shadow: 0 1px 3px 0 rgba($black, 0.16), 0 1px 2px 0 rgba($black, 0.24);
&:disabled {
background: $blue-50;
background-color: $white;
box-shadow: none;
}
&:hover {
&:hover:not(:disabled):not(.disabled) {
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);
color: $black;
}
&: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);
background-color: $blue-50;
color: $black;
}
&:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {

View File

@@ -0,0 +1,168 @@
// Inputs and textareas
input, textarea, input.form-control, textarea.form-control {
border-radius: 3px;
font-size: 14px;
line-height: 1.714;
padding: 4px 12px;
color: $gray-50;
border: 1px solid $gray-400;
&:hover:not(:disabled):not(:read-only):not(:focus):not(:disabled):not(.input-valid):not(.input-invalid):not(.dark) {
border-color: $gray-200;
}
&:active:not(:disabled):not(:read-only), &:focus:not(:disabled):not(:read-only),
&:active:not(:disabled):not(:read-only).dark, &:focus:not(:disabled):not(:read-only).dark {
border: 1px solid $purple-400;
outline: 1px solid $purple-400;
box-shadow: none;
}
&.input-valid {
padding-right: 27px;
background-image: url(@/assets/svg/for-css/check.svg);
background-size: 1rem;
border-color: $green-10;
}
&.input-invalid, .input-invalid:hover {
padding-right: 40px;
background-image: url(@/assets/svg/for-css/alert.svg);
background-size: 16px 16px;
border-color: $red-100;
}
&::-webkit-input-placeholder { /* Chrome/Opera/Safari */
color: $gray-200;
}
&::-moz-placeholder { /* Firefox 19+ */
color: $gray-200;
}
&:-ms-input-placeholder { /* IE 10+ */
color: $gray-200;
}
&:-moz-placeholder { /* Firefox 18- */
color: $gray-200;
}
&::placeholder { // Standard browsers
color: $gray-200;
}
.input-invalid.input-with-error {
margin-bottom: 0.5em;
}
&.dark {
background-color: $purple-100;
color: $white;
&:not(.input-valid):not(.input-invalid) {
border: 1px solid $purple-300;
}
&:focus {
background-color: $purple-100;
color: $white;
}
&:hover:not(:focus):not(:disabled):not(.input-valid):not(.input-invalid) {
border-color: $purple-400;
}
&::-webkit-input-placeholder { /* Chrome/Opera/Safari */
color: $purple-500;
}
&::-moz-placeholder { /* Firefox 19+ */
color: $purple-500;
}
&:-ms-input-placeholder { /* IE 10+ */
color: $purple-500;
}
&:-moz-placeholder { /* Firefox 18- */
color: $purple-500;
}
&::placeholder { // Standard browsers
color: $purple-500;
}
}
}
.input-error {
font-size: 12px;
line-height: 1.33;
color: $maroon-500;
}
// checkboxes
.custom-checkbox {
.custom-control-label::before {
border-radius: 2px;
}
.custom-control-input {
&:hover~.custom-control-label::before {
border-color: $gray-100;
}
&:checked~.custom-control-label::before {
background-color: $purple-300;
border-color: $purple-300;
}
&:hover:checked:not(:disabled)~.custom-control-label::before,
&:active:not(:disabled)~.custom-control-label::before {
background-color: $purple-400;
border-color: $purple-400;
}
&:checked~.custom-control-label::after {
width: 18px;
height: 18px;
background-image: url(@/assets/svg/for-css/checkbox-white.svg);
background-size: 13px 10px;
}
&:checked:disabled~.custom-control-label::after {
background-image: url(@/assets/svg/for-css/checkbox-gray.svg);
}
&:active~.custom-control-label::before {
background-color: inherit;
}
&:focus~.custom-control-label::before,
&:active~.custom-control-label::before {
border-color: $purple-400;
box-shadow: none;
}
&:disabled~.custom-control-label::before, &:disabled:checked~.custom-control-label::before {
border-color: $gray-400;
background-color: $gray-400;
}
&.dark {
~.custom-control-label::before {
border-color: $purple-100;
}
&:hover~.custom-control-label::before,
&:active~.custom-control-label::before {
border-color: $purple-50;
}
&:checked~.custom-control-label::before {
background-color: $purple-100;
border-color: $purple-100;
}
&:focus~.custom-control-label::before,
&:active~.custom-control-label::before {
border-color: $purple-400;
box-shadow: none;
}
&:disabled~.custom-control-label::before, &:disabled:checked~.custom-control-label::before {
border-color: $gray-400;
background-color: $gray-400;
}
}
}
}

View File

@@ -0,0 +1,16 @@
.privacy-banner {
position: fixed;
bottom: 24px;
border-radius: 8px;
background-color: $white;
z-index: 5;
box-shadow: 0px 3px 6px 0px rgba(26, 24, 29, 0.16), 0px 3px 6px 0px rgba(26, 24, 29, 0.24);
width: calc(66vw + 96px);
@media only screen and (max-width: 992px) {
margin: auto 12.5%;
}
@media only screen and (min-width: 992px) {
margin: auto 14.5%;
}
}

View File

@@ -14,7 +14,7 @@
color: $purple-200;
}
li, p {
li, p:not(.purple-600) {
font-size: 16px;
}

View File

@@ -111,6 +111,10 @@ h4 {
background-color: $green-100 !important;
}
.bg-purple-50 {
background-color: $purple-50 !important;
}
.bg-purple-100 {
background-color: $purple-100 !important;
}
@@ -119,6 +123,10 @@ h4 {
background-color: $purple-300 !important;
}
.bg-yellow-50 {
background-color: $yellow-50 !important;
}
.bg-white {
background-color: $white !important;
}
@@ -131,6 +139,10 @@ h4 {
color: $gray-50 !important;
}
.gray-100 {
color: $gray-100 !important;
}
.gray-200 {
color: $gray-200 !important;
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g fill="#FFFFFF" fill-rule="nonzero">
<polygon points="12.1973467 2 14 3.80265326 9.80187117 8 14 12.1973467 12.1973467 14 8 9.80187117 3.80265326 14 2 12.1973467 6.19812883 8 2 3.80265326 3.80265326 2 8 6.19812883"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 10">
<path fill="#878190" fill-rule="evenodd" d="M4.662 9.832c-.312 0-.61-.123-.83-.344L0 5.657l1.662-1.662 2.934 2.934L10.534 0l1.785 1.529-6.764 7.893a1.182 1.182 0 0 1-.848.409l-.045.001"/>
</svg>

After

Width:  |  Height:  |  Size: 261 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,29 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.1792 31.6843L46.8536 22.3769L23.918 28.6988L18.861 42.5218L44.341 58.5813L58.1792 31.6843Z" fill="#FF944C"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L46.1108 26.1328L36.2812 28.8422L46.6218 34.5148Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M30.2393 39.0304L26.4518 31.5515L36.2813 28.8422L30.2393 39.0304Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L36.2813 28.8422L30.2393 39.0304L46.6218 34.5148Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M53.8301 32.5279L46.1108 26.1328L46.6218 34.5148L53.8301 32.5279Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M23.0309 41.0173L26.4518 31.5516L30.2393 39.0304L23.0309 41.0173Z" fill="#FA8537"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M53.8301 32.5279L46.6218 34.5148L43.0424 53.79L53.8301 32.5279Z" fill="#FA8537"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M23.0309 41.0173L30.2393 39.0304L43.0425 53.79L23.0309 41.0173Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L30.2393 39.0304L43.0425 53.79L46.6218 34.5148Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M50.555 4.15937L47.026 0.420004L38.7773 1.59601L36.4144 6.17539L44.5675 12.8919L50.555 4.15937Z" fill="#FFBE5D"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L46.6034 1.6924L43.0682 2.1964L46.414 4.62854Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M40.5221 5.46854L39.5331 2.7004L43.0682 2.1964L40.5221 5.46854Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L43.0683 2.1964L40.5221 5.46855L46.414 4.62854Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M49.0064 4.25894L46.6034 1.6924L46.414 4.62854L49.0064 4.25894Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M37.9296 5.83815L39.5331 2.70041L40.5221 5.46855L37.9296 5.83815Z" fill="#FFA624"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M49.0064 4.25893L46.414 4.62853L44.3259 11.1688L49.0064 4.25893Z" fill="#FFA624"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M37.9297 5.83815L40.5221 5.46855L44.326 11.1688L37.9297 5.83815Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L40.5221 5.46855L44.326 11.1688L46.414 4.62854Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.2986 16.7775L24.6513 8.36623L11.1016 3.94533L4.07056 9.19883L11.614 25.6769L27.2986 16.7775Z" fill="#FF6165"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L23.0573 10.0026L17.2502 8.10789L20.5864 14.3719Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M10.908 11.2141L11.4432 6.21322L17.2502 8.10789L10.908 11.2141Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L17.2502 8.10789L10.9081 11.2141L20.5864 14.3719Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M24.8449 15.7613L23.0573 10.0026L20.5864 14.3719L24.8449 15.7613Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M6.64955 9.82464L11.4432 6.21321L10.908 11.2141L6.64955 9.82464Z" fill="#F23035"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M24.8449 15.7613L20.5864 14.3719L12.5221 22.8464L24.8449 15.7613Z" fill="#F23035"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M6.64959 9.82464L10.9081 11.2141L12.5221 22.8463L6.64959 9.82464Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L10.9081 11.2141L12.5221 22.8463L20.5864 14.3719Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,29 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.82083 31.6843L17.1464 22.3769L40.082 28.6988L45.139 42.5218L19.659 58.5813L5.82083 31.6843Z" fill="#24CC8F"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L17.8892 26.1328L27.7188 28.8422L17.3782 34.5148Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M33.7607 39.0304L37.5482 31.5515L27.7187 28.8422L33.7607 39.0304Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L27.7187 28.8422L33.7607 39.0304L17.3782 34.5148Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M10.1699 32.5279L17.8892 26.1328L17.3782 34.5148L10.1699 32.5279Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M40.9691 41.0173L37.5482 31.5516L33.7607 39.0304L40.9691 41.0173Z" fill="#1CA372"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M10.1699 32.5279L17.3782 34.5148L20.9576 53.79L10.1699 32.5279Z" fill="#1CA372"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M40.9691 41.0173L33.7607 39.0304L20.9575 53.79L40.9691 41.0173Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L33.7607 39.0304L20.9575 53.79L17.3782 34.5148Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.445 4.15937L16.974 0.420004L25.2227 1.59601L27.5856 6.17539L19.4325 12.8919L13.445 4.15937Z" fill="#925CF3"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L17.3966 1.6924L20.9318 2.1964L17.586 4.62854Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M23.4779 5.46854L24.4669 2.7004L20.9318 2.1964L23.4779 5.46854Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L20.9317 2.1964L23.4779 5.46855L17.586 4.62854Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M14.9936 4.25894L17.3966 1.6924L17.586 4.62854L14.9936 4.25894Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M26.0704 5.83815L24.4669 2.70041L23.4779 5.46855L26.0704 5.83815Z" fill="#4F2A93"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M14.9936 4.25893L17.586 4.62853L19.6741 11.1688L14.9936 4.25893Z" fill="#4F2A93"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M26.0703 5.83815L23.4779 5.46855L19.674 11.1688L26.0703 5.83815Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L23.4779 5.46855L19.674 11.1688L17.586 4.62854Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.7014 16.7775L39.3487 8.36623L52.8984 3.94533L59.9294 9.19883L52.386 25.6769L36.7014 16.7775Z" fill="#50B5E9"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L40.9427 10.0026L46.7498 8.10789L43.4136 14.3719Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M53.092 11.2141L52.5568 6.21322L46.7498 8.10789L53.092 11.2141Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L46.7498 8.10789L53.0919 11.2141L43.4136 14.3719Z" fill="white"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M39.1551 15.7613L40.9427 10.0026L43.4136 14.3719L39.1551 15.7613Z" fill="white"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M57.3504 9.82464L52.5568 6.21321L53.092 11.2141L57.3504 9.82464Z" fill="#46A7D9"/>
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M39.1551 15.7613L43.4136 14.3719L51.4779 22.8464L39.1551 15.7613Z" fill="#46A7D9"/>
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M57.3504 9.82464L53.0919 11.2141L51.4779 22.8463L57.3504 9.82464Z" fill="white"/>
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L53.0919 11.2141L51.4779 22.8463L43.4136 14.3719Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,5 +1,5 @@
<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 width="145" height="32" viewBox="0 0 145 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M72.7484 0.0131836C71.1152 0.0131836 69.7861 1.33328 69.7861 2.96139C69.7861 4.5895 71.1152 5.90959 72.7484 5.90959C74.3816 5.90959 75.7106 4.58461 75.7106 2.96139C75.7106 1.33816 74.3816 0.0131836 72.7484 0.0131836Z" fill="#FF6165"/>
<path d="M99.3498 0.0131836C97.7166 0.0131836 96.3875 1.33328 96.3875 2.96139C96.3875 4.5895 97.7166 5.90959 99.3498 5.90959C100.983 5.90959 102.312 4.58461 102.312 2.96139C102.312 1.33816 100.983 0.0131836 99.3498 0.0131836Z" fill="#50B5E9"/>
<path d="M123.181 28.1326C121.263 30.5919 118.37 32 115.236 32C109.91 32 105.511 27.8784 105.221 22.6176C105.207 22.3536 105.207 19.2244 105.207 19.1951C105.207 13.7192 109.704 9.26509 115.236 9.26509C117.973 9.26509 120.626 10.3945 122.509 12.36C123.083 12.9565 123.392 13.7436 123.367 14.5699C123.348 15.3962 122.999 16.1638 122.391 16.7309C121.146 17.9044 119.169 17.8506 117.982 16.6185C117.256 15.8655 116.281 15.45 115.231 15.45C113.142 15.45 111.445 17.127 111.445 19.1902C111.445 19.5129 111.445 21.9966 111.455 22.3389C111.572 24.2701 113.235 25.8102 115.231 25.8102C116.418 25.8102 117.511 25.2773 118.232 24.3484C119.282 22.9989 121.249 22.7496 122.607 23.791C123.269 24.2946 123.691 25.0231 123.799 25.8445C123.907 26.6659 123.681 27.4775 123.176 28.1326H123.181ZM144.995 12.316V28.9442C144.995 30.6261 143.627 31.9951 141.945 31.9951C141.047 31.9951 140.213 31.5991 139.645 30.9439C138.286 31.6333 136.785 31.9951 135.26 31.9951C130.091 31.9951 125.819 27.9419 125.54 22.7642C125.525 22.5051 125.525 19.4347 125.525 19.4005C125.525 14.0174 129.895 9.64156 135.26 9.64156C136.702 9.64156 138.124 9.96914 139.434 10.595C139.988 9.77846 140.92 9.2602 141.95 9.2602C143.632 9.2602 145 10.6292 145 12.3111L144.995 12.316ZM138.889 22.4807C138.894 22.0798 138.899 19.7183 138.899 19.4053C138.899 17.3861 137.266 15.7482 135.26 15.7482C133.254 15.7482 131.621 17.391 131.621 19.4053C131.621 19.7134 131.621 22.1629 131.631 22.5051C131.744 24.3875 133.343 25.8934 135.26 25.8934C137.178 25.8934 138.767 24.3972 138.889 22.4807ZM42.3338 12.316V28.9442C42.3338 30.6261 40.9655 31.9951 39.2833 31.9951C38.3858 31.9951 37.5521 31.5991 36.9832 30.9439C35.6247 31.6333 34.124 31.9951 32.5988 31.9951C27.4247 31.9951 23.158 27.9419 22.8785 22.7642C22.8638 22.5345 22.8638 20.1338 22.8638 19.4005C22.8638 14.0174 27.2335 9.64156 32.5988 9.64156C34.0406 9.64156 35.4678 9.96914 36.7723 10.595C37.3265 9.77846 38.2583 9.2602 39.2882 9.2602C40.9704 9.2602 42.3387 10.6292 42.3387 12.3111L42.3338 12.316ZM32.5988 15.7482C30.5929 15.7482 28.9598 17.391 28.9598 19.4053C28.9598 19.7231 28.9598 22.158 28.9696 22.5002C29.0824 24.3875 30.6812 25.8934 32.5988 25.8934C34.5163 25.8934 36.1053 24.3972 36.2279 22.4807C36.2328 22.0798 36.2377 19.7231 36.2377 19.4053C36.2377 17.3861 34.6046 15.7482 32.5988 15.7482ZM72.7452 9.42643C71.1121 9.42643 69.783 10.7954 69.783 12.4773V28.9442C69.783 30.6261 71.1121 31.9951 72.7452 31.9951C74.3783 31.9951 75.7074 30.6261 75.7074 28.9442V12.4773C75.7074 10.7954 74.3783 9.42643 72.7452 9.42643ZM99.346 9.42643C97.7129 9.42643 96.3838 10.7954 96.3838 12.4773V28.9442C96.3838 30.6261 97.7129 31.9951 99.346 31.9951C100.979 31.9951 102.308 30.6261 102.308 28.9442V12.4773C102.308 10.7954 100.979 9.42643 99.346 9.42643ZM66.041 19.244C66.041 19.2733 66.0411 22.3878 66.0264 22.6469C65.7468 27.893 61.4752 32 56.3061 32C54.7808 32 53.2801 31.6333 51.9216 30.9342C51.3527 31.5991 50.519 32 49.6166 32C47.9344 32 46.5662 30.6164 46.5662 28.9198V3.08021C46.5662 1.38365 47.9344 0 49.6166 0C51.2988 0 52.6671 1.38365 52.6671 3.08021V10.0816C53.8245 9.60734 55.0604 9.35798 56.3061 9.35798C61.6762 9.35798 66.041 13.7925 66.041 19.244ZM56.3061 15.5233C54.3002 15.5233 52.6671 17.1905 52.6671 19.244C52.6671 19.5667 52.6671 22.0358 52.6769 22.3829C52.7946 24.3044 54.3885 25.8347 56.3061 25.8347C58.2236 25.8347 59.8077 24.3141 59.9352 22.3682C59.9352 21.9526 59.945 19.552 59.945 19.244C59.945 17.1905 58.3119 15.5233 56.3061 15.5233ZM9.62221 9.35798C8.43537 9.35798 7.26325 9.57311 6.15978 9.98381V3.0851C6.15978 1.38854 4.77677 0.00488934 3.07989 0.00488934C1.38301 0.00488934 0 1.38854 0 3.0851V28.9198C0 30.6212 1.38301 32 3.07989 32C4.77677 32 6.15978 30.6164 6.15978 28.9198V17.6941C6.28729 17.5572 7.4349 15.5233 9.62221 15.5233C11.6624 15.5233 13.32 17.1905 13.32 19.244C13.32 19.6547 13.32 28.3526 13.3102 28.8416C13.2661 30.5235 14.551 31.912 16.2185 31.9951C16.2773 31.9951 16.3313 31.9951 16.3901 31.9951C18.0281 31.9951 19.3768 30.7092 19.4602 29.0714C19.4749 28.8171 19.4749 19.3369 19.4749 19.244C19.4749 13.7925 15.0561 9.35798 9.62221 9.35798ZM91.2147 9.35798H89.41V3.08021C89.41 1.38365 88.0122 0 86.2908 0C84.5694 0 83.1717 1.38365 83.1717 3.08021V9.35798H81.3669C79.6504 9.35798 78.2478 10.7416 78.2478 12.4431C78.2478 14.1445 79.6455 15.5233 81.3669 15.5233H83.1717V28.9198C83.1717 30.6212 84.5694 32 86.2908 32C88.0122 32 89.41 30.6164 89.41 28.9198V15.5233H91.2147C92.9361 15.5233 94.3339 14.1396 94.3339 12.4431C94.3339 10.7465 92.9361 9.35798 91.2147 9.35798Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -117,7 +117,7 @@ export default {
closeWithAction () {
this.close();
setTimeout(() => {
this.$router.push({ name: 'achievements' });
this.$router.push(`/profile/${this.$store.state.user.data._id}#achievements`);
}, 200);
},
},

View File

@@ -81,7 +81,7 @@ export default {
watch: {
userIdentifier () {
this.isSearching = true;
this.$store.dispatch('adminPanel:searchUsers', { userIdentifier: this.userIdentifier }).then(users => {
this.$store.dispatch('admin:searchUsers', { userIdentifier: this.userIdentifier }).then(users => {
this.isSearching = false;
if (users.length === 1) {
this.loadUser(users[0]._id);

View File

@@ -5,6 +5,12 @@
class="row"
>
<div class="form col-12">
<button
class="btn btn-danger mt-3 float-right"
@click="confirmDeleteHero"
>
Begin Member deletion
</button>
<basic-details
:user-id="hero._id"
:auth="hero.auth"
@@ -96,6 +102,53 @@
:reset-counter="resetCounter"
@clear-data="clearData"
/>
<b-modal
id="delete-member-modal"
title="Delete Member"
ok-title="Delete"
ok-variant="danger"
cancel-title="Cancel"
@ok="deleteHero"
>
<b-modal-body>
<p>
Are you sure you want to delete this member?
</p>
<p class="errorMessage">
Please note: This action cannot be undone!
</p>
<div class="ml-4">
<div class="form-check">
<input
id="deleteAccountCheck"
v-model="deleteHabiticaAccount"
class="form-check-input"
type="checkbox"
>
<label
class="form-check-label"
for="deleteAccountCheck"
>
Delete Habitica account
</label>
</div>
<div class="form-check">
<input
id="deleteAmplitudeCheck"
v-model="deleteAmplitudeData"
class="form-check-input"
type="checkbox"
>
<label
class="form-check-label"
for="deleteAmplitudeCheck"
>
Delete Amplitude data
</label>
</div>
</div>
</b-modal-body>
</b-modal>
</div>
</div>
</div>
@@ -184,6 +237,8 @@ export default {
hasParty: false,
partyNotExistError: false,
adminHasPrivForParty: true,
deleteHabiticaAccount: true,
deleteAmplitudeData: true,
};
},
watch: {
@@ -249,6 +304,25 @@ export default {
this.resetCounter += 1; // tell child components to reinstantiate from scratch
},
confirmDeleteHero () {
if (this.hero._id === this.user._id) {
window.alert('You cannot delete your own account.');
return;
}
this.$root.$emit('bv::show::modal', 'delete-member-modal');
},
deleteHero () {
this.$store.dispatch('hall:deleteHero', {
uuid: this.hero._id,
deleteHabiticaAccount: this.deleteHabiticaAccount,
deleteAmplitudeData: this.deleteAmplitudeData,
}).then(() => {
this.$root.$emit('bv::hide::modal', 'delete-member-modal');
this.$router.push({ name: 'adminPanel' });
}).catch(err => {
window.alert(err);
});
},
hasUnsavedChanges (...comparisons) {
for (const index in comparisons) {
if (index && comparisons[index]) {

View File

@@ -37,7 +37,11 @@
Party ID
</label>
<strong class="col-sm-9 col-form-label">
{{ groupPartyData._id }}
<router-link
:to="{'name': 'groupAdminGroup', 'params': {'groupId': groupPartyData._id}}"
>
{{ groupPartyData._id }}
</router-link>
</strong>
</div>
<div class="form-group row">

View File

@@ -15,6 +15,25 @@
:class="{ 'open': expand }"
>
Subscription, Monthly Perks
<span
v-if="isSubscribed() && !isCancelled()"
class="text-success float-right ml-3"
>
Active
</span>
<span
v-else-if="isSubscribed() && isCancelled()"
class="text-success float-right ml-3"
>
Active until {{ dateFormat(hero.purchased.plan.dateTerminated) }}
</span>
<span
v-else-if="hero.purchased.plan.customerId && hero.purchased.plan.dateTerminated"
class="text-warning float-right ml-3"
>
Inactive
</span>
<b
v-if="hasUnsavedChanges && !expand"
class="text-warning float-right"
@@ -46,7 +65,7 @@
class="form-control"
type="text"
>
<option value="groupPlan">
<option value="Group Plan">
Group Plan
</option>
<option value="Stripe">
@@ -154,7 +173,11 @@
>
<div class="card-body">
<h6 class="card-title">
{{ group.name }}
<router-link
:to="{ name: 'groupAdminGroup', params: { groupId: group._id } }"
>
{{ group.name }}
</router-link>
<small class="float-right">{{ group._id }}</small>
</h6>
<p class="card-text">
@@ -245,8 +268,7 @@
</div>
</div>
<small
v-if="!hero.purchased.plan.dateTerminated
&& hero.purchased.plan.planId"
v-if="isSubscribed() && !isCancelled()"
class="text-success"
>
The subscription does not have a termination date and is active.
@@ -419,6 +441,79 @@
>
</div>
</div>
<div class="form-group row">
<h2>Payment Details</h2>
</div>
<div class="form-group row">
<div class="offset-sm-3 col-sm-9 mb-3">
<button
type="button"
class="btn btn-secondary btn-sm"
@click="getSubscriptionPaymentDetails"
>
Get Subscription Payment Details
</button>
</div>
</div>
<div
v-if="paymentDetails"
>
<div
v-for="(value, key) in paymentDetails"
:key="key"
class="form-group row"
>
<label class="col-sm-3 col-form-label">
{{ getHumanReadablePaymentDetails(key).label }}:
<span
:id="`${key}_tooltip`"
v-b-tooltip.hover.right="getHumanReadablePaymentDetails(key).help"
class="info-icon"
>?</span>
</label>
<strong class="col-sm-9 col-form-label">
<span v-if="value === true">Yes</span>
<span v-else-if="value === false">No</span>
<span
v-else-if="value instanceof String && isDate(value)"
v-b-tooltip.hover="value"
>
{{ formatDate(value) }}
</span>
<span v-else-if="value === null">---</span>
<span v-else>{{ value }}</span>
</strong>
</div>
<div class="form-group row">
<div class="offset-sm-3 col-sm-9">
<a
v-if="hero.purchased.plan.paymentMethod === 'Google'"
class="btn btn-primary btn-sm"
target="_blank"
:href="playOrdersUrl"
>
Play Console
</a>
<a
v-else-if="hero.purchased.plan.paymentMethod === 'Paypal'"
class="btn btn-primary btn-sm"
target="_blank"
:href="'https://www.paypal.com/billing/subscriptions/' + paymentDetails.customerId"
>
PayPal Dashboard
</a>
<a
v-else-if="hero.purchased.plan.paymentMethod === 'Stripe'"
class="btn btn-primary btn-sm"
target="_blank"
:href="'https://dashboard.stripe.com/customers/' + paymentDetails.customerId"
>
Stripe Dashboard
</a>
</div>
</div>
</div>
</div>
<div
v-if="expand"
@@ -474,17 +569,36 @@
<style lang="scss" scoped>
@import '@/assets/scss/colors.scss';
.input-group-append {
width: auto;
.input-group-text {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
font-weight: 600;
font-size: 0.8rem;
color: $gray-200;
.form-group {
margin-bottom: 0.4rem;
}
.input-group-append {
width: auto;
.input-group-text {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
font-weight: 600;
font-size: 0.8rem;
color: $gray-200;
}
}
.info-icon {
font-size: 0.8rem;
color: $purple-400;
cursor: pointer;
margin-left: 0.2rem;
background-color: $gray-500;
padding: 0.1rem 0.3rem;
border-radius: 0.2rem;
}
.info-icon:hover {
background-color: $purple-400;
color: white;
}
}
</style>
<script>
@@ -495,6 +609,55 @@ import subscriptionBlocks from '@/../../common/script/content/subscriptionBlocks
import saveHero from '../mixins/saveHero';
import LoadingSpinner from '@/components/ui/loadingSpinner';
const PLAY_CONSOLE_ORDERS_BASE_URL = import.meta.env.PLAY_CONSOLE_ORDERS_BASE_URL;
const humanReadablePaymentDetails = {
customerId: {
label: 'Customer ID',
help: 'The unique identifier for the customer in the payment system.',
},
purchaseDate: {
label: 'Purchase Date',
help: 'The date when the subscription was purchased or renewed.',
},
originalPurchaseDate: {
label: 'Original Purchase Date',
help: 'The date when the subscription was first purchased.',
},
productId: {
label: 'Product ID',
help: 'The identifier for the product associated with the subscription.',
},
transactionId: {
label: 'Transaction ID',
help: 'The unique identifier for the last transaction in the payment system.',
},
isCanceled: {
label: 'Is Canceled',
help: 'Indicates whether the subscription has been canceled by the user or the system.',
},
isExpired: {
label: 'Is Expired',
help: 'Indicates whether the subscription has expired. A cancelled subscription may still be active until the end of the billing cycle.',
},
expirationDate: {
label: 'Termination Date',
help: 'The date when the subscription will expire or has expired.',
},
nextPaymentDate: {
label: 'Next Payment Date',
help: 'The date when the next payment is due. If the subscription is canceled or expired, this may be null.',
},
lastPaymentDate: {
label: 'Last Payment Date',
help: 'The date when the lastpayment was made for the subscription.',
},
failedPayments: {
label: 'Failed Payments',
help: 'Number of times the payment failed for this subscription.',
},
};
export default {
components: {
LoadingSpinner,
@@ -520,6 +683,7 @@ export default {
isConvertingToGroupPlan: false,
groupPlanID: '',
subscriptionBlocks,
paymentDetails: null,
};
},
computed: {
@@ -553,6 +717,9 @@ export default {
}
return terminationDate;
},
playOrdersUrl () {
return `${PLAY_CONSOLE_ORDERS_BASE_URL}${this.paymentDetails?.transactionId || ''}`;
},
},
methods: {
dateFormat (date) {
@@ -583,6 +750,20 @@ export default {
this.isConvertingToGroupPlan = true;
this.hero.purchased.plan.owner = '';
},
getSubscriptionPaymentDetails () {
this.$store.dispatch('admin:getSubscriptionPaymentDetails', { userIdentifier: this.hero._id })
.then(details => {
if (details) {
this.paymentDetails = details;
} else {
alert('No payment details found.');
}
})
.catch(error => {
console.error('Error fetching subscription payment details:', error);
alert(`Failed to fetch payment details: ${error.message || 'Unknown error'}`);
});
},
saveClicked (e) {
e.preventDefault();
if (this.isConvertingToGroupPlan) {
@@ -601,6 +782,31 @@ export default {
this.$emit('changeUserIdentifier', id);
}
},
getHumanReadablePaymentDetails (key) {
return humanReadablePaymentDetails[key] || { label: key, help: '' };
},
isDate (date) {
return moment(date).isValid();
},
formatDate (date) {
return date ? moment(date).format('MM/DD/YYYY') : '---';
},
isSubscribed () {
console.log(this.hero.purchased.plan.customerId, this.hero.purchased.plan.dateTerminated);
return this.hero.purchased.plan
&& this.hero.purchased.plan.customerId
&& this.hero.purchased.plan.planId
&& this.hero.purchased.plan.paymentMethod
&& (
!this.hero.purchased.plan.dateTerminated
|| moment(this.hero.purchased.plan.dateTerminated).isAfter(moment())
);
},
isCancelled () {
return this.hero.purchased.plan
&& this.hero.purchased.plan.dateTerminated
&& this.hero.purchased.plan.dateTerminated !== '';
},
},
};
</script>

View File

@@ -226,7 +226,7 @@ export default {
}
},
async retrieveUserHistory () {
const history = await this.$store.dispatch('adminPanel:getUserHistory', { userIdentifier: this.hero._id });
const history = await this.$store.dispatch('admin:getUserHistory', { userIdentifier: this.hero._id });
this.armoire = history.armoire;
this.questInviteResponses = history.questInviteResponses;
this.cron = history.cron;

View File

@@ -8,6 +8,13 @@
>
{{ $t('adminPanel') }}
</router-link>
<router-link
v-if="hasPermission(user, 'groupSupport')"
class="nav-link"
:to="{name: 'groupAdmin'}"
>
{{ $t('groupAdmin') }}
</router-link>
<router-link
v-if="hasPermission(user, 'accessControl')"
class="nav-link"

View File

@@ -0,0 +1,47 @@
<template>
<div class="form-group row">
<label class="col-sm-3 col-form-label"><slot name="label">{{ label }}</slot></label>
<div class="col-sm-9">
<slot>
<textarea
v-if="inputType === 'textarea'"
:value="value"
class="form-control"
:rows="rows"
@input="$emit('input', $event.target.value)"
></textarea>
<input
v-else
:value="value"
class="form-control"
:type="inputType"
@input="$emit('input', $event.target.value)"
>
</slot>
</div>
</div>
</template>
<script>
export default {
model: {
prop: 'value',
event: 'input',
},
props: {
label: {
type: String,
},
value: {
type: [String, Boolean],
},
inputType: {
type: String,
default: 'text',
},
rows: {
default: 3,
},
},
};
</script>

View File

@@ -0,0 +1,45 @@
<template>
<form>
<form-row
v-model="group.name"
:label="$t('groupName')"
/>
<form-row
v-model="group.summary"
:label="$t('guildSummary')"
input-type="textarea"
/>
<form-row
v-model="group.description"
:label="$t('groupDescription')"
input-type="textarea"
rows="6"
/>
<form-row
v-model="group.bannedWordsAllowed"
:label="$t('bannedWordsAllowed')"
input-type="checkbox"
/>
<form-row
v-model="group.leaderOnly.challenges"
:label="$t('leaderOnlyChallenges')"
input-type="checkbox"
/>
</form>
</template>
<script>
import formRow from '@/components/admin/formRow.vue';
export default {
components: {
formRow,
},
props: {
group: {
type: Object,
required: true,
},
},
};
</script>

View File

@@ -0,0 +1,69 @@
<template>
<div v-if="hasPermission(user, 'groupSupport')">
<h2>{{ group.name }}</h2>
<supportContainer
:title="$t('groupData')"
>
<groupData
:group="group"
/>
</supportContainer>
<supportContainer
:title="$t('groupPlanSubscription')"
/>
<supportContainer
v-if="group.type === 'party'"
:title="$t('questDetails')"
/>
<supportContainer
:title="$t('members')"
>
<members
:group="group"
/>
</supportContainer>
</div>
</template>
<script>
import { userStateMixin } from '../../../../mixins/userState';
import supportContainer from '../../supportContainer.vue';
import groupData from './groupData.vue';
import members from './members.vue';
export default {
components: {
supportContainer,
groupData,
members,
},
mixins: [userStateMixin],
data () {
return {
groupId: '',
group: {},
};
},
watch: {
groupId () {
this.loadGroup(this.groupId);
},
},
mounted () {
this.groupId = this.$route.params.groupId;
},
methods: {
clearData () {
this.group = {};
},
async loadGroup (groupId) {
this.$emit('changeGroupId', groupId);
this.group = await this.$store.dispatch('admin:getGroup', { groupId });
},
async updateGroup () {
await this.$store.dispatch('admin:updateGroup', { group: this.group });
this.$emit('groupSaved', this.group);
},
},
};
</script>

View File

@@ -0,0 +1,29 @@
<template>
<form-row
:label="$t('groupLeader')"
>
<strong class="col-form-label">
<router-link
:to="{'name': 'adminPanelUser', 'params': {'userIdentifier': group.leader }}"
>
{{ group.leader }}
</router-link>
</strong>
</form-row>
</template>
<script>
import formRow from '@/components/admin/formRow.vue';
export default {
components: {
formRow,
},
props: {
group: {
type: Object,
required: true,
},
},
};
</script>

View File

@@ -0,0 +1,93 @@
<template>
<div class="row standard-page col-12 d-flex justify-content-center">
<div class="group-admin-content">
<h1>{{ $t("groupAdmin") }}</h1>
<form
class="form-inline"
@submit.prevent="loadGroup(groupID)"
>
<div class="input-group col pl-0 pr-0">
<input
v-model="groupID"
class="form-control"
type="text"
placeholder="Group ID"
>
<div class="input-group-append">
<button
class="btn btn-primary"
type="button"
:disabled="!groupID"
@click="loadGroup(groupID)"
>
Load
</button>
</div>
</div>
</form>
<router-view
class="mt-3"
@changeGroupId="changeGroupId"
/>
</div>
</div>
</template>
<style lang="scss" scoped>
.uidField {
min-width: 45ch;
}
.input-group-append {
width:auto;
}
.group-admin-content {
flex: 0 0 800px;
max-width: 800px;
}
</style>
<script>
import VueRouter from 'vue-router';
import { mapState } from '@/libs/store';
const { isNavigationFailure, NavigationFailureType } = VueRouter;
export default {
data () {
return {
groupID: '',
};
},
computed: {
...mapState({ user: 'user.data' }),
},
mounted () {
this.$store.dispatch('common:setTitle', {
section: this.$t('groupAdmin'),
});
},
methods: {
changeGroupId (id) {
this.groupID = id;
},
async loadGroup (groupId) {
if (this.$router.currentRoute.name === 'groupAdminGroup') {
await this.$router.push({
name: 'groupAdmin',
});
}
await this.$router.push({
name: 'groupAdminGroup',
params: { groupId },
}).catch(failure => {
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
this.$router.go();
}
});
},
},
};
</script>

View File

@@ -0,0 +1,53 @@
<template>
<div class="card mt-2">
<div class="card-header">
<h3
class="mb-0 mt-0"
:class="{'open': expand}"
@click="expand = !expand"
>
<slot name="title">
{{ title }}
</slot>
</h3>
</div>
<div
v-if="expand"
class="card-body"
>
<slot></slot>
</div>
<div
v-if="expand && onSave"
class="card-footer"
>
<button
class="btn btn-primary mt-1"
@click="onSave"
>
{{ $t('save') }}
</button>
</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: false,
},
onSave: {
type: Function,
required: false,
},
},
data () {
return {
expand: false,
};
},
};
</script>

View File

@@ -1,6 +1,7 @@
<template>
<div>
<buy-gems-modal v-if="user" />
<privacy-modal />
<footer>
<!-- Product -->
<div class="product">
@@ -848,6 +849,7 @@ import heart from '@/assets/svg/heart.svg?raw';
// components & modals
import { mapState } from '@/libs/store';
import buyGemsModal from './payments/buyGemsModal.vue';
import privacyModal from './settings/privacyModal.vue';
import reportBug from '@/mixins/reportBug.js';
import { worldStateMixin } from '@/mixins/worldState';
@@ -864,6 +866,7 @@ if (import.meta.env.TIME_TRAVEL_ENABLED === 'true') {
export default {
components: {
buyGemsModal,
privacyModal,
},
mixins: [
reportBug,

View File

@@ -140,11 +140,6 @@
>
{{ $t('passwordConfirmationMatch') }}
</div>
<small
v-once
class="form-text"
v-html="$t('termsAndAgreement')"
></small>
</div>
<div class="text-center">
<div

View File

@@ -3,6 +3,9 @@
<div id="top-background">
<div class="seamless_stars_varied_opacity_repeat"></div>
</div>
<privacy-banner
class="privacy-banner"
/>
<form
v-if="!forgotPassword && !resetPasswordSetNewOne"
id="login-form"
@@ -10,17 +13,18 @@
>
<div class="text-center">
<div>
<div
class="svg-icon svg habitica-logo"
<a
href="/static/home"
class="svg-icon svg habitica-logo mx-auto mb-4"
v-html="icons.habiticaIcon"
></div>
></a>
</div>
</div>
<div class="form-group row text-center">
<div class="col-12 col-md-12">
<div class="form-group">
<div>
<div
class="btn btn-secondary social-button"
@click="socialAuth('google')"
@click="proceed('google')"
>
<div
class="svg-icon social-icon"
@@ -29,18 +33,16 @@
<div
class="text"
>
{{ registering
? $t('signUpWithSocial', {social: 'Google'})
: $t('loginWithSocial', {social: 'Google'}) }}
{{ $t('signUpWithSocial', {social: 'Google'}) }}
</div>
</div>
</div>
</div>
<div class="form-group row text-center">
<div class="col-12 col-md-12">
<div class="form-group">
<div>
<div
class="btn btn-secondary social-button"
@click="socialAuth('apple')"
@click="proceed('apple')"
>
<div
class="svg-icon social-icon"
@@ -49,43 +51,18 @@
<div
class="text"
>
{{ registering
? $t('signUpWithSocial', {social: 'Apple'})
: $t('loginWithSocial', {social: 'Apple'}) }}
{{ $t('signUpWithSocial', {social: 'Apple'}) }}
</div>
</div>
</div>
</div>
<div class="strike">
<div class="strike mb-3">
<span>{{ $t('or') }}</span>
</div>
<div
v-if="registering"
class="form-group"
>
<label
v-once
for="usernameInput"
>{{ $t('username') }}</label>
<input
id="usernameInput"
v-model="username"
class="form-control input-with-error"
type="text"
:placeholder="$t('usernamePlaceholder')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
>
<div
v-for="issue in usernameIssues"
:key="issue"
class="input-error"
>
{{ issue }}
</div>
</div>
<div
v-if="!registering"
class="form-group"
:class="{ 'mb-2': usernameIssues.length > 0 }"
>
<label
v-once
@@ -94,11 +71,22 @@
<input
id="usernameInput"
v-model="username"
class="form-control"
class="form-control dark"
type="text"
:placeholder="$t('emailOrUsername')"
:class="{
'input-valid': usernameValid,
'input-invalid': usernameInvalid,
}"
>
</div>
<div
v-for="issue in usernameIssues"
:key="issue"
class="input-error"
>
{{ issue }}
</div>
<div
v-if="registering"
class="form-group"
@@ -110,13 +98,25 @@
<input
id="emailInput"
v-model="email"
class="form-control"
class="form-control dark"
type="email"
:placeholder="$t('emailPlaceholder')"
:class="{'input-invalid': emailInvalid, 'input-valid': emailValid}"
:class="{
'input-invalid input-with-error': emailError,
'input-valid': emailValid,
}"
>
<div
v-if="emailError"
class="input-error"
>
{{ emailError }}
</div>
</div>
<div class="form-group">
<div
class="form-group"
:class="{ 'mt-2': usernameIssues.length > 0 }"
>
<label
v-once
for="passwordInput"
@@ -130,12 +130,12 @@
<input
id="passwordInput"
v-model="password"
class="form-control"
class="form-control dark"
type="password"
:placeholder="$t(registering ? 'passwordPlaceholder' : 'password')"
:class="{
'input-invalid input-with-error': registering && passwordInvalid,
'input-valid': registering && passwordValid
'input-invalid input-with-error': passwordInvalid,
'input-valid': passwordValid
}"
>
<div
@@ -144,10 +144,16 @@
>
{{ $t('minPasswordLength') }}
</div>
<div
v-if="passwordInvalid && !registering"
class="input-error"
>
{{ $t('minPasswordLengthLogin') }}
</div>
</div>
<div
v-if="registering"
class="form-group"
class="form-group mb-4"
>
<label
v-once
@@ -156,7 +162,7 @@
<input
id="confirmPasswordInput"
v-model="passwordConfirm"
class="form-control input-with-error"
class="form-control dark input-with-error"
type="password"
:placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
@@ -167,30 +173,26 @@
>
{{ $t('passwordConfirmationMatch') }}
</div>
<small
v-once
class="form-text"
v-html="$t('termsAndAgreement')"
></small>
</div>
<div class="text-center">
<button
v-if="registering"
id="continue-button"
type="submit"
class="btn btn-info"
:disabled="signupFormInvalid"
class="btn btn-info w-100 mb-4"
:disabled="!(emailValid && passwordValid && passwordConfirmValid)"
>
{{ $t('joinHabitica') }}
{{ $t('continue') }}
</button>
<button
v-if="!registering"
v-once
type="submit"
class="btn btn-info"
class="btn btn-info w-100 mb-4"
:disabled="!usernameValid || !passwordValid"
>
{{ $t('login') }}
</button>
<div class="toggle-links">
<div>
<router-link
v-if="registering"
:to="{name: 'login'}"
@@ -198,7 +200,7 @@
>
<a
v-once
class="toggle-link"
class="white"
v-html="$t('alreadyHaveAccountLogin')"
></a>
</router-link>
@@ -209,7 +211,7 @@
>
<a
v-once
class="toggle-link"
class="white"
v-html="$t('dontHaveAccountSignup')"
></a>
</router-link>
@@ -221,45 +223,66 @@
id="forgot-form"
@submit.prevent="handleSubmit"
>
<div class="text-center">
<div>
<div>
<div class="svg-icon gryphon"></div>
</div>
<div>
<div
class="svg-icon habitica-logo"
<a
href="/static/home"
class="svg-icon habitica-logo mx-auto mb-4"
v-html="icons.habiticaIcon"
></div>
></a>
</div>
<div class="header">
<h2 v-once>
<h2
v-once
class="text-center"
>
{{ $t('emailNewPass') }}
</h2>
<p v-once>
<p
v-once
class="purple-600 text-left"
>
{{ $t('forgotPasswordSteps') }}
</p>
</div>
</div>
<div class="form-group row text-center">
<label
v-once
for="usernameInput"
>{{ $t('emailOrUsername') }}</label>
<input
id="usernameInput"
v-model="username"
class="form-control"
type="text"
:placeholder="$t('emailUsernamePlaceholder')"
>
</div>
<div class="text-center">
<div
v-once
class="btn btn-info"
@click="forgotPasswordLink()"
class="form-group"
:class="{
'mb-2': usernameIssues.length > 0,
'mb-4': usernameIssues.length === 0,
}"
>
{{ $t('sendLink') }}
<label
v-once
for="usernameInput"
>{{ $t('emailOrUsername') }}</label>
<input
id="usernameInput"
v-model="username"
class="form-control dark"
type="text"
:placeholder="$t('emailUsernamePlaceholder')"
:class="{
'input-valid': usernameValid,
'input-invalid': usernameInvalid,
}"
>
</div>
<div
v-for="issue in usernameIssues"
:key="issue"
class="input-error mb-2"
>
{{ issue }}
</div>
<div class="text-center">
<button
class="btn btn-info w-100"
:disabled="!username || usernameIssues.length > 0"
@click="forgotPasswordLink()"
>
{{ $t('sendLink') }}
</button>
</div>
</div>
</form>
@@ -270,10 +293,11 @@
>
<div class="text-center">
<div>
<div
class="svg-icon habitica-logo"
<a
href="/static/home"
class="svg-icon habitica-logo mx-auto mb-4"
v-html="icons.habiticaIcon"
></div>
></a>
</div>
<div class="header">
<h2>{{ $t('passwordResetPage') }}</h2>
@@ -329,15 +353,6 @@
</div>
</div>
</form>
<div
id="bottom-wrap"
:class="`bottom-wrap-${!registering ? 'login' : 'register'}`"
>
<div id="bottom-background">
<div class="seamless_mountains_demo_repeat"></div>
<div class="midground_foreground_extended2"></div>
</div>
</div>
</div>
</template>
@@ -354,30 +369,10 @@
<style lang="scss" scoped>
@import '@/assets/scss/colors.scss';
@media only screen and (min-height: 1080px) {
.bottom-wrap-register {
margin-top: 6em;
position: fixed !important;
width: 100%;
bottom: 0;
}
}
@media only screen and (min-height: 862px) {
.bottom-wrap-login {
margin-top: 6em;
position: fixed !important;
width: 100%;
bottom: 0;
}
}
@import '@/assets/scss/forms.scss';
@import '@/assets/scss/privacy.scss';
@media only screen and (max-width: 768px) {
#login-form {
width: 100% !important;
}
.form-group {
padding-left: .5em;
padding-right: .5em;
@@ -388,28 +383,28 @@
background-color: $purple-200;
background: $purple-200; /* For browsers that do not support gradients */
background: linear-gradient(to bottom, #4f2a93, #6133b4); /* Standard syntax */
min-height: 100vh;
}
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
color: $purple-400;
color: $purple-500;
}
::-moz-placeholder { /* Firefox 19+ */
color: $purple-400;
color: $purple-500;
}
:-ms-input-placeholder { /* IE 10+ */
color: $purple-400;
color: $purple-500;
}
:-moz-placeholder { /* Firefox 18- */
color: $purple-400;
color: $purple-500;
}
::placeholder { // Standard browsers
color: $purple-400;
color: $purple-500;
}
#login-form, #forgot-form, #reset-password-set-new-one-form {
margin: 0 auto;
width: 40em;
width: 448px;
height: 700px;
padding-top: 5em;
padding-bottom: 4em;
position: relative;
@@ -417,39 +412,23 @@
.header {
h2 {
font-size: 24px;
color: $white;
}
color: $white;
}
.gryphon {
background-size: cover;
color: $white;
height: 69.4px;
margin: 0 auto;
width: 63.2px;
p {
line-height: 1.714;
}
}
.habitica-logo {
width: 175px;
height: 64px;
margin: 2em auto 0;
z-index: 0;
width: 145px;
}
label {
color: $white;
font-weight: bold;
}
input {
margin-bottom: 2em;
border-radius: 2px;
background-color: #432874;
border-color: transparent;
height: 50px;
color: $white;
line-height: 1.714;
}
.input-with-error.input-invalid {
@@ -497,54 +476,10 @@
}
}
#bottom-wrap {
margin-top: 6em;
position: static;
width: 100%;
bottom: 0;
}
#bottom-background {
position: relative;
.seamless_mountains_demo_repeat {
background-image: url('@/assets/images/auth/seamless_mountains_demo.png');
background-repeat: repeat-x;
width: 100%;
height: 300px;
position: absolute;
z-index: 0;
bottom: 0;
}
.midground_foreground_extended2 {
background-image: url('@/assets/images/auth/midground_foreground_extended2.png');
position: relative;
width: 1500px;
max-width: 100%;
height: 150px;
margin: 0 auto;
}
}
.toggle-links {
margin-top: 1em;
}
.toggle-link {
color: $white !important;
}
.forgot-password {
color: #bda8ff !important;
}
.input-error {
color: #fff;
font-size: 90%;
width: 100%;
}
.warning-banner {
color: $white;
background-color: $maroon-100;
@@ -569,14 +504,13 @@
text-align: center;
overflow: hidden;
white-space: nowrap;
margin-top: 1.5em;
margin-bottom: 1.5em;
}
.strike > span {
font-weight: 700;
position: relative;
display: inline-block;
line-height: 1.14;
line-height: 1.714;
color: #fff;
}
@@ -587,7 +521,7 @@
top: 50%;
width: 9999px;
height: 1px;
background: #fff;
background: $purple-400;
}
.strike > span:before {
@@ -603,26 +537,24 @@
<script>
import axios from 'axios';
import hello from 'hellojs';
import debounce from 'lodash/debounce';
import isEmail from 'validator/es/lib/isEmail';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import { buildAppleAuthUrl } from '../../libs/auth';
import PrivacyBanner from '@/components/header/banners/privacy';
import notifications from '@/mixins/notifications';
import sanitizeRedirect from '@/mixins/sanitizeRedirect';
import accountCreation from '@/mixins/accountCreation';
import exclamation from '@/assets/svg/exclamation.svg?raw';
import gryphon from '@/assets/svg/gryphon.svg?raw';
import habiticaIcon from '@/assets/svg/logo-horizontal.svg?raw';
import habiticaIcon from '@/assets/svg/habitica-logo.svg?raw';
import googleIcon from '@/assets/svg/google.svg?raw';
import appleIcon from '@/assets/svg/apple_black.svg?raw';
export default {
mixins: [sanitizeRedirect],
components: {
PrivacyBanner,
},
mixins: [accountCreation, notifications, sanitizeRedirect],
data () {
const data = {
username: '',
email: '',
password: '',
passwordConfirm: '',
forgotPassword: false,
resetPasswordSetNewOneData: {
hasError: null,
@@ -633,7 +565,6 @@ export default {
data.icons = Object.freeze({
exclamation,
gryphon,
habiticaIcon,
googleIcon,
appleIcon,
@@ -654,14 +585,6 @@ export default {
}
return false;
},
emailValid () {
if (this.email.length < 1) return false;
return isEmail(this.email);
},
emailInvalid () {
if (this.email.length < 1) return false;
return !this.emailValid;
},
usernameValid () {
if (this.username.length < 1) return false;
return this.usernameIssues.length === 0;
@@ -670,28 +593,6 @@ export default {
if (this.username.length < 1) return false;
return !this.usernameValid;
},
passwordValid () {
if (this.password.length <= 0) return false;
return this.password.length >= MINIMUM_PASSWORD_LENGTH;
},
passwordInvalid () {
if (this.password.length <= 0) return false;
return this.password.length < MINIMUM_PASSWORD_LENGTH;
},
passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password;
},
passwordConfirmInvalid () {
if (this.passwordConfirm.length <= 3) return false;
return !this.passwordConfirmValid;
},
signupFormInvalid () {
return this.usernameInvalid
|| this.emailInvalid
|| this.passwordInvalid
|| this.passwordConfirmInvalid;
},
},
watch: {
$route: {
@@ -729,71 +630,11 @@ export default {
this.username = this.$route.query.email;
}
}
hello.init({
google: import.meta.env.GOOGLE_CLIENT_ID, // eslint-disable-line
});
},
methods: {
// eslint-disable-next-line func-names
validateUsername: debounce(function (username) {
if (username.length <= 3 || !this.registering) {
return;
}
this.$store.dispatch('auth:verifyUsername', {
username: this.username,
}).then(res => {
if (res.issues !== undefined) {
this.usernameIssues = res.issues;
} else {
this.usernameIssues = [];
}
});
}, 500),
async register () {
// @TODO do not use alert
if (!this.email) {
window.alert(this.$t('missingEmail')); // eslint-disable-line no-alert
return;
}
if (this.password !== this.passwordConfirm) {
window.alert(this.$t('passwordConfirmationMatch')); // eslint-disable-line no-alert
return;
}
// @TODO: implement language and invite accepting
// var url = ApiUrl.get() + "/api/v4/user/auth/local/register";
// if (location.search && location.search.indexOf('Invite=') !== -1)
// { // matches groupInvite and partyInvite
// url += location.search;
// }
//
// if($rootScope.selectedLanguage) {
// var toAppend = url.indexOf('?') !== -1 ? '&' : '?';
// url = url + toAppend + 'lang=' + $rootScope.selectedLanguage.code;
// }
await this.$store.dispatch('auth:register', {
username: this.username,
email: this.email,
password: this.password,
passwordConfirm: this.passwordConfirm,
});
const redirectTo = this.sanitizeRedirect(this.$route.query.redirectTo);
// @TODO do not reload entire page
// problem is that app.vue created hook should be called again
// after user is logged in / just signed up
// ALSO it's the only way to make sure language data
// is reloaded and correct for the logged in user
// Same situation in login and socialAuth functions
window.location.href = redirectTo;
},
async login () {
await this.$store.dispatch('auth:login', {
username: this.username,
// email: this.email,
password: this.password,
});
@@ -801,31 +642,6 @@ export default {
window.location.href = redirectTo;
},
// @TODO: Abstract hello in to action or lib
async socialAuth (network) {
if (network === 'apple') {
window.location.href = buildAppleAuthUrl();
} else {
try {
await hello(network).logout();
} catch (e) {} // eslint-disable-line
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
const auth = await hello(network).login({
scope: 'email',
// explicitly pass the redirect url or it might redirect to /home
redirect_uri: redirectUrl, // eslint-disable-line camelcase
});
await this.$store.dispatch('auth:socialAuth', {
auth,
});
const redirectTo = this.sanitizeRedirect(this.$route.query.redirectTo);
window.location.href = redirectTo;
}
},
setTitle () {
if (this.resetPasswordSetNewOne) {
return;
@@ -840,7 +656,7 @@ export default {
},
handleSubmit () {
if (this.registering) {
this.register();
this.proceed('local');
return;
}
@@ -896,6 +712,20 @@ export default {
this.resetPasswordSetNewOneData.hasError = false;
this.$router.push({ name: 'login' });
},
validateUsername: debounce(function valUsername (username) {
const usernameIssues = [];
if (username.length > 0 && !isEmail(username)) {
if (username.length > 20) {
usernameIssues.push(this.$t('usernameIssueLength'));
}
const invalidCharsRegex = /[^a-z0-9_-]/i;
const match = username.match(invalidCharsRegex);
if (match !== null && match[0] !== null) {
usernameIssues.push(this.$t('usernameIssueInvalidCharacters'));
}
}
this.usernameIssues = usernameIssues;
}, 500),
},
};
</script>

View File

@@ -0,0 +1,267 @@
<template>
<div class="gradient-bg">
<div
id="privacy-tos"
class="w-100"
>
<privacy-banner
class="privacy-banner"
/>
<div class="d-flex flex-column mx-auto pt-5 w-448px">
<img
class="mx-auto"
src="@/assets/images/home/signup-quill@2x.png"
width="120px"
>
<h1 class="mt-0 mb-3 white mx-auto">
{{ $t('whatToCallYou') }}
</h1>
<form
class="form mx-auto"
@submit.prevent.stop="register()"
>
<input
id="usernameInput"
v-model="username"
class="form-control dark"
type="text"
:placeholder="$t('username')"
:class="{
'mb-3': !usernameInvalid,
'input-valid': username && usernameValid,
'input-invalid mb-2': usernameInvalid,
}"
>
<!-- eslint-disable vue/require-v-for-key -->
<div
v-for="issue in usernameIssues"
class="input-error"
>
<!-- eslint-enable vue/require-v-for-key -->
{{ issue }}
</div>
<p class="purple-600">
{{ $t('usernameLimitations') }}
</p>
<div class="custom-control custom-checkbox mb-4">
<input
id="privacyTOS"
v-model="privacyAccepted"
class="custom-control-input dark"
type="checkbox"
>
<label
v-once
class="custom-control-label purple-600"
for="privacyTOS"
v-html="$t('acceptPrivacyTOS')"
></label>
</div>
<button
class="btn btn-info d-block w-100 sign-up mx-auto mb-5"
:disabled="!username || usernameInvalid || !privacyAccepted"
type="submit"
>
{{ $t('getStarted') }}
</button>
</form>
</div>
</div>
</div>
</template>
<style lang="scss">
@import '@/assets/scss/colors.scss';
#privacy-tos {
position: relative;
z-index: 2;
width: 448px;
background-image: url('@/assets/images/auth/seamless_stars_varied_opacity.png');
background-repeat: repeat-x;
a {
color: $white;
font-weight: bold;
text-decoration: underline;
}
.privacy-banner a {
color: $purple-300;
font-weight: normal;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
</style>
<style lang="scss" scoped>
@import '@/assets/scss/colors.scss';
@import '@/assets/scss/privacy.scss';
@import '@/assets/scss/forms.scss';
p.purple-600 {
line-height: 1.714;
}
.custom-checkbox {
.custom-control-label::before {
border-radius: 2px;
margin-top: 2px;
}
.custom-control-input:checked~.custom-control-label::after {
margin-top: 2px;
}
}
.gradient-bg {
background: linear-gradient(to bottom, $purple-200, $purple-300);
height: 700px;
}
.input-error {
font-size: 90%;
width: 100%;
margin-bottom: 1em;
}
.sign-up {
border: 2px solid transparent;
box-shadow: 0 1px 3px 0 rgba($black, 0.16), 0 1px 3px 0 rgba($black, 0.24);
&: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);
}
}
.w-448px {
width: 448px;
}
</style>
<script>
import debounce from 'lodash/debounce';
import PrivacyBanner from '@/components/header/banners/privacy';
import sanitizeRedirect from '@/mixins/sanitizeRedirect';
export default {
components: {
PrivacyBanner,
},
mixins: [sanitizeRedirect],
data () {
return {
authData: {},
email: '',
password: '',
passwordConfirm: '',
privacyAccepted: false,
registrationMethod: null,
username: '',
usernameIssues: [],
};
},
computed: {
usernameValid () {
if (this.username?.length < 1) return false;
return this.usernameIssues.length === 0;
},
usernameInvalid () {
if (this.username?.length < 1) return false;
return !this.usernameValid;
},
},
watch: {
username () {
this.validateUsername(this.username || '');
},
},
mounted () {
if (window.sessionStorage.getItem('apple-token')) {
this.registrationMethod = 'apple';
} else if (!this.$store.state.registrationOptions.registrationMethod) {
this.$router.push('/');
} else {
this.registrationMethod = this.$store.state.registrationOptions.registrationMethod;
}
this.authData = this.$store.state.registrationOptions.authData;
this.email = this.$store.state.registrationOptions.email;
this.username = this.$store.state.registrationOptions.username;
this.password = this.$store.state.registrationOptions.password;
this.passwordConfirm = this.$store.state.registrationOptions.passwordConfirm;
if (!this.email) {
return;
}
const usernameToCheck = this.email.split('@')[0].replace(/[^a-zA-Z0-9\-_]/g, '');
this.$store.dispatch('auth:verifyUsername', {
username: usernameToCheck,
}).then(res => {
if (!res.issues) {
this.username = usernameToCheck;
}
});
document.getElementById('usernameInput').focus();
},
methods: {
async register () {
if (this.registrationMethod === 'local') {
let groupInvite = '';
if (this.$route.query && this.$route.query.p) {
groupInvite = this.$route.query.p;
}
if (this.$route.query && this.$route.query.groupInvite) {
groupInvite = this.$route.query.groupInvite;
}
await this.$store.dispatch('auth:register', {
username: this.username,
email: this.email,
password: this.password,
passwordConfirm: this.passwordConfirm,
groupInvite,
});
const redirect = this.sanitizeRedirect(this.$route.query.redirectTo);
window.location.href = redirect;
} else if (this.registrationMethod === 'apple') {
await this.$store.dispatch('auth:appleAuth', {
idToken: window.sessionStorage.getItem('apple-token'),
name: window.sessionStorage.getItem('apple-name'),
username: this.username,
allowRegister: true,
});
} else {
await this.$store.dispatch('auth:socialAuth', {
auth: this.authData,
username: this.username,
});
}
window.location.href = '/';
},
// eslint-disable-next-line func-names
validateUsername: debounce(function (username) {
if (username.length < 1) {
return;
}
this.$store.dispatch('auth:verifyUsername', {
username: this.username,
}).then(res => {
if (res.issues !== undefined) {
this.usernameIssues = res.issues;
} else {
this.usernameIssues = [];
}
});
}, 500),
},
};
</script>

View File

@@ -321,8 +321,8 @@ export default {
return null;
},
petClass () {
const foolEvent = this.currentEventList?.find(event => moment()
.isBetween(event.start, event.end) && event.aprilFools);
const foolEvent = this.currentEventList?.find(event => event.aprilFools && moment()
.isBetween(event.start, event.end));
if (foolEvent) {
return this.foolPet(this.member.items.currentPet, foolEvent.aprilFools);
}

View File

@@ -43,9 +43,11 @@ export default {
const AUTH_SETTINGS = localStorage.getItem(LOCALSTORAGE_AUTH_KEY);
const parseSettings = JSON.parse(AUTH_SETTINGS);
const userId = parseSettings ? parseSettings.auth.apiId : '';
const username = this.$store?.state?.user?.data?.auth?.local?.username || '';
return this.$t('accountSuspended', {
userId,
username,
communityManagerEmail: COMMUNITY_MANAGER_EMAIL,
});
},

View File

@@ -72,32 +72,40 @@
</div>
<div class="col-12 col-md-6 text-right">
<div
class="box member-count"
class="box member-count p-2"
@click="showMemberModal()"
>
<div
class="svg-icon member-icon"
v-html="icons.memberIcon"
></div>
{{ challenge.memberCount }}
<div
v-once
class="details"
>
{{ $t('participantsTitle') }}
<div class="box-content">
<div class="icon-number-row">
<div
class="svg-icon member-icon"
v-html="icons.memberIcon"
></div>
<span class="number">{{ challenge.memberCount }}</span>
</div>
<div
v-once
class="details"
>
{{ $t('participantsTitle') }}
</div>
</div>
</div>
<div class="box">
<div
class="svg-icon gem-icon"
v-html="icons.gemIcon"
></div>
{{ challenge.prize || 0 }}
<div
v-once
class="details"
>
{{ $t('prize') }}
<div class="box prize-count p-2">
<div class="box-content">
<div class="icon-number-row">
<div
class="svg-icon gem-icon"
v-html="icons.gemIcon"
></div>
<span class="number">{{ challenge.prize || 0 }}</span>
</div>
<div
v-once
class="details"
>
{{ $t('prize') }}
</div>
</div>
</div>
</div>
@@ -304,7 +312,6 @@
.box {
display: inline-block;
padding: 1em;
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);
@@ -314,22 +321,88 @@
text-align: center;
font-size: 20px;
vertical-align: bottom;
overflow: hidden;
position: relative;
&.member-count:hover {
cursor: pointer;
}
.box-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
}
.icon-number-row {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.1em;
.number {
font-size: 20px;
font-weight: normal;
margin-left: 0.2em;
}
}
.svg-icon {
width: 30px;
display: inline-block;
margin-right: .2em;
vertical-align: bottom;
}
.details {
font-size: 12px;
margin-top: 0.4em;
color: $gray-200;
width: 100%;
padding: 0 4px;
line-height: 1.15;
word-break: break-word;
max-height: 2.3em;
overflow: visible;
}
&.member-count {
.icon-number-row {
.svg-icon {
width: 24px;
height: 24px;
}
.number {
font-size: 18px;
}
}
.details {
font-size: 11px;
line-height: 1.1;
max-height: 2.2em;
}
}
&.prize-count {
.icon-number-row {
.svg-icon {
width: 24px;
height: 24px;
}
.number {
font-size: 18px;
}
}
.details {
font-size: 11px;
line-height: 1.1;
max-height: 2.2em;
}
}
}

View File

@@ -4,6 +4,7 @@
id="close-challenge-modal"
:title="$t('endChallenge')"
size="md"
:hide-header="false"
>
<div
slot="modal-header"
@@ -15,6 +16,9 @@
>
{{ $t('endChallenge') }}
</h2>
<close-x
@close="$root.$emit('bv::hide::modal', 'close-challenge-modal')"
/>
</div>
<div class="row text-center">
<span
@@ -28,28 +32,67 @@
class="col-12"
>
<div class="col-12">
<div class="support-habitica">
<!-- @TODO: Add challenge achievement badge here-->
<div class="badge-section">
<div
class="gems-left"
v-html="icons.gemsOrange"
></div>
<div
class="challenge-badge"
v-html="icons.endChallengeBadge"
></div>
<div
class="gems-right"
v-html="icons.gemsPurple"
></div>
</div>
</div>
<div class="col-12">
<strong v-once>{{ $t('selectChallengeWinnersDescription') }}</strong>
</div>
<div class="col-12">
<member-search-dropdown
:text="winnerText"
:members="members"
:challenge-id="challengeId"
@member-selected="selectMember"
/>
<div class="col-12 search-input-container">
<div class="search-input-wrapper">
<div
class="search-icon"
v-html="icons.search"
></div>
<input
v-model="searchTerm"
class="search-input"
type="text"
placeholder="@Username"
@input="searchMembers"
@focus="showResults = true"
@blur="handleBlur"
>
<div
v-if="showResults && filteredMembers.length > 0"
class="search-results"
>
<div
v-for="member in filteredMembers"
:key="member._id"
class="search-result-item"
@mousedown="selectMember(member)"
>
{{ getMemberDisplayName(member) }}
</div>
</div>
</div>
</div>
<div class="col-12">
<button
v-once
class="btn btn-primary"
class="btn award-winner-btn"
:class="{'has-winner': winner._id}"
:disabled="!winner._id"
@click="closeChallenge"
>
{{ $t('awardWinners') }}
<span>{{ $t('awardWinners') }}</span>
<div
class="gem-icon"
v-html="icons.gem"
></div>
<span>{{ prize }} {{ prize === 1 ? $t('gem') : $t('gems') }}</span>
</button>
</div>
</span>
@@ -60,14 +103,27 @@
</div>
</div>
<div class="col-12">
<strong v-once>{{ $t('doYouWantedToDeleteChallenge') }}</strong>
<strong
v-once
class="delete-challenge-text"
>{{ $t('doYouWantedToDeleteChallenge') }}</strong>
</div>
<div
v-once
class="col-12 refund-text"
>
{{ $t('deleteChallengeRefundDescription') }}
</div>
<div class="col-12">
<button
v-once
class="btn btn-danger"
class="btn btn-danger delete-challenge-btn"
@click="deleteChallenge()"
>
<div
class="svg-icon color delete-icon"
v-html="icons.deleteIcon"
></div>
{{ $t('deleteChallenge') }}
</button>
</div>
@@ -82,6 +138,7 @@
<style lang='scss'>
@import '@/assets/scss/colors.scss';
@import '@/assets/scss/button.scss';
#close-challenge-modal {
h2 {
@@ -94,26 +151,190 @@
.header-wrap {
width: 100%;
padding-top: 2em;
padding-top: 32px;
position: relative;
}
.support-habitica {
background-image: url('@/assets/svg/for-css/support-habitica-gems.svg?raw');
width: 325px;
height: 89px;
.modal-close {
position: absolute;
right: 16px;
top: 16px;
padding: 0;
margin: 0;
}
.search-input-container {
margin-top: 1em !important;
}
.search-input-wrapper {
position: relative;
width: 384px;
margin: 0 auto;
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-55%);
width: 16px;
height: 16px;
color: $gray-200;
pointer-events: none;
display: flex;
align-items: center;
}
.search-input {
width: 100%;
height: 32px;
padding-left: 36px;
padding-right: 12px;
border: 1px solid $gray-400;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s ease, border-width 0.2s ease;
&:focus {
outline: none;
border: 2px solid $purple-400;
}
&::placeholder {
color: $gray-300;
}
}
.search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: $white;
border: 1px solid $gray-400;
border-top: none;
border-radius: 0 0 4px 4px;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.search-result-item {
padding: 8px 16px;
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
&:hover {
background-color: $purple-600;
color: $purple-300;
}
}
}
}
.delete-challenge-text {
color: $maroon-50;
}
.refund-text {
font-family: 'Roboto', sans-serif;
font-size: 14px;
line-height: 24px;
font-weight: 400;
color: $gray-50;
margin-top: 0.5em !important;
}
.delete-challenge-btn {
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: 700;
line-height: 24px;
display: inline-flex;
align-items: center;
gap: 8px;
.delete-icon {
width: 16px;
height: 16px;
display: inline-flex;
}
}
.award-winner-btn {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 32px;
padding: 4px 12px;
transition: all 0.2s ease;
&:not(:disabled) {
background-color: $white;
color: $gray-200;
border: 1px solid $gray-400;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
&.has-winner {
background-color: $purple-200;
color: $white;
border-color: $purple-200;
}
&:hover:not(.has-winner) {
background-color: $gray-700;
}
}
.gem-icon {
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
color: $gems-color;
}
}
.badge-section {
display: flex;
align-items: center;
justify-content: center;
gap: 1.5rem;
margin: -24px auto 0;
padding: 0.5rem 0;
.gems-left, .gems-right {
width: 64px;
height: 64px;
flex-shrink: 0;
}
.challenge-badge {
width: 48px;
height: 52px;
flex-shrink: 0;
}
}
.modal-footer, .modal-header {
border: none !important;
}
.modal-header {
padding: 0 !important;
}
.footer-wrap {
display: none;
}
.col-12 {
margin-top: 2em;
margin-top: 1.5em;
}
.col-12:first-child {
margin-top: 0;
}
.or {
@@ -123,21 +344,39 @@
margin-right: auto;
margin-left: auto;
font-weight: bold;
color: $gray-100;
}
}
</style>
<script>
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
import searchIcon from '@/assets/svg/for-css/search.svg?raw';
import deleteIcon from '@/assets/svg/delete.svg?raw';
import gemIcon from '@/assets/svg/gem.svg?raw';
import endChallengeBadge from '@/assets/svg/for-css/end_challenge_badge.svg?raw';
import gemsOrange from '@/assets/svg/for-css/orange100_red100_yellow100_gems.svg?raw';
import gemsPurple from '@/assets/svg/for-css/purple200_green10_blue100_gems.svg?raw';
import closeX from '@/components/ui/closeX';
export default {
components: {
memberSearchDropdown,
closeX,
},
props: ['challengeId', 'members', 'prize', 'flagCount'],
data () {
return {
winner: {},
searchTerm: '',
showResults: false,
filteredMembers: [],
icons: Object.freeze({
search: searchIcon,
deleteIcon,
gem: gemIcon,
endChallengeBadge,
gemsOrange,
gemsPurple,
}),
};
},
computed: {
@@ -150,8 +389,35 @@ export default {
},
},
methods: {
searchMembers () {
if (!this.searchTerm) {
this.filteredMembers = [];
return;
}
const searchLower = this.searchTerm.toLowerCase().replace('@', '');
this.filteredMembers = this.members.filter(member => {
const username = member.auth?.local?.username || '';
const displayName = member.profile?.name || '';
return username.toLowerCase().includes(searchLower)
|| displayName.toLowerCase().includes(searchLower);
}).slice(0, 10);
},
getMemberDisplayName (member) {
if (member.auth?.local?.username) {
return `@${member.auth.local.username}`;
}
return member.profile?.name || '';
},
selectMember (member) {
this.winner = member;
this.searchTerm = this.getMemberDisplayName(member);
this.showResults = false;
},
handleBlur () {
setTimeout(() => {
this.showResults = false;
}, 200);
},
async closeChallenge () {
this.challenge = await this.$store.dispatch('challenges:selectChallengeWinner', {

View File

@@ -5,7 +5,7 @@
:size="editing ? 'lg' : 'md'"
:hide-header="true"
:hide-footer="true"
:modal-class="{'page-2':modalPage > 1 && !editing}"
:modal-class="{'page-2': !editing}"
:no-close-on-esc="!editing"
:no-close-on-backdrop="!editing"
>
@@ -13,20 +13,6 @@
v-if="editing"
@close="close()"
/>
<div
v-if="modalPage === 1 && !editing"
class="section row welcome-section"
>
<div class="col-6 offset-3 text-center">
<h3 v-once>
{{ $t('welcomeTo') }}
</h3>
<div
class="svg-icon logo"
v-html="icons.logoPurple"
></div>
</div>
</div>
<h2
v-if="editing"
class="text-center pt-2 mt-4 mb-4"
@@ -34,7 +20,6 @@
{{ $t('editAvatar') }}
</h2>
<div
v-if="modalPage > 1"
class="avatar-section d-flex justify-content-center"
:class="{'page-2': modalPage === 2}"
>
@@ -433,8 +418,7 @@
</div>
<div
v-if="!editing"
class="section d-flex justify-content-center justin-outer-section"
:class="{top: modalPage > 1}"
class="section d-flex justify-content-center justin-outer-section top"
>
<div class="justin-section d-flex align-items-center">
<div class="featured-label">
@@ -459,17 +443,8 @@
class="corner-decoration"
:style="{bottom: '-2px', left: '-2px'}"
></div>
<div v-if="modalPage === 1">
<p
v-once
v-html="$t('justinIntroMessage1')"
></p>
<p v-once>
{{ $t('justinIntroMessageUsername') }}
</p>
</div>
<div v-if="modalPage === 2">
<p>{{ $t('justinIntroMessageAppearance') }}</p>
<p v-html="$t('justinIntroMessage1')"></p>
</div>
<div v-if="modalPage === 3">
<p v-once>
@@ -484,25 +459,12 @@
</div>
</div>
<div
v-if="modalPage === 1"
class="section mr-5 ml-5 first-page-footer"
>
<username-form
:avatar-intro="true"
@usernameConfirmed="modalPage += 1"
/>
<div
class="small text-center"
v-html="$t('usernameTOSRequirements')"
></div>
</div>
<div
v-if="!editing && !(modalPage === 1)"
v-if="!editing"
class="section container footer"
>
<div class="footer-left">
<div
v-if="modalPage > 1"
v-if="modalPage > 2"
class="prev-outer"
@click="prev()"
>
@@ -519,10 +481,6 @@
</div>
</div>
<div class="footer-center text-center circles">
<div
class="circle"
:class="{active: modalPage === 1}"
></div>
<div
class="circle"
:class="{active: modalPage === 2}"
@@ -991,7 +949,6 @@ import forEach from 'lodash/forEach';
import content from '@/../../common/script/content/index';
import { mapState } from '@/libs/store';
import avatar from './avatar';
import usernameForm from './settings/usernameForm';
import guide from '@/mixins/guide';
import notifications from '@/mixins/notifications';
import customizeBanner from './avatarModal/customize-banner';
@@ -1025,7 +982,6 @@ export default {
extraSettings,
hairSettings,
skinSettings,
usernameForm,
Sprite,
},
mixins: [guide, notifications, avatarEditorUtilities],
@@ -1053,7 +1009,7 @@ export default {
arrowLeft,
close: svgClose,
}),
modalPage: 1,
modalPage: 2,
activeTopPage: 'body',
activeSubPage: 'size',
taskCategories: [],

View File

@@ -0,0 +1,126 @@
<template>
<div
class="banner d-flex align-items-center justify-content-between py-3 px-4"
id="privacy-banner"
v-if="!hidden"
>
<p
class="mr-3 mb-0"
v-html="$t('privacyOverview') + ' ' + $t('learnMorePrivacy')"
>
</p>
<div
class="navigation d-flex flex-column justify-content-around text-center ml-2"
:class="{ static: isStaticPage }"
>
<button
class="btn btn-primary mb-2"
@click="consent(true)"
>
{{ $t('acceptAllCookies') }}
</button>
<button
class="btn btn-secondary mb-2"
@click="consent(false)"
>
{{ $t('denyNonEssentialCookies') }}
</button>
<a
v-if="isStaticPage"
@click="showPrivacyModal"
>
{{ $t('managePrivacyPreferences') }}
</a>
<router-link
v-else
to="/user/settings/siteData"
>
{{ $t('managePrivacyPreferences') }}
</router-link>
</div>
</div>
</template>
<style lang="scss" scoped>
button {
width: 558px;
}
a, p {
line-height: 1.714;
}
@media only screen and (max-width: 1300px) {
.banner {
flex-direction: column !important;
button {
width: 100%;
}
.navigation {
width: 100%;
margin-left: 0px !important;
}
p {
margin-bottom: 16px !important;
}
}
}
</style>
<script>
import { nextTick } from 'vue';
import { GenericUserPreferencesMixin } from '@/pages/settings/components/genericUserPreferencesMixin';
import { EVENTS } from '@/libs/events';
import { mapState } from '@/libs/store';
export default {
mixins: [GenericUserPreferencesMixin],
computed: {
isStaticPage () {
return this.$route.meta.requiresLogin === false;
},
...mapState({
user: 'user.data',
}),
},
data () {
return {
hidden: false,
};
},
mounted () {
if (localStorage.getItem('analyticsConsent') !== null
|| this.user?.preferences?.analyticsConsent !== undefined
|| navigator.globalPrivacyControl
) {
this.hidden = true;
}
this.$root.$on('privacy-complete', () => {
this.close();
});
},
methods: {
close () {
this.hidden = true;
nextTick(() => {
this.$root.$emit(EVENTS.BANNER_HEIGHT_UPDATED);
});
},
consent (decision) {
if (this.user) {
this.user.preferences.analyticsConsent = decision;
this.setUserPreference('analyticsConsent');
} else {
localStorage.setItem('analyticsConsent', decision);
}
this.close();
},
showPrivacyModal () {
this.$root.$emit('bv::show::modal', 'privacy-preferences');
},
},
};
</script>

View File

@@ -1,37 +1,43 @@
<template>
<div
class="notification d-flex flex-column justify-content-center text-center"
class="notification d-flex justify-content-center align-items-center"
>
<strong
v-once
class="mx-auto mb-2"
<img
src="@/assets/images/gifts_start.svg"
class="gift-start"
alt=""
>
{{ $t('g1g1') }}
</strong>
<small
v-once
class="mx-4 mb-3"
>
{{ $t('g1g1Details') }}
</small>
<div
class="btn-secondary mx-auto d-flex"
@click="showSelectUser()"
>
<div
<div class="content-wrapper d-flex flex-column justify-content-center text-center">
<strong
v-once
class="m-auto"
class="mx-auto mb-2"
>
{{ $t('g1g1') }}
</strong>
<small
v-once
class="mx-4 mb-3"
>
{{ $t('g1g1Details') }}
</small>
<button
class="btn btn-secondary mx-auto"
@click="showSelectUser()"
>
{{ $t('sendGift') }}
</div>
</button>
</div>
<img
src="@/assets/images/gifts_start.svg"
class="gift-end"
alt=""
>
<div
class="notification-remove"
@click.stop="remove()"
class="close-x"
@click="remove()"
>
<div
v-once
class="svg-icon"
class="svg-icon svg-close"
v-html="icons.close"
></div>
</div>
@@ -41,51 +47,89 @@
<style lang='scss' scoped>
@import '@/assets/scss/colors.scss';
small, strong {
small {
color: $white;
font-family: 'Roboto', sans-serif;
font-weight: 400;
font-style: normal;
font-size: 14px;
line-height: 1.714;
letter-spacing: 0;
}
strong {
color: $white;
font-family: 'Roboto', sans-serif;
font-weight: 700;
font-style: normal;
font-size: 14px;
line-height: 1.714;
}
.notification {
background-image: url('@/assets/images/g1g1-notif.png');
background-image: url('@/assets/images/gifts_bg.svg');
background-size: cover;
background-position: center;
height: 10rem;
padding: 3rem;
padding: 0;
position: relative;
overflow: hidden;
white-space: normal;
cursor: pointer;
}
.notification-remove {
position: absolute;
width: 18px;
height: 18px;
padding: 4px;
right: 24px;
top: 24px;
.svg-icon {
width: 10px;
height: 10px;
}
.content-wrapper {
flex: 1;
padding: 2rem;
z-index: 1;
}
.btn-secondary {
width: 5.75rem;
min-height: 1.5rem;
border-radius: 2px;
border-color: $white;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
font-size: 12px;
font-weight: bold;
.gift-start {
height: 96px;
width: auto;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
z-index: 0;
}
.gift-end {
height: 96px;
width: auto;
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%) scaleX(-1);
z-index: 0;
}
.close-x {
position: absolute;
right: 16px;
top: 16px;
cursor: pointer;
z-index: 2;
&:hover .svg-close {
opacity: 0.75;
}
.svg-close {
width: 18px;
height: 18px;
opacity: 0.5;
transition: opacity 0.2s ease;
pointer-events: none;
}
}
</style>
<script>
import closeIcon from '@/assets/svg/close-teal.svg?raw';
import { mapActions } from '@/libs/store';
import closeIcon from '@/assets/svg/close-white.svg?raw';
export default {
props: ['notification'],
props: ['notification', 'eventKey'],
data () {
return {
icons: Object.freeze({
@@ -94,11 +138,11 @@ export default {
};
},
methods: {
...mapActions({
readNotification: 'notifications:readNotification',
}),
remove () {
this.readNotification({ notificationId: this.notification.id });
if (this.eventKey) {
window.sessionStorage.setItem(`hide-g1g1-${this.eventKey}`, 'true');
}
this.$emit('notification-removed');
},
showSelectUser () {
this.$root.$emit('bv::show::modal', 'select-user-modal');

View File

@@ -71,7 +71,7 @@ export default {
props: ['notification', 'canRemove'],
methods: {
action () {
this.$router.push({ name: 'achievements' });
this.$router.push(`/profile/${this.$store.state.user.data._id}#achievements`);
},
},
};

View File

@@ -43,7 +43,7 @@ export default {
},
methods: {
action () {
this.$router.push({ name: 'stats' });
this.$router.push(`/profile/${this.$store.state.user.data._id}#stats`);
},
},
};

View File

@@ -49,6 +49,12 @@
v-if="showOnboardingGuide"
:never-seen="hasSpecialBadge"
/>
<gift-one-get-one-notification
v-if="shouldShowG1g1"
:notification="g1g1Notification"
:event-key="g1g1EventKey"
@notification-removed="handleG1g1Removed"
/>
<component
:is="notification.type"
v-for="notification in notifications"
@@ -114,6 +120,7 @@
<script>
import * as quests from '@/../../common/script/content/quests';
import { hasCompletedOnboarding } from '@/../../common/script/libs/onboarding';
import find from 'lodash/find';
import { mapState, mapActions } from '@/libs/store';
import notificationsIcon from '@/assets/svg/notifications.svg?raw';
import MenuDropdown from '../ui/customMenuDropdown';
@@ -151,6 +158,7 @@ export default {
CARD_RECEIVED,
CHALLENGE_INVITATION,
GIFT_ONE_GET_ONE,
GiftOneGetOneNotification: GIFT_ONE_GET_ONE,
GROUP_TASK_ASSIGNED,
GROUP_TASK_CLAIMED,
GROUP_TASK_NEEDS_WORK,
@@ -178,17 +186,14 @@ export default {
hasSpecialBadge: false,
quests,
openStatus: undefined,
g1g1Hidden: false,
actionableNotifications: [
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
'QUEST_INVITATION',
],
// A list of notifications handled by this component,
// listed in the order they should appear in the notifications panel.
// NOTE: Those not listed here won't be shown in the notification panel!
handledNotifications: [
'NEW_STUFF',
'ITEM_RECEIVED',
'GIFT_ONE_GET_ONE',
'GROUP_TASK_NEEDS_WORK',
'GUILD_INVITATION',
'PARTY_INVITATION',
@@ -207,7 +212,10 @@ export default {
};
},
computed: {
...mapState({ user: 'user.data' }),
...mapState({
user: 'user.data',
currentEventList: 'worldState.data.currentEventList',
}),
notificationsOrder () {
// Returns a map of NOTIFICATION_TYPE -> POSITION
const orderMap = {};
@@ -286,9 +294,9 @@ export default {
return notifications;
},
// The total number of notification, shown inside the dropdown
notificationsCount () {
return this.notifications.length;
const g1g1Count = this.shouldShowG1g1 ? 1 : 0;
return this.notifications.length + g1g1Count;
},
hasUnseenNotifications () {
return this.notifications.some(notification => (notification.seen === false));
@@ -299,6 +307,30 @@ export default {
showOnboardingGuide () {
return !hasCompletedOnboarding(this.user);
},
currentG1g1Event () {
return find(this.currentEventList, event => event.promo === 'g1g1');
},
g1g1EventKey () {
if (!this.currentG1g1Event || !this.currentG1g1Event.start) return null;
const startDate = new Date(this.currentG1g1Event.start);
return `${startDate.getFullYear()}-${startDate.getMonth()}`;
},
shouldShowG1g1 () {
if (!this.currentG1g1Event) return false;
const eventKey = this.g1g1EventKey;
if (eventKey && window.sessionStorage.getItem(`hide-g1g1-${eventKey}`) === 'true') {
return false;
}
return !this.g1g1Hidden;
},
g1g1Notification () {
return {
type: 'GIFT_ONE_GET_ONE',
id: `g1g1-event-${this.currentG1g1Event?.start || 'default'}`,
data: {},
seen: false,
};
},
},
mounted () {
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
@@ -364,6 +396,9 @@ export default {
isActionable (notification) {
return this.actionableNotifications.indexOf(notification.type) !== -1;
},
handleG1g1Removed () {
this.g1g1Hidden = true;
},
},
};

View File

@@ -176,7 +176,12 @@ export default {
}
},
showProfile (startingPage) {
this.$router.push({ name: startingPage });
const userId = this.$store.state.user.data._id;
let path = `/profile/${userId}`;
if (startingPage !== 'profile') {
path += `#${startingPage}`;
}
this.$router.push(path);
},
toLearnMore () {
this.$router.push({ name: 'subscription' });

View File

@@ -454,17 +454,14 @@ export default {
},
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);
if (!username) return false;
const usernamePattern = new RegExp(`@${escapeRegExp(username)}(?:\\b|(?=[^a-zA-Z0-9_]))`, 'i');
message.highlight = usernamePattern.test(message.text);
return message.highlight;
},
flagCountDescription () {

View File

@@ -0,0 +1,122 @@
<template>
<b-modal
id="privacy-preferences"
size="md"
:hide-footer="true"
:hide-header="true"
>
<close-x
@close="close()"
/>
<h1 class="purple-200 mb-3">
{{ $t('yourPrivacyPreferences') }}
</h1>
<p>
{{ $t('privacySettingsOverview') }}
</p>
<div class="mb-4">
<div
class="d-flex justify-content-between align-items-center mb-1"
>
<label class="mb-0">
{{ $t('performanceAnalytics') }}
</label>
<toggle-switch
v-model="privacyConsent"
/>
</div>
<small>
{{ $t('usedForSupport') }}
</small>
</div>
<div class="mt-1 mb-4">
<div
class="d-flex justify-content-between align-items-center mb-1"
>
<label class="mb-0">
{{ $t('strictlyNecessary') }}
</label>
<toggle-switch
:checked="true"
:disabled="true"
/>
</div>
<small>
{{ $t('requiredToRun') }}
</small>
</div>
<div class="d-flex flex-column text-center">
<button
class="btn btn-primary mb-2"
@click="consent(true)"
>
{{ $t('acceptAllCookies') }}
</button>
<button
class="btn btn-primary mb-2"
@click="consent(false)"
>
{{ $t('denyNonEssentialCookies') }}
</button>
<button
class="btn btn-secondary mb-3"
@click="consent(privacyConsent)"
>
{{ $t('savePreferences') }}
</button>
<a
href="/static/privacy"
target="_blank"
>
{{ $t('habiticaPrivacyPolicy') }}
</a>
</div>
</b-modal>
</template>
<style lang="scss">
#privacy-preferences {
.modal-body {
padding: 24px;
}
.modal-content {
width: 448px;
}
}
</style>
<style lang="scss" scoped>
label {
font-weight: 700;
}
label, p {
line-height: 1.714;
}
</style>
<script>
import closeX from '@/components/ui/closeX';
import ToggleSwitch from '@/components/ui/toggleSwitch.vue';
export default {
components: {
closeX,
ToggleSwitch,
},
data () {
return {
privacyConsent: true,
};
},
methods: {
consent (decision) {
localStorage.setItem('analyticsConsent', decision);
this.$root.$emit('privacy-complete');
this.close();
},
close () {
this.$root.$emit('bv::hide::modal', 'privacy-preferences');
},
},
};
</script>

View File

@@ -12,14 +12,12 @@
class="staff col-6 p-0"
>
<div class="d-flex">
<router-link
<div
class="title"
:to="{'name': 'userProfile', 'params': {'userId': user.uuid}}"
>
{{ user.name }}
</router-link>
</div>
<div
v-if="user.type === 'Staff'"
class="svg-icon staff-icon ml-1"
v-html="icons.tierStaff"
></div>

View File

@@ -851,7 +851,7 @@ export default {
return;
}
if (this.genericPurchase) {
this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
await this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
await this.purchased(this.item.text);
}
}

View File

@@ -15,17 +15,17 @@
</div>
<div
class="notification callout pt-0"
class="notification callout py-2 ml-2"
:class="classes"
>
<div
v-if="notification.type === 'error'"
class="row"
class="row pr-4"
>
<div class="text">
<div v-html="notification.text"></div>
</div>
<close-icon />
<close-x />
</div>
<div
v-if="notification.type === 'streak'"
@@ -52,7 +52,7 @@
<div class="text">
<div>{{ message }}</div>
</div>
<div class="icon d-flex align-items-center">
<div class="icon d-flex align-items-center ml-3">
<div
v-if="notification.type === 'hp'"
class="svg-icon"
@@ -74,7 +74,7 @@
v-html="icons.mana"
></div>
<div
class="icon-text"
class="icon-text ml-1"
v-html="notification.text"
></div>
</div>
@@ -86,13 +86,13 @@
<div class="text">
<div>{{ message }}</div>
</div>
<div class="icon d-flex align-items-center">
<div class="icon d-flex align-items-center ml-3">
<div
class="svg-icon"
v-html="icons.sword"
></div>
<div
class="icon-text"
class="icon-text ml-1"
v-html="notification.text"
></div>
</div>
@@ -121,6 +121,15 @@
<style lang="scss" scoped>
@import '@/assets/scss/colors.scss';
::v-deep .modal-close {
top: 11px;
right: 11px;
.svg-close {
color: $black;
}
}
.notification-holder {
display: flex;
flex-direction: row;
@@ -132,14 +141,14 @@
}
.notification {
color: $black;
max-width: 330px;
border-radius: 4px;
background-color: $green-50;
padding-left: 12px;
padding-right: 12px;
background-color: $green-100;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
color: white;
margin-left: 0.5rem;
padding-left: 1rem !important;
padding-right: 1rem !important;
line-height: 1.714;
transition: opacity .5s, top .5s;
@@ -149,45 +158,21 @@
}
.info {
background-color: $blue-50;
background-color: $blue-100;
padding-top: .5rem;
}
.error {
background-color: $maroon-100;
color: $white;
background-color: $red-100;
position: relative;
padding-right: 1.5rem !important;
cursor: pointer;
::v-deep button {
height: 9px;
width: 9px;
top: 0.525rem;
right: 0.525rem;
padding: 0;
opacity: 0.5;
svg path {
stroke: white;
}
}
&:hover {
::v-deep button {
opacity: 1;
}
}
}
.negative {
background-color: $maroon-100;
background-color: $red-100;
}
.text {
padding: .5rem 0;
::v-deep p:last-of-type {
margin-bottom: 0; // remove last markdown padding
}
@@ -196,10 +181,10 @@
.svg-icon {
width: 24px;
height: 24px;
margin: 0.35rem;
}
.drop {
color: $white;
background-color: $gray-50;
}
@@ -210,7 +195,6 @@
}
.icon-text {
color: $white;
font-weight: bold;
}
@@ -230,12 +214,12 @@ import gold from '@/assets/svg/gold.svg?raw';
import star from '@/assets/svg/star.svg?raw';
import mana from '@/assets/svg/mana.svg?raw';
import sword from '@/assets/svg/sword.svg?raw';
import CloseIcon from '../shared/closeIcon';
import CloseX from '@/components/ui/closeX';
import Sprite from '@/components/ui/sprite';
export default {
components: {
CloseIcon,
CloseX,
Sprite,
},
props: ['notification', 'visibleAmount'],

View File

@@ -96,6 +96,7 @@ export default {
notificationTopY: '0px',
preventMultipleWatchExecution: false,
eventPromoBannerHeight: null,
privacyBannerHeight: null,
sleepingBannerHeight: null,
warningBannerHeight: null,
};
@@ -114,6 +115,10 @@ export default {
notificationBannerHeight () {
let scrollPosToCheck = 56;
if (this.privacyBannerHeight) {
scrollPosToCheck += this.privacyBannerHeight;
}
if (this.warningBannerHeight) {
scrollPosToCheck += this.warningBannerHeight;
}
@@ -164,6 +169,9 @@ export default {
window.addEventListener('scroll', this.updateScrollY, {
passive: true,
});
window.addEventListener('resize', this.updateBannerHeightAndScrollY, {
passive: true,
});
this.$root.$on(EVENTS.BANNER_HEIGHT_UPDATED, () => {
this.updateBannerHeightAndScrollY();
@@ -339,6 +347,7 @@ export default {
updateBannerHeightAndScrollY () {
this.updateEventBannerHeight();
this.privacyBannerHeight = document.getElementById('privacy-banner')?.getBoundingClientRect().height || 0;
this.warningBannerHeight = getBannerHeight('chat-warning');
this.sleepingBannerHeight = getBannerHeight('damage-paused');
this.updateScrollY();

View File

@@ -16,7 +16,7 @@
.static-view p {
padding-top: 100px;
font-size: 2em
font-size: 2em;
}
</style>
@@ -26,13 +26,19 @@ export default {
async mounted () {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const reqParams = { code: urlParams.get('code') };
const reqParams = { code: urlParams.get('code'), allowRegister: false };
if (urlParams.has('name')) {
reqParams.name = urlParams.get('name');
window.sessionStorage.setItem('apple-name', reqParams.name);
}
const response = await this.$store.dispatch('auth:appleAuth', reqParams);
if (response.id) {
window.sessionStorage.removeItem('apple-token');
window.location.href = '/';
} else {
window.sessionStorage.setItem('apple-token', response.idToken);
window.location.href = '/username';
}
await this.$store.dispatch('auth:appleAuth', reqParams);
window.location.href = '/';
},
};
</script>

View File

@@ -120,9 +120,9 @@
>
<ul>
<li>
{{ $t('commGuideAKA', {habitName: 'heyeilatan', realName: 'Natalie'}) }}
({{ $t('commGuideOnGitHub', {gitHubName: 'CuriousMagpie'}) }})
- Web Developer
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
({{ $t('commGuideOnGitHub', {gitHubName: 'phillipthelen'}) }})
- Developer
</li>
<li>
{{ $t('commGuideAKA', {habitName: 'redphoenix', realName: 'Vicky'}) }}
@@ -133,10 +133,6 @@
{{ $t('commGuideAKA', {habitName: 'Beffymaroo', realName: 'Beth'}) }}
- Art, Community Management, Many Hats
</li>
<li>
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Sabe'}) }}
- Web Developer
</li>
<li>
{{ $t('commGuideAKA', {habitName: 'Apollo', realName: 'Tressley'}) }}
- Designer
@@ -146,8 +142,13 @@
- Mobile Designer
</li>
<li>
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
- Mobile Developer
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Kalista'}) }}
- Web Developer
</li>
<li>
{{ $t('commGuideAKA', {habitName: 'fizself', realName: 'Hafiz'}) }}
({{ $t('commGuideOnGitHub', {gitHubName: 'hafizzle'}) }})
- Developer
</li>
</ul>
<p v-html="$t('commGuidePara013')"></p>
@@ -156,7 +157,7 @@
<em>
Lemoness, lefnire, Slappybag, litenull, Shaner, Bobbyroberts99, wc8,
Breadstrings, Megan, Blade, Daniel the Bard, deilann, shanaqui, Nakonana,
Dewines, Alys, Fox_town, MaybeSteveRogers, and Cantras.
Dewines, Alys, Fox_town, MaybeSteveRogers, Cantras, and heyeilatan.
</em>
</p>
<h2 id="final">

View File

@@ -117,18 +117,6 @@
</div>
<div class="bot-right"></div>
</div>
<b-modal
id="group-plan"
title
size="md"
:hide-footer="true"
:hide-header="true"
>
<div>
<h2>{{ $t('letsMakeAccount') }}</h2>
<auth-form @authenticate="authenticate()" />
</div>
</b-modal>
</div>
</div>
<div
@@ -315,12 +303,10 @@
<script>
import { setup as setupPayments } from '@/libs/payments';
import paymentsMixin from '../../mixins/payments';
import AuthForm from '../auth/authForm.vue';
import GroupPlanCreationModal from '../group-plans/groupPlanCreationModal.vue';
export default {
components: {
AuthForm,
GroupPlanCreationModal,
},
mixins: [paymentsMixin],
@@ -353,13 +339,11 @@ export default {
},
methods: {
authenticate () {
this.$root.$emit('bv::hide::modal', 'group-plan');
this.$root.$emit('bv::show::modal', 'create-group');
},
goToNewGroupPage () {
if (this.isStaticPage && !this.user) {
this.modalOption = 'static';
return this.$root.$emit('bv::show::modal', 'group-plan');
return this.$router.push('/register');
}
if (this.upgradingGroup._id) {
return this.stripeGroup({ group: this.upgradingGroup, upgrade: true });

View File

@@ -8,124 +8,117 @@
<br />
<a href="https://www.enable-javascript.com/" target="_blank">{{ $t('jsDisabledLink') }}</a>
</noscript>
<div
id="intro-signup"
class="purple-1"
>
<div class="container">
<div class="row">
<div class="col-12 col-md-6 col-lg-6">
<img
src="@/assets/images/home/home-main@3x.png"
width="357px"
>
<h1>{{ $t('motivateYourself') }}</h1>
<p class="section-main">
{{ $t('timeToGetThingsDone', {userCountInMillions}) }}
</p>
</div>
<div class="col-12 col-md-6 col-lg-6">
<h3 class="text-center">
{{ $t('singUpForFree') }}
</h3>
<form
class="form"
@submit.prevent.stop="register()"
>
<p class="form-text">
{{ $t('usernameLimitations') }}
<privacy-banner
class="privacy-banner"
/>
<div class="bg-purple-300 white">
<div>
<div
id="intro-signup"
>
<div class="d-flex justify-content-center">
<div class="w-33 mr-5 mt-5">
<img
src="@/assets/images/home/home-main@3x.png"
width="357px"
>
<h1>{{ $t('motivateYourself') }}</h1>
<p class="section-main">
{{ $t('timeToGetThingsDone', {userCountInMillions}) }}
</p>
<input
id="usernameInput"
v-model="username"
class="form-control input-with-error"
type="text"
:placeholder="$t('username')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
>
<!-- eslint-disable vue/require-v-for-key -->
<div
v-for="issue in usernameIssues"
class="input-error"
>
<!-- eslint-enable vue/require-v-for-key -->
{{ issue }}
</div>
<input
v-model="email"
class="form-control"
type="email"
:placeholder="$t('email')"
:class="{'input-invalid': emailInvalid, 'input-valid': emailValid}"
>
<input
v-model="password"
class="form-control input-with-error"
type="password"
:placeholder="$t('password')"
:class="{
'input-valid': passwordValid,
'input-invalid': passwordInvalid,
}"
>
<div
v-if="passwordInvalid"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
<input
v-model="passwordConfirm"
class="form-control input-with-error"
type="password"
:placeholder="$t('confirmPassword')"
:class="{
'input-invalid': passwordConfirmInvalid,
'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<p
v-once
class="form-text"
v-html="$t('termsAndAgreement')"
></p>
<button
class="btn btn-block btn-info sign-up"
:disabled="signupFormInvalid"
type="submit"
>
{{ $t('signup') }}
</button>
</form>
<div class="strike">
<span>{{ $t('or') }}</span>
</div>
<div class="text-center">
<button
class="social-button"
@click="socialAuth('google')"
<div class="w-33 ml-5">
<h3 class="text-center">
{{ $t('singUpForFree') }}
</h3>
<form
class="form pb-0"
@submit.prevent.stop="proceed('local')"
>
<input
v-model="email"
class="form-control input-with-error dark"
type="email"
:placeholder="$t('email')"
:class="{
'mb-3': !emailError,
'input-valid': emailValid,
'input-invalid mb-2': emailError,
}"
>
<div
class="svg-icon social-icon"
v-html="icons.googleIcon"
></div>
<span>{{ $t('signUpWithSocial', {social: 'Google'}) }}</span>
</button>
<button
class="social-button"
@click="socialAuth('apple')"
>
v-if="emailError"
class="input-error"
>
{{ emailError }}
</div>
<input
v-model="password"
class="form-control input-with-error dark"
type="password"
:placeholder="$t('password')"
:class="{
'mb-3': !passwordInvalid,
'input-valid': passwordValid,
'input-invalid mb-2': passwordInvalid,
}"
>
<div
class="svg svg-icon social-icon apple-icon color"
v-html="icons.appleIcon"
></div>
<span>{{ $t('signUpWithSocial', {social: 'Apple'}) }}</span>
</button>
v-if="passwordInvalid"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
<input
v-model="passwordConfirm"
class="form-control input-with-error dark"
type="password"
:placeholder="$t('confirmPassword')"
:class="{
'mb-3': !passwordConfirmInvalid,
'input-invalid mb-2': passwordConfirmInvalid,
'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<button
id="continue-button"
class="btn btn-block btn-info"
:disabled="!(emailValid && passwordValid && passwordConfirmValid)"
type="submit"
>
{{ $t('continue') }}
</button>
</form>
<div class="strike">
<span>{{ $t('or') }}</span>
</div>
<div class="text-center">
<button
class="social-button"
@click="proceed('google')"
>
<div
class="svg-icon social-icon"
v-html="icons.googleIcon"
></div>
<span>{{ $t('signUpWithSocial', {social: 'Google'}) }}</span>
</button>
<button
class="social-button"
@click="proceed('apple')"
>
<div
class="svg svg-icon social-icon apple-icon color"
v-html="icons.appleIcon"
></div>
<span>{{ $t('signUpWithSocial', {social: 'Apple'}) }}</span>
</button>
</div>
</div>
</div>
<div class="col-12">
@@ -135,218 +128,218 @@
></div>
</div>
</div>
</div>
</div>
<div
id="gamify-life"
class="purple-2"
>
<div class="container-fluid">
<div
class="pixel-horizontal svg-icon"
v-html="icons.pixelHorizontal"
></div>
</div>
<div class="container">
<div class="row">
<div class="col-12 col-sm-6 col-md-6 col-lg-6 offset-sm-3 text-center">
<h2>{{ $t('gamifyYourLife') }}</h2>
<p class="section-main">
{{ $t('aboutHabitica') }}
</p>
id="gamify-life"
class="bg-purple-100 white"
>
<div class="container-fluid">
<div
class="pixel-horizontal svg-icon"
v-html="icons.pixelHorizontal"
></div>
</div>
</div>
<div class="row">
<div class="col-12 col-md-4">
<img
class="track-habits"
src="@/assets/images/home/track-habits@3x.png"
width="354px"
height="228px"
>
<strong>{{ $t('trackYourGoals') }}</strong>
<p>{{ $t('trackYourGoalsDesc') }}</p>
<div class="container">
<div class="row">
<div class="col-12 col-sm-6 col-md-6 col-lg-6 offset-sm-3 text-center">
<h2>{{ $t('gamifyYourLife') }}</h2>
<p class="section-main">
{{ $t('aboutHabitica') }}
</p>
</div>
</div>
<div class="row">
<div class="col-12 col-md-4">
<img
class="track-habits"
src="@/assets/images/home/track-habits@3x.png"
width="354px"
height="228px"
>
<strong>{{ $t('trackYourGoals') }}</strong>
<p>{{ $t('trackYourGoalsDesc') }}</p>
</div>
<div class="col-12 col-md-4">
<img
src="@/assets/images/home/earn-rewards@3x.png"
width="316px"
height="244px"
>
<strong>{{ $t('earnRewards') }}</strong>
<p>{{ $t('earnRewardsDesc') }}</p>
</div>
<div class="col-12 col-md-4">
<img
src="@/assets/images/home/battle-monsters@3x.png"
width="303px"
height="244px"
>
<strong>{{ $t('battleMonsters') }}</strong>
<p>{{ $t('battleMonstersDesc') }}</p>
</div>
</div>
</div>
<div class="col-12 col-md-4">
<img
src="@/assets/images/home/earn-rewards@3x.png"
width="316px"
height="244px"
>
<strong>{{ $t('earnRewards') }}</strong>
<p>{{ $t('earnRewardsDesc') }}</p>
</div>
<div class="col-12 col-md-4">
<img
src="@/assets/images/home/battle-monsters@3x.png"
width="303px"
height="244px"
>
<strong>{{ $t('battleMonsters') }}</strong>
<p>{{ $t('battleMonstersDesc') }}</p>
</div>
</div>
</div>
<div class="col-12">
<div
class="spacer svg-icon"
v-html="icons.spacer"
></div>
</div>
</div>
<div
id="use-cases"
class="purple-2"
>
<div class="container text-center">
<div class="row">
<div class="col-12">
<h2>{{ $t('playersUseToImprove') }}</h2>
<div
class="spacer svg-icon"
v-html="icons.spacer"
></div>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-4">
<img
src="@/assets/images/home/health-fitness@3x.png"
width="300px"
height="300px"
>
<strong>{{ $t('healthAndFitness') }}</strong>
<p>{{ $t('healthAndFitnessDesc') }}</p>
</div>
<div class="col-12 col-sm-4">
<img
src="@/assets/images/home/school-work@3x.png"
width="300px"
height="300px"
>
<strong>{{ $t('schoolAndWork') }}</strong>
<p>{{ $t('schoolAndWorkDesc') }}</p>
</div>
<div class="col-12 col-sm-4">
<img
src="@/assets/images/home/much-more@3x.png"
width="300px"
height="300px"
>
<strong>{{ $t('muchmuchMore') }}</strong>
<p>{{ $t('muchmuchMoreDesc') }}</p>
</div>
</div>
</div>
<div class="col-12">
<div
class="spacer svg-icon"
v-html="icons.spacer"
></div>
</div>
<div class="container-fluid">
id="use-cases"
class="bg-purple-100 white"
>
<div class="container text-center">
<div class="row">
<div class="col-12">
<h2>{{ $t('playersUseToImprove') }}</h2>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-4">
<img
src="@/assets/images/home/health-fitness@3x.png"
width="300px"
height="300px"
>
<strong>{{ $t('healthAndFitness') }}</strong>
<p>{{ $t('healthAndFitnessDesc') }}</p>
</div>
<div class="col-12 col-sm-4">
<img
src="@/assets/images/home/school-work@3x.png"
width="300px"
height="300px"
>
<strong>{{ $t('schoolAndWork') }}</strong>
<p>{{ $t('schoolAndWorkDesc') }}</p>
</div>
<div class="col-12 col-sm-4">
<img
src="@/assets/images/home/much-more@3x.png"
width="300px"
height="300px"
>
<strong>{{ $t('muchmuchMore') }}</strong>
<p>{{ $t('muchmuchMoreDesc') }}</p>
</div>
</div>
</div>
<div class="col-12">
<div
class="spacer svg-icon"
v-html="icons.spacer"
></div>
</div>
<div class="container-fluid">
<div
class="pixel-horizontal-2 svg-icon"
v-html="icons.pixelHorizontal2"
></div>
</div>
</div>
<div
class="pixel-horizontal-2 svg-icon"
v-html="icons.pixelHorizontal2"
></div>
</div>
</div>
<div
id="level-up-anywhere"
class="purple-3"
>
<div class="container">
<div class="row">
<div class="col-12 col-md-6 col-lg-6">
<div class="iphones"></div>
id="level-up-anywhere"
class="bg-purple-50 white"
>
<div class="container">
<div class="row">
<div class="col-12 col-md-6 col-lg-6">
<div class="iphones"></div>
</div>
<div class="col-12 col-md-6 col-lg-6 text-column">
<h2>{{ $t('levelUpAnywhere') }}</h2>
<p>{{ $t('levelUpAnywhereDesc') }}</p>
<a
class="app svg-icon"
href="https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica"
target="_blank"
v-html="icons.googlePlay"
></a>
<a
class="app svg-icon"
href="https://itunes.apple.com/us/app/habitica-gamified-task-manager/id994882113?mt=8"
target="_blank"
v-html="icons.iosAppStore"
></a>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-lg-6 text-column">
<h2>{{ $t('levelUpAnywhere') }}</h2>
<p>{{ $t('levelUpAnywhereDesc') }}</p>
<a
class="app svg-icon"
href="https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica"
target="_blank"
v-html="icons.googlePlay"
></a>
<a
class="app svg-icon"
href="https://itunes.apple.com/us/app/habitica-gamified-task-manager/id994882113?mt=8"
target="_blank"
v-html="icons.iosAppStore"
></a>
<div class="container-fluid">
<div
class="pixel-horizontal-3 svg-icon"
v-html="icons.pixelHorizontal3"
></div>
</div>
</div>
</div>
<div class="container-fluid">
<div
class="pixel-horizontal-3 svg-icon"
v-html="icons.pixelHorizontal3"
></div>
</div>
</div>
<div
id="call-to-action"
class="purple-4"
>
<div class="container featured">
<div class="row text-center">
<h3 class="col-12">
{{ $t('joinMany', {userCountInMillions}) }}
</h3>
</div>
<div class="row">
<div class="col-12 text-center">
<button
class="btn btn-primary btn-front join-button"
@click="playButtonClick()"
>
{{ $t('joinToday') }}
</button>
id="call-to-action"
class="purple-4 white"
>
<div class="container featured">
<div class="row text-center">
<h3 class="col-12">
{{ $t('joinMany', {userCountInMillions}) }}
</h3>
</div>
<div class="row">
<div class="col-12 text-center">
<button
class="btn btn-primary btn-front join-button"
@click="playButtonClick()"
>
{{ $t('joinToday') }}
</button>
</div>
</div>
<div class="row featured">
<div class="col-12 text-center">
<strong>{{ $t('featuredIn') }}</strong>
</div>
</div>
</div>
<div class="container-fluid featured">
<div class="row">
<div class="col-12 text-center">
<div
class="lifehacker svg-icon"
v-html="icons.lifehacker"
></div>
<div
class="thenewyorktimes svg-icon"
v-html="icons.thenewyorktimes"
></div>
<div
class="makeuseof svg-icon"
v-html="icons.makeuseof"
></div>
<div
class="forbes svg-icon"
v-html="icons.forbes"
></div>
<div
class="cnet svg-icon"
v-html="icons.cnet"
></div>
<div
class="kickstarter svg-icon"
v-html="icons.kickstarter"
></div>
<div
class="fast-company svg-icon"
v-html="icons.fastCompany"
></div>
<div
class="discover svg-icon"
v-html="icons.discover"
></div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row seamless_stars_varied_opacity_repeat"></div>
</div>
</div>
<div class="row featured">
<div class="col-12 text-center">
<strong>{{ $t('featuredIn') }}</strong>
</div>
</div>
</div>
<div class="container-fluid featured">
<div class="row">
<div class="col-12 text-center">
<div
class="lifehacker svg-icon"
v-html="icons.lifehacker"
></div>
<div
class="thenewyorktimes svg-icon"
v-html="icons.thenewyorktimes"
></div>
<div
class="makeuseof svg-icon"
v-html="icons.makeuseof"
></div>
<div
class="forbes svg-icon"
v-html="icons.forbes"
></div>
<div
class="cnet svg-icon"
v-html="icons.cnet"
></div>
<div
class="kickstarter svg-icon"
v-html="icons.kickstarter"
></div>
<div
class="fast-company svg-icon"
v-html="icons.fastCompany"
></div>
<div
class="discover svg-icon"
v-html="icons.discover"
></div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row seamless_stars_varied_opacity_repeat"></div>
</div>
</div>
</div>
@@ -354,16 +347,27 @@
<style lang='scss'>
@import '@/assets/scss/static.scss';
#front .form-text a {
color: $white !important;
#front {
.form-text a {
color: $white !important;
}
.privacy-banner p {
font-size: 14px;
}
}
</style>
<style lang="scss" scoped>
@import '@/assets/scss/colors.scss';
@import '@/assets/scss/privacy.scss';
@import '@/assets/scss/forms.scss';
@import url('https://fonts.googleapis.com/css?family=Varela+Round');
.w-33 {
width: 33%;
}
#front {
.container-fluid {
margin: 0;
@@ -374,22 +378,10 @@
padding-bottom: 5em;
}
.purple-1, .purple-2, .purple-3, .purple-4, h1, h2, h3, h4, h5 {
.custom-control-label, h1, h2, h3, h4, h5 {
color: $white;
}
.purple-1 {
background-color: $purple-300;
}
.purple-2 {
background-color: $purple-100;
}
.purple-3 {
background-color: $purple-50;
}
.purple-4 {
background-color: $header-dark-background;
}
@@ -423,18 +415,26 @@
color: $header-dark-background;
}
h1, h2, h3, h4, h5, h6, button, .strike > span, input {
h1, h2, h3, h4, h5, h6, .strike > span {
font-family: 'Varela Round', sans-serif;
font-weight: normal;
}
.seamless_stars_varied_opacity_repeat {
background-image: url('@/assets/images/auth/seamless_stars_varied_opacity.png');
background-repeat: repeat-x;
height: 500px;
width: 100%;
}
}
#intro-signup {
background-image: url('@/assets/svg/for-css/confetti.svg?raw');
img {
margin: 0 auto;
display: block;
@media only screen and (min-width: 992px) {
margin-left: 15%;
}
}
h1 {
@@ -458,6 +458,7 @@
transition: .5s;
span {
font-weight: 700;
transition: none;
}
}
@@ -521,64 +522,6 @@
padding-top: 1em;
padding-bottom: 1em;
}
input {
margin-bottom: 1em;
border-radius: 2px;
background-color: $purple-100;
border-color: $purple-100;
color: $purple-400;
border: solid 2px transparent;
transition-timing-function: ease;
transition: border .5s, color .5s;
}
.input-invalid.input-with-error {
margin-bottom: 0.5em;
}
.input-valid {
color: $white;
}
input:focus {
border: solid 2px $purple-400;
color: #fff;
background-color: $purple-50;
}
input:hover {
background-color: $purple-50;
}
.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 */
color: $purple-400;
}
::-moz-placeholder { /* Firefox 19+ */
color: $purple-400;
}
:-ms-input-placeholder { /* IE 10+ */
color: $purple-400;
}
:-moz-placeholder { /* Firefox 18- */
color: $purple-400;
}
::placeholder { // Standard browsers
color: $purple-400;
}
}
#gamify-life {
@@ -779,20 +722,14 @@
}
.input-error {
color: $white;
font-size: 90%;
width: 100%;
margin-bottom: 1em;
}
</style>
<script>
import hello from 'hellojs';
import debounce from 'lodash/debounce';
import isEmail from 'validator/es/lib/isEmail';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import { buildAppleAuthUrl } from '../../libs/auth';
import sanitizeRedirect from '@/mixins/sanitizeRedirect';
import notifications from '@/mixins/notifications';
import accountCreation from '@/mixins/accountCreation';
import PrivacyBanner from '@/components/header/banners/privacy';
import googlePlay from '@/assets/images/home/google-play-badge.svg?raw';
import iosAppStore from '@/assets/images/home/ios-app-store.svg?raw';
import iphones from '@/assets/images/home/iphones.svg?raw';
@@ -813,7 +750,10 @@ import makeuseof from '@/assets/images/home/make-use-of.svg?raw';
import thenewyorktimes from '@/assets/images/home/the-new-york-times.svg?raw';
export default {
mixins: [sanitizeRedirect],
components: {
PrivacyBanner,
},
mixins: [accountCreation, notifications],
data () {
return {
icons: Object.freeze({
@@ -837,132 +777,17 @@ export default {
thenewyorktimes,
}),
userCountInMillions: 4,
username: '',
password: '',
passwordConfirm: '',
email: '',
usernameIssues: [],
};
},
computed: {
emailValid () {
if (this.email.length < 1) return false;
return isEmail(this.email);
},
emailInvalid () {
if (this.email.length < 1) return false;
return !isEmail(this.email);
},
usernameValid () {
if (this.username.length < 1) return false;
return this.usernameIssues.length === 0;
},
usernameInvalid () {
if (this.username.length < 1) return false;
return !this.usernameValid;
},
passwordValid () {
if (this.password.length <= 0) return false;
return this.password.length >= MINIMUM_PASSWORD_LENGTH;
},
passwordInvalid () {
if (this.password.length <= 0) return false;
return this.password.length < MINIMUM_PASSWORD_LENGTH;
},
passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password;
},
passwordConfirmInvalid () {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm !== this.password;
},
signupFormInvalid () {
return this.usernameInvalid
|| this.emailInvalid
|| this.passwordInvalid
|| this.passwordConfirmInvalid;
},
},
watch: {
username () {
this.validateUsername(this.username);
},
},
mounted () {
hello.init({
google: import.meta.env.GOOGLE_CLIENT_ID, // eslint-disable-line
});
this.$store.dispatch('common:setTitle', {
fullTitle: 'Habitica - Gamify Your Life',
});
},
methods: {
// eslint-disable-next-line func-names
validateUsername: debounce(function (username) {
if (username.length < 1) {
return;
}
this.$store.dispatch('auth:verifyUsername', {
username: this.username,
}).then(res => {
if (res.issues !== undefined) {
this.usernameIssues = res.issues;
} else {
this.usernameIssues = [];
}
});
}, 500),
// @TODO this is totally duplicate from the registerLogin component
async register () {
let groupInvite = '';
if (this.$route.query && this.$route.query.p) {
groupInvite = this.$route.query.p;
}
if (this.$route.query && this.$route.query.groupInvite) {
groupInvite = this.$route.query.groupInvite;
}
await this.$store.dispatch('auth:register', {
username: this.username,
email: this.email,
password: this.password,
passwordConfirm: this.passwordConfirm,
groupInvite,
});
const redirect = this.sanitizeRedirect(this.$route.query.redirectTo);
window.location.href = redirect;
},
playButtonClick () {
this.$router.push('/register');
},
// @TODO: Abstract hello in to action or lib
async socialAuth (network) {
if (network === 'apple') {
window.location.href = buildAppleAuthUrl();
} else {
try {
await hello(network).logout();
} catch (e) {} // eslint-disable-line
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
const auth = await hello(network).login({
scope: 'email',
// explicitly pass the redirect url or it might redirect to /home
redirect_uri: redirectUrl, // eslint-disable-line camelcase
});
await this.$store.dispatch('auth:socialAuth', {
auth,
});
window.location.href = '/';
}
},
},
};
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,821 +0,0 @@
<template>
<!-- eslint-disable max-len -->
<div class="container-fluid">
<h1>HabitRPG Privacy Policy</h1>
<p class="strong pagemeta">
Last Updated September 1, 2025.
</p>
<p>
This Privacy Policy applies when you interact with us through <a href="https://habitica.com">Habitica.com</a> (the <strong>Site</strong>), our mobile apps, and/or through any other feature or service owned or controlled by HabitRPG, Inc. ("<strong>HabitRPG</strong>", "<strong>we</strong>", or "<strong>us</strong>") that posts, links to, or references this Privacy Policy (collectively, the "<strong>Service(s)</strong>"). This Privacy Policy informs you of our practices regarding the collection, use, and disclosure of personal information we receive from users of our Services. By accessing or using the Services, you consent to our Privacy Policy and our collection, use, and disclosure of your information as described in this policy, our <a href="https://habitica.com/static/terms">Terms of Use</a>, and any additional policies and terms you may agree to in connection with the Services.
</p>
<h1>Table of Contents</h1>
<ol>
<li>
<a href="#section_1">Collection of Information</a>
<ol>
<li><a href="#section_1_1">Information you Provide Directly</a></li>
<li><a href="#section_1_2">Information We Collect Automatically</a></li>
<li><a href="#section_1_3">Location Data</a></li>
<li><a href="#section_1_2">Information Collected By and/or Disclosed to Third Parties</a></li>
</ol>
</li>
<li><a href="#section_2">Purpose and Use of Information We Collect</a></li>
<li><a href="#section_3">Service Providers</a></li>
<li><a href="#section_4">Sweepstakes, Contests, and Promotions</a></li>
<li><a href="#section_5">Security</a></li>
<li><a href="#section_6">Data Retention</a></li>
<li><a href="#section_7">General Audience Services</a></li>
<li><a href="#section_8">Consent to International Transfer</a></li>
<li><a href="#section_9">Your Choices</a></li>
<li><a href="#section_10">Changes to This Privacy Policy</a></li>
<li><a href="#section_11">Jurisdiction-Specific Rights</a></li>
<li><a href="#section_12">Contact Us</a></li>
</ol>
<h2 id="section_1">
1. Collection of Information
</h2>
<p>
We and our third-party service providers and business partners may collect information from you directly and/or automatically when you visit the Site or use the Services. We and our third-party service providers may also collect information about you from third parties. Some of this information may be considered "personal information" or "personal data" under applicable laws (collectively, personal information). We consider information that identifies you as a specific, identified individual (such as your name, phone number, and email address) to be personal information. We also, where required by applicable law, consider additional types of identifying data, like IP addresses and cookie identifiers, to be personal information.
</p>
<p>
We may, in accordance with applicable law, take your personal information and de-identify or pseudonymize it to make it non-personally identifiable, either by combining it with information about other individuals and/or by hashing the information or otherwise removing characteristics that make the information personally identifiable. We maintain and use de-identified or pseudonymized data without attempting to re-identify it, except where permitted by applicable law, such as to determine whether our de-identification processes satisfy legal requirements. We will treat de-identified or pseudonymized information as non-personal to the fullest extent allowed by applicable law.
</p>
<p>
We may collect the following categories of personal information from or about you:
</p>
<p>
<u>Identifiers and Contact Information</u>. This category includes names, addresses, telephone numbers, mobile numbers, email addresses, signatures, account names, dates of birth, bank account information, and other similar contact information and identifiers.
</p>
<p>
<u>Commercial Information</u>. This category includes, without limitation, products and services purchased, obtained, or considered, or other purchasing or consuming histories or tendencies.
</p>
<p>
<u>Internet or Other Electronic Network Activity Information</u>. This category includes, without limitation, browsing history, search history, or a consumers interactions with a website, application, or advertisement.
</p>
<p>
<u>Geolocation Data</u>. This category includes, without limitation, location information collected when using our Services.
</p>
<p>
<u>Sensitive Personal Information</u>. This category includes:
</p>
<ul>
<li>racial or ethnic origin, citizenship or immigration status, or religious beliefs;</li>
<li>mental or physical health diagnosis;</li>
<li>sexual orientation; and</li>
<li>information collected from a known child.</li>
</ul>
<p><strong>
NOTE: Please do not provide us sensitive personal information or sensitive personal data, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary.
</strong></p>
<h3 id="section_1_1">
1.1 Information You Provide Directly
</h3>
<p>
We may ask you to provide certain personal information when you use the Services. This information may include contact information (such as your name and email), account information (such as your email address and, if you choose to log in through Google or Apple, the associated user ID and email address), transaction information (such as your billing address and mailing address), or user content you choose to upload (such as photos and task lists). Note that all payments are handled by our third-party payment providers who may collect relevant information in order to complete your transaction (such as your payment card, billing address, and phone number). Our current providers are Apple, Google, PayPal, and Stripe. We encourage you to review their respective privacy policies for additional information regarding their privacy practices.
</p>
<p>
We may also ask you to provide the contact information of another individual, such as when you invite another user to the Services. By providing us this information, you represent to us that you do so with the consent of the individual to whom it relates. We will only use this information for the specific reason for which it was provided.
</p>
<h3 id="section_1_2">
1.2 Information We Collect Automatically
</h3>
<p>
We and third-party companies and business partners may use a variety of technologies, including, without limitation, cookies, pixels, embedded scripts, and session events (collectively, cookies) that automatically or passively collect certain information whenever you visit our Site, use our Services, or otherwise interact with us or our content (<strong>Usage Information</strong>). Usage Information may include the hardware model, browser, and operating system you are using, the URL or advertisement that referred you to the Site you are visiting or Service you are using, all of the areas within the Site that you visit or Services that you use, your time zone, non-precise location information, and mobile network (if applicable), among other information. In addition, we automatically collect your IP address or other unique identifier ("<strong>Device Identifier</strong>") for any computer, mobile phone or other device you use to access our Services. In some cases, we may directly collect location information through your device. You may be able to turn off the collection of location information through the settings on your device. Usage Information is generally non-identifying, but if HabitRPG associates it with you as a specific and identifiable person, HabitRPG treats it as personal information.
</p>
<h4>Types of Cookies</h4>
<ul>
<li><u>First and Third-Party Cookies</u>. First party cookies are generally placed on your computer or device by the website you are visiting. For example, we may use a first party cookie to improve Site security. Third-party cookies are placed on your computer or device by a source other than the website you are visiting in order to enable third-party features such as advertising, analytics, videos, or interactive content.</li>
<li><u>Essential or Strictly Necessary Cookies</u>. These cookies are necessary for the Site to function and cannot be switched off in our systems. They are usually set in response to actions taken by you which amount to a request for Services, such as setting your privacy preferences, logging in, or filling in forms.</li>
<li><u>Performance and Functionality Cookies</u>. Although these are non-essential cookies, they help the Site perform and function as designed. For example, performance and functionality cookies may help the Site display videos, enable chat sessions, or recognize whether you visited the Site before.</li>
<li><u>Analytics Cookies</u>. These cookies track your usage of the Site. The information these cookies collect can be used for various purposes such as understanding how visitors use the Site, Site and content customization, or advertising and marketing.</li>
<li><u>Advertising Cookies</u>. Advertising and marketing cookies perform functions such as helping customize your Site experience, personalizing ads based on your online activities and interests, measuring the effectiveness of ads, preventing an ad from reappearing, and serving you targeted advertisements. Information from these cookies may be disclosed to third parties or third parties may place these cookies on your computer or device. Advertising and marketing cookies may track your online activities.</li>
</ul>
<p>
The Site uses essential, functional, and performance cookies to function and perform as designed; analytics cookies to understand how you use the Site, improve its functionality, and other related purposes; and advertising cookies to help us with our advertising and marketing activities. We and our third-party partners and service providers may collect and track information about your online activities over time and across different websites, applications, and devices.
</p>
<h4>Google Analytics</h4>
<p>
We use Google Analytics, a service which uses cookies to collect and analyze data about the use of the Services and report on activities and trends. This service may also collect data about the use of other websites, apps, and online services. You can <a
href="https://policies.google.com/technologies/partner-sites"
target="_blank"
>learn about</a> Google's practices, and opt out of them, by downloading the <a
href="https://tools.google.com/dlpage/gaoptout"
target="_blank"
>Google Analytics opt-out browser add-on</a>.
</p>
<h4>Controlling Cookies</h4>
<p>
You may control cookies, including preventing or stopping the installation and storage of cookies, through your browser settings and other tools. Most browsers will allow you to block or refuse cookies. However, you may need to manually adjust your preferences each time you visit a site. For more information, see the Help section of your browser. Please note that if you block certain cookies, some of the services and functionalities of our Site may not work.
</p>
<p class="important">
IMPORTANT: BY USING THE SITE, YOU CONSENT TO THE PROCESSING OF ANY PERSONAL INFORMATION FOR THE PURPOSES AND FUNCTIONS DESCRIBED ABOVE.
</p>
<h4>Do Not Track</h4>
<p>
“Do Not Track” is a privacy preference that you can set in your Internet search browser that sends a signal to a website that you do not want the website operator to track certain browsing information about you. However, because our Site is not configured to detect Do Not Track signals from a users computer, we are unable to respond to Do Not Track requests.
</p>
<h3 id="section_1_3">
1.3 Location Data
</h3>
<p>
We do not collect your precise location. However, please note that we may still be able to collect or infer your approximate location through other information we collect, such as IP address. In addition, some mobile service providers may also provide us or our third-party service providers with information regarding the non-precise physical location of the device you use to access our Services.
</p>
<h3 id="section_1_4">
1.4 Information Collected By and/or Disclosed to Third Parties
</h3>
<p>
We may receive information about you from third parties. For example, you may have the opportunity to log in through or otherwise connect your Apple and Google accounts. Additionally, when you interact with us through social media, you will be choosing to share information about your interactions with HabitRPG with that social media service.
</p>
<p>
The following chart sets out by category the personal information we may collect ("<strong>Category</strong>"), the purposes for which we may collect it ("<strong>A. Purposes</strong>"), the categories of third parties to which we may disclose it for business purposes ("<strong>B. Disclosed To</strong>"), and the categories of third parties to which we may sell it for monetary value or other valuable consideration or share it for cross-context behavioral advertising/targeted marketing ("<strong>C. Sold/Shared To</strong>")
</p>
<button
for="showThirdParties"
class="btn btn-primary"
@click="toggleThirdPartyInfo"
>
{{ showThirdPartyInfo ? "Hide Information from Third Parties" : "Show Information from Third Parties" }}
</button>
<div v-if="showThirdPartyInfo">
<table class="table table-bordered">
<thead>
<tr>
<th
rowspan="3"
class="first-column"
>
Category of Data
</th>
<th>A. Purpose for Collection;</th>
</tr>
<tr>
<th>B. Categories of Third Parties to which Personal Information may be Disclosed;</th>
</tr>
<tr>
<th>C. Categories of Third Parties to which the Personal Information may be Sold/Shared for Targeted Advertising</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">
<strong>Contact Information</strong> (such as your name, email address, mailing address)
</td>
<td>
<h6>A. Purposes for Collection:</h6>
<p>
<strong>(1) Provide You the Service</strong> (to process and fulfill your purchases of products; enable you to use the Services and other products or services we offer, including maintaining your account information and verifying information you provide to us, such as that your email address is active and valid; send you transactional messages regarding your use of the Services, your purchases, and other account-related communications; respond to your inquiries and provide customer support or request feedback from you); <strong>(2) Advertising Activities and Marketing Messages</strong> (to customize and optimize content for you, such as to send you marketing communications if you have provided us your email address, as permitted by applicable law); <strong>(3) Administrative</strong> (to verify your identity, recognize you across the Services and your devices; contact you regarding your use of the Services, content, features, or products you use or request, and, in our discretion, changes to our policies); <strong>(4) Internal Business Purposes</strong> (to operate and improve the Services and our products, services, and marketing endeavors; market research; analyze the effectiveness of our marketing efforts through our third-party service providers; detect and troubleshoot problems; monetize our Services); <strong>(5) Security</strong> (to protect integrity of the Services); <strong>(6) Legal</strong> (to comply with the law; resolve disputes; enforce our agreements and policies; cooperate in governmental or other legal inquiries; fulfill regulatory reporting obligations; protect our or others' rights, assets, safety or security); <strong>(7) Business Transitions</strong> (in connection with a merger, acquisition, consolidation, bankruptcy, or other corporate transition, including during due diligence); <strong>(8) Other Purposes</strong> as disclosed at the time of collection, with your consent (where required by law), or as permitted by applicable law.
</p>
</td>
</tr>
<tr>
<td>
<h6>B. Categories of Third Parties to which Personal Information may be Disclosed:</h6>
<p>
Analytics Partners (such as analytics companies); Social Media Platforms; Business Partners; Technology Systems Service Providers (such as webhosts, database hosts, cloud computing providers, software-as-a-service providers, and technology maintenance and repair vendors); Security Vendors; Communications Service Providers (such as email delivery and direct mail vendors); Customer Service Providers; Government Agencies and Law Enforcement/Where Required by Law (including third parties in connection with a court or other legal proceeding); Third Parties in connection with court or other legal actions; Third Parties in connection with business transitions; Third Parties to whom you agree or direct us to share your data; Other Service Providers we engage to provide services to us and/or you.
</p>
</td>
</tr>
<tr>
<td>
<h6>C. Categories of Third Parties to which Personal Information may be Sold/Shared for Targeted Advertising:</h6>
<p>
Analytics Partners.
</p>
</td>
</tr>
</tbody>
</table>
<table class="table table-bordered">
<thead>
<tr>
<th
rowspan="3"
class="first-column"
>
Category of Data
</th>
<th>A. Purpose for Collection;</th>
</tr>
<tr>
<th>B. Categories of Third Parties to which Personal Information may be Disclosed;</th>
</tr>
<tr>
<th>C. Categories of Third Parties to which the Personal Information may be Sold/Shared for Targeted Advertising</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">
<strong>User Account Information</strong> (such as account name, communication preferences, and account settings)
</td>
<td>
<h6>A. Purposes for Collection:</h6>
<p>
<strong>(1) Provide You the Service</strong> (to process your purchases; to enable you to use the Services and other products or services we offer, including maintaining your account information and verifying information you provide to us, such as that your email address is active and valid; send you transactional messages regarding your use of the Services and other account-related communications; respond to your inquiries and provide customer support or request feedback from you); <strong>(2) Advertising Activities and Marketing Messages</strong> (to customize and optimize content for you, such as to send you in-app announcements and marketing communications if you have provided us your email address, as permitted by applicable law); <strong>(3) Personalization</strong> (to tailor the content we display to you on the Services, including content in emails we may send to you, to tell you about new products, promotions, opportunities, or other general information about HabitRPG or our products that we believe will be of interest to you); <strong>(4) Administrative</strong> (to verify your identity, recognize you across the Services and your devices; contact you regarding your use of the Services, content, features, or products you use or request, and, in our discretion, changes to our policies); <strong>(5) Internal Business Purposes</strong> (to operate and improve the Services and our products, services, and marketing endeavors; market research; analyze the effectiveness of our marketing efforts; detect and troubleshoot problems; understand how you use the Services including tracking traffic, usage, trends, and navigation patterns); <strong>(6) Security</strong> (to protect integrity of the Services; to investigate, prevent, and detect, and protect against misuse of our systems, abuse, fraud or other crime, or illegal activities or those that violate our policies; to detect and troubleshoot problems); <strong>(7) Legal</strong> (to comply with the law; resolve disputes; enforce our agreements and policies; cooperate in governmental or other legal inquiries; fulfill regulatory reporting obligations; protect our or others' rights, assets, safety or security); <strong>(8) Business Transitions</strong> (in connection with a merger, acquisition, consolidation, bankruptcy, or other corporate transition, including during due diligence); <strong>(9) Other Purposes</strong> as disclosed at the time of collection, with your consent (where required by applicable law), or as permitted by applicable law.
</p>
</td>
</tr>
<tr>
<td>
<h6>B. Categories of Third Parties to which Personal Information may be Disclosed:</h6>
<p>
Technology Systems Service Providers (such as webhosts, database hosts, cloud computing providers, software- as-a-service providers, and technology maintenance and repair vendors); Security Vendors; Communications Service Providers; Customer Service Providers; Government Agencies and Law Enforcement/Where Required by Law (including third parties in connection with a court or other legal proceeding); Third Parties in connection with court or other legal actions; Third Parties in connection with business transitions; Third Parties to whom you agree or direct us to share your data; Other Service Providers we engage to provide services to us and/or you.
</p>
</td>
</tr>
<tr>
<td>
<h6>C. Categories of Third Parties to which Personal Information may be Sold/Shared for Targeted Advertising:</h6>
<p>
This category of data is not sold or shared for targeted advertising.
</p>
</td>
</tr>
</tbody>
</table>
<table class="table table-bordered">
<thead>
<tr>
<th
rowspan="3"
class="first-column"
>
Category of Data
</th>
<th>A. Purpose for Collection;</th>
</tr>
<tr>
<th>B. Categories of Third Parties to which Personal Information may be Disclosed;</th>
</tr>
<tr>
<th>C. Categories of Third Parties to which the Personal Information may be Sold/Shared for Targeted Advertising</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">
<strong>Demographic Information</strong> (such as your zip code, birth day/month)
</td>
<td>
<h6>A. Purposes for Collection:</h6>
<p>
<strong>(1) Provide You the Service</strong> (to enable you to use the Services and other products or services we offer, including verifying information you provide to us); <strong>(2) Advertising Activities and Marketing Messages</strong> (to customize and optimize content for you); <strong>(3) Personalization</strong> (to tailor the content and advertising we display to you or others, on the Services or elsewhere, including content in emails we may send to you); <strong>(4) Administrative</strong> (to verify your identity, recognize you across the Services and your devices); <strong>(5) Internal Business Purposes</strong> (to operate and improve the Services and our products, services, and marketing endeavors; market research; analyze the effectiveness of our marketing efforts; detect and troubleshoot problems; understand how you use the Services including tracking traffic, usage, trends, and navigation patterns); <strong>(6) Security</strong> (to protect integrity of the Services; to investigate, prevent, and detect, and protect against misuse of our systems, abuse, fraud or other crime, or illegal activities or those that violate our policies; to detect and troubleshoot problems); <strong>(7) Legal</strong> (to comply with the law; resolve disputes; enforce our agreements and policies; cooperate in governmental or other legal inquiries; fulfill regulatory reporting obligations; protect our or others' rights, assets, safety or security); <strong>(8) Business Transitions</strong> (in connection with a merger, acquisition, consolidation, bankruptcy, or other corporate transition, including during due diligence); <strong>(9) Other Purposes</strong> as disclosed at the time of collection, with your consent (where required by applicable law), or as permitted by applicable law.
</p>
</td>
</tr>
<tr>
<td>
<h6>B. Categories of Third Parties to which Personal Information may be Disclosed:</h6>
<p>
Social Media Platforms and Other Third Party Platforms; Third Party Business Partners; Order Fulfillment Vendors (such as payment processors); Technology Systems Service Providers (such as webhosts, database hosts, cloud computing providers, software-as-a-service providers, and technology maintenance and repair vendors); Security Vendors; Customer Service Providers; Government Agencies and Law Enforcement/Where Required by Law (including third parties in connection with a court or other legal proceeding); Third Parties in connection with court or other legal actions; Third Parties in connection with business transitions; Third Parties to whom you agree or direct us to share your data; Other Service Providers we engage to provide services to us and/or you.
</p>
</td>
</tr>
<tr>
<td>
<h6>C. Categories of Third Parties to which Personal Information may be Sold/Shared for Targeted Advertising:</h6>
<p>
This category of data is not sold or shared for targeted advertising.
</p>
</td>
</tr>
</tbody>
</table>
<table class="table table-bordered">
<thead>
<tr>
<th
rowspan="3"
class="first-column"
>
Category of Data
</th>
<th>A. Purpose for Collection;</th>
</tr>
<tr>
<th>B. Categories of Third Parties to which Personal Information may be Disclosed;</th>
</tr>
<tr>
<th>C. Categories of Third Parties to which the Personal Information may be Sold/Shared for Targeted Advertising</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">
<strong>Device ID/Interaction Information</strong> (such as IP address or other unique ID for any computer, mobile phone or other device used to access the Services, device information (model, browser, operating system version), URL or advertisement that referred you to the Services, any search terms you entered into a search engine that led you to the Services; areas within Services that you visit, time of day you visited the Services, your time zone, location information, and mobile network (if applicable) information)
</td>
<td>
<h6>A. Purposes for Collection:</h6>
<p>
<strong>(1) Provide You the Service</strong> (to process and fulfill your purchases of products; to enable you to use the Services and other products or services we offer, including maintaining your account information and verifying information you provide to us, such as that your email address is active and valid; send you transactional messages regarding your use of the Services and other account-related communications); <strong>(2) Advertising Activities and Marketing Messages</strong> (to customize and optimize content for you, such as to send you in-app announcements and marketing communications if you have provided us your email address, as permitted by applicable law); <strong>(3) Personalization</strong> (to tailor the content and advertising we display to you or others, on the Services or elsewhere, including content in emails we may send to you, to tell you about new products, promotions, opportunities, or other general information about HabitRPG or our products that we believe will be of interest to you); <strong>(4) Administrative</strong> (to verify your identity, recognize you across the Services and your devices; contact you regarding your use of the Services, content, features, or products you use or request, and, in our discretion, changes to our policies); <strong>(5) Internal Business Purposes</strong> (to operate and improve the Services and our products, services, and marketing endeavors; market research; analyze the effectiveness of our marketing efforts; detect and troubleshoot problems; monetize our Services; understand how you use the Services including tracking traffic, usage, trends, and navigation patterns); <strong>(6) Security</strong> (to protect integrity of the Services; to investigate, prevent, and detect, and protect against misuse of our systems, abuse, fraud or other crime, or illegal activities or those that violate our policies; to detect and troubleshoot problems); <strong>(7) Legal</strong> (to comply with the law; resolve disputes; enforce our agreements and policies; cooperate in governmental or other legal inquiries; fulfill regulatory reporting obligations; protect our or others' rights, assets, safety or security); <strong>(8) Business Transitions</strong> (in connection with a merger, acquisition, consolidation, bankruptcy, or other corporate transition, including during due diligence); <strong>(9) Other Purposes</strong> as disclosed at the time of collection, with your consent (where required by law), or as permitted by applicable law.
</p>
</td>
</tr>
<tr>
<td>
<h6>B. Categories of Third Parties to which Personal Information may be Disclosed:</h6>
<p>
Analytics Partners; Social Media Platforms and Other Third Party Platforms; Third Party Business Partners; Order Fulfillment Vendors (such as payment processors, fulfillment centers, delivery services, order tracking vendors); Technology Systems Service Providers (such as webhosts, database hosts, cloud computing providers, software-as- a-service providers, and technology maintenance and repair vendors); Security Vendors; Communications Service Providers; Customer Service Providers; Government Agencies and Law Enforcement/Where Required by Law (including third parties in connection with a court or other legal proceeding); Third Parties in connection with court or other legal actions; Third Parties in connection with business transitions; Third Parties to whom you agree or direct us to share your data; Other Service Providers we engage to provide services to us and/or you.
</p>
</td>
</tr>
<tr>
<td>
<h6>C. Categories of Third Parties to which Personal Information may be Sold/Shared for Targeted Advertising:</h6>
<p>
Analytics Partners.
</p>
</td>
</tr>
</tbody>
</table>
<table class="table table-bordered">
<thead>
<tr>
<th
rowspan="3"
class="first-column"
>
Category of Data
</th>
<th>A. Purpose for Collection;</th>
</tr>
<tr>
<th>B. Categories of Third Parties to which Personal Information may be Disclosed;</th>
</tr>
<tr>
<th>C. Categories of Third Parties to which the Personal Information may be Sold/Shared for Targeted Advertising</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">
<strong>Transaction Information</strong> (such as data about the products you purchase, obtained, or considered, customer service contacts)
</td>
<td>
<h6>A. Purposes for Collection:</h6>
<p>
<strong>(1) Provide You the Service</strong> (to enable you to use the Services and other products or services we offer, including maintaining your account information and verifying information you provide to us; send you transactional messages regarding your use of the Services, your purchases, and other account-related communications; respond to your inquiries and provide customer support or request feedback from you); <strong>(2) Advertising Activities and Marketing Messages</strong> (to customize and optimize content for you, such as to send you in-app announcements and marketing communications if you have provided us your email address, as permitted by applicable law); <strong>(3) Personalization</strong> (to tailor the content we display to you on the Services, including content in emails we may send to you, to tell you about new products, promotions, opportunities, or other general information about HabitRPG or our products that we believe will be of interest to you); <strong>(4) Administrative</strong> (to verify your identity, recognize you across the Services and your devices; contact you regarding your use of the Services, content, features, or products you use or request, and, in our discretion, changes to our policies); <strong>(5) Internal Business Purposes</strong> (to operate and improve the Services and our products, services, and marketing endeavors; market research; analyze the effectiveness of our marketing efforts; detect and troubleshoot problems; monetize our Services; understand how you use the Services including tracking traffic, usage, trends, and navigation patterns); <strong>(6) Security</strong> (to protect integrity of the Services; to investigate, prevent, and detect, and protect against misuse of our systems, abuse, fraud or other crime, or illegal activities or those that violate our policies; to detect and troubleshoot problems); <strong>(7) Legal</strong> (to comply with the law; resolve disputes; enforce our agreements and policies; cooperate in governmental or other legal inquiries; fulfill regulatory reporting obligations; protect our or others' rights, assets, safety or security); <strong>(8) Business Transitions</strong> (in connection with a merger, acquisition, consolidation, bankruptcy, or other corporate transition, including during due diligence); <strong>(9) Other Purposes</strong> as disclosed at the time of collection, with your consent (where required by applicable law), or as permitted by applicable law.
</p>
</td>
</tr>
<tr>
<td>
<h6>B. Categories of Third Parties to which Personal Information may be Disclosed:</h6>
<p>
Order Fulfillment Vendors (such as payment processors, fulfillment centers, delivery services, order tracking vendors); Technology Systems Service Providers (such as webhosts, database hosts, cloud computing providers, software-as- a-service providers, and technology maintenance and repair vendors); Security Vendors; Communications Service Providers; Customer Service Providers; Government Agencies and Law Enforcement/Where Required by Law (including third parties in connection with a court or other legal proceeding); Third Parties in connection with court or other legal actions; Third Parties in connection with business transitions; Third Parties to whom you agree or direct us to share your data; Other Service Providers we engage to provide services to us and/or you.
</p>
</td>
</tr>
<tr>
<td>
<h6>C. Categories of Third Parties to which Personal Information may be Sold/Shared for Targeted Advertising:</h6>
<p>
This category of data is not sold or shared for targeted advertising.
</p>
</td>
</tr>
</tbody>
</table>
<table class="table table-bordered">
<thead>
<tr>
<th
rowspan="3"
class="first-column"
>
Category of Data
</th>
<th>A. Purpose for Collection;</th>
</tr>
<tr>
<th>B. Categories of Third Parties to which Personal Information may be Disclosed;</th>
</tr>
<tr>
<th>C. Categories of Third Parties to which the Personal Information may be Sold/Shared for Targeted Advertising</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">
<strong>Information about your Interests and Preferences</strong> (such as information about the products you purchased, obtained, or considered)
</td>
<td>
<h6>A. Purposes for Collection:</h6>
<p>
<strong>(1) Provide You the Service</strong> (to enable you to use the Services and other products or services we offer, including maintaining your account information and verifying information you provide to us; send you transactional messages regarding your use of the Services and other account-related communications; respond to your inquiries and provide customer support or request feedback from you); <strong>(2) Personalization</strong> (to tailor the content we display to you or others, on the Services or elsewhere, including content in emails we may send to you, to tell you about new products, promotions, opportunities, or other general information about HabitRPG or our products that we believe will be of interest to you); <strong>(3) Internal Business Purposes</strong> (to operate and improve the Services and our products, services, and marketing endeavors; market research; analyze the effectiveness of our marketing efforts; detect and troubleshoot problems; monetize our Services; understand how you use the Services including tracking traffic, usage, trends, and navigation patterns); <strong>(4) Security</strong> (to protect integrity of the Services; to investigate, prevent, and detect, and protect against misuse of our systems, abuse, fraud or other crime, or illegal activities or those that violate our policies; to detect and troubleshoot problems); <strong>(5) Legal</strong> (to comply with the law; resolve disputes; enforce our agreements and policies; cooperate in governmental or other legal inquiries; fulfill regulatory reporting obligations; protect our or others' rights, assets, safety or security); <strong>(6) Business Transitions</strong> (in connection with a merger, acquisition, consolidation, bankruptcy, or other corporate transition, including during due diligence); <strong>(7) Other Purposes</strong> as disclosed at the time of collection, with your consent (where required by applicable law), or as permitted by applicable law.
</p>
</td>
</tr>
<tr>
<td>
<h6>B. Categories of Third Parties to which Personal Information may be Disclosed:</h6>
<p>
Social Media Platforms and Other Third Party Platforms; Third Party Business Partners; Technology Systems Service Providers (such as webhosts, database hosts, cloud computing providers, software-as-a-service providers, and technology maintenance and repair vendors); Security Vendors; Communications Service Providers; Customer Service Providers; Government Agencies and Law Enforcement/Where Required by Law (including third parties in connection with a court or other legal proceeding); Third Parties in connection with court or other legal actions; Third Parties in connection with business transitions; Third Parties to whom you agree or direct us to share your data; Other Service Providers we engage to provide services to us and/or you.
</p>
</td>
</tr>
<tr>
<td>
<h6>C. Categories of Third Parties to which Personal Information may be Sold/Shared for Targeted Advertising:</h6>
<p>
This category of data is not sold or shared for targeted advertising.
</p>
</td>
</tr>
</tbody>
</table>
<table class="table table-bordered">
<thead>
<tr>
<th
rowspan="3"
class="first-column"
>
Category of Data
</th>
<th>A. Purpose for Collection;</th>
</tr>
<tr>
<th>B. Categories of Third Parties to which Personal Information may be Disclosed;</th>
</tr>
<tr>
<th>C. Categories of Third Parties to which the Personal Information may be Sold/Shared for Targeted Advertising</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">
<strong>Photo/Video Data, and Other User Generated Content</strong> (such as tasks, profile, chat messages)
</td>
<td>
<h6>A. Purposes for Collection:</h6>
<p>
<strong>(1) Provide You the Service</strong> (to enable you to use the Services and other products or services we offer, including maintaining your account information and permitting you to create to do lists and upload related content; send you transactional messages regarding your use of the Services and other account-related communications; respond to your inquiries and provide customer support or request feedback from you); <strong>(2) Administrative</strong> (to verify your identity, recognize you across the Services and your devices; contact you regarding your use of the Services, content, features, or products you use or request, and, in our discretion, changes to our policies); <strong>(3) Internal Business Purposes</strong> (to operate and improve the Services and our products, services, and marketing endeavors; market research; detect and troubleshoot problems; monetize our Services; understand how you use the Services including tracking traffic, usage, trends, and navigation patterns); <strong>(4) Security</strong> (to protect integrity of the Services; to investigate, prevent, and detect, and protect against misuse of our systems, abuse, fraud or other crime, or illegal activities or those that violate our policies; to detect and troubleshoot problems); <strong>(5) Legal</strong> (to comply with the law; resolve disputes; enforce our agreements and policies; cooperate in governmental or other legal inquiries; fulfill regulatory reporting obligations; protect our or others' rights, assets, safety or security); <strong>(6) Business Transitions</strong> (in connection with a merger, acquisition, consolidation, bankruptcy, or other corporate transition, including during due diligence); <strong>(7) Other Purposes</strong> as disclosed at the time of collection, with your consent (where required by applicable law), or as permitted by applicable law.
</p>
</td>
</tr>
<tr>
<td>
<h6>B. Categories of Third Parties to which Personal Information may be Disclosed:</h6>
<p>
Third Party Business Partners; Technology Systems Service Providers (such as webhosts, database hosts, cloud computing providers, software-as-a-service providers, and technology maintenance and repair vendors); Security Vendors; Communications Service Providers; Customer Service Providers; Government Agencies and Law Enforcement/Where Required by Law (including third parties in connection with a court or other legal proceeding); Third Parties in connection with court or other legal actions; Third Parties in connection with business transitions; Third Parties to whom you agree or direct us to share your data; Other Service Providers we engage to provide services to us and/or you.
</p>
</td>
</tr>
<tr>
<td>
<h6>C. Categories of Third Parties to which Personal Information may be Sold/Shared for Targeted Advertising:</h6>
<p>
This category of data is not sold or shared for targeted advertising.
</p>
</td>
</tr>
</tbody>
</table>
</div>
<h2 id="section_2">
2. Purpose and Use of Information We Collect
</h2>
<p>
We may use non-personal information for any purpose, including for research and marketing purposes. We also use information that we collect, including personal information and Usage Information, as disclosed in this Privacy Policy and as follows:
</p>
<ul>
<li>to provide the Services to you and allow you to use the features we offer;</li>
<li>to verify your identity and to otherwise manage your user account;</li>
<li>to tailor and target content, recommendations, and offers we display to you via the Services;</li>
<li>to send you communications with information about our products and Services;</li>
<li>to fulfill your order, send you an order confirmation, process your payment, and communicate with you about your order;</li>
<li>to respond to your inquiries, customer service questions, feedback, or requests;</li>
<li>to provide you with technical support;</li>
<li>to improve our Services and for legal, regulatory, and internal business purposes; and</li>
<li>to fulfill any other purpose consistent with this Privacy Policy.</li>
</ul>
<p>
We may also use your personal information for any other purpose disclosed to you at the time of collection, with your consent (where required by applicable law), or permitted by applicable law.
</p>
<h2 id="section_3">
3. Service Providers
</h2>
<p>
Our service providers may collect information on our behalf and at our direction, in order to provide Services on our behalf to help with our business activities.
</p>
<p>
These companies are authorized to use your personal information only as necessary to provide these Services to us and for other purposes permitted by applicable law.
</p>
<p>
Our Service Providers include, without limitation:
</p>
<table class="table table-bordered">
<thead>
<th>
Service Provider Name
</th>
<th>
Product(s)
</th>
</thead>
<tbody>
<tr>
<td>Google Cloud</td>
<td>cloud computing; storage</td>
</tr>
<tr>
<td>MongoDB</td>
<td>database</td>
</tr>
<tr>
<td>Heroku</td>
<td>cloud-based testing</td>
</tr>
<tr>
<td>Amazon Web Services</td>
<td>content storage</td>
</tr>
<tr>
<td>Hetzner</td>
<td>translations and push notifications</td>
</tr>
<tr>
<td>Stripe</td>
<td>payment processing</td>
</tr>
<tr>
<td>PayPal</td>
<td>payment processing</td>
</tr>
<tr>
<td>Amazon Payments</td>
<td>payment processing</td>
</tr>
<tr>
<td>Apple App Store</td>
<td>app host</td>
</tr>
<tr>
<td>Google Play Store</td>
<td>app host</td>
</tr>
<tr>
<td>Mailchimp</td>
<td>email marketing</td>
</tr>
<tr>
<td>Gmail</td>
<td>internal communications</td>
</tr>
<tr>
<td>Redislabs</td>
<td>rate limiting</td>
</tr>
<tr>
<td>Loggly</td>
<td>log management and analytics</td>
</tr>
<tr>
<td>Slack</td>
<td>internal communications</td>
</tr>
<tr>
<td>Amplitude</td>
<td>analytics</td>
</tr>
</tbody>
</table>
<h2 id="section_4">
4. Sweepstakes, Contents and Promotions
</h2>
<p>
We may offer sweepstakes, contests, or other promotions (collectively, "<strong>Promotion(s)</strong>") that may require registration. By participating in a Promotion, you agree to the provisions, conditions, or official rules that govern the Promotion, which may contain specific requirements of you (including, except where prohibited by law, allowing the sponsor(s) of the Promotion to use your name, voice, likeness, or other indicia of persona in advertising or marketing materials). If you choose to enter a Promotion, personal information may be disclosed to co-promotion partners, third parties or the public in connection with the administration of such Promotion, including in connection with winner selection, prize fulfillment, as required by law, or as permitted by the Promotion's terms or official rules.
</p>
<h2 id="section_5">
5. Security
</h2>
<p>
HabitRPG maintains commercially reasonable administrative, physical, and technical safeguards that are designed to secure your personal information; however, no data transmission over the Internet, wireless transmission, or electronic storage of data can be guaranteed to be 100% secure. HabitRPG cannot ensure or warrant the security of any data we collect. You use the Services and provide us your data at your own risk.
</p>
<h2 id="section_6">
6. Data Retention
</h2>
<p>
We retain your personal information for as long as necessary to provide you Services and in accordance with our data retention schedule. We may retain your personal information for longer if it is necessary to comply with our legal or reporting obligations, resolve disputes, enforce contracts, or address other legitimate business needs, or as permitted or required by applicable law. We may also retain your personal information in a deidentified or aggregated form so that it can no longer be associated with you. To determine the appropriate retention period for your personal information, we consider various factors, such as the amount, nature, and sensitivity of your information; the potential risk of unauthorized access, use, or disclosure; the purposes for which we collect or process your personal information; and applicable legal requirements.
</p>
<h2 id="section_7">
7. General Audience Services
</h2>
<p>
The Service are intended for users 13 years or older; you are not permitted to access or use the Service if you are younger than 13. We do not knowingly collect personal information from children under the age of 13 through the Service. We encourage parents and legal guardians to monitor their children's Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 13 has provided personal information to us, please contact us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>, and we will delete that information from our databases.
</p>
<h2 id="section_8">
8. Consent to International Transfer
</h2>
<p>
HabitRPG is based in the United States. Please be aware that information we collect will be transferred to and processed in the United States and other countries. HabitRPG makes no representation that this Privacy Policy or the practices described in it comply with the laws of any other jurisdiction. By using the Services, or providing us with any information, you fully understand and unambiguously consent to this transfer, processing, and storage of your information in the United States and other jurisdictions for which the privacy laws may not be as comprehensive as those in the country where you reside and/or are a citizen. As a result, this information may be subject to access requests from governments, courts, or law enforcement in the United States and other countries according to laws in those jurisdictions.
</p>
<h2 id="section_9">
9. Your Choices
</h2>
<p>
<em>Edit Your Information</em>: On the website, you can update the information in your user profile at any time by going to the user icon in the upper right, selecting the "Profile" option, then clicking "Edit Profile". You can update your username and email address by going to the user icon in the upper right and selecting the "Settings" option. If you are using the mobile app, you can update your user profile, username, and email by tapping the Settings gear in the menu and then selecting the "My Account" option.
</p>
<p>
<em>Reset Your Account</em>: You can fully delete or reset your account by selecting the user icon in the upper right, selecting the "Settings", and then looking under "General Settings". You can fully delete or reset your account on the mobile apps by tapping the Settings gear in the menu and then selecting the "My Account" submenu. Please note that in order to fully delete all data associated with your account, you will need to email us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>. Note that we may be required to retain certain data about you to comply with applicable laws.
</p>
<p>
<em>Newsletter</em>: You may also sign-up to receive our email newsletter. If you would like to discontinue receiving this information, you may update your email preferences by using the "Unsubscribe" link found in emails we send to you, or by contacting us. Please note that we reserve the right to send you certain communications relating to your account or use of the Services, such as administrative and services announcements. These transactional account messages may be unaffected if you choose to opt out from marketing emails.
</p>
<p>
<em>Push Notifications</em>: With your consent, we may send promotional and non-promotional push notifications or alerts to your mobile device. You can elect to stop receiving those messages by changing the notification settings in the app or on your mobile device.
</p>
<p>
<em>Other Privacy Rights</em>: Certain jurisdictions provide additional rights. Please see the "<a href="#section_11">Jurisdiction-Specific Rights</a>" section below for more information.
</p>
<h2 id="section_10">
10. Changes to This Privacy Policy
</h2>
<p>
To the extent permitted by applicable law, we reserve the right to change or modify this Privacy Policy at our discretion at any time. We will notify you of any material changes by posting the changed or modified Privacy Policy on our Services. We may also provide notice to you in other ways, such as through contact information you have provided. Any changes will be effective immediately upon the posting of the revised Privacy Policy unless otherwise specified. Your continued use of the Services after the effective date of the revised Privacy Policy (or such other act as specified in the revised Privacy Policy) will, to the fullest extent permitted by applicable law, constitute your consent to those changes. However, we will provide notice and obtain your consent (opt-in or opt-out) if required by law. We encourage you to regularly review this Privacy Policy for the latest information on our privacy practices.
</p>
<h2 id="section_11">
11. Jurisdiction-Specific Rights
</h2>
<p><strong><u>California, Nebraska, and Texas Residents</u></strong></p>
<p>
This section applies only to California, Nebraska, and Texas residents. It supplements and amends the information contained in the Policy with respect to such individuals. The other provisions of the Policy continue to apply, except as modified in this section.
</p>
<p>
<em>Shine the Light [California residents only]</em>. California Civil Code Section 1798.83 permits you to request information regarding the disclosure of your personal information by us to third parties for the third parties direct marketing purposes. Such requests must be submitted to us in accordance with the instructions in the Contact Us section of this Policy. Please mention when contacting us that you are making a California Shine the Light inquiry. Within 30 days of receiving such a request, we will provide a list of the categories of personal information disclosed to third parties for third-party direct marketing purposes during the immediately preceding calendar year, along with the names and addresses of these third parties. This request may be made no more than once per calendar year. We reserve our right not to respond to requests submitted other than in accordance with the instructions specified in this paragraph.
</p>
<p>
<em>Eraser Law [California residents only]</em>. If you are a California resident under the age of 18, and a registered user of any site where this policy is posted, California law permits you to request and obtain removal of content or information you have publicly posted. You may submit your request using the contact information at the end of this Policy. Please be aware that such request does not ensure complete or comprehensive removal of the content or information you have posted and that there may be circumstances in which the law does not require or allow removal even if requested.
</p>
<p>
<u><em>Your Rights</em></u>
</p>
<p>
You may have certain rights related to your personal information, subject to certain exceptions (including, as applicable and without limitation, safety, security, and protecting the rights of other users). Specifically:
</p>
<p>
<em>Right to Confirm and Access</em>. You may have the right to request that we confirm whether we process your personal information and, if we do, that we grant you access to that information. If you previously provided us your personal information, and that information is available in a digital format, you may have the right to obtain a copy of the information in a portable and, to the extent technically feasible, readily usable format that allows you to transmit the information to another person without hindrance.
</p>
<p>
<em>Right to Delete</em>. You may have the right to request that we delete your personal information from our records, subject to certain exceptions.
</p>
<p>
<em>Right to Correct</em>. You may have the right, subject to certain limitations, to request that we correct any inaccurate personal information we maintain about you.
</p>
<p>
<em>Opt - Out of the Sale of Personal Information or Use of Such Information for Targeted Advertising or Profiling</em>. We engage in common marketing and advertising practices to provide more relevant content and ads to users of our Site and Services. Certain of these practices may involve the selling of personal information, or the use of such information for targeted advertising or profiling, as those terms are defined in the Texas Data Privacy and Security Act (TDPSA) and the Nebraska Data Privacy Act. We do not sell personal information under the more commonly understood meaning of that wordi.e., providing personal information to third parties in exchange for money. Nor do we have actual knowledge of selling personal information of minors under the age of 16. To opt-out of the selling of your personal information, or use of that information for targeted advertising or profiling, please submit a request to <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>. Note: We also treat Global Privacy Control browser signals as opt-out of sale/disclosure for targeted advertising or profiling requests. To opt-out via the Global Privacy Control, please follow the instructions available <a
href="https://globalprivacycontrol.org/"
target="_blank"
>here</a>.
</p>
<p>
<em>Right to Consent to Use or Disclosure of Sensitive Personal Information</em>. Where required by applicable law, we will process your sensitive personal information only with your consent.
</p>
<p>
<em>Right Against Discrimination</em>. You have the right not to be discriminated against for exercising any of the rights described in this section. For example, we generally will not provide you a different level or quality of goods or services if you exercise these rights.
</p>
<p>
<em>Submitting Data Subject Rights Requests</em>. To submit a data subject rights request, please email us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>. We reserve the right to only respond to verifiable requests. To verify your identity, we may ask you to verify personal information we already have on file for you. If we cannot verify your identity from the information we have on file, we may request additional information from you, which we will only use to verify your identity, and for security or fraud-prevention purposes. You will need to describe your request with sufficient detail to allow us to review, understand, assess, and respond to it, and we may not be able to respond to your request, or provide you with personal information, if we cannot verify your identity or authority to make the request and confirm the personal information relates to you. You may authorize another person to act on your behalf with respect to your rights under this section. We reserve the right to deny requests from persons claiming to be authorized agents that do not submit sufficient proof of their authorization.
</p>
<p>
<em>Our Response</em> . You may have the right, subject to applicable law, to submit up to two (2) requests per year free of charge. We reserve the right to charge a fee to process or respond to your request if it is excessive, repetitive, or manifestly unfounded. If we determine that a request warrants a fee, we will attempt to notify you as to why we made that decision and provide a cost estimate before completing your request. We will endeavor to respond to verifiable requests within forty-five (45) calendar days of receipt, but we may require an extension of up to forty-five (45) additional calendar days to respond and we will notify you of the need for the extension.
</p>
<p>
If you have an account with us, we will deliver our written response to that account. If you do not have an account with us, we will deliver our written response by email. In the event we deny your request, in whole or in part, we will explain the bases for that denial.
</p>
<p>
<em>Appeal of Our Response</em>. In the event you believe we have erroneously denied your request, in full or in part, you may, within 60 days of receipt of that denial, submit an appeal to <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>. In your appeal submission, please explain why you believe our decision to deny your request was incorrect and please provide any additional information you believe we should consider in connection with your appeal. Within 60 days of receipt of your appeal, we will advise you in writing of any action we have taken, or refrained from taking, in response to your appeal, along with an explanation of why we have taken, or refrained from taking, such action.
</p>
<p><strong><u>Nevada Residents</u></strong></p>
<p>
Nevada residents may opt out of the sale of certain covered information collected by operators of websites or online services. We currently do not sell covered information, as sale is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>.
</p>
<p><strong><u>Notice to United Kingdom/European/Switzerland Residents.</u></strong></p>
<p>
If you are a resident of the United Kingdom (UK), European Economic Area (EEA), or of Switzerland, the following information applies.
</p>
<p>
<strong>Purposes of processing and legal basis for processing:</strong> As explained above, we process personal information in various ways depending upon your use of our Services. We process personal information on the following legal bases: (1) with your consent; (2) as necessary to perform our agreement to provide the Services; (3) compliance with our legal obligations; and (4) as necessary for our legitimate interests in providing the Service where those interests do not override your fundamental rights and freedoms related to data privacy such as for:
<ul>
<li>preventing fraud;</li>
<li>ensuring network and information security, including preventing unauthorized access to our computer and electronic communication systems and preventing malicious software distribution;</li>
<li>supporting internal administration;</li>
<li>improving and developing the Services; and</li>
<li>conducting data analytics analyses to review and better understand consumer interaction.</li>
</ul>
</p>
<p>
<strong>Right to lodge a complaint:</strong> Users that reside in the UK, EEA, or Switzerland have the right to seek information and assistance or lodge a complaint about our data collection and processing actions with the supervisory authority where they reside. Contact details for data protection authorities are available here. UK: <a
href="https://ico.org.uk/"
target="_blank"
>https://ico.org.uk/</a> EEA: <a
href="https://edpb.europa.eu/about-edpb/board/members_en"
target="_blank"
>https://edpb.europa.eu/about-edpb/board/members_en</a> Switzerland: <a
href="https//www.edoeb.admin.ch/edoeb/en/home/deredoeb/kontakt.html"
target="_blank"
>https//www.edoeb.admin.ch/edoeb/en/home/deredoeb/kontakt.html</a>.
</p>
<p>
<strong>Transfers:</strong> Personal information we collect may be transferred to, and stored and processed in, the United States or any other country in which we or our affiliates or subcontractors maintain facilities. Transfers of personal data to a third country without an adequacy decision (as that term is understood pursuant to Article 45 of GDPR) are required to be subject to appropriate safeguards such as standard contractual clauses. In certain cases, we rely on your consent to facilitate transfer, processing, and storage of your data in the United States and other jurisdictions, where laws regarding processing of personal information may be less stringent than the laws in the EEA, UK, and Switzerland.
</p>
<p>
<strong>Withdraw consent:</strong> If we have collected personal information with your consent, you have the right to withdraw that consent at any time.
</p>
<p>
<strong>Access:</strong> You have the right to request access to personal information we collected about you and information about its sources, purposes, and sharing.
</p>
<p>
<strong>Correction:</strong> You have the right to request that we correct the personal information we hold about you if it is inaccurate or incomplete.
</p>
<p>
<strong>Erasure:</strong> You have the right to request that we erase data we have collected from you. Please note that we may have a reason to deny your deletion request or delete data in a more limited way than you anticipated, e.g., because of a legal obligation to retain it.
</p>
<p>
<strong>Portability:</strong> You have the right, in certain circumstances, to request that we provide your personal information to you in a format that can be transferred to another entity.
</p>
<p>
<strong>Restrict Processing:</strong> You have the right, in certain circumstances, to request that we limit our processing of your personal information if you are (1) contesting the accuracy of your personal information, (2) asserting that our processing is unlawful; (3) asserting that we no longer need to keep the information for reasons related to the establishment, exercise, or defense of legal claims, or you object to our processing. You have the right, in certain circumstances, to request that we limit our processing of your personal information if you are contesting the accuracy of your personal information; asserting that our processing is unlawful; asserting that we no longer need to keep the information for reasons related to the establishment, exercise, or defense of legal claims, or you object to our processing
</p>
<p>
<strong>Objection:</strong> You have the right to object to our processing if we are processing your personal information based on legitimate interests, using your personal information for direct marketing (including profiling), or processing your personal information for purposes of scientific or historical research and statistics.
</p>
<p>
<strong>Verification Procedures:</strong> We must verify your identity for everyone's protection, so we may require you to provide us with verification information prior to accessing any records containing personal information about you. We do this by asking you to provide personal identifiers we can match against information we may have collected from you previously and confirm your request using the email stated in the request.
</p>
<p>
We will use the information you provide for verification only for the purpose of verification. We may have a reason under the law why we do not have to respond to your request or respond to it in a more limited way than you anticipated. If we do, we will explain that to you in our response.
</p>
<h2 id="section_12">
12. Contact Us
</h2>
<p>
If you have any questions or concerns about this Privacy Policy, please contact us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a> with “Privacy Policy” in the subject line. You may also write to us at:
</p>
<address class="ml-4">
HabitRPG, Inc. c/o Workbar<br>
120 Washington Street Suite 202<br>
Salem, MA 01970-6396
</address>
</div>
<!-- eslint-enable max-len -->
</template>
<style lang="scss" scoped>
@import '@/assets/scss/colors.scss';
h4 {
text-decoration: underline;
font-style: italic;
font-weight: normal;
color: $gray-50;
}
.important {
font-weight: 900;
}
table {
margin-top: 20px;
}
.first-column {
width: 25%
}
</style>
<script>
export default {
name: 'PrivacyReview',
data () {
return {
showThirdPartyInfo: true,
};
},
methods: {
toggleThirdPartyInfo () {
this.showThirdPartyInfo = !this.showThirdPartyInfo;
},
},
};
</script>

View File

@@ -2,7 +2,7 @@
<div>
<chat-banner />
<static-header
v-if="showContentWrap"
v-if="showContentWrap && !loginFlow"
:class="{
'home-header': ['home', 'front'].indexOf($route.name) !== -1,
'white-header': $route.name === 'plans'
@@ -15,17 +15,23 @@
<router-view />
</div>
<div
id="bottom-background"
v-if="loginFlow"
class="bg-purple-300"
>
<div class="seamless_mountains_demo_repeat"></div>
<div class="midground_foreground_extended2"></div>
</div>
<app-footer
v-if="showContentWrap"
:id="footerId"
>
<app-footer />
</div>
/>
<div
v-if="showContentWrap && footerId"
id="bottom-wrap"
class="purple-4"
>
<div id="bottom-background">
<div id="bottom-background" v-if="!loginFlow">
<div class="seamless_mountains_demo_repeat"></div>
<div class="midground_foreground_extended2"></div>
</div>
@@ -263,13 +269,16 @@ export default {
StaticHeader,
},
computed: {
showContentWrap () {
return this.$route.name !== 'news';
},
footerId () {
if (this.$route.name === 'plans') return null;
return 'purple-footer';
},
loginFlow () {
return ['login', 'register', 'username'].indexOf(this.$route.name) !== -1;
},
showContentWrap () {
return this.$route.name !== 'news';
},
},
};
</script>

View File

@@ -3,49 +3,130 @@
<div class="container-fluid">
<h1>Terms of Service</h1>
<p class="strong pagemeta">
Last Updated: December 14, 2021
Last Updated September 1, 2025.
</p>
<p>Thanks for choosing Habitica!</p>
<p>
Our Service is provided by HabitRPG, Inc. ("HabitRPG"). By accepting these Terms of Service and our Privacy Policy located at: <a
href="https://habitica.com/static/privacy"
target="_blank"
>https://habitica.com/static/privacy</a> (collectively, the "Agreement"), registering for the Service (as defined below), accessing or using any part of the Service, or otherwise manifesting your assent to the Agreement, you acknowledge that you have read, understood, and agree to be legally bound by the Agreement. If you do not agree to (or cannot comply with) the Agreement, you are not permitted to access or use the Service.
>https://habitica.com/static/privacy</a> (collectively, the "Agreement"), using our website, Habitica.com, or our other features or services (collectively, “the Services”), or otherwise manifesting your assent to the Agreement, you acknowledge that you have read, understood, and agree to be legally bound by the Agreement. If you do not agree to (or cannot comply with) the Agreement, you are not permitted to access or use the Service. By accepting or agreeing to this Agreement on behalf of a company or other legal entity, you represent and warrant that you have the authority to bind that company or other legal entity to the Agreement and, in such event, "you" and "your" will refer and apply to that company or other legal entity. You further represent and warrant that your assent to this Agreement constitutes an electronic signature as defined by the Electronic Signatures in Global and National Commerce Act (“E-Sign”) and the Uniform Electronic Transactions Act (“UETA”) and that you have formed, executed, entered into, and accepted the terms of and otherwise authenticated the Agreement and acknowledged and agreed that the Agreement is an electronic record for purposes of E- Sign, UETA, and the Uniform Computer Information Transactions Act and, as such, is completely valid, has legal effect, is enforceable, and is binding on, and non- refutable by, you and/or any entity on whose behalf you are acting.
</p>
<p>By accepting or agreeing to this Agreement on behalf of a company or other legal entity, you represent and warrant that you have the authority to bind that company or other legal entity to the Agreement and, in such event, "you" and "your" will refer and apply to that company or other legal entity.</p>
<p class="strong">
THE SECTIONS BELOW TITLED "BINDING ARBITRATION," AND "CLASS ACTION WAIVER" CONTAIN A BINDING ARBITRATION AGREEMENT AND CLASS ACTION WAIVER. THEY AFFECT YOUR LEGAL RIGHTS. PLEASE READ THEM.
THE SECTIONS BELOW TITLED "BINDING ARBITRATION' AND "CLASS ACTION WAIVER" CONTAIN A BINDING ARBITRATION AGREEMENT AND CLASS ACTION WAIVER. THEY AFFECT YOUR LEGAL RIGHTS. PLEASE READ THEM CAREFULLY.
</p>
<h2>Changes to the Terms of Service</h2>
<p>These Terms of Service are effective as of the last updated date stated at the top of this page. We may change these Terms of Service from time to time with or without notice to you. By accessing the Service after we make any such changes to this Terms of Service, you are deemed to have accepted such changes. Please be aware that, to the extent permitted by applicable law, our use of the information collected is governed by the Terms of Service in effect at the time we collect the information. Please refer back to this Terms of Service on a regular basis.</p>
<p>Our Service allows you to upload, store, send, download, or receive content, including but not limited to information, text, graphics, artwork, or other material ("Content"). You retain ownership of any intellectual property rights that you have in your Content. You hereby grant HabitRPG a worldwide, perpetual, irrevocable, sublicenseable, transferable, assignable, non-exclusive, and royalty-free right and license to use, reproduce, distribute, adapt, modify, translate, create derivative works of, publicly perform, publicly display, digitally perform, make, have made, sell, offer for sale, and import your Content, including all intellectual property rights therein. You represent, warrant, and agree that your Content does not and will not violate any third-party intellectual property, privacy, or other rights, and that you have all right, title and interest in and to your Content required to grant us the license above. We reserve the right at all times, but have no obligation, to delete or refuse to use or distribute any Content on or through the Service, including your Content.</p>
<h2>Ideas and Suggestions/Requests</h2>
<p>HabitRPG appreciates receiving your ideas, comments, suggestions and requests regarding the Service ("Unsolicited Ideas"). By submitting your Unsolicited Ideas (in any form or medium), you are transferring all your right, title and interest therein exclusively to HabitRPG. As the owner of Unsolicited Ideas, we have unrestricted rights to use, disclose and process the Unsolicited Ideas for any purpose whatsoever without any compensation to you. You also give up any claim that any use, disclosure and processing by us or our licensees of your Unsolicited Ideas violates any of your rights, including moral rights, privacy rights, rights to publicity, proprietary or other rights, and rights to credit for the material or ideas set for therein.</p>
<h2>Software in Our Service</h2>
<p>When the Service requires or includes downloadable software ("Software"), it may may update automatically on your device once a new version or features become available to you. Some platforms may let you adjust your automatic update settings.</p>
<p>HabitRPG hereby grants you a personal, worldwide, royalty-free, non-assignable and non-exclusive license to use the Software provided by HabitRPG as part of the Service. You may not copy, modify, distribute, sell, or lease any part of our Service or included Software you are explicitly allowed to do so by the GPL-3.0 license, or you have our written permission for those parts not covered by the open source license.</p>
<p>Third party applications may use one of the permitted logos and signifiers in order to represent their applicability to the Service, but may not claim formal association with and/or impersonate HabitRPG or our staff without prior written consent. Third Party applications, the companies that own or provide them, and their employees and agents, are not authorized to make any promises or representations on our behalf, or change the terms of this Agreement.</p>
<p>We allow for personal, non-commercial uses like fanart under Commercial Commons License CC-NC-SA 3.0 terms.</p>
<p>Outside the above explicitly allowed use cases, you may not use our trademarks, service marks, trade names, logos, domain names, taglines, or trade dress without a signed written contract with us granting you a license to do so.</p>
<h2>Modifying and Termination of Service</h2>
<p>HabitRPG reserves the right, in its sole discretion, to add, modify, or remove functionalities or features from the Service, and improve, change and/or update the Service. We may also suspend or terminate the Service at any time, with or without notice to you.</p>
<p>You can choose to stop using our Service at any time. We may suspend or cease providing the Service to you at any time, including if we determine in our sole discretion, that:</p>
<p>
These Terms of Service are effective as of the last updated date stated at the top of this page. We may change these Terms of Service from time to time with or without notice to you. By accessing the Service after we make any such changes to this Terms of Service, you are deemed to have accepted such changes. Please be aware that, to the extent permitted by applicable law, our use of the information collected is governed by the Terms of Service in effect at the time we collect the information. Please refer back to this Terms of Service on a regular basis.
</p>
<h2>Intellectual Property</h2>
<p>
Our Services allow you to upload, store, send, download, or receive content, including but not limited to information, text, graphics, artwork, or other material ("Content"). You retain ownership of any intellectual property rights that you had in your Content prior to using it in connection with the Service. You hereby grant HabitRPG a worldwide, perpetual, irrevocable, sublicenseable, transferable, assignable, non-exclusive, and royalty-free right and license to use, reproduce, distribute, adapt, modify, translate, create derivative works of, publicly perform, publicly display, digitally perform, make, have made, sell, offer for sale, and import your Content, including all intellectual property rights therein. You represent, warrant, and agree that your Content does not and will not violate any third-party intellectual property, privacy, or other rights, and that you have all right, title and interest in and to your Content required to grant us the license above. We reserve the right at all times, but have no obligation, to delete or refuse to use or distribute any Content on or through the Service, including your Content.
</p>
<p>
HabitRPG appreciates receiving your ideas, comments, suggestions and requests regarding the Service ("Unsolicited Ideas"). By submitting your Unsolicited Ideas (in any form or medium), you are transferring all your right, title and interest therein exclusively to HabitRPG. As the owner of Unsolicited Ideas, we have unrestricted rights to use, disclose and process the Unsolicited Ideas for any purpose whatsoever without any compensation to you. You also give up any claim that any use, disclosure, and/or processing by us or our licensees of your Unsolicited Ideas violates any of your rights, including moral rights, privacy rights, rights to publicity, proprietary or other rights, and rights to credit for the material or ideas set forth therein.
</p>
<p>
<strong>DMCA Notice</strong>. We respect the intellectual property rights of third parties. We respond to notices of alleged copyright infringement according to the Digital Millennium Copyright Act (“DMCA”) at 17 U.S.C. § 512 et seq. Regardless of whether or not the we believe that we are liable for any copyright infringement for which we are provided notice, our response may include removing or disabling access to material claimed to be the subject of infringing activity and/or terminating an individuals access to the Service, in our sole discretion and operating within the parameters of the DMCA.
</p>
<p>
If you believe that your work has been copied in a manner that constitutes copyright infringement, please contact us at <a href="mailto:admin@habitica.com">admin@habitica.com</a> with the following information:
</p>
<ul>
<li>Your name, address, telephone number, and email address;</li>
<li>A description of the copyrighted work that you claim has been infringed;</li>
<li>A description of the allegedly infringing material and where it is located on the Service;</li>
<li>A statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agents, or the law;</li>
<li>A statement by you under penalty of perjury that your notice is accurate, that you are the copyright owner, or that the copyright holder has authorized you to act on its behalf; and</li>
<li>Your written or electronic signature attesting to the above.</li>
</ul>
<p>
If your content has been removed from the Service in response to our receipt of a DMCA Notification as outlined above, and you believe the removal was inappropriate, you may submit a DMCA Counter-Notification by contacting us using the information above with the following information:
</p>
<ul>
<li>Your name, address, telephone number, and email address;</li>
<li>A statement that you consent to the jurisdiction of Federal District Court for the judicial district in which your address is located, or if your address is outside of the United States, for any judicial district for which jurisdiction for us would be appropriate, and that you will accept service of process from the person who submitted the DMCA notification or an agent of such person;</li>
<li>A description of the material that has been removed or to which access has been disabled and the location at which the material appeared before it was removed or access to it was disabled;</li>
<li>A statement by you under penalty of perjury that you have a good faith belief that the material was removed or disabled as a result of mistake or misidentification of the material to be removed or disabled; and</li>
<li>Your written or electronic signature attesting to the above.</li>
</ul>
<h2>Software in our Service</h2>
<p>
When the Service requires or includes downloadable software ("Software"), it may update automatically on your device once a new version or features become available to you. Some platforms may let you adjust your automatic update settings.
</p>
<p>
HabitRPG hereby grants you a non-commercial, worldwide, royalty-free, non-assignable and non-exclusive license to use the Software provided by HabitRPG as part of the Service. You may not copy, modify, distribute, sell, or lease any part of our Service or included Software unless you are explicitly allowed to do so by the GPL-3.0 license, or you have our written permission to do so with respect to those parts not covered by the open-source license.
</p>
<p>
Third party applications may use one of the permitted logos and signifiers in order to represent their compatibility with the Service, but may not claim formal association with and/or impersonate HabitRPG or our staff without prior written consent. Third Party applications, the companies that own or provide them, and their employees and agents, are not authorized to make any promises or representations on our behalf, or change the terms of this Agreement.
</p>
<p>
We allow for personal, non-commercial uses like fanart under Commercial Commons License CC-NC-SA 3.0 terms.
</p>
<p>
Outside the above explicitly allowed use cases, you may not use our trademarks, service marks, trade names, logos, domain names, taglines, or trade dress without a signed written contract with us granting you a license to do so.
</p>
<h2>Modification and Termination of Service</h2>
<p>
HabitRPG reserves the right, in its sole discretion, to add, modify, or remove functionalities or features from the Service, and improve, change and/or update the Service. We may also suspend or terminate the Service at any time, with or without notice to you.
</p>
<p>
You can choose to stop using our Service at any time. We may suspend or cease providing the Service to you at any time, including if we determine in our sole discretion, that:
</p>
<ul>
<li>You have violated any part of this Agreement, the Privacy Policy, or the Community Guidelines;</li>
<li>We have stopped offering the Service in your region; or</li>
<li>Doing so would be in the best interests of our community, the Service, or the rights of a third party.</li>
</ul>
<p>If your account is terminated, you will no longer have access to it, including to any of the associated data or Content. You will not be entitled to any refunds and we will have no liability to you. We also reserve the right to terminate any other accounts you may have created, as well as access to any other HabitRPG Service (also without refunds or liability to you).</p>
<p>You understand and agree that using the Service comes with the risk that your account may be terminated or suspended at our discretion and at any time. Please keep this risk in mind and comport yourself appropriately.</p>
<p>
If your account is terminated, you will no longer have access to it, including to any of the associated data or Content. We also reserve the right to terminate any other accounts you may have created, as well as access to any other HabitRPG Service.
</p>
<p>
You understand and agree that using the Service comes with the risk that your account may be terminated or suspended at our discretion and at any time. Please keep this risk in mind and comport yourself appropriately.
</p>
<h2>API</h2>
<p>You may access your Service data via the Application Program Interface ("API"). By using API you are automatically bound by the Agreement.</p>
<p>
You may access your Service data via the Application Program Interface ("API"). By using API you are automatically bound by the Agreement.
</p>
<h2>Links to Third-Party Sites</h2>
<p>
The Service may contain links to other web sites (“Linked Sites”). The Linked Sites are not under our control and we are not responsible for the contents of any Linked Site, including, without limitation, any link contained in a Linked Site, or any changes or updates to a Linked Site.
</p>
<p>
By providing these links, we do not endorse, sponsor, or recommend such sites or the materials disseminated by or services provided by them, and are not responsible for the materials, services, or other situations at or related to or from any other site. We are not responsible for webcasting or any other form of transmission received from any Linked Site. We are providing these links to you only as a convenience, and the inclusion of any link does not imply endorsement by us of the Linked Site or any association with its operators. We reserve the right to disable links from any third-party sites to the Service.
</p>
<p>
Please exercise discretion while using the Service. You should be aware that when you are using the Service, you could be directed to other sites that are beyond our control. There are links to other sites from the Service pages that take you outside of the Service. These other sites may send their own cookies to users, collect data, solicit personal information, or contain information that you may find inappropriate or offensive.
</p>
<h2>Links to Third-Party Integrations</h2>
<p>
We may provide links to third-party integrations. Third-party integrations are websites or platforms that synchronize with our Service to provide you with additional functionality, tools, or services, such as delivering content based on your location.
</p>
<p>
You acknowledge and agree that we are not responsible for the availability of such sites or resources and do not endorse and are not responsible or liable for any content, advertising, goods, services, or other materials on, available through, or provided by such sites or resources.
</p>
<p>
We are not responsible for the privacy or other practices of such sites and cannot guarantee the security of any personal information that you provide to such sites or that such sites collect. We encourage you to review the privacy policies and terms and conditions on those linked sites.
</p>
<h2>Using Our Service</h2>
<p>You must follow any policies made available to you within the Service, including but not limited to the Terms of Service, Privacy Policy, and Community Guidelines. You may only use our Service as permitted by law. HabitRPG may investigate and/or suspend or terminate our Service to you at any time if we find your use of our Service violates the Terms and/or any policies.</p>
<p>Using our Service does not grant you ownership of any intellectual property rights in our Service or the content you may have access to. You may not use any copyrighted content in our Service unless you obtain permission from the content owner and/or are otherwise permitted by law. The Terms do not grant you the right to use any branding or logos used in our Service. Our Service may display some logos, trademarks, or branding materials that are not the property of HabitRPG. Such content is the sole responsibility of the entity that makes it available.</p>
<p>You may not abuse and/or misuse our Service, including but not limited to the following actions:</p>
<p>
You must follow any policies made available to you within the Service, including but not limited to the Terms of Service, Privacy Policy, and Community Guidelines. You may only use our Service as permitted by law. HabitRPG may investigate and/or suspend or terminate our Service to you at any time if we find your use of our Service violates the Agreement, applicable law, and/or any of our policies.
</p>
<p>
Using our Service does not grant you ownership of any intellectual property rights in our Service or the content you may have access to. You may not use any copyrighted content in our Service unless you obtain permission from the content owner and/or are otherwise permitted by law. The Agreement does not grant you the right to use any branding or logos used in our Service. Our Service may display some logos, trademarks, or branding materials that are not the property of HabitRPG. Such content is the sole property of the entity that makes it available.
</p>
<p>
You may not abuse and/or misuse our Service, including but not limited to the following actions:
</p>
<ul>
<li>Using the Service for any unlawful purposes or activities;</li>
<li>Uploading any content to the Service in violation of any applicable law, including but not limited to intellectual property laws and publicity laws;</li>
<li>Uploading any content to the Service in violation of any applicable law, including but not limited to intellectual property laws, privacy laws, and publicity laws;</li>
<li>Sending unsolicited promotions or advertisements;</li>
<li>Accessing or tampering with the Service's server systems;</li>
<li>Interfering with or disrupting the access of any user, host, or network;</li>
@@ -53,53 +134,113 @@
<li>Spamming chat, whether for personal or commercial purposes, by disrupting the flow of conversation with repeated postings;</li>
<li>Impersonating any person, business, or entity, including an employee of HabitRPG, or member of the Habitica moderation team, or communicating in any way that makes it appear that the communication originates from Habitica staff or HabitRPG;</li>
<li>Transmitting or communicating any content which, in the sole and exclusive discretion of HabitRPG, is deemed offensive, including language that is unlawful, harmful, threatening, abusive, harassing, defamatory, vulgar, obscene, sexually explicit, or racially, ethically, or otherwise objectionable,</li>
<li>Participating in any action which, in the sole and exclusive judgment of HabitRPG, defrauds any other user of the Game, including by scamming or social engineering; or</li>
<li>Participating in any action which, in the sole and exclusive judgment of HabitRPG, defrauds any other user of the Service, including by scamming or social engineering; or</li>
<li>Inducing or encouraging others to violate the Community Guidelines or the Agreement.</li>
</ul>
<p>HabitRPG, in its sole discretion, will determine what constitutes abuse and/or misuse of our Service.</p>
<p>
HabitRPG, in its sole discretion, will determine what constitutes abuse and/or misuse of our Service.
</p>
<h2>Premium Service and Payments</h2>
<p>You may choose our free Service or paid Service ("Premium") depending on your needs. We do not guarantee when, if ever, Premium features will be available in the free Service. You may upgrade from free Service to Premium at any time by any of the following methods:</p>
<p>
You may choose our free Service or paid Service ("Premium") depending on your needs. We do not guarantee when, if ever, Premium features will be available in the free Service. You may upgrade from free Service to Premium at any time by any of the following methods:
</p>
<ul>
<li>Web: Selecting the user icon in the top right corner and selecting "Subscription" from the dropdown menu,</li>
<li>Web: Clicking the green gem icon in the navigation bar at the top of the screen and following instructions to Subscribe or Buy Gems,</li>
<li>Android: Tap the menu icon in the top left corner of the main screen and select "Gems & Subscription". Follow the instructions to make a purchase.</li>
<li>iOS: Tap the menu icon in the lower right and select "Gems & Subscriptions". Follow the instructions to make a purchase.</li>
<li>Android: Tap the menu icon in the top left corner of the main screen and select “Purchase Gems” or “Subscription. Follow the instructions to make a purchase.</li>
<li>iOS: Tap the menu icon in the lower right and select “Purchase Gems” or “Subscription. Follow the instructions to make a purchase.</li>
</ul>
<p>You will be charged the amount shown on Pricing before you can access Premium Service. All prices shown on Pricing are inclusive of any applicable sales taxes, levies, value-added taxes, or duties imposed by taxing authorities, and you are responsible for payment of all such taxes, levies, or duties. We may revise the Pricing at any time and may, from time to time, modify, amend, or supplement our fees and fee-billing methods. Such changes shall be effective upon posting on the Pricing page or elsewhere in the Service. If there is a dispute regarding payment of fees to us, we reserve the right to terminate or suspend your account at our sole discretion.</p>
<p>BY PURCHASING PREMIUM YOU EXPRESSLY UNDERSTAND AND AGREE TO OUR REFUND POLICY:</p>
<p>WITHIN THIRTY (30) DAYS OF YOUR PREMIUM PAYMENT DATE AS SHOWN ON YOUR PAYMENT BILL, YOU CAN REQUEST A FULL REFUND BY CONTACTING US AT ADMIN@HABITICA.COM. AFTER THIRTY (30) DAYS OF YOUR PREMIUM PAYMENT DATE, ANY PAYMENT REFUND IS SOLELY SUBJECT TO OUR DISCRETION. THE REFUND SHALL BE YOUR SOLE AND EXCLUSIVE REMEDY.</p>
<p>
You will be charged the amount shown on Pricing before you can access Premium Service. All prices shown on Pricing are inclusive of any applicable sales taxes, levies, value-added taxes, or duties imposed by taxing authorities, and you are responsible for payment of all such taxes, levies, or duties. We may revise the Pricing at any time and may, from time to time, modify, amend, or supplement our fees and fee-billing methods. Such changes shall be effective upon posting on the Pricing page or elsewhere in the Service. If there is a dispute regarding payment of fees to us, we reserve the right to terminate or suspend your account at our sole discretion.
</p>
<p>
BY PURCHASING PREMIUM YOU EXPRESSLY UNDERSTAND AND AGREE TO OUR REFUND POLICY:
</p>
<p>
YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href='mailto:admin@habitica.com'>ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD).
</p>
<p>
FOR ANY CUSTOMER WHO PURCHASED PREMIUM IN APPLE INC.'s APP STORE ("APP STORE"), PLEASE CONTACT APPLE INC.'s SUPPORT TEAM: <a
href="https://reportaproblem.apple.com"
target="_blank"
>https://reportaproblem.apple.com</a>. APPLE'S APP STORE DOES NOT ALLOW DEVELOPERS TO ISSUE REFUND FOR APP STORE PURCHASES MADE BY CUSTOMERS.
>https://reportaproblem.apple.com</a>. APPLE'S APP STORE DOES NOT ALLOW DEVELOPERS TO ISSUE REFUNDS FOR APP STORE PURCHASES MADE BY CUSTOMERS.
</p>
<h2>Warranty Disclaimer</h2>
<p>THE SERVICE AND ANY CONTENT MADE AVAILABLE BY HABITRPG VIA THE SERVICE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT ANY WARRANTIES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, THAT THE SERVICE OR CONTENT WILL OPERATE ERROR-FREE OR THAT THE SERVICE OR CONTENT OR ITS SERVERS ARE FREE OF COMPUTER VIRUSES OR SIMILAR CONTAMINATION OR DESTRUCTIVE FEATURES.</p>
<p>WE DISCLAIM ALL WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF TITLE, MERCHANTABILITY, NON-INFRINGEMENT OF THIRD PARTIES' RIGHTS, AND FITNESS FOR PARTICULAR PURPOSE AND ANY WARRANTIES ARISING FROM A COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE.</p>
<p>WE RESERVE THE RIGHT TO MAKE CHANGES, CORRECTIONS, AND/OR IMPROVEMENTS TO THE SERVICE OR THE CONTENT AT ANY TIME WITHOUT NOTICE.</p>
<p>IN CONNECTION WITH ANY WARRANTY, CONTRACT, OR COMMON LAW TORT CLAIMS: (I) WE AND OUR LICENSORS SHALL NOT BE LIABLE FOR ANY INCIDENTAL OR CONSEQUENTIAL DAMAGES, LOST PROFITS, OR DAMAGES RESULTING FROM LOST DATA OR BUSINESS INTERRUPTION RESULTING FROM THE USE OR INABILITY TO ACCESS AND USE THE SERVICE OR CONTENT POSTED BY HABITPRG, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES; AND (II) ANY DIRECT DAMAGES THAT YOU MAY SUFFER AS A RESULT OF YOUR USE OF THE PLATFORM SHALL BE LIMITED TO THE GREATER OF (I) MONIES YOU HAVE PAID US IN CONNECTION WITH YOUR USE OF THE PLATFORM DURING THE TWELVE (12) MONTHS IMMEDIATELY PRECEDING THE EVENTS GIVING RISE TO THE CLAIM, OR (II) ONE HUNDRED US DOLLARS ($100).</p>
<p>SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES. THEREFORE, SOME OF THE ABOVE LIMITATIONS ON WARRANTIES IN THIS SECTION MAY NOT APPLY TO YOU. NOTHING IN THIS AGREEMENT SHALL AFFECT ANY NON-WAIVABLE STATUTORY RIGHTS THAT APPLY TO YOU.</p>
<h2>Warranty Disclaimer and Limitation on Liability</h2>
<p>
THE SERVICE AND ANY CONTENT MADE AVAILABLE BY HABITRPG VIA THE SERVICE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT ANY WARRANTIES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, THAT THE SERVICE OR CONTENT WILL OPERATE ERROR-FREE OR THAT THE SERVICE OR CONTENT OR ITS SERVERS ARE FREE OF COMPUTER VIRUSES OR SIMILAR CONTAMINATION OR DESTRUCTIVE FEATURES.
</p>
<p>
WE DISCLAIM ALL WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF TITLE, MERCHANTABILITY, NON-INFRINGEMENT OF THIRD PARTIES' RIGHTS, AND FITNESS FOR PARTICULAR PURPOSE AND ANY WARRANTIES ARISING FROM A COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE.
</p>
<p>
WE RESERVE THE RIGHT TO MAKE CHANGES, CORRECTIONS, AND/OR IMPROVEMENTS TO THE SERVICE OR THE CONTENT AT ANY TIME WITHOUT NOTICE. IN CONNECTION WITH ANY WARRANTY, CONTRACT, OR COMMON LAW TORT CLAIMS: (I) WE AND OUR LICENSORS SHALL NOT BE LIABLE FOR ANY INCIDENTAL OR CONSEQUENTIAL DAMAGES, LOST PROFITS, OR DAMAGES RESULTING FROM LOST DATA OR BUSINESS INTERRUPTION RESULTING FROM THE USE OR INABILITY TO ACCESS AND USE THE SERVICE OR CONTENT POSTED BY HABITRPG, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES; AND (II) ANY DIRECT DAMAGES THAT YOU MAY SUFFER AS A RESULT OF YOUR USE OF THE PLATFORM SHALL BE LIMITED TO THE GREATER OF (I) MONIES YOU HAVE PAID US IN CONNECTION WITH YOUR USE OF THE PLATFORM DURING THE TWELVE (12) MONTHS IMMEDIATELY PRECEDING THE EVENTS GIVING RISE TO THE CLAIM, OR (II) ONE HUNDRED US DOLLARS ($100).
</p>
<p>
SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES. THEREFORE, SOME OF THE ABOVE LIMITATIONS ON WARRANTIES IN THIS SECTION MAY NOT APPLY TO YOU. NOTHING IN THIS AGREEMENT SHALL AFFECT ANY NON-WAIVABLE STATUTORY RIGHTS THAT APPLY TO YOU.
</p>
<h2>Indemnification</h2>
<p>You agree to defend, indemnify, and hold us and our officers, directors, employees, agents, successors, licensees, licensors, and assigns harmless from and against any damages, liabilities, losses, expenses, claims, actions, and/or demands, including, without limitation, reasonable legal and accounting fees, arising or resulting from: (i) your breach of any of your representations, warranties or other obligations under this Agreement; (ii) your use or misuse of the Service or content posted or made available by HabitRPG; and/or (iii) your violation of any third-party rights, including without limitation any copyright, trademark, property, publicity, or privacy right. We shall provide notice to you of any such claim, suit, or proceeding and shall assist you, at your expense, in defending any such claim, suit, or proceeding. We reserve the right to assume the exclusive defense and control (at your expense) of any matter that is subject to indemnification under this section. In such case, you agree to cooperate with any reasonable requests assisting our defense of such matter.</p>
<p>
You agree to defend, indemnify, and hold us and our officers, directors, employees, agents, successors, licensees, licensors, and assigns harmless from and against any damages, liabilities, losses, expenses, claims, actions, and/or demands, including, without limitation, reasonable legal and accounting fees, arising or resulting from: (i) your breach of any of your representations, warranties or other obligations under this Agreement; (ii) your use or misuse of the Service or content posted or made available by HabitRPG; and/or (iii) your violation of any third-party rights in connection with your use of the Service, including without limitation any copyright, trademark, property, publicity, or privacy right. We shall provide notice to you of any such claim, suit, or proceeding and shall assist you, at your expense, in defending any such claim, suit, or proceeding. We reserve the right to participate in the defense (at our expense) of any matter that is subject to indemnification under this section. In such case, you agree to cooperate with any reasonable requests assisting our defense of such matter.
</p>
<h2>Electronic Communications Privacy Act Notice (18 U.S.C. 2701-2711)</h2>
<p>
We make no guaranty of confidentiality or privacy with respect to any communication or information transmitted via the Service. We will not be liable for the privacy of the information, e-mail addresses, registration, and identification information, disk space, communications, confidential or trade-secret information, or any other content transmitted over networks accessed by the Service, or otherwise connected with your use of the Service.
</p>
<h2>Compliance with Applicable Laws</h2>
<p>The Service is based in the United States. We make no claims concerning whether the Service or posted content may be downloaded, viewed, or be appropriate for use outside of the United States. If you access the Service or such content from outside of the United States, you do so at your own risk. Whether inside or outside of the United States, you are solely responsible for ensuring compliance with the laws of your specific jurisdiction.</p>
<p>
The Service is based in the United States. We make no claims concerning whether the Service or posted content may be downloaded, viewed, or be appropriate for use outside of the United States. If you access the Service or such content from outside of the United States, you do so at your own risk. Whether inside or outside of the United States, you are solely responsible for ensuring compliance with the laws of your specific jurisdiction.
</p>
<h2>Binding Arbitration</h2>
<p>
In the event of a dispute arising under or relating to this Agreement or the Service (each, a "<u>Dispute</u>"), such dispute will be finally and exclusively resolved by binding arbitration governed by the Federal Arbitration Act ("<u>FAA</u>"). Any election to arbitrate, at any time, shall be final and binding on the other party. NEITHER PARTY SHALL HAVE THE RIGHT TO LITIGATE SUCH CLAIM IN COURT OR TO HAVE A JURY TRIAL, EXCEPT EITHER PARTY MAY BRING ITS CLAIM IN ITS LOCAL SMALL CLAIMS COURT, IF PERMITTED BY THAT SMALL CLAIMS COURT RULES AND IF WITHIN SUCH COURT'S JURISDICTION. ARBITRATION IS DIFFERENT FROM COURT, AND DISCOVERY AND APPEAL RIGHTS MAY ALSO BE LIMITED IN ARBITRATION. All disputes will be resolved before a neutral arbitrator selected jointly by the parties, whose decision will be final, except for a limited right of appeal under the FAA. The arbitration shall be commenced and conducted by JAMS pursuant to its then current Comprehensive Arbitration Rules and Procedures and in accordance with the Expedited Procedures in those rules, or, where appropriate, pursuant to JAMS' Streamlined Arbitration Rules and Procedures. All applicable JAMS' rules and procedures are available at the JAMS website <a
In the event of a dispute arising under or relating to this Agreement or the Service (each, a "Dispute"), such dispute will be finally and exclusively resolved by binding arbitration governed by the Federal Arbitration Act ("FAA"). Any election to arbitrate, at any time, shall be final and binding on the other party. NEITHER PARTY SHALL HAVE THE RIGHT TO LITIGATE SUCH CLAIM IN COURT OR TO HAVE A JURY TRIAL, EXCEPT EITHER PARTY MAY BRING ITS CLAIM IN ITS LOCAL SMALL CLAIMS COURT, IF PERMITTED BY THAT SMALL CLAIMS COURT RULES AND IF WITHIN SUCH COURT'S JURISDICTION. ARBITRATION IS DIFFERENT FROM COURT, AND DISCOVERY AND APPEAL RIGHTS MAY ALSO BE LIMITED IN ARBITRATION.
</p>
<p>
All disputes will be resolved before a neutral arbitrator selected jointly by the parties, whose decision will be final, except for a limited right of appeal under the FAA. The arbitration shall be commenced and conducted by JAMS pursuant to its then current Comprehensive Arbitration Rules and Procedures and in accordance with the Expedited Procedures in those rules, or, where appropriate, pursuant to JAMS' Streamlined Arbitration Rules and Procedures. All applicable JAMS rules and procedures are available at the JAMS website <a
href="https://www.jamsadr.com"
target="_blank"
>www.jamsadr.com</a>. Each party will be responsible for paying any JAMS filing, administrative, and arbitrator fees in accordance with JAMS rules. Judgment on the arbitrator's award may be entered in any court having jurisdiction. This clause shall not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction. The arbitration may be conducted in person, through the submission of documents, by phone, or online. If conducted in person, the arbitration shall take place in the United States county where you reside. The parties may litigate in court to compel arbitration, to stay a proceeding pending arbitration, or to confirm, modify, vacate, or enter judgment on the award entered by the arbitrator. The parties shall cooperate in good faith in the voluntary and informal exchange of all non-privileged documents and other information (including electronically stored information) relevant to the Dispute immediately after commencement of the arbitration. As set forth below, nothing in this Agreement will prevent us from seeking injunctive relief in any court of competent jurisdiction as necessary to protect our proprietary interests.
</p>
<p>ANY CLAIMS, ACTIONS OR PROCEEDINGS BY YOU MUST BE COMMENCED WITHIN ONE YEAR AFTER THE EVENT THAT GAVE RISE TO YOUR CLAIM OCCURS. ALL OTHER CLAIMS YOU MAY HAVE ARE PERMANENTLY BARRED.</p>
<p>
ANY CLAIMS, ACTIONS OR PROCEEDINGS BY YOU MUST BE COMMENCED WITHIN ONE YEAR AFTER THE EVENT THAT GAVE RISE TO YOUR CLAIM OCCURS. ALL OTHER CLAIMS YOU MAY HAVE ARE PERMANENTLY BARRED.
</p>
<h2>Class Action Waiver</h2>
<p>You agree that any arbitration or proceeding shall be limited to the Dispute between us and you individually. To the full extent permitted by law, (i) no arbitration or proceeding shall be joined with any other; (ii) there is no right or authority for any Dispute to be arbitrated or resolved on a class action-basis or to utilize class action procedures; and (iii) there is no right or authority for any Dispute to be brought in a purported representative capacity on behalf of the general public or any other persons. YOU AGREE THAT YOU MAY BRING CLAIMS AGAINST US ONLY IN YOUR INDIVIDUAL CAPACITY AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING.</p>
<p>
You agree that any arbitration or proceeding shall be limited to the Dispute between us and you individually. To the full extent permitted by law, (i) no arbitration or proceeding shall be joined with any other; (ii) there is no right or authority for any Dispute to be arbitrated or resolved on a class action-basis or to utilize class action procedures; and (iii) there is no right or authority for any Dispute to be brought in a purported representative capacity on behalf of the general public or any other persons. YOU AGREE THAT YOU MAY BRING CLAIMS AGAINST US ONLY IN YOUR INDIVIDUAL CAPACITY AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING.
</p>
<h2>Jury Trial Waiver</h2>
<p>
IF FOR ANY REASON A DISPUTE OR CLAIM MAY PROCEED IN COURT RATHER THAN IN ARBITRATION, EACH PARTY TO THIS AGREEMENT IRREVOCABLY WAIVES, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, ANY RIGHT IT MAY HAVE TO A TRIAL BY JURY IN ANY LEGAL PROCEEDING DIRECTLY OR INDIRECTLY ARISING OUT OF OR RELATING TO THESE TERMS OR THE SERVICE (WHETHER BASED ON CONTRACT, TORT, OR ANY OTHER THEORY), UNLESS SUCH WAIVERS ARE UNENFORCEABLE.
</p>
<h2>Equitable Relief</h2>
<p>You acknowledge and agree that in the event of a breach or threatened violation of our intellectual property rights and confidential and proprietary information by you, we will suffer irreparable harm and will therefore be entitled to injunctive relief to enforce this Agreement. We may, without waiving any other remedies under this Agreement, seek from any court having jurisdiction any interim, equitable, provisional, or injunctive relief that is necessary to protect our rights and property pending the outcome of the arbitration referenced above.</p>
<p>
You acknowledge and agree that in the event of a breach or threatened violation by you of our intellectual property rights and/or rights related to our confidential and proprietary information, we will suffer irreparable harm and will therefore be entitled to injunctive relief to enforce this Agreement. We may, without waiving any other remedies under this Agreement, seek from any court having jurisdiction any interim, equitable, provisional, or injunctive relief that is necessary to protect our rights and property pending the outcome of the arbitration referenced above.
</p>
<h2>Contact Us</h2>
<p>If you have any questions about the Agreement, or want to report a violation (including DMCA take-down notices relating to infringement of copyright) please contact us at <a href="mailto:admin@habitica.com">admin@habitica.com</a>.</p>
<p>
If you have any questions about the Agreement, or want to report a violation, please contact us at <a href="mailto:admin@habitica.com">admin@habitica.com</a>.
</p>
<h2>Miscellaneous</h2>
<p>This Agreement and any action related thereto will be governed by the laws of the State of California without regard to its conflict of laws provisions. Our failure to act on or enforce any provision of this Agreement shall not be construed as a waiver of that provision or any other provision therein. No waiver shall be effective against us unless made in writing, and no such waiver shall be construed as a waiver in any other or subsequent instance. Except as expressly agreed by us and you in writing, this Agreement constitutes the entire agreement between you and us with respect to the subject matter, and supersedes all previous or contemporaneous agreements, whether written or oral, between the parties with respect to the subject matter. The section headings are provided merely for convenience and shall not be given any legal import. This Agreement will inure to the benefit of our successors, assigns, licensees, and sublicensees.</p>
<ul>
<li>This Agreement and any action related thereto will be governed by the laws of the State of California without regard to its conflict of laws provisions.</li>
<li>Our failure to act on or enforce any provision of this Agreement shall not be construed as a waiver of that provision or any other provision therein. No waiver shall be effective against us unless made in writing, and no such waiver shall be construed as a waiver in any other or subsequent instance.</li>
<li>Except as expressly agreed by us and you in writing, this Agreement constitutes the entire agreement between you and us with respect to the subject matter, and supersedes all previous or contemporaneous agreements, whether written or oral, between the parties with respect to the subject matter.</li>
<li>The section headings are provided merely for convenience and shall not be given any legal import. This Agreement will inure to the benefit of our successors, assigns, licensees, and sublicensees.</li>
<li>You agree that no joint venture, partnership, employment, or agency relationship exists between you and us as a result of the Agreement or your use of the Service.</li>
<li>Our performance under the Agreement is subject to existing laws and legal process, and nothing contained in the Agreement is in derogation of our right to comply with governmental, court, and law enforcement requests, or requirements relating to your use of the Service or information provided to or gathered by us with respect to such use.</li>
<li>If any part of the Agreement is determined to be invalid or unenforceable pursuant to applicable law, including, without limitation, the warranty disclaimers and liability limitations set forth above, then the invalid or unenforceable provision will be deemed superseded by a valid, enforceable provision that most closely matches the intent of the original provision and the remainder of the Agreement shall continue in effect.</li>
<li>A printed version of the Agreement and of any notice given in electronic form shall be admissible in judicial or administrative proceedings based upon or relating to the Agreement to the same extent and subject to the same conditions as other business documents and records originally generated and maintained in printed form.</li>
</ul>
</div>
<!-- eslint-enable max-len -->
</template>

View File

@@ -1,244 +0,0 @@
<template>
<!-- eslint-disable max-len -->
<div class="container-fluid">
<h1>Terms of Service</h1>
<p class="strong pagemeta">
Last Updated September 1, 2025.
</p>
<p>Thanks for choosing Habitica!</p>
<p>
Our Service is provided by HabitRPG, Inc. ("HabitRPG"). By accepting these Terms of Service and our Privacy Policy located at: <a
href="https://habitica.com/static/privacy"
target="_blank"
>https://habitica.com/static/privacy</a> (collectively, the "Agreement"), using our website, Habitica.com, or our other features or services (collectively, “the Services”, or otherwise manifesting your assent to the Agreement, you acknowledge that you have read, understood, and agree to be legally bound by the Agreement. If you do not agree to (or cannot comply with) the Agreement, you are not permitted to access or use the Service. By accepting or agreeing to this Agreement on behalf of a company or other legal entity, you represent and warrant that you have the authority to bind that company or other legal entity to the Agreement and, in such event, "you" and "your" will refer and apply to that company or other legal entity. You further represent and warrant that your assent to this Agreement constitutes an electronic signature as defined by the Electronic Signatures in Global and National Commerce Act (“E-Sign”) and the Uniform Electronic Transactions Act (“UETA”) and that you have formed, executed, entered into, and accepted the terms of and otherwise authenticated the Agreement and acknowledged and agreed that the Agreement is an electronic record for purposes of E- Sign, UETA, and the Uniform Computer Information Transactions Act and, as such, is completely valid, has legal effect, is enforceable, and is binding on, and non- refutable by, you and/or any entity on whose behalf you are acting.
</p>
<p class="strong">
THE SECTIONS BELOW TITLED "BINDING ARBITRATION' AND "CLASS ACTION WAIVER" CONTAIN A BINDING ARBITRATION AGREEMENT AND CLASS ACTION WAIVER. THEY AFFECT YOUR LEGAL RIGHTS. PLEASE READ THEM CAREFULLY.
</p>
<h2>Changes to the Terms of Service</h2>
<p>
These Terms of Service are effective as of the last updated date stated at the top of this page. We may change these Terms of Service from time to time with or without notice to you. By accessing the Service after we make any such changes to this Terms of Service, you are deemed to have accepted such changes. Please be aware that, to the extent permitted by applicable law, our use of the information collected is governed by the Terms of Service in effect at the time we collect the information. Please refer back to this Terms of Service on a regular basis.
</p>
<h2>Intellectual Property</h2>
<p>
Our Services allow you to upload, store, send, download, or receive content, including but not limited to information, text, graphics, artwork, or other material ("Content"). You retain ownership of any intellectual property rights that you had in your Content prior to using it in connection with the Service. You hereby grant HabitRPG a worldwide, perpetual, irrevocable, sublicenseable, transferable, assignable, non-exclusive, and royalty-free right and license to use, reproduce, distribute, adapt, modify, translate, create derivative works of, publicly perform, publicly display, digitally perform, make, have made, sell, offer for sale, and import your Content, including all intellectual property rights therein. You represent, warrant, and agree that your Content does not and will not violate any third-party intellectual property, privacy, or other rights, and that you have all right, title and interest in and to your Content required to grant us the license above. We reserve the right at all times, but have no obligation, to delete or refuse to use or distribute any Content on or through the Service, including your Content.
</p>
<p>
HabitRPG appreciates receiving your ideas, comments, suggestions and requests regarding the Service ("Unsolicited Ideas"). By submitting your Unsolicited Ideas (in any form or medium), you are transferring all your right, title and interest therein exclusively to HabitRPG. As the owner of Unsolicited Ideas, we have unrestricted rights to use, disclose and process the Unsolicited Ideas for any purpose whatsoever without any compensation to you. You also give up any claim that any use, disclosure, and/or processing by us or our licensees of your Unsolicited Ideas violates any of your rights, including moral rights, privacy rights, rights to publicity, proprietary or other rights, and rights to credit for the material or ideas set forth therein.
</p>
<p>
<strong>DMCA Notice</strong>. We respect the intellectual property rights of third parties. We respond to notices of alleged copyright infringement according to the Digital Millennium Copyright Act (“DMCA”) at 17 U.S.C. § 512 et seq. Regardless of whether or not the we believe that we are liable for any copyright infringement for which we are provided notice, our response may include removing or disabling access to material claimed to be the subject of infringing activity and/or terminating an individuals access to the Service, in our sole discretion and operating within the parameters of the DMCA.
</p>
<p>
If you believe that your work has been copied in a manner that constitutes copyright infringement, please contact us at <a href="mailto:admin@habitica.com">admin@habitica.com</a> with the following information:
</p>
<ul>
<li>Your name, address, telephone number, and email address;</li>
<li>A description of the copyrighted work that you claim has been infringed;</li>
<li>A description of the allegedly infringing material and where it is located on the Service;</li>
<li>A statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agents, or the law;</li>
<li>A statement by you under penalty of perjury that your notice is accurate, that you are the copyright owner, or that the copyright holder has authorized you to act on its behalf; and</li>
<li>Your written or electronic signature attesting to the above.</li>
</ul>
<p>
If your content has been removed from the Service in response to our receipt of a DMCA Notification as outlined above, and you believe the removal was inappropriate, you may submit a DMCA Counter-Notification by contacting us using the information above with the following information:
</p>
<ul>
<li>Your name, address, telephone number, and email address;</li>
<li>A statement that you consent to the jurisdiction of Federal District Court for the judicial district in which your address is located, or if your address is outside of the United States, for any judicial district for which jurisdiction for us would be appropriate, and that you will accept service of process from the person who submitted the DMCA notification or an agent of such person;</li>
<li>A description of the material that has been removed or to which access has been disabled and the location at which the material appeared before it was removed or access to it was disabled;</li>
<li>A statement by you under penalty of perjury that you have a good faith belief that the material was removed or disabled as a result of mistake or misidentification of the material to be removed or disabled; and</li>
<li>Your written or electronic signature attesting to the above.</li>
</ul>
<h2>Software in our Service</h2>
<p>
When the Service requires or includes downloadable software ("Software"), it may update automatically on your device once a new version or features become available to you. Some platforms may let you adjust your automatic update settings.
</p>
<p>
HabitRPG hereby grants you a non-commercial, worldwide, royalty-free, non-assignable and non-exclusive license to use the Software provided by HabitRPG as part of the Service. You may not copy, modify, distribute, sell, or lease any part of our Service or included Software unless you are explicitly allowed to do so by the GPL-3.0 license, or you have our written permission to do so with respect to those parts not covered by the open-source license.
</p>
<p>
Third party applications may use one of the permitted logos and signifiers in order to represent their compatibility with the Service, but may not claim formal association with and/or impersonate HabitRPG or our staff without prior written consent. Third Party applications, the companies that own or provide them, and their employees and agents, are not authorized to make any promises or representations on our behalf, or change the terms of this Agreement.
</p>
<p>
We allow for personal, non-commercial uses like fanart under Commercial Commons License CC-NC-SA 3.0 terms.
</p>
<p>
Outside the above explicitly allowed use cases, you may not use our trademarks, service marks, trade names, logos, domain names, taglines, or trade dress without a signed written contract with us granting you a license to do so.
</p>
<h2>Modifying and Termination of Service</h2>
<p>
HabitRPG reserves the right, in its sole discretion, to add, modify, or remove functionalities or features from the Service, and improve, change and/or update the Service. We may also suspend or terminate the Service at any time, with or without notice to you.
</p>
<p>
You can choose to stop using our Service at any time. We may suspend or cease providing the Service to you at any time, including if we determine in our sole discretion, that:
</p>
<ul>
<li>You have violated any part of this Agreement, the Privacy Policy, or the Community Guidelines;</li>
<li>We have stopped offering the Service in your region; or</li>
<li>Doing so would be in the best interests of our community, the Service, or the rights of a third party.</li>
</ul>
<p>
If your account is terminated, you will no longer have access to it, including to any of the associated data or Content. You will not be entitled to any refunds and we will have no liability to you. We also reserve the right to terminate any other accounts you may have created, as well as access to any other HabitRPG Service (also without refunds or liability to you).
You understand and agree that using the Service comes with the risk that your account may be terminated or suspended at our discretion and at any time. Please keep this risk in mind and comport yourself appropriately.
</p>
<h2>API</h2>
<p>
You may access your Service data via the Application Program Interface ("API"). By using API you are automatically bound by the Agreement.
</p>
<h2>Links to Third-Party Sites</h2>
<p>
The Service may contain links to other web sites (“Linked Sites”). The Linked Sites are not under our control and we are not responsible for the contents of any Linked Site, including, without limitation, any link contained in a Linked Site, or any changes or updates to a Linked Site.
</p>
<p>
By providing these links, we do not endorse, sponsor, or recommend such sites or the materials disseminated by or services provided by them, and are not responsible for the materials, services, or other situations at or related to or from any other site. We are not responsible for webcasting or any other form of transmission received from any Linked Site. We are providing these links to you only as a convenience, and the inclusion of any link does not imply endorsement by us of the Linked Site or any association with its operators. We reserve the right to disable links from any third-party sites to the Service.
</p>
<p>
Please exercise discretion while using the Service. You should be aware that when you are using the Service, you could be directed to other sites that are beyond our control. There are links to other sites from the Service pages that take you outside of the Service. These other sites may send their own cookies to users, collect data, solicit personal information, or contain information that you may find inappropriate or offensive.
</p>
<h2>Links to Third-Party Integrations</h2>
<p>
We may provide links to third-party integrations. Third-party integrations are websites or platforms that synchronize with our Service to provide you with additional functionality, tools, or services, such as delivering content based on your location.
</p>
<p>
You acknowledge and agree that we are not responsible for the availability of such sites or resources and do not endorse and are not responsible or liable for any content, advertising, goods, services, or other materials on, available through, or provided by such sites or resources.
</p>
<p>
We are not responsible for the privacy or other practices of such sites and cannot guarantee the security of any personal information that you provide to such sites or that such sites collect. We encourage you to review the privacy policies and terms and conditions on those linked sites.
</p>
<h2>Using Our Service</h2>
<p>
You must follow any policies made available to you within the Service, including but not limited to the Terms of Service, Privacy Policy, and Community Guidelines. You may only use our Service as permitted by law. HabitRPG may investigate and/or suspend or terminate our Service to you at any time if we find your use of our Service violates the Agreement, applicable law, and/or any of our policies.
</p>
<p>
Using our Service does not grant you ownership of any intellectual property rights in our Service or the content you may have access to. You may not use any copyrighted content in our Service unless you obtain permission from the content owner and/or are otherwise permitted by law. The Agreement does not grant you the right to use any branding or logos used in our Service. Our Service may display some logos, trademarks, or branding materials that are not the property of HabitRPG. Such content is the sole property of the entity that makes it available.
</p>
<p>
You may not abuse and/or misuse our Service, including but not limited to the following actions:
</p>
<ul>
<li>Using the Service for any unlawful purposes or activities;</li>
<li>Uploading any content to the Service in violation of any applicable law, including but not limited to intellectual property laws, privacy laws, and publicity laws;</li>
<li>Sending unsolicited promotions or advertisements;</li>
<li>Accessing or tampering with the Service's server systems;</li>
<li>Interfering with or disrupting the access of any user, host, or network;</li>
<li>Abusing or submitting excessively frequent requests to the Service via the API</li>
<li>Spamming chat, whether for personal or commercial purposes, by disrupting the flow of conversation with repeated postings;</li>
<li>Impersonating any person, business, or entity, including an employee of HabitRPG, or member of the Habitica moderation team, or communicating in any way that makes it appear that the communication originates from Habitica staff or HabitRPG;</li>
<li>Transmitting or communicating any content which, in the sole and exclusive discretion of HabitRPG, is deemed offensive, including language that is unlawful, harmful, threatening, abusive, harassing, defamatory, vulgar, obscene, sexually explicit, or racially, ethically, or otherwise objectionable,</li>
<li>Participating in any action which, in the sole and exclusive judgment of HabitRPG, defrauds any other user of the Service, including by scamming or social engineering; or</li>
<li>Inducing or encouraging others to violate the Community Guidelines or the Agreement.</li>
</ul>
<p>
HabitRPG, in its sole discretion, will determine what constitutes abuse and/or misuse of our Service.
</p>
<h2>Premium Service and Payments</h2>
<p>
You may choose our free Service or paid Service ("Premium") depending on your needs. We do not guarantee when, if ever, Premium features will be available in the free Service. You may upgrade from free Service to Premium at any time by any of the following methods:
</p>
<ul>
<li>Web: Selecting the user icon in the top right corner and selecting "Subscription" from the dropdown menu,</li>
<li>Web: Clicking the green gem icon in the navigation bar at the top of the screen and following instructions to Subscribe or Buy Gems,</li>
<li>Android: Tap the menu icon in the top left corner of the main screen and select “Purchase Gems” or “Subscription”. Follow the instructions to make a purchase.</li>
<li>iOS: Tap the menu icon in the lower right and select “Purchase Gems” or “Subscription”. Follow the instructions to make a purchase.</li>
</ul>
<p>
You will be charged the amount shown on Pricing before you can access Premium Service. All prices shown on Pricing are inclusive of any applicable sales taxes, levies, value-added taxes, or duties imposed by taxing authorities, and you are responsible for payment of all such taxes, levies, or duties. We may revise the Pricing at any time and may, from time to time, modify, amend, or supplement our fees and fee-billing methods. Such changes shall be effective upon posting on the Pricing page or elsewhere in the Service. If there is a dispute regarding payment of fees to us, we reserve the right to terminate or suspend your account at our sole discretion.
</p>
<p>
BY PURCHASING PREMIUM YOU EXPRESSLY UNDERSTAND AND AGREE TO OUR REFUND POLICY:
</p>
<p>
WITHIN THIRTY (30) DAYS OF YOUR PREMIUM PAYMENT DATE AS SHOWN ON YOUR PAYMENT BILL, YOU CAN REQUEST A FULL REFUND BY CONTACTING US AT <a href="mailto:admin@habitica.com">ADMIN@HABITICA.COM</a>. AFTER THIRTY (30) DAYS OF YOUR PREMIUM PAYMENT DATE, ANY PAYMENT REFUND IS SOLELY SUBJECT TO OUR DISCRETION. THE REFUND SHALL BE YOUR SOLE AND EXCLUSIVE REMEDY.
</p>
<p>
FOR ANY CUSTOMER WHO PURCHASED PREMIUM IN APPLE INC.'s APP STORE ("APP STORE"), PLEASE CONTACT APPLE INC.'s SUPPORT TEAM: <a
href="https://reportaproblem.apple.com"
target="_blank"
>https://reportaproblem.apple.com</a>. APPLE'S APP STORE DOES NOT ALLOW DEVELOPERS TO ISSUE REFUNDS FOR APP STORE PURCHASES MADE BY CUSTOMERS.
</p>
<h2>Warranty Disclaimer and Limitation on Liability</h2>
<p>
THE SERVICE AND ANY CONTENT MADE AVAILABLE BY HABITRPG VIA THE SERVICE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT ANY WARRANTIES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, THAT THE SERVICE OR CONTENT WILL OPERATE ERROR-FREE OR THAT THE SERVICE OR CONTENT OR ITS SERVERS ARE FREE OF COMPUTER VIRUSES OR SIMILAR CONTAMINATION OR DESTRUCTIVE FEATURES.
</p>
<p>
WE DISCLAIM ALL WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF TITLE, MERCHANTABILITY, NON-INFRINGEMENT OF THIRD PARTIES' RIGHTS, AND FITNESS FOR PARTICULAR PURPOSE AND ANY WARRANTIES ARISING FROM A COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE.
</p>
<p>
WE RESERVE THE RIGHT TO MAKE CHANGES, CORRECTIONS, AND/OR IMPROVEMENTS TO THE SERVICE OR THE CONTENT AT ANY TIME WITHOUT NOTICE. IN CONNECTION WITH ANY WARRANTY, CONTRACT, OR COMMON LAW TORT CLAIMS: (I) WE AND OUR LICENSORS SHALL NOT BE LIABLE FOR ANY INCIDENTAL OR CONSEQUENTIAL DAMAGES, LOST PROFITS, OR DAMAGES RESULTING FROM LOST DATA OR BUSINESS INTERRUPTION RESULTING FROM THE USE OR INABILITY TO ACCESS AND USE THE SERVICE OR CONTENT POSTED BY HABITRPG, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES; AND (II) ANY DIRECT DAMAGES THAT YOU MAY SUFFER AS A RESULT OF YOUR USE OF THE PLATFORM SHALL BE LIMITED TO THE GREATER OF (I) MONIES YOU HAVE PAID US IN CONNECTION WITH YOUR USE OF THE PLATFORM DURING THE TWELVE (12) MONTHS IMMEDIATELY PRECEDING THE EVENTS GIVING RISE TO THE CLAIM, OR (II) ONE HUNDRED US DOLLARS ($100).
</p>
<p>
SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES. THEREFORE, SOME OF THE ABOVE LIMITATIONS ON WARRANTIES IN THIS SECTION MAY NOT APPLY TO YOU. NOTHING IN THIS AGREEMENT SHALL AFFECT ANY NON-WAIVABLE STATUTORY RIGHTS THAT APPLY TO YOU.
</p>
<h2>Indemnification</h2>
<p>
You agree to defend, indemnify, and hold us and our officers, directors, employees, agents, successors, licensees, licensors, and assigns harmless from and against any damages, liabilities, losses, expenses, claims, actions, and/or demands, including, without limitation, reasonable legal and accounting fees, arising or resulting from: (i) your breach of any of your representations, warranties or other obligations under this Agreement; (ii) your use or misuse of the Service or content posted or made available by HabitRPG; and/or (iii) your violation of any third-party rights in connection with your use of the Service, including without limitation any copyright, trademark, property, publicity, or privacy right. We shall provide notice to you of any such claim, suit, or proceeding and shall assist you, at your expense, in defending any such claim, suit, or proceeding. We reserve the right to participate in the defense (at our expense) of any matter that is subject to indemnification under this section. In such case, you agree to cooperate with any reasonable requests assisting our defense of such matter.
</p>
<h2>Electronic Communications Privacy Act Notice (18 U.S.C. 2701-2711)</h2>
<p>
We make no guaranty of confidentiality or privacy with respect to any communication or information transmitted via the Service. We will not be liable for the privacy of the information, e-mail addresses, registration, and identification information, disk space, communications, confidential or trade-secret information, or any other content transmitted over networks accessed by the Service, or otherwise connected with your use of the Service.
</p>
<h2>Compliance with Applicable Laws</h2>
<p>
The Service is based in the United States. We make no claims concerning whether the Service or posted content may be downloaded, viewed, or be appropriate for use outside of the United States. If you access the Service or such content from outside of the United States, you do so at your own risk. Whether inside or outside of the United States, you are solely responsible for ensuring compliance with the laws of your specific jurisdiction.
</p>
<h2>Binding Arbitration</h2>
<p>
In the event of a dispute arising under or relating to this Agreement or the Service (each, a "Dispute"), such dispute will be finally and exclusively resolved by binding arbitration governed by the Federal Arbitration Act ("FAA"). Any election to arbitrate, at any time, shall be final and binding on the other party. NEITHER PARTY SHALL HAVE THE RIGHT TO LITIGATE SUCH CLAIM IN COURT OR TO HAVE A JURY TRIAL, EXCEPT EITHER PARTY MAY BRING ITS CLAIM IN ITS LOCAL SMALL CLAIMS COURT, IF PERMITTED BY THAT SMALL CLAIMS COURT RULES AND IF WITHIN SUCH COURT'S JURISDICTION. ARBITRATION IS DIFFERENT FROM COURT, AND DISCOVERY AND APPEAL RIGHTS MAY ALSO BE LIMITED IN ARBITRATION.
</p>
<p>
All disputes will be resolved before a neutral arbitrator selected jointly by the parties, whose decision will be final, except for a limited right of appeal under the FAA. The arbitration shall be commenced and conducted by JAMS pursuant to its then current Comprehensive Arbitration Rules and Procedures and in accordance with the Expedited Procedures in those rules, or, where appropriate, pursuant to JAMS' Streamlined Arbitration Rules and Procedures. All applicable JAMS rules and procedures are available at the JAMS website <a
href="https://www.jamsadr.com"
target="_blank"
>www.jamsadr.com</a>. Each party will be responsible for paying any JAMS filing, administrative, and arbitrator fees in accordance with JAMS rules. Judgment on the arbitrator's award may be entered in any court having jurisdiction. This clause shall not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction. The arbitration may be conducted in person, through the submission of documents, by phone, or online. If conducted in person, the arbitration shall take place in the United States county where you reside. The parties may litigate in court to compel arbitration, to stay a proceeding pending arbitration, or to confirm, modify, vacate, or enter judgment on the award entered by the arbitrator. The parties shall cooperate in good faith in the voluntary and informal exchange of all non-privileged documents and other information (including electronically stored information) relevant to the Dispute immediately after commencement of the arbitration. As set forth below, nothing in this Agreement will prevent us from seeking injunctive relief in any court of competent jurisdiction as necessary to protect our proprietary interests.
</p>
<p>
ANY CLAIMS, ACTIONS OR PROCEEDINGS BY YOU MUST BE COMMENCED WITHIN ONE YEAR AFTER THE EVENT THAT GAVE RISE TO YOUR CLAIM OCCURS. ALL OTHER CLAIMS YOU MAY HAVE ARE PERMANENTLY BARRED.
</p>
<h2>Class Action Waiver</h2>
<p>
You agree that any arbitration or proceeding shall be limited to the Dispute between us and you individually. To the full extent permitted by law, (i) no arbitration or proceeding shall be joined with any other; (ii) there is no right or authority for any Dispute to be arbitrated or resolved on a class action-basis or to utilize class action procedures; and (iii) there is no right or authority for any Dispute to be brought in a purported representative capacity on behalf of the general public or any other persons. YOU AGREE THAT YOU MAY BRING CLAIMS AGAINST US ONLY IN YOUR INDIVIDUAL CAPACITY AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING.
</p>
<h2>Jury Trial Waiver</h2>
<p>
IF FOR ANY REASON A DISPUTE OR CLAIM MAY PROCEED IN COURT RATHER THAN IN ARBITRATION, EACH PARTY TO THIS AGREEMENT IRREVOCABLY WAIVES, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, ANY RIGHT IT MAY HAVE TO A TRIAL BY JURY IN ANY LEGAL PROCEEDING DIRECTLY OR INDIRECTLY ARISING OUT OF OR RELATING TO THESE TERMS OR THE SERVICE (WHETHER BASED ON CONTRACT, TORT, OR ANY OTHER THEORY), UNLESS SUCH WAIVERS ARE UNENFORCEABLE.
</p>
<h2>Equitable Relief</h2>
<p>
You acknowledge and agree that in the event of a breach or threatened violation by you of our intellectual property rights and/or rights related to our confidential and proprietary information, we will suffer irreparable harm and will therefore be entitled to injunctive relief to enforce this Agreement. We may, without waiving any other remedies under this Agreement, seek from any court having jurisdiction any interim, equitable, provisional, or injunctive relief that is necessary to protect our rights and property pending the outcome of the arbitration referenced above.
</p>
<h2>Contact Us</h2>
<p>
If you have any questions about the Agreement, or want to report a violation, please contact us at <a href="mailto:admin@habitica.com">admin@habitica.com</a>.
</p>
<h2>Miscellaneous</h2>
<ul>
<li>This Agreement and any action related thereto will be governed by the laws of the State of California without regard to its conflict of laws provisions.</li>
<li>Our failure to act on or enforce any provision of this Agreement shall not be construed as a waiver of that provision or any other provision therein. No waiver shall be effective against us unless made in writing, and no such waiver shall be construed as a waiver in any other or subsequent instance.</li>
<li>Except as expressly agreed by us and you in writing, this Agreement constitutes the entire agreement between you and us with respect to the subject matter, and supersedes all previous or contemporaneous agreements, whether written or oral, between the parties with respect to the subject matter.</li>
<li>The section headings are provided merely for convenience and shall not be given any legal import. This Agreement will inure to the benefit of our successors, assigns, licensees, and sublicensees.</li>
<li>You agree that no joint venture, partnership, employment, or agency relationship exists between you and us as a result of the Agreement or your use of the Service.</li>
<li>Our performance under the Agreement is subject to existing laws and legal process, and nothing contained in the Agreement is in derogation of our right to comply with governmental, court, and law enforcement requests, or requirements relating to your use of the Service or information provided to or gathered by us with respect to such use.</li>
<li>If any part of the Agreement is determined to be invalid or unenforceable pursuant to applicable law, including, without limitation, the warranty disclaimers and liability limitations set forth above, then the invalid or unenforceable provision will be deemed superseded by a valid, enforceable provision that most closely matches the intent of the original provision and the remainder of the Agreement shall continue in effect.</li>
<li>A printed version of the Agreement and of any notice given in electronic form shall be admissible in judicial or administrative proceedings based upon or relating to the Agreement to the same extent and subject to the same conditions as other business documents and records originally generated and maintained in printed form.</li>
</ul>
</div>
<!-- eslint-enable max-len -->
</template>

View File

@@ -86,8 +86,8 @@
v-if="taskList.length > 0"
ref="tasksList"
class="sortable-tasks"
:options="{disabled: activeFilter.label === 'scheduled' || !canBeDragged(),
scrollSensitivity: 64}"
:disabled="activeFilter.label === 'scheduled' || !canBeDragged()"
scrollSensitivity="64"
:delay-on-touch-only="true"
:delay="100"
@update="taskSorted"

View File

@@ -21,11 +21,9 @@
>
<draggable
v-model="checklist"
:options="{
handle: '.grippy',
filter: '.task-dropdown',
disabled: disabled,
}"
handle=".grippy"
filter=".task-dropdown"
:disabled="disabled"
@update="updateChecklist"
>
<div

View File

@@ -35,7 +35,7 @@
</button>
<button
class="btn btn-secondary d-flex align-items-center justify-content-center"
:class="{disabled: !canSave}"
:class="{'btn-disabled': !canSave}"
type="button"
@click="submit()"
>
@@ -162,13 +162,13 @@
>
<div
class="habit-option-icon svg-icon no-transition"
:class="task.up ? '' : 'disabled'"
:class="task.up ? '' : 'icon-disabled'"
v-html="icons.positive"
></div>
</div>
<div
class="habit-option-label no-transition"
:class="task.up ? cssClass('icon') : 'disabled'"
:class="task.up ? cssClass('icon') : 'label-disabled'"
>
{{ $t('positive') }}
</div>
@@ -188,13 +188,13 @@
>
<div
class="habit-option-icon no-transition svg-icon negative mx-auto"
:class="task.down ? '' : 'disabled'"
:class="task.down ? '' : 'icon-disabled'"
v-html="icons.negative"
></div>
</div>
<div
class="habit-option-label no-transition"
:class="task.down ? cssClass('icon') : 'disabled'"
:class="task.down ? cssClass('icon') : 'label-disabled'"
>
{{ $t('negative') }}
</div>
@@ -592,7 +592,7 @@
<button
class="btn btn-primary btn-footer
d-flex align-items-center justify-content-center"
:class="{disabled: !canSave}"
:class="{'btn-disabled': !canSave}"
type="button"
@click="submit()"
>
@@ -881,12 +881,14 @@
}
}
.disabled {
.btn-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);
cursor: not-allowed;
opacity: 0.6;
&:focus {
background-color: $white;
@@ -948,7 +950,7 @@
height: 10px;
color: $white;
&.disabled {
&.icon-disabled {
color: $gray-200;
}
@@ -962,7 +964,7 @@
font-weight: bold;
text-align: center;
&.disabled {
&.label-disabled {
color: $gray-100;
font-weight: normal;
}
@@ -1018,7 +1020,7 @@
border: 0;
}
.disabled .input-group-text {
.input-group-outer.disabled .input-group-text {
color: $gray-200;
}

View File

@@ -26,12 +26,17 @@
:checked="isChecked"
:value="value"
@change="handleChange"
:disabled="disabled"
>
<label
class="toggle-switch-label"
:for="toggleId"
:class="{ disabled }"
>
<span class="toggle-switch-inner"></span>
<span
class="toggle-switch-inner"
>
</span>
<span
class="toggle-switch-switch"
tabindex="0"
@@ -121,6 +126,14 @@
text-align: right;
}
.disabled {
cursor: auto;
.toggle-switch-inner:before, .toggle-switch-inner:after {
opacity: 0.5;
}
}
.toggle-switch-switch {
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
display: block;
@@ -181,6 +194,10 @@ export default {
hoverText: {
type: String,
},
disabled: {
type: Boolean,
default: false,
},
},
data () {
return {

View File

@@ -1126,7 +1126,12 @@ export default {
this.loadUser();
this.oldTitle = this.$store.state.title;
this.handleExternalLinks();
this.selectPage(this.startingPage);
// Check if there's a hash in the URL to determine the starting page
let pageToSelect = this.startingPage;
if (window.location.hash && (window.location.hash === '#stats' || window.location.hash === '#achievements')) {
pageToSelect = window.location.hash.substring(1);
}
this.selectPage(pageToSelect);
this.$root.$on('habitica:report-profile-result', () => {
this.loadUser();
});
@@ -1211,10 +1216,15 @@ export default {
},
selectPage (page) {
this.selectedPage = page || 'profile';
window.history.replaceState(null, null, '');
const profileUserId = this.userId || this.userLoggedIn._id;
let newPath = `/profile/${profileUserId}`;
if (page !== 'profile') {
newPath += `#${page}`;
}
window.history.replaceState(null, null, newPath);
this.$store.dispatch('common:setTitle', {
section: this.$t('user'),
subSection: this.$t(this.startingPage),
subSection: this.$t(page),
});
},
getNextIncentive () {

View File

@@ -2,26 +2,24 @@ import forEach from 'lodash/forEach';
import isEqual from 'lodash/isEqual';
import keys from 'lodash/keys';
import pick from 'lodash/pick';
import includes from 'lodash/includes';
import amplitude from 'amplitude-js';
import Vue from 'vue';
import getStore from '@/store';
const IS_PRODUCTION = import.meta.env.NODE_ENV === 'production'; // eslint-disable-line no-process-env
const AMPLITUDE_KEY = import.meta.env.AMPLITUDE_KEY; // eslint-disable-line no-process-env
const GA_ID = import.meta.env.GA_ID; // eslint-disable-line no-process-env
const AMPLITUDE_KEY = import.meta.env.AMPLITUDE_KEY;
const REQUIRED_FIELDS = ['eventCategory', 'eventAction'];
const REQUIRED_FIELDS = ['hitType', 'eventCategory', 'eventAction'];
const ALLOWED_HIT_TYPES = [
'pageview',
'screenview',
'event',
'transaction',
'item',
'social',
'exception',
'timing',
];
let analyticsLoading = false;
let analyticsReady = false;
function _getConsentedUser () {
const store = getStore();
const user = store.state.user.data;
if (!user?.preferences?.analyticsConsent) {
return false;
}
return user;
}
function _doesNotHaveRequiredFields (properties) {
if (!isEqual(keys(pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) {
@@ -34,17 +32,6 @@ function _doesNotHaveRequiredFields (properties) {
return false;
}
function _doesNotHaveAllowedHitType (properties) {
if (!includes(ALLOWED_HIT_TYPES, properties.hitType)) {
// @TODO: Log with Winston?
// console.log('Hit type of Analytics event must be one
// of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES));
return true;
}
return false;
}
function _gatherUserStats (properties) {
const store = getStore();
const user = store.state.user.data;
@@ -75,24 +62,26 @@ function _gatherUserStats (properties) {
if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId;
}
export function setUser () {
const store = getStore();
const user = store.state.user.data;
amplitude.getInstance().setUserId(user._id);
window.ga('set', { userId: user._id });
export function safeSetup (userId) {
if (analyticsLoading || analyticsReady) return;
analyticsLoading = true;
amplitude.getInstance().init(AMPLITUDE_KEY, userId);
analyticsReady = true;
analyticsLoading = false;
}
export function track (properties, options = {}) {
const user = _getConsentedUser();
if (!user) return;
safeSetup(user._id);
// Use nextTick to avoid blocking the UI
Vue.nextTick(() => {
if (_doesNotHaveRequiredFields(properties)) return;
if (_doesNotHaveAllowedHitType(properties)) return;
const trackOnClient = options && options.trackOnClient === true;
// Track events on the server by default
if (trackOnClient === true) {
amplitude.getInstance().logEvent(properties.eventAction, properties);
window.ga('send', properties);
} else {
const store = getStore();
store.dispatch('analytics:trackEvent', properties);
@@ -101,45 +90,15 @@ export function track (properties, options = {}) {
}
export function updateUser (properties = {}) {
const user = _getConsentedUser();
if (!user) return;
safeSetup(user._id);
// Use nextTick to avoid blocking the UI
Vue.nextTick(() => {
_gatherUserStats(properties);
forEach(properties, (value, key) => {
const identify = new amplitude.Identify().set(key, value);
amplitude.getInstance().identify(identify);
});
window.ga('set', properties);
});
}
export function setup () {
// Setup queues until the real scripts are loaded
/* eslint-disable */
// Amplitude
amplitude.getInstance().init(AMPLITUDE_KEY);
// Google Analytics (aka Universal Analytics)
window['GoogleAnalyticsObject'] = 'ga';
window['ga'] = window['ga'] || function() {
(window['ga'].q = window['ga'].q || []).push(arguments)
}, window['ga'].l = 1 * new Date();
ga('create', GA_ID);
/* eslint-enable */
}
export function load () {
// Load real scripts
if (!IS_PRODUCTION) return;
let firstScript = document.getElementsByTagName('script')[0];
// Google Analytics
const gaScript = document.createElement('script');
[firstScript] = document.getElementsByTagName('script');
gaScript.async = 1;
gaScript.src = '//www.google-analytics.com/analytics.js';
firstScript.parentNode.insertBefore(gaScript, firstScript);
}

View File

@@ -21,7 +21,7 @@ function loadLocale (i18nData) {
script.type = 'text/javascript';
script.text = i18nData.momentLang;
head.appendChild(script);
moment.locale(language.momentLangCode);
moment.updateLocale(language.momentLangCode);
}
}

View File

@@ -1,7 +1,16 @@
import habiticaMarkdown from 'habitica-markdown/withMentions';
import escapeRegExp from 'lodash/escapeRegExp';
export default function renderWithMentions (text, user) {
if (!text) return null;
const env = { userName: user.auth.local.username, displayName: user.profile.name };
return habiticaMarkdown.render(String(text), env);
const env = { userName: user.auth.local.username };
let html = habiticaMarkdown.render(String(text), env);
if (user.auth.local.username) {
const username = escapeRegExp(user.auth.local.username);
const regex = new RegExp(`(<span class="at-text">@)(${username})(</span>)`, 'gi');
html = html.replace(regex, (match, p1, p2, p3) => `${p1.replace('at-text', 'at-text at-highlight')}${p2}${p3}`);
}
return html;
}

View File

@@ -30,8 +30,8 @@ export default [
uuid: '61b2c855-0a30-444c-bcc6-1cac876460b0',
},
{
name: 'heyeilatan',
name: 'fizself',
type: 'Staff',
uuid: 'f4e5c6da-0617-48bf-b3bd-9f97636774a8',
uuid: 'e39ea3eb-28d2-48da-8568-7a5b0e64498e',
},
];

View File

@@ -13,9 +13,6 @@ import {
} from 'bootstrap-vue';
import Fragment from 'vue-fragment';
import AppComponent from './app';
import {
setup as setupAnalytics,
} from '@/libs/analytics';
import { setUpLogging } from '@/libs/logging';
import router from './router/index';
import getStore from './store';
@@ -50,7 +47,6 @@ Vue.use(CollapsePlugin);
Vue.use(Fragment.Plugin);
setUpLogging();
setupAnalytics(); // just create queues for analytics, no scripts loaded at this time
const store = getStore();
if (import.meta.env.TIME_TRAVEL_ENABLED === 'true') {

View File

@@ -0,0 +1,128 @@
import debounce from 'lodash/debounce';
import isEmail from 'validator/es/lib/isEmail';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import hello from 'hellojs';
import { buildAppleAuthUrl } from '../libs/auth';
export default {
data () {
return {
authData: {},
email: '',
emailError: null,
emailValid: false,
password: '',
passwordConfirm: '',
passwordValid: false,
passwordInvalid: false,
passwordConfirmValid: false,
passwordConfirmInvalid: false,
registrationMethod: null,
username: '',
};
},
watch: {
email () {
this.validateEmail(this.email);
},
password () {
this.validatePassword(this.password);
},
passwordConfirm () {
this.validatePasswordConfirm(this.passwordConfirm);
},
},
// @TODO: Abstract hello in to action or lib
mounted () {
hello.init({
google: import.meta.env.GOOGLE_CLIENT_ID, // eslint-disable-line
});
},
methods: {
validateEmail: debounce(function valEmail (email) {
if (!email) {
this.emailValid = false;
this.emailError = null;
return;
}
if (!isEmail(email)) {
this.emailValid = false;
this.emailError = this.$t('enterValidEmail');
return;
}
this.$store.dispatch('auth:checkEmail', {
email,
}).then(res => {
if (!res.valid) {
this.emailValid = false;
this.emailError = this.$t('cannotFulfillReq');
return;
}
this.emailValid = true;
this.emailError = null;
});
}, 500),
validatePassword: debounce(function valPass (password) {
if (!password) {
this.passwordValid = false;
this.passwordInvalid = false;
return;
}
this.passwordValid = password.length >= MINIMUM_PASSWORD_LENGTH;
this.passwordInvalid = password.length < MINIMUM_PASSWORD_LENGTH;
}, 500),
validatePasswordConfirm: debounce(function valPassConf (passwordConfirm) {
if (!passwordConfirm) {
this.passwordConfirmValid = false;
this.passwordConfirmInvalid = false;
return;
}
this.passwordConfirmValid = passwordConfirm === this.password;
this.passwordConfirmInvalid = passwordConfirm !== this.password;
}, 500),
async proceed (accountType) {
if (accountType === 'apple') {
window.location.href = buildAppleAuthUrl();
} else {
window.sessionStorage.removeItem('apple-token');
}
if (accountType === 'local') {
this.$store.state.registrationOptions = {
email: this.email,
password: this.password,
passwordConfirm: this.passwordConfirm,
registrationMethod: 'local',
};
} else {
this.authData = await this.socialAuth(accountType);
const authId = await this.$store.dispatch('auth:socialAuth', {
auth: this.authData,
allowRegister: false,
});
if (authId) {
window.location.href = '/';
} else {
this.$store.state.registrationOptions = {
authData: this.authData,
email: window.sessionStorage.getItem('social-email'),
registrationMethod: accountType,
};
}
}
this.$router.push({ name: 'username', query: this.$route.query });
},
async socialAuth (network) {
try {
await hello(network).logout();
} catch (e) {} // eslint-disable-line
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
const auth = await hello(network).login({
scope: 'email',
// explicitly pass the redirect url or it might redirect to /home
redirect_uri: redirectUrl, // eslint-disable-line camelcase
});
return auth;
},
},
};

View File

@@ -98,16 +98,10 @@
}
.settings-content {
flex: 0 0 732px;
flex: 0 0 751px;
max-width: unset;
::v-deep {
line-height: 1.71;
.small {
line-height: 1.33;
}
table td {
padding: 0.5rem;
}
@@ -123,6 +117,14 @@
width: 23%;
}
small {
line-height: 1.33;
}
.settings-label, .settings-value, a, p {
line-height: 1.71;
}
.input-area .settings-label {
width: unset;
}

View File

@@ -5,7 +5,7 @@
>
<button
v-if="!hideSave"
class="btn btn-save"
class="btn btn-save mb-2"
:class="primaryButtonColor ?? 'btn-primary'"
type="submit"
:disabled="disableSave"
@@ -61,8 +61,4 @@ export default {
margin-top: 1.5rem;
}
}
.btn-save {
margin-bottom: 1rem;
}
</style>

View File

@@ -16,6 +16,7 @@
<table class="table">
<user-id-row />
<user-data-row />
<privacy-preferences-row />
<tr>
<td colspan="3">
</td>
@@ -45,6 +46,7 @@
<script>
import UserIdRow from '@/pages/settings/siteDataRows/userIdRow.vue';
import UserDataRow from '@/pages/settings/siteDataRows/userDataRow.vue';
import PrivacyPreferencesRow from '@/pages/settings/siteDataRows/privacyPreferencesRow.vue';
import ApiRow from '@/pages/settings/siteDataRows/apiRow.vue';
import WebhooksRow from '@/pages/settings/siteDataRows/webhooksRow.vue';
import DeveloperModeRow from '@/pages/settings/siteDataRows/developerModeRow.vue';
@@ -56,6 +58,7 @@ export default {
ApiRow,
UserDataRow,
UserIdRow,
PrivacyPreferencesRow,
},
mounted () {
this.$store.dispatch('common:setTitle', {

View File

@@ -0,0 +1,202 @@
<template>
<tr>
<td colspan="3"
v-if="!mixinData.inlineSettingMixin.modalVisible"
>
<div class="d-flex justify-content-between align-items-center">
<h3
v-once
class="gray-50 mb-0"
>
{{ $t('yourPrivacyPreferences') }}
</h3>
<a
class="edit-link"
@click.prevent="openModal()"
>
{{ $t('edit') }}
</a>
</div>
</td>
<td colspan="3"
v-if="mixinData.inlineSettingMixin.modalVisible"
>
<h3
v-once
class="purple-200 mb-0"
>
{{ $t('yourPrivacyPreferences') }}
</h3>
<p
v-once
class="gray-50 mb-4"
v-html="$t('privacySettingsOverview') + ' ' + $t('learnMorePrivacy')"
>
</p>
<div
v-if="gpcEnabled"
class="mx-4 px-3 py-2 mb-4 gpc-alert d-flex align-items-center black bg-yellow-50"
>
<div
class="svg svg-icon mr-2"
v-html="icons.alert"
>
</div>
<div
class="gpc-message"
v-html="gpcInfo"
>
</div>
</div>
<div
class="d-flex justify-content-center"
>
<div class="w-66">
<div
class="d-flex justify-content-between align-items-center mb-1"
>
<label class="settings-label w-50 mb-0">
{{ $t('performanceAnalytics') }}
</label>
<toggle-switch
class="mb-auto"
v-model="user.preferences.analyticsConsent"
@change="prefToggled()"
/>
</div>
<div class="mb-28p">
<small class="gray-50">
{{ $t('usedForSupport') }}
</small>
</div>
<div
class="d-flex justify-content-between align-items-center mb-1"
>
<label class="settings-label w-50 mb-0">
{{ $t('strictlyNecessary') }}
</label>
<toggle-switch
:checked="true"
:disabled="true"
/>
</div>
<small class="gray-50">
{{ $t('requiredToRun') }}
</small>
<save-cancel-buttons
class="mb-4"
:disable-save="!mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues"
@saveClicked="finalize()"
@cancelClicked="requestCloseModal()"
/>
</div>
</div>
</td>
</tr>
</template>
<style lang="scss" scoped>
@import '@/assets/scss/colors.scss';
button {
width: fit-content;
}
small {
line-height: 1.33;
}
.gpc-alert {
border-radius: 4px;
line-height: 1.714;
.gpc-message {
opacity: 0.9;
}
::v-deep a {
color: $black;
text-decoration: underline;
}
.svg-icon {
width: 16px;
opacity: 0.75;
::v-deep svg path {
fill: $black;
}
}
}
.mb-28p {
margin-bottom: 28px;
}
.popover-box {
margin-top: 1px;
}
.w-66 {
width: 66.7%;
}
</style>
<script>
import SaveCancelButtons from '@/pages/settings/components/saveCancelButtons.vue';
import ToggleSwitch from '@/components/ui/toggleSwitch.vue';
import { GenericUserPreferencesMixin } from '@/pages/settings/components/genericUserPreferencesMixin';
import { InlineSettingMixin } from '../components/inlineSettingMixin';
import { mapState } from '@/libs/store';
import alert from '@/assets/svg/for-css/alert.svg?raw';
export default {
mixins: [
GenericUserPreferencesMixin,
InlineSettingMixin,
],
components: {
SaveCancelButtons,
ToggleSwitch,
},
data () {
return {
icons: Object.freeze({
alert,
}),
};
},
computed: {
...mapState({
user: 'user.data',
}),
gpcEnabled () {
return navigator.globalPrivacyControl;
},
gpcInfo () {
const gpcUrl = 'https://globalprivacycontrol.org/';
if (this.user.preferences.analyticsConsent) {
return this.$t('gpcPlusAnalytics', { url: gpcUrl });
}
return this.$t('gpcWarning', { url: gpcUrl });
},
},
methods: {
finalize () {
this.setUserPreference('analyticsConsent');
localStorage.setItem('analyticsConsent', this.user.preferences.analyticsConsent);
this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues = false;
},
prefToggled () {
const newVal = !this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues;
this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues = newVal;
},
resetControls () {
if (this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues) {
this.user.preferences.analyticsConsent = !this.user.preferences.analyticsConsent;
this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues = false;
}
},
},
};
</script>

View File

@@ -15,6 +15,7 @@
<external-link-modal />
<birthday-modal />
<template v-if="isUserLoaded">
<privacy-banner />
<chat-banner />
<damage-paused-banner />
<gems-promo-banner />
@@ -118,11 +119,12 @@ import { loadProgressBar } from 'axios-progress-bar';
import birthdayModal from '@/components/news/birthdayModal';
import AppMenu from '@/components/header/menu';
import AppHeader from '@/components/header/index';
import BirthdayBanner from '@/components/header/banners/birthdayBanner';
import ChatBanner from '@/components/header/banners/chatBanner';
import DamagePausedBanner from '@/components/header/banners/damagePaused';
import GemsPromoBanner from '@/components/header/banners/gemsPromo';
import GiftPromoBanner from '@/components/header/banners/giftPromo';
import BirthdayBanner from '@/components/header/banners/birthdayBanner';
import PrivacyBanner from '@/components/header/banners/privacy';
import AppFooter from '@/components/appFooter';
import notificationsDisplay from '@/components/notifications';
import { mapState } from '@/libs/store';
@@ -159,6 +161,7 @@ export default {
GemsPromoBanner,
GiftPromoBanner,
BirthdayBanner,
PrivacyBanner,
notificationsDisplay,
BuyModal,
SelectMembersModal,
@@ -260,18 +263,24 @@ export default {
this.$store.dispatch('tasks:fetchUserTasks'),
]).then(() => {
this.$store.state.isUserLoaded = true;
Analytics.setUser();
let analyticsConsent = localStorage.getItem('analyticsConsent');
if (analyticsConsent !== null) {
analyticsConsent = analyticsConsent === 'true';
if (analyticsConsent !== this.user.preferences.analyticsConsent) {
this.$store.dispatch('user:set', { 'preferences.analyticsConsent': analyticsConsent });
}
}
if (window && window['habitica-i18n']) {
if (this.user.preferences.language === window['habitica-i18n'].language.code) {
return null;
}
}
if (window && window['habitica-i18n']) {
if (this.user.preferences.language === window['habitica-i18n'].language.code) {
return null;
}
}
Analytics.updateUser();
if (window && window['habitica-i18n']) {
if (this.user.preferences.language === window['habitica-i18n'].language.code) {
return null;
}
}
if (window && window['habitica-i18n']) {
if (this.user.preferences.language === window['habitica-i18n'].language.code) {
return null;
}
}
return axios.get(
'/api/v4/i18n/browser-script',
{

View File

@@ -24,6 +24,8 @@ const AdminContainerPage = () => import(/* webpackChunkName: "admin-panel" */'@/
const AdminPanelPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/admin-panel');
const AdminPanelUserPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/admin-panel/user-support');
const AdminPanelSearchPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/admin-panel/search');
const GroupAdminPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/groups');
const GroupAdminGroupPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/groups/group-support');
const BlockerPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/blocker');
// Tasks
@@ -76,12 +78,6 @@ const router = new VueRouter({
// NOTE: when adding a new route entry make sure to implement the `common:setTitle` action
// in the route component to set a specific subtitle for the page.
routes: [
{
name: 'register', path: '/register', component: RegisterLoginReset, meta: { requiresLogin: false },
},
{
name: 'login', path: '/login', component: RegisterLoginReset, meta: { requiresLogin: false },
},
{ name: 'logout', path: '/logout', component: Logout },
{
name: 'resetPassword', path: '/reset-password', component: RegisterLoginReset, meta: { requiresLogin: false },
@@ -94,6 +90,9 @@ const router = new VueRouter({
path: '/profile/:userId',
props: true,
},
{ name: 'profile', path: '/user/profile' },
{ name: 'stats', path: '/user/stats' },
{ name: 'achievements', path: '/user/achievements' },
{
path: '/inventory',
component: InventoryContainer,
@@ -222,6 +221,28 @@ const router = new VueRouter({
},
],
},
{
name: 'groupAdmin',
path: 'groups',
component: GroupAdminPage,
meta: {
privilegeNeeded: [ // any one of these is enough to give access
'groupSupport',
],
},
children: [
{
name: 'groupAdminGroup',
path: ':groupId',
component: GroupAdminGroupPage,
meta: {
privilegeNeeded: [
'groupsSupport',
],
},
},
],
},
{
name: 'blockers',
path: 'blockers',
@@ -351,6 +372,10 @@ router.beforeEach(async (to, from, next) => {
if (to.params.startingPage !== undefined) {
startingPage = to.params.startingPage;
}
// Check if there's a hash in the URL for stats or achievements
if (to.hash === '#stats' || to.hash === '#achievements') {
startingPage = to.hash.substring(1);
}
if (from.name === null) {
store.state.postLoadModal = `profile/${to.params.userId}`;
return next({ name: 'tasks' });
@@ -371,10 +396,18 @@ router.beforeEach(async (to, from, next) => {
}
if ((to.name === 'stats' || to.name === 'achievements' || to.name === 'profile') && from.name !== null) {
const userId = store.state.user.data._id;
let redirectPath = `/profile/${userId}`;
if (to.name === 'stats') {
redirectPath += '#stats';
} else if (to.name === 'achievements') {
redirectPath += '#achievements';
}
router.app.$emit('habitica:show-profile', {
userId,
startingPage: to.name,
fromPath: from.path,
toPath: to.path,
toPath: redirectPath,
});
return null;
}

View File

@@ -6,13 +6,12 @@ const StaticWrapper = () => import('@/components/static/staticWrapper');
const HomePage = () => import('@/components/static/home');
const AppleRedirectPage = () => import('@/components/static/appleRedirect');
const ChatSunsetFaq = () => import('@/components/static/chatSunsetFaq');
const ClearBrowserDataPage = () => import('@/components/static/clearBrowserData');
const CommunityGuidelinesPage = () => import('@/components/static/communityGuidelines');
const ContentScheduleFaq = () => import('@/components/static/contentScheduleFaq');
const ContactPage = () => import('@/components/static/contact');
const FAQPage = () => import('@/components/static/faq');
const ChatSunsetFaq = () => import('@/components/static/chatSunsetFaq');
const ContentScheduleFaq = () => import('@/components/static/contentScheduleFaq');
const SubscriptionBenefitsFaq = () => import('@/components/static/subscriptionBenefitsFaq');
const FeaturesPage = () => import('@/components/static/features');
const GroupPlansPage = () => import('@/components/static/groupPlans');
// Commenting out merch page see
@@ -22,9 +21,10 @@ const NewsPage = () => import('@/components/static/newStuff');
const OverviewPage = () => import('@/components/static/overview');
const PressKitPage = () => import('@/components/static/pressKit');
const PrivacyPage = () => import('@/components/static/privacy');
const PrivacyReviewPage = () => import('@/components/static/privacyReview');
const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'@/components/auth/registerLoginReset');
const RegisterUsername = () => import(/* webpackChunkName: "auth" */'@/components/auth/registerUsername');
const SubscriptionBenefitsFaq = () => import('@/components/static/subscriptionBenefitsFaq');
const TermsPage = () => import('@/components/static/terms');
const TermsReviewPage = () => import('@/components/static/termsReview');
export const STATIC_ROUTES = {
path: '/static',
@@ -57,6 +57,9 @@ export const STATIC_ROUTES = {
{
name: 'features', path: 'features', component: FeaturesPage, meta: { requiresLogin: false },
},
{
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
},
{
name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: { requiresLogin: false },
},
@@ -64,7 +67,7 @@ export const STATIC_ROUTES = {
name: 'home', path: 'home', component: HomePage, meta: { requiresLogin: false },
},
{
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
name: 'login', path: '/login', component: RegisterLoginReset, meta: { requiresLogin: false },
},
{
name: 'news', path: 'new-stuff', component: NewsPage, meta: { requiresLogin: false },
@@ -82,13 +85,19 @@ export const STATIC_ROUTES = {
name: 'privacy', path: 'privacy', component: PrivacyPage, meta: { requiresLogin: false },
},
{
name: 'privacyReview', path: 'privacy-review', component: PrivacyReviewPage, meta: { requiresLogin: false },
name: 'privacyReview', path: 'privacy-review', component: PrivacyPage, meta: { requiresLogin: false },
},
{
name: 'register', path: '/register', component: RegisterLoginReset, meta: { requiresLogin: false },
},
{
name: 'terms', path: 'terms', component: TermsPage, meta: { requiresLogin: false },
},
{
name: 'termsReview', path: 'terms-review', component: TermsReviewPage, meta: { requiresLogin: false },
name: 'termsReview', path: 'terms-review', component: TermsPage, meta: { requiresLogin: false },
},
{
name: 'username', path: '/username', component: RegisterUsername, meta: { requiresLogin: false },
},
{
name: 'notFound', path: 'not-found', component: NotFoundPage, meta: { requiresLogin: false },

View File

@@ -0,0 +1,31 @@
import axios from 'axios';
export async function searchUsers (store, payload) {
const url = `/api/v4/admin/search/${payload.userIdentifier}`;
const response = await axios.get(url);
return response.data.data;
}
export async function getUserHistory (store, payload) {
const url = `/api/v4/admin/user/${payload.userIdentifier}/history`;
const response = await axios.get(url);
return response.data.data;
}
export async function getSubscriptionPaymentDetails (store, payload) {
const url = `/api/v4/admin/user/${payload.userIdentifier}/subscription-payment-details`;
const response = await axios.get(url);
return response.data.data;
}
export async function getGroup (store, payload) {
const url = `/api/v4/admin/groups/${payload.groupId}`;
const response = await axios.get(url);
return response.data.data;
}
export async function updateGroup (store, payload) {
const url = `/api/v4/admin/groups/${payload.groupId || payload.group._id}`;
const response = await axios.put(url, payload.group);
return response.data.data;
}

View File

@@ -1,13 +0,0 @@
import axios from 'axios';
export async function searchUsers (store, payload) {
const url = `/api/v4/admin/search/${payload.userIdentifier}`;
const response = await axios.get(url);
return response.data.data;
}
export async function getUserHistory (store, payload) {
const url = `/api/v4/admin/user/${payload.userIdentifier}/history`;
const response = await axios.get(url);
return response.data.data;
}

View File

@@ -64,34 +64,63 @@ export async function verifyDisplayName (store, params) {
return result.data.data;
}
export async function checkEmail (store, params) {
const url = '/api/v4/user/auth/check-email';
const result = await axios.post(url, {
email: params.email,
});
return result.data.data;
}
export async function socialAuth (store, params) {
const url = '/api/v4/user/auth/social';
const result = await axios.post(url, {
allowRegister: params.allowRegister,
username: params.username,
network: params.auth.network,
authResponse: params.auth.authResponse,
});
if (!result.data) {
return null;
}
const user = result.data.data;
saveLocalDataAuth(store, user.id, user.apiToken);
return user.id;
}
export async function appleAuth (store, params) {
const url = '/api/v4/user/auth/apple';
const result = await axios.get(url, {
params: {
allowRegister: params.allowRegister,
code: params.code,
id_token: params.idToken,
name: params.name,
username: params.username,
},
});
if (!result.data) {
return null;
}
if (result.data.message && result.data.id_token) {
return { idToken: result.data.id_token };
}
const user = result.data.data;
saveLocalDataAuth(store, user.id, user.apiToken);
return { id: user.id };
}
export function logout (store, options = {}) {
localStorage.clear();
sessionStorage.clear();
const query = options.redirectToLogin === true ? '?redirectToLogin=true' : '';
window.location.href = `/logout-server${query}`;
}

View File

@@ -38,3 +38,9 @@ export async function getHeroGroupPlans (store, payload) {
const response = await axios.get(url);
return response.data.data;
}
export async function deleteHero (store, payload) {
const url = `/api/v4/members/${payload.uuid}?deleteAccount=${payload.deleteHabiticaAccount}&deleteAmplitude=${payload.deleteAmplitudeData}`;
const response = await axios.delete(url);
return response.data.data;
}

View File

@@ -1,6 +1,6 @@
import { flattenAndNamespace } from '@/libs/store/helpers/internals';
import * as adminPanel from './adminPanel';
import * as admin from './admin';
import * as common from './common';
import * as user from './user';
import * as tasks from './tasks';
@@ -26,7 +26,7 @@ import * as blockers from './blockers';
// Example: fetch in user.js -> 'user:fetch'
const actions = flattenAndNamespace({
adminPanel,
admin,
common,
user,
tasks,

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