mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-12 19:54:04 -05:00
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 51b3b0c4c7 | |||
| 174a4e69f9 | |||
| 1ce060eac6 | |||
| 4fe8b63748 | |||
| b5c64185f0 | |||
| 894558f2df | |||
| f1381878e7 | |||
| 9bd039b17b | |||
| 90b34c4dac | |||
| 96a919ed4b | |||
| e56b672226 | |||
| 91cbf7a2a9 | |||
| 04e2a39a9f | |||
| bdd926e110 | |||
| a8e9c9bc70 | |||
| 497073a714 | |||
| f1fa6a8456 | |||
| 3f690c24da | |||
| f24d81d895 | |||
| 82c5e40b92 | |||
| 6b27e18699 | |||
| 4f70a6fbf4 | |||
| 300c2bb0a8 | |||
| 4b4f073089 | |||
| 1d8e3d45a1 | |||
| 116068effa | |||
| f2aaee15f3 | |||
| 25c7d52d6a | |||
| 837c1c20a3 | |||
| 02b11a61bc | |||
| a0e28f7db4 | |||
| fdfa2d6df4 | |||
| 4dca69f14b | |||
| 1378b1e1ad | |||
| 734a611a5c | |||
| 11496f3e0c | |||
| 9a3e3aaf42 | |||
| d9250fd780 | |||
| 70a5124815 | |||
| 532fa2816b | |||
| d22f191f83 | |||
| 2b49a800a5 | |||
| 978e8c4320 | |||
| 0e6ece95a4 | |||
| b08ed8b0fb | |||
| f6e5360bdd | |||
| 9e98e56e9b | |||
| c16207c9ba | |||
| 53fb28cc48 | |||
| 1c0710b45b | |||
| 2add227b97 | |||
| 4cc1f902c8 | |||
| 1b52529822 | |||
| 222ba544d7 | |||
| 2372efa22e | |||
| 18ec3eb355 | |||
| 62b4315b3d | |||
| 56805e6c90 | |||
| 0c6070dd9a | |||
| 19c26c01e3 | |||
| 0f3bc980d9 | |||
| 7f87120d34 | |||
| f7a03d2eb5 | |||
| 90250d1a25 | |||
| 22a0c72f6e | |||
| a4326498d1 | |||
| 8f26a22bd4 | |||
| 0b2cf5bceb | |||
| f43a0d8289 | |||
| 39be8db4f9 | |||
| f0a1f11a16 | |||
| 84c4b3536c | |||
| cf834f57d7 | |||
| 97be341ff6 | |||
| 15c68abafa | |||
| 21a1b9449b | |||
| 0ec7784fb1 | |||
| 9ddd0f29d0 | |||
| 37791dfe8d | |||
| 0322b657b8 | |||
| cc39f6e4e9 | |||
| 452b516c67 | |||
| 235eae32b0 | |||
| de9f1be7b9 | |||
| e75610447f | |||
| bd4c65cd3e | |||
| baf60dc951 | |||
| 70e88d601c | |||
| 104ec60adb | |||
| e97454e0e7 | |||
| 144baa98b1 | |||
| 02e33853b1 | |||
| 8c0d41d084 | |||
| 9d4f70371d |
+1
-1
Submodule habitica-images updated: 941bf731d6...d66a5ea922
Generated
+341
-784
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.249.4",
|
||||
"version": "4.251.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/preset-env": "^7.19.1",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/register": "^7.18.9",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.1.3",
|
||||
@@ -68,7 +68,7 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.2.2",
|
||||
"stripe": "^10.13.0",
|
||||
"superagent": "^8.0.2",
|
||||
"superagent": "^8.0.3",
|
||||
"universal-analytics": "^0.5.3",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^8.3.2",
|
||||
@@ -111,10 +111,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"chai": "^4.3.6",
|
||||
"chai": "^4.3.7",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
"chalk": "^5.1.0",
|
||||
"chalk": "^5.1.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
@@ -122,7 +122,7 @@
|
||||
"monk": "^7.3.4",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.7",
|
||||
"sinon": "^14.0.1",
|
||||
"sinon": "^14.0.2",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -11,10 +11,13 @@ import {
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import * as worldState from '../../../../../website/server/libs/worldState';
|
||||
import { TransactionModel } from '../../../../../website/server/models/transaction';
|
||||
|
||||
describe('payments/index', () => {
|
||||
let user; let group; let data; let
|
||||
plan;
|
||||
let user;
|
||||
let group;
|
||||
let data;
|
||||
let plan;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
@@ -104,6 +107,23 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||
});
|
||||
|
||||
it('add a transaction entry to the recipient', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||
|
||||
const transactions = await TransactionModel
|
||||
.find({ userId: recipient._id })
|
||||
.sort({ createdAt: -1 })
|
||||
.exec();
|
||||
|
||||
expect(transactions).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('does not set negative extraMonths if plan has past dateTerminated date', async () => {
|
||||
const dateTerminated = moment().subtract(2, 'months').toDate();
|
||||
recipient.purchased.plan.dateTerminated = dateTerminated;
|
||||
|
||||
@@ -344,6 +344,24 @@ describe('POST /user/auth/local/register', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('enforces maximum length for the password', async () => {
|
||||
const username = generateRandomUserName();
|
||||
const email = `${username}@example.com`;
|
||||
const password = '12345678910111213141516171819202122232425262728293031323334353637383940';
|
||||
const confirmPassword = '12345678910111213141516171819202122232425262728293031323334353637383940';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a username', async () => {
|
||||
const email = `${generateRandomUserName()}@example.com`;
|
||||
const password = 'password';
|
||||
|
||||
Generated
+9
-18
@@ -17072,9 +17072,9 @@
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz",
|
||||
"integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA=="
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
|
||||
"integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.7.0",
|
||||
@@ -28824,7 +28824,6 @@
|
||||
"version": "npm:vue-loader@16.8.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
|
||||
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
@@ -28835,7 +28834,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
@@ -28844,7 +28842,6 @@
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
@@ -28854,7 +28851,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
@@ -28862,26 +28858,22 @@
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"optional": true
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"optional": true
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"optional": true
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
|
||||
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
|
||||
"optional": true,
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
|
||||
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
@@ -28892,7 +28884,6 @@
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"bootstrap-vue": "^2.22.0",
|
||||
"chai": "^4.3.6",
|
||||
"core-js": "^3.26.0",
|
||||
"dompurify": "^2.4.0",
|
||||
"dompurify": "^2.4.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
@@ -49,6 +49,7 @@
|
||||
"sass": "^1.34.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.19.1",
|
||||
"stopword": "^2.0.5",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"svg-url-loader": "^7.1.1",
|
||||
"svgo": "^1.3.2",
|
||||
|
||||
@@ -690,6 +690,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_branches_of_a_holiday_tree {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_branches_of_a_holiday_tree.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_brick_wall_with_ivy {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_brick_wall_with_ivy.png');
|
||||
width: 141px;
|
||||
@@ -1309,6 +1314,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_inside_a_crystal {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_inside_a_crystal.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_inside_a_potion_bottle {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_inside_a_potion_bottle.png');
|
||||
width: 141px;
|
||||
@@ -1714,6 +1724,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_snowy_village {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_snowy_village.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_south_pole {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_south_pole.png');
|
||||
width: 141px;
|
||||
@@ -2291,6 +2306,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_branches_of_a_holiday_tree {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_branches_of_a_holiday_tree.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_brick_wall_with_ivy {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_brick_wall_with_ivy.png');
|
||||
width: 68px;
|
||||
@@ -2915,6 +2935,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_inside_a_crystal {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_inside_a_crystal.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_inside_a_potion_bottle {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_inside_a_potion_bottle.png');
|
||||
width: 68px;
|
||||
@@ -3320,6 +3345,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_snowy_village {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_snowy_village.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_south_pole {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_south_pole.png');
|
||||
width: 68px;
|
||||
@@ -18405,6 +18435,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_jewelersApron {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_jewelersApron.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_lamplightersGreatcoat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_lamplightersGreatcoat.png');
|
||||
width: 114px;
|
||||
@@ -18620,6 +18655,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.eyewear_armoire_jewelersEyeLoupe {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/eyewear_armoire_jewelersEyeLoupe.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.eyewear_armoire_plagueDoctorMask {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/eyewear_armoire_plagueDoctorMask.png');
|
||||
width: 90px;
|
||||
@@ -19200,6 +19240,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_jewelersPliers {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_jewelersPliers.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_lifeBuoy {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_lifeBuoy.png');
|
||||
width: 114px;
|
||||
@@ -19625,6 +19670,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_armoire_jewelersApron {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_jewelersApron.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_armoire_lamplightersGreatcoat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_lamplightersGreatcoat.png');
|
||||
width: 68px;
|
||||
@@ -19855,6 +19905,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_eyewear_armoire_jewelersEyeLoupe {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_armoire_jewelersEyeLoupe.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_eyewear_armoire_plagueDoctorMask {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_armoire_plagueDoctorMask.png');
|
||||
width: 68px;
|
||||
@@ -20435,6 +20490,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_armoire_jewelersPliers {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_armoire_jewelersPliers.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_armoire_lifeBuoy {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_armoire_lifeBuoy.png');
|
||||
width: 68px;
|
||||
@@ -20760,6 +20820,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_finelyCutGem {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_finelyCutGem.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_floridFan {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_floridFan.png');
|
||||
width: 68px;
|
||||
@@ -21315,6 +21380,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_jewelersApron {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_jewelersApron.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_lamplightersGreatcoat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_lamplightersGreatcoat.png');
|
||||
width: 114px;
|
||||
@@ -21645,6 +21715,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_finelyCutGem {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_finelyCutGem.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_floridFan {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_floridFan.png');
|
||||
width: 114px;
|
||||
@@ -27415,6 +27490,46 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_mystery_202212 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202212.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_mystery_202212 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_mystery_202212.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_armor_mystery_202212 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_mystery_202212.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_mystery_202212 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_mystery_202212.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_set_mystery_202212 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202212.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_mystery_202212 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_mystery_202212.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.slim_armor_mystery_202212 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202212.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_mystery_202212 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202212.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_mystery_301404 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png');
|
||||
width: 90px;
|
||||
@@ -32909,204 +33024,6 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.headAccessory_special_bearEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_bearEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_bearEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_bearEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_blackHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_blackHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_blueHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_blueHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_cactusEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_cactusEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_cactusEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_cactusEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_foxEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_foxEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_foxEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_foxEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_greenHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_greenHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_lionEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pandaEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pandaEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_pandaEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pandaEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pigEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pigEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_pigEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pigEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pinkHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pinkHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_redHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_redHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_tigerEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_tigerEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_tigerEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_tigerEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_whiteHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_whiteHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_wolfEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_wolfEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_wolfEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_wolfEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_yellowHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_yellowHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_headAccessory_special_bearEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_bearEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_blackHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_blackHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_blueHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_blueHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_cactusEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_cactusEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_foxEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_foxEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_greenHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_greenHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_lionEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pandaEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pandaEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pigEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pigEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pinkHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pinkHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_redHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_redHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_tigerEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_tigerEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_whiteHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_whiteHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_wolfEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_wolfEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_yellowHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_yellowHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.head_0 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_0.png');
|
||||
width: 90px;
|
||||
@@ -33488,6 +33405,204 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.headAccessory_special_bearEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_bearEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_bearEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_bearEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_blackHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_blackHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_blueHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_blueHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_cactusEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_cactusEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_cactusEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_cactusEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_foxEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_foxEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_foxEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_foxEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_greenHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_greenHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_lionEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pandaEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pandaEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_pandaEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pandaEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pigEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pigEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_pigEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pigEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pinkHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pinkHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_redHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_redHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_tigerEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_tigerEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_tigerEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_tigerEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_whiteHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_whiteHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_wolfEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_wolfEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_wolfEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_wolfEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_yellowHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_yellowHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_headAccessory_special_bearEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_bearEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_blackHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_blackHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_blueHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_blueHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_cactusEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_cactusEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_foxEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_foxEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_greenHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_greenHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_lionEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pandaEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pandaEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pigEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pigEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pinkHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pinkHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_redHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_redHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_tigerEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_tigerEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_whiteHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_whiteHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_wolfEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_wolfEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_yellowHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_yellowHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shield_healer_1 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_healer_1.png');
|
||||
width: 90px;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="10" viewBox="0 0 13 10">
|
||||
<path fill-rule="evenodd" d="M4.662 9.832c-.312 0-.61-.123-.831-.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 xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path d="M6.54,13c-.3,0-.59-.13-.81-.35l-3.73-3.9,1.62-1.69,2.86,2.98L12.26,3l1.74,1.56L7.41,12.58c-.21,.25-.51,.4-.83,.42-.01,0-.03,0-.04,0Z" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 236 B |
@@ -267,7 +267,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4">
|
||||
<sidebar-section :title="$t('staffAndModerators')">
|
||||
<sidebar-section :title="$t('staff')">
|
||||
<div class="row">
|
||||
<div
|
||||
v-for="user in staff"
|
||||
@@ -289,19 +289,6 @@
|
||||
class="svg-icon staff-icon"
|
||||
v-html="icons.tierStaff"
|
||||
></div>
|
||||
<div
|
||||
v-if="user.type === 'Moderator' && user.name !== 'It\'s Bailey'"
|
||||
class="svg-icon mod-icon"
|
||||
v-html="icons.tierMod"
|
||||
></div>
|
||||
<div
|
||||
v-if="user.name === 'It\'s Bailey'"
|
||||
class="svg-icon npc-icon"
|
||||
v-html="icons.tierNPC"
|
||||
></div>
|
||||
</div>
|
||||
<div class="type">
|
||||
{{ user.type }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -787,18 +787,15 @@ export default {
|
||||
if (sortBy === 'sortByColor') {
|
||||
groupKey = 'potionKey';
|
||||
} else if (sortBy === 'AZ') {
|
||||
groupKey = '';
|
||||
groupKey = i => i.eggName[0];
|
||||
} else if (sortBy === 'sortByHatchable') {
|
||||
groupKey = i => (i.isHatchable() ? 0 : 1);
|
||||
}
|
||||
const groupedPets = groupBy(pets, groupKey);
|
||||
|
||||
// Pets are rendered as grouped "rows". Count helps decide if show more button is necessary.
|
||||
if (sortBy === 'AZ') {
|
||||
this.petRowCount[animalGroup.key] = 1;
|
||||
} else {
|
||||
this.petRowCount[animalGroup.key] = Object.keys(groupedPets).length;
|
||||
}
|
||||
this.petRowCount[animalGroup.key] = Object.keys(groupedPets).length;
|
||||
|
||||
return groupedPets;
|
||||
},
|
||||
mounts (animalGroup, hideMissing, sortBy, searchText) {
|
||||
@@ -814,14 +811,12 @@ export default {
|
||||
if (sortBy === 'sortByColor') {
|
||||
groupKey = 'potionKey';
|
||||
} else if (sortBy === 'AZ') {
|
||||
groupKey = '';
|
||||
groupKey = i => i.eggName[0];
|
||||
}
|
||||
const groupedMounts = groupBy(mounts, groupKey);
|
||||
if (sortBy === 'AZ') {
|
||||
this.mountRowCount[animalGroup.key] = 1;
|
||||
} else {
|
||||
this.mountRowCount[animalGroup.key] = Object.keys(groupedMounts).length;
|
||||
}
|
||||
|
||||
this.mountRowCount[animalGroup.key] = Object.keys(groupedMounts).length;
|
||||
|
||||
return groupedMounts;
|
||||
},
|
||||
// Actions
|
||||
|
||||
@@ -359,8 +359,8 @@
|
||||
|
||||
.svg-icon.check {
|
||||
color: $purple-400;
|
||||
width: 0.77rem;
|
||||
height: 0.615rem;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.text-leadin {
|
||||
|
||||
@@ -385,7 +385,6 @@ import EquipmentAttributesGrid from '../inventory/equipment/attributesGrid.vue';
|
||||
import Item from '@/components/inventory/item';
|
||||
import Avatar from '@/components/avatar';
|
||||
|
||||
import seasonalShopConfig from '@/../../common/script/libs/shops-seasonal.config';
|
||||
import { drops as dropEggs } from '@/../../common/script/content/eggs';
|
||||
import { drops as dropPotions } from '@/../../common/script/content/hatching-potions';
|
||||
|
||||
@@ -438,7 +437,6 @@ export default {
|
||||
|
||||
selectedAmountToBuy: 1,
|
||||
isPinned: false,
|
||||
endDate: seasonalShopConfig.dateRange.end,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -489,6 +487,9 @@ export default {
|
||||
nonSubscriberHourglasses () {
|
||||
return (!this.user.purchased.plan.customerId && !this.user.purchased.plan.consecutive.trinkets && this.getPriceClass() === 'hourglasses');
|
||||
},
|
||||
endDate () {
|
||||
return moment(this.item.event.end);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
item: function itemChanged () {
|
||||
|
||||
@@ -8,16 +8,6 @@
|
||||
:popover-position="'top'"
|
||||
@click="itemSelected(item)"
|
||||
>
|
||||
<span slot="popoverContent">
|
||||
<strong v-if="item.key === 'gem' && gemsLeft === 0">{{ $t('maxBuyGems') }}</strong>
|
||||
<h4 class="popover-content-title">{{ item.text }}</h4>
|
||||
<div
|
||||
v-if="item.event"
|
||||
class="mt-2"
|
||||
>
|
||||
{{ limitedString }}
|
||||
</div>
|
||||
</span>
|
||||
<template
|
||||
slot="itemBadge"
|
||||
slot-scope="ctx"
|
||||
@@ -32,11 +22,9 @@
|
||||
import _filter from 'lodash/filter';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _map from 'lodash/map';
|
||||
import moment from 'moment';
|
||||
import { mapState } from '@/libs/store';
|
||||
import pinUtils from '@/mixins/pinUtils';
|
||||
import planGemLimits from '@/../../common/script/libs/planGemLimits';
|
||||
import seasonalShopConfig from '@/../../common/script/libs/shops-seasonal.config';
|
||||
|
||||
import ShopItem from '../shopItem';
|
||||
import CategoryItem from './categoryItem';
|
||||
@@ -48,12 +36,6 @@ export default {
|
||||
},
|
||||
mixins: [pinUtils],
|
||||
props: ['hideLocked', 'hidePinned', 'searchBy', 'sortBy', 'category'],
|
||||
data () {
|
||||
return {
|
||||
timer: '',
|
||||
limitedString: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
@@ -106,43 +88,10 @@ export default {
|
||||
return result;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.countdownString();
|
||||
this.timer = setInterval(this.countdownString, 1000);
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.cancelAutoUpdate();
|
||||
},
|
||||
methods: {
|
||||
itemSelected (item) {
|
||||
this.$root.$emit('buyModal::showItem', item);
|
||||
},
|
||||
countdownString () {
|
||||
const diffDuration = moment.duration(moment(seasonalShopConfig.dateRange.end).diff(moment()));
|
||||
|
||||
if (diffDuration.asSeconds() <= 0) {
|
||||
this.limitedString = this.$t('noLongerAvailable');
|
||||
} else if (diffDuration.days() > 0 || diffDuration.months() > 0) {
|
||||
this.limitedString = this.$t('limitedAvailabilityDays', {
|
||||
days: moment(seasonalShopConfig.dateRange.end).diff(moment(), 'days'),
|
||||
hours: diffDuration.hours(),
|
||||
minutes: diffDuration.minutes(),
|
||||
});
|
||||
} else if (diffDuration.asMinutes() > 2) {
|
||||
this.limitedString = this.$t('limitedAvailabilityHours', {
|
||||
hours: diffDuration.hours(),
|
||||
minutes: diffDuration.minutes(),
|
||||
});
|
||||
} else {
|
||||
this.limitedString = this.$t('limitedAvailabilityMinutes', {
|
||||
minutes: diffDuration.minutes(),
|
||||
seconds: diffDuration.seconds(),
|
||||
});
|
||||
}
|
||||
},
|
||||
cancelAutoUpdate () {
|
||||
clearInterval(this.timer);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -263,8 +263,8 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { mapState } from '@/libs/store';
|
||||
import seasonalShopConfig from '@/../../common/script/libs/shops-seasonal.config';
|
||||
|
||||
import svgClock from '@/assets/svg/clock.svg';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
@@ -319,7 +319,6 @@ export default {
|
||||
|
||||
isPinned: false,
|
||||
selectedAmountToBuy: 1,
|
||||
endDate: seasonalShopConfig.dateRange.end,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -343,6 +342,9 @@ export default {
|
||||
if (this.priceType === 'hourglasses') return this.icons.hourglass;
|
||||
return this.icons.gem;
|
||||
},
|
||||
endDate () {
|
||||
return moment(this.item.event.end);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
item: function itemChanged () {
|
||||
|
||||
@@ -402,6 +402,8 @@ import _sortBy from 'lodash/sortBy';
|
||||
import _throttle from 'lodash/throttle';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
import _map from 'lodash/map';
|
||||
import _each from 'lodash/each';
|
||||
import * as stopword from 'stopword/dist/stopword.esm.mjs';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import ShopItem from '../shopItem';
|
||||
@@ -426,6 +428,51 @@ import SelectTranslatedArray from '@/components/tasks/modal-controls/selectTrans
|
||||
import QuestPopover from './questPopover';
|
||||
import { worldStateMixin } from '@/mixins/worldState';
|
||||
|
||||
function splitMultipleDelims (text, delims) {
|
||||
const omniDelim = 'θνι';
|
||||
let workingText = text;
|
||||
for (const delim of delims) {
|
||||
workingText = workingText.replace(new RegExp(delim, 'g'), omniDelim);
|
||||
}
|
||||
return workingText.split(omniDelim);
|
||||
}
|
||||
|
||||
function removeStopwordsFromText (text, language) {
|
||||
// list of supported languages https://www.npmjs.com/package/stopword
|
||||
const langs = {
|
||||
bg: stopword.bul,
|
||||
cs: stopword.ces,
|
||||
da: stopword.dan,
|
||||
de: stopword.deu,
|
||||
en: stopword.eng,
|
||||
en_GB: stopword.eng,
|
||||
'en@pirate': stopword.eng.concat(["th'"]),
|
||||
es: stopword.spa,
|
||||
es_419: stopword.spa,
|
||||
fr: stopword.fra,
|
||||
he: stopword.heb,
|
||||
hu: stopword.hun,
|
||||
id: stopword.ind,
|
||||
it: stopword.ita,
|
||||
ja: stopword.jpn,
|
||||
nl: stopword.nld,
|
||||
pl: stopword.pol,
|
||||
pt: stopword.por,
|
||||
pt_BR: stopword.porBr,
|
||||
ro: stopword.ron,
|
||||
ru: stopword.rus,
|
||||
sk: stopword.slv,
|
||||
// sr: stopword.,
|
||||
sv: stopword.swe,
|
||||
tr: stopword.tur,
|
||||
uk: stopword.ukr,
|
||||
zh: stopword.zho,
|
||||
zh_TW: stopword.zho,
|
||||
};
|
||||
const splitText = splitMultipleDelims(text, [' ', "'"]);
|
||||
return stopword.removeStopwords(splitText, langs[language] || stopword.eng).join(' ').toLowerCase();
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
QuestPopover,
|
||||
@@ -539,7 +586,14 @@ export default {
|
||||
|
||||
switch (sortBy) { // eslint-disable-line default-case
|
||||
case 'AZ': {
|
||||
result = _sortBy(result, ['text']);
|
||||
if (category.identifier === 'pet' || category.identifier === 'hatchingPotion') {
|
||||
_each(result, item => {
|
||||
item.sortText = removeStopwordsFromText(item.text, this.user.preferences.language);
|
||||
});
|
||||
result = _sortBy(result, ['sortText']);
|
||||
} else {
|
||||
result = _sortBy(result, ['text']);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -206,12 +206,11 @@
|
||||
}
|
||||
|
||||
span.svg-icon.inline.check {
|
||||
height: 12px;
|
||||
width: 10px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 8px;
|
||||
margin-top: 0;
|
||||
left: 4px;
|
||||
top: 4px;
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,25 +13,29 @@
|
||||
:key="index"
|
||||
class="faq-question"
|
||||
>
|
||||
<h2
|
||||
v-b-toggle="heading"
|
||||
role="tab"
|
||||
variant="info"
|
||||
@click="handleClick($event)"
|
||||
<div
|
||||
v-if="heading !== 'world-boss'"
|
||||
>
|
||||
{{ $t(`faqQuestion${index}`) }}
|
||||
</h2>
|
||||
<b-collapse
|
||||
:id="heading"
|
||||
:visible="isVisible(heading)"
|
||||
accordion="faq"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
v-markdown="$t(`webFaqAnswer${index}`, replacements)"
|
||||
class="card-body"
|
||||
></div>
|
||||
</b-collapse>
|
||||
<h2
|
||||
v-b-toggle="heading"
|
||||
role="tab"
|
||||
variant="info"
|
||||
@click="handleClick($event)"
|
||||
>
|
||||
{{ $t(`faqQuestion${index}`) }}
|
||||
</h2>
|
||||
<b-collapse
|
||||
:id="heading"
|
||||
:visible="isVisible(heading)"
|
||||
accordion="faq"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
v-markdown="$t(`webFaqAnswer${index}`, replacements)"
|
||||
class="card-body"
|
||||
></div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<p v-markdown="$t('webFaqStillNeedHelp')"></p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="task-wrapper">
|
||||
<div class="task-wrapper" draggable>
|
||||
<div
|
||||
class="task transition"
|
||||
:class="[{
|
||||
@@ -773,9 +773,9 @@
|
||||
}
|
||||
|
||||
.check.svg-icon {
|
||||
width: 12.3px;
|
||||
height: 9.8px;
|
||||
margin: 9px 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.challenge.broken {
|
||||
|
||||
@@ -34,44 +34,4 @@ export default [
|
||||
type: 'Staff',
|
||||
uuid: 'f4e5c6da-0617-48bf-b3bd-9f97636774a8',
|
||||
},
|
||||
{
|
||||
name: 'Alys',
|
||||
type: 'Moderator',
|
||||
uuid: 'd904bd62-da08-416b-a816-ba797c9ee265',
|
||||
},
|
||||
{
|
||||
name: 'Cantras',
|
||||
type: 'Moderator',
|
||||
uuid: '28771972-ca6d-4c03-8261-e1734aa7d21d',
|
||||
},
|
||||
{
|
||||
name: 'deilann',
|
||||
type: 'Moderator',
|
||||
uuid: 'e7b5d1e2-3b6e-4192-b867-8bafdb03eeec',
|
||||
},
|
||||
{
|
||||
name: 'Dewines',
|
||||
type: 'Moderator',
|
||||
uuid: '262a7afb-6b57-4d81-88e0-80d2e9f6cbdc',
|
||||
},
|
||||
{
|
||||
name: 'Fox_town',
|
||||
type: 'Moderator',
|
||||
uuid: 'a05f0152-d66b-4ef1-93ac-4adb195d0031',
|
||||
},
|
||||
{
|
||||
name: 'MaybeSteveRogers',
|
||||
type: 'Moderator',
|
||||
uuid: '767e5d92-0e13-4e30-acb1-d8bba62824fc',
|
||||
},
|
||||
{
|
||||
name: 'Nakonana',
|
||||
type: 'Moderator',
|
||||
uuid: '33bb14bd-814d-40cb-98a4-7b76a752761c',
|
||||
},
|
||||
{
|
||||
name: 'shanaqui',
|
||||
type: 'Moderator',
|
||||
uuid: 'bb089388-28ae-4e42-a8fa-f0c2bfb6f779',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -835,6 +835,14 @@
|
||||
"backgroundAutumnBridgeText": "Bridge in Autumn",
|
||||
"backgroundAutumnBridgeNotes": "Admire the beauty of a Bridge in Autumn.",
|
||||
|
||||
"backgrounds122022": "SET 103: Released December 2022",
|
||||
"backgroundBranchesOfAHolidayTreeText": "Branches of a Holiday Tree",
|
||||
"backgroundBranchesOfAHolidayTreeNotes": "Frolic on the Branches of a Holiday Tree.",
|
||||
"backgroundInsideACrystalText": "Inside A Crystal",
|
||||
"backgroundInsideACrystalNotes": "Peer out from Inside A Crystal.",
|
||||
"backgroundSnowyVillageText": "Snowy Village",
|
||||
"backgroundSnowyVillageNotes": "Admire a Snowy Village.",
|
||||
|
||||
"timeTravelBackgrounds": "Steampunk Backgrounds",
|
||||
"backgroundAirshipText": "Airship",
|
||||
"backgroundAirshipNotes": "Become a sky sailor on board your very own Airship.",
|
||||
|
||||
@@ -66,10 +66,10 @@
|
||||
"androidFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and Skills will damage the Boss as usual.\n\n You can also be in a normal Quest at the same time. Your tasks and Skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n\n You can read more about [past World Bosses](https://habitica.fandom.com/wiki/World_Bosses) on the wiki.",
|
||||
"webFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and Skills will damage the Boss as usual. You can also be in a normal Quest at the same time. Your tasks and Skills will count towards both the World Boss and the Boss/Collection Quest in your party. A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change. You can read more about [past World Bosses](https://habitica.fandom.com/wiki/World_Bosses) on the wiki.",
|
||||
|
||||
"faqQuestion13": "What is a Group Plan?",
|
||||
"webFaqAnswer13": "## How do Group Plans work?\n\nA [Group Plan](/group-plans) gives your Party or Guild access to a shared task board that’s similar to your personal task board! It’s a shared Habitica experience where tasks can be created and checked off by anyone in the group.\n\nThere are also features available like member roles, status view, and task assigning that give you a more controlled experience. [Visit our wiki](https://habitica.fandom.com/wiki/Group_Plans) to learn more about our Group Plans’ features!\n\n## Who benefits from a Group Plan?\n\nGroup Plans work best when you have a small team of people who want to collaborate together. We recommend 2-5 members.\n\nGroup Plans are great for families, whether it’s a parent and child or you and a partner. Shared goals, chores, or responsibilities are easy to keep track of on one board.\n\nGroup Plans can also be useful for teams of colleagues that have shared goals, or managers that want to introduce their employees to gamification.\n\n## Quick tips for using Groups\n\nHere are some quick tips to get you started with your new Group. We’ll provide more details in the following sections:\n\n* Make a member a manager to give them the ability to create and edit tasks\n* Leave tasks unassigned if anyone can complete it and it only needs done once\n* Assign a task to one person to make sure no one else can complete their task\n* Assign a task to multiple people if they all need to complete it\n* Toggle the ability to display shared tasks on your personal board to not miss anything\n* You get rewarded for the tasks you complete, even multi-assigned\n* Task completion rewards aren’t shared or split between Team members\n* Use task color on the team board to judge the average completion rate of tasks\n* Regularly review the tasks on your Team Board to make sure they are still relevant\n* Missing a Daily won’t damage you or your team, but the task will degrade in color\n\n## How can others in the group create tasks?\n\nOnly the group leader and managers can create tasks. If you’d like a group member to be able to create tasks, then you should promote them to be a manager by going to the Group Information tab, viewing the member list, and clicking the dot icon by their name.\n\n## How does assigning a task work?\n\nGroup Plans give you the unique ability to assign tasks to other group members. Assigning a task is great for delegating. If you assign a task to someone, then other members are prevented from completing it.\n\nYou can also assign a task to multiple people if it needs to be completed by more than one member. For example, if everyone has to brush their teeth, create a task and assign it to each group member. They will all be able to check it off and get their individual rewards for doing so. The main task will show as complete once everyone checks it off.\n\n## How do unassigned tasks work?\n\nUnassigned tasks can be completed by anyone in the group, so leave a task unassigned to allow any member to complete it. For example, taking out the trash. Whoever takes out the trash can check off the unassigned task and it will show as completed for everyone.\n\n## How does the synchronized day reset work?\n\nShared tasks will reset at the same time for everyone to keep the shared task board in sync. This time is visible on the shared task board and is determined by the group leader’s day start time. Because shared tasks reset automatically, you will not get a chance to complete yesterday’s uncompleted shared Dailies when you check in the next morning.\n\nShared Dailies will not do damage if they are missed, however they will degrade in color to help visualize progress. We don’t want the shared experience to be a negative one!\n\n## How do I use my Group on the mobile apps?\n\nWhile the mobile apps don’t fully support all Group Plans functionality yet, you can still complete shared tasks from the iOS and Android app by copying the tasks onto your personal task board. You can switch this preference on from Settings in the mobile apps or from the group task board on the browser version. Now open and assigned shared tasks will display on your personal task board across all platforms.\n\n## What’s the difference between a Group’s shared tasks and Challenges?\n\nGroup Plan shared task boards are more dynamic than Challenges, in that they can constantly be updated and interacted with. Challenges are great if you have one set of tasks to send out to many people.\n\nGroup Plans are also a paid feature, while Challenges are available free to everyone.\n\nYou cannot assign specific tasks in Challenges, and Challenges do not have a shared day reset. In general, Challenges offer less control and direct interaction.",
|
||||
|
||||
"iosFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ), come ask in the Tavern chat under Menu > Tavern! We're happy to help.",
|
||||
"androidFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ), come ask in the Tavern chat under Menu > Tavern! We're happy to help.",
|
||||
"webFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ), come ask in the [Habitica Help guild](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We're happy to help.",
|
||||
|
||||
"faqQuestion13": "What is a Group Plan?",
|
||||
"webFaqAnswer13": "## How do Group Plans work?\n\nA [Group Plan](/group-plans) gives your Party or Guild access to a shared task board that’s similar to your personal task board! It’s a shared Habitica experience where tasks can be created and checked off by anyone in the group.\n\nThere are also features available like member roles, status view, and task assigning that give you a more controlled experience. [Visit our wiki](https://habitica.fandom.com/wiki/Group_Plans) to learn more about our Group Plans’ features!\n\n## Who benefits from a Group Plan?\n\nGroup Plans work best when you have a small team of people who want to collaborate together. We recommend 2-5 members.\n\nGroup Plans are great for families, whether it’s a parent and child or you and a partner. Shared goals, chores, or responsibilities are easy to keep track of on one board.\n\nGroup Plans can also be useful for teams of colleagues that have shared goals, or managers that want to introduce their employees to gamification.\n\n## Quick tips for using Groups\n\nHere are some quick tips to get you started with your new Group. We’ll provide more details in the following sections:\n\n* Make a member a manager to give them the ability to create and edit tasks\n* Leave tasks unassigned if anyone can complete it and it only needs done once\n* Assign a task to one person to make sure no one else can complete their task\n* Assign a task to multiple people if they all need to complete it\n* Toggle the ability to display shared tasks on your personal board to not miss anything\n* You get rewarded for the tasks you complete, even multi-assigned\n* Task completion rewards aren’t shared or split between Team members\n* Use task color on the team board to judge the average completion rate of tasks\n* Regularly review the tasks on your Team Board to make sure they are still relevant\n* Missing a Daily won’t damage you or your team, but the task will degrade in color\n\n## How can others in the group create tasks?\n\nOnly the group leader and managers can create tasks. If you’d like a group member to be able to create tasks, then you should promote them to be a manager by going to the Group Information tab, viewing the member list, and clicking the dot icon by their name.\n\n## How does assigning a task work?\n\nGroup Plans give you the unique ability to assign tasks to other group members. Assigning a task is great for delegating. If you assign a task to someone, then other members are prevented from completing it.\n\nYou can also assign a task to multiple people if it needs to be completed by more than one member. For example, if everyone has to brush their teeth, create a task and assign it to each group member. They will all be able to check it off and get their individual rewards for doing so. The main task will show as complete once everyone checks it off.\n\n## How do unassigned tasks work?\n\nUnassigned tasks can be completed by anyone in the group, so leave a task unassigned to allow any member to complete it. For example, taking out the trash. Whoever takes out the trash can check off the unassigned task and it will show as completed for everyone.\n\n## How does the synchronized day reset work?\n\nShared tasks will reset at the same time for everyone to keep the shared task board in sync. This time is visible on the shared task board and is determined by the group leader’s day start time. Because shared tasks reset automatically, you will not get a chance to complete yesterday’s uncompleted shared Dailies when you check in the next morning.\n\nShared Dailies will not do damage if they are missed, however they will degrade in color to help visualize progress. We don’t want the shared experience to be a negative one!\n\n## How do I use my Group on the mobile apps?\n\nWhile the mobile apps don’t fully support all Group Plans functionality yet, you can still complete shared tasks from the iOS and Android app by copying the tasks onto your personal task board. You can switch this preference on from Settings in the mobile apps or from the group task board on the browser version. Now open and assigned shared tasks will display on your personal task board across all platforms.\n\n## What’s the difference between a Group’s shared tasks and Challenges?\n\nGroup Plan shared task boards are more dynamic than Challenges, in that they can constantly be updated and interacted with. Challenges are great if you have one set of tasks to send out to many people.\n\nGroup Plans are also a paid feature, while Challenges are available free to everyone.\n\nYou cannot assign specific tasks in Challenges, and Challenges do not have a shared day reset. In general, Challenges offer less control and direct interaction."
|
||||
"webFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ), come ask in the [Habitica Help guild](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We're happy to help."
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"mobileAndroid": "Android App",
|
||||
"mobileIOS": "iOS App",
|
||||
"oldNews": "News",
|
||||
"newsArchive": "News archive on Wikia (multilingual)",
|
||||
"newsArchive": "News archive on Fandom (multilingual)",
|
||||
"setNewPass": "Set New Password",
|
||||
"password": "Password",
|
||||
"playButton": "Play",
|
||||
|
||||
@@ -484,6 +484,8 @@
|
||||
"weaponMystery202209Notes": "This book will guide you through your journey into magic-making. Confers no benefit. September 2022 Subscriber Item.",
|
||||
"weaponMystery202211Text": "Electromancer Staff",
|
||||
"weaponMystery202211Notes": "Harness the awesome power of a lightning storm with this staff. Confers no benefit. November 2022 Subscriber Item.",
|
||||
"weaponMystery202212Text": "Glacial Wand",
|
||||
"weaponMystery202212Notes": "The glowing snowflake in this wand holds the power to warm hearts on even the coldest winter night! Confers no benefit. December 2022 Subscriber Item.",
|
||||
"weaponMystery301404Text": "Steampunk Cane",
|
||||
"weaponMystery301404Notes": "Excellent for taking a turn about town. March 3015 Subscriber Item. Confers no benefit.",
|
||||
|
||||
@@ -669,7 +671,8 @@
|
||||
"weaponArmoireFeatherDusterNotes": "Let these fancy feathers fly over all your old objects to make them shine like new. Just beware of the disturbed dust so you don’t sneeze! Increases Constitution and Perception by <%= attrs %> each. Enchanted Armoire: Cleaning Supplies Set (Item 2 of 3)",
|
||||
"weaponArmoireMagicSpatulaText": "Magic Spatula",
|
||||
"weaponArmoireMagicSpatulaNotes": "Watch your food fly and flip in the air. You get good luck for the day if it magically flips over three times and then lands back on your spatula. Increases Perception by <%= per %>. Enchanted Armoire: Cooking Implements Set (Item 1 of 2).",
|
||||
|
||||
"weaponArmoireFinelyCutGemText": "Finely Cut Gem",
|
||||
"weaponArmoireFinelyCutGemNotes": "What a find! This stunning, precision-cut gem will be the prize of your collection. And it might contain some special magic, just waiting for you to tap into it. Increases Constitution by <%= con %>. Enchanted Armoire: Jeweler Set (Item 4 of 4).",
|
||||
|
||||
"armor": "armor",
|
||||
"armorCapitalized": "Armor",
|
||||
@@ -1218,6 +1221,8 @@
|
||||
"armorMystery202207Notes": "This armor will have you looking glamorous and gelatinous. Confers no benefit. July 2022 Subscriber Item.",
|
||||
"armorMystery202210Text": "Ominous Ophidian Armor",
|
||||
"armorMystery202210Notes": "Try slithering for a change, you may find it's quite an efficient mode of transportation! Confers no benefit. October 2022 Subscriber Item.",
|
||||
"armorMystery202212Text": "Glacial Dress",
|
||||
"armorMystery202212Notes": "The universe can be cold, but this charming dress will keep you cozy as you fly. Confers no benefit. December 2022 Subscriber Item.",
|
||||
"armorMystery301404Text": "Steampunk Suit",
|
||||
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
|
||||
"armorMystery301703Text": "Steampunk Peacock Gown",
|
||||
@@ -1397,6 +1402,8 @@
|
||||
"armorArmoireFancyPirateSuitNotes": "Wear this fine jacket well as you organize your ship’s library or talk it through as a crew. Increases Constitution and Intelligence by <%= attrs %> each. Enchanted Armoire: Fancy Pirate Set (Item 1 of 3).",
|
||||
"armorArmoireSheetGhostCostumeText": "Sheet Ghost Costume",
|
||||
"armorArmoireSheetGhostCostumeNotes": "Boo! This is the scariest costume in all of Habitica, so wear it wisely... and watch your step so you don’t trip. Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
|
||||
"armorArmoireJewelersApronText": "Jeweler's Apron",
|
||||
"armorArmoireJewelersApronNotes": "This heavy-duty apron is just the thing to wear when you feel creative. Best of all, there are dozens of small pockets to hold everything you need. Increases Intelligence by <%= int %>. Enchanted Armoire: Jeweler Set (Item 1 of 4).",
|
||||
|
||||
"headgear": "helm",
|
||||
"headgearCapitalized": "Headgear",
|
||||
@@ -1972,7 +1979,6 @@
|
||||
"headMystery202210Notes": "This scaly hood will surely terrify your To-Do list into submission! Confers no benefit. October 2022 Subscriber Item.",
|
||||
"headMystery202211Text": "Electromancer Hat",
|
||||
"headMystery202211Notes": "Be careful with this powerful hat, its effect on admirers can be quite shocking! Confers no benefit. November 2022 Subscriber Item.",
|
||||
|
||||
"headMystery301404Text": "Fancy Top Hat",
|
||||
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
|
||||
"headMystery301405Text": "Basic Top Hat",
|
||||
@@ -2547,6 +2553,8 @@
|
||||
"shieldArmoireDustpanNotes": "Have this handy handheld dustpan ready every time you clean. A vanishing spell cast on it means you never have to search for a trash can to empty it into. Increases Intelligence and Constitution by <%= attrs %> each. Enchanted Armoire: Cleaning Supplies Set (Item 3 of 3).",
|
||||
"shieldArmoireBubblingCauldronText": "Bubbling Cauldron",
|
||||
"shieldArmoireBubblingCauldronNotes": "The perfect cauldron for brewing up a productivity potion or cooking a savory soup. In fact, there is little difference between the two! Increases Constitution by <%= con %>. Enchanted Armoire: Cooking Implements Set (Item 2 of 2).",
|
||||
"shieldArmoireJewelersPliersText": "Jeweler's Pliers",
|
||||
"shieldArmoireJewelersPliersNotes": "They cut, twist, pinch, and more. This tool can help you create whatever you can imagine. Increases Strength by <%= str %>. Enchanted Armoire: Jeweler Set (Item 3 of 4).",
|
||||
|
||||
|
||||
"back": "Back Accessory",
|
||||
@@ -2816,6 +2824,8 @@
|
||||
"headAccessoryMystery202203Notes": "Need an extra boost of speed? The tiny decorative wings on this circlet are more powerful than they look! Confers no benefit. March 2022 Subscriber Item.",
|
||||
"headAccessoryMystery202205Text": "Dusk-Winged Dragon Horns",
|
||||
"headAccessoryMystery202205Notes": "These dazzling horns are as bright as a desert sunset. Confers no benefit. May 2022 Subscriber Item.",
|
||||
"headAccessoryMystery202212Text": "Glacial Tiara",
|
||||
"headAccessoryMystery202212Notes": "Magnify your warmth and friendship to new heights with this ornate golden tiara. Confers no benefit. December 2022 Subscriber Item.",
|
||||
"headAccessoryMystery301405Text": "Headwear Goggles",
|
||||
"headAccessoryMystery301405Notes": "\"Goggles are for your eyes,\" they said. \"Nobody wants goggles that you can only wear on your head,\" they said. Hah! You sure showed them! Confers no benefit. August 3015 Subscriber Item.",
|
||||
|
||||
@@ -2919,6 +2929,8 @@
|
||||
"eyewearArmoireComedyMaskNotes": "Cheerily! Here is a quaint mask for thine happy heart, playing, heralding joy, and expressing merriment and mirth upon the stage. Increases Constitution by <%= con %>. Enchanted Armoire: Theatre Masks Set (Item 1 of 2).",
|
||||
"eyewearArmoireTragedyMaskText": "Tragedy Mask",
|
||||
"eyewearArmoireTragedyMaskNotes": "Alas! Here sits a heavy mask for thine poor player, strutting, fretting, and expressing woe and sorrow upon the stage. Increases Intelligence by <%= int %>. Enchanted Armoire: Theatre Masks Set (Item 2 of 2).",
|
||||
"eyewearArmoireJewelersEyeLoupeText": "Jeweler's Eye Loupe",
|
||||
"eyewearArmoireJewelersEyeLoupeNotes": "This eye loupe magnifies what you’re working on so you can see absolutely every detail. Increases Perception by <%= per %>. Enchanted Armoire: Jeweler Set (Item 2 of 4).",
|
||||
|
||||
"twoHandedItem": "Two-handed item."
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"wiki": "Wiki",
|
||||
"resources": "Resources",
|
||||
"communityGuidelines": "Community Guidelines",
|
||||
"bannedWordUsed": "Oops! Looks like this post contains a swearword, religious oath, or reference to an addictive substance or adult topic (<%= swearWordsUsed %>). Habitica has users from all backgrounds, so we keep our chat very clean. Feel free to edit your message so you can post it!",
|
||||
"bannedWordUsed": "Oops! Looks like this post contains a swearword or reference to an addictive substance or adult topic (<%= swearWordsUsed %>). Habitica keeps our chat very clean. Feel free to edit your message so you can post it! You must remove the word, not just censor it.",
|
||||
"bannedSlurUsed": "Your post contained inappropriate language, and your chat privileges have been revoked.",
|
||||
"party": "Party",
|
||||
"usernameCopied": "Username copied to clipboard.",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"usernameIssueForbidden": "Usernames may not contain restricted words.",
|
||||
"usernameIssueLength": "Usernames must be between 1 and 20 characters.",
|
||||
"usernameIssueInvalidCharacters": "Usernames can only contain letters a to z, numbers 0 to 9, hyphens, or underscores.",
|
||||
"passwordIssueLength": "Passwords must be between 8 and 64 characters.",
|
||||
"currentUsername": "Current username:",
|
||||
"displaynameIssueLength": "Display Names must be between 1 and 30 characters.",
|
||||
"bannedWordUsedInProfile": "Your Display Name or About text contained inappropriate language.",
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
"mysterySet202209": "Magical Scholar Set",
|
||||
"mysterySet202210": "Ominous Ophidian Set",
|
||||
"mysterySet202211": "Electromancer Set",
|
||||
"mysterySet202212": "Glacial Guardian Set",
|
||||
"mysterySet301404": "Steampunk Standard Set",
|
||||
"mysterySet301405": "Steampunk Accessories Set",
|
||||
"mysterySet301703": "Peacock Steampunk Set",
|
||||
|
||||
@@ -30,6 +30,7 @@ export const GUILDS_PER_PAGE = 30; // number of guilds to return per page when u
|
||||
export const PARTY_LIMIT_MEMBERS = 29;
|
||||
|
||||
export const MINIMUM_PASSWORD_LENGTH = 8;
|
||||
export const MAXIMUM_PASSWORD_LENGTH = 64;
|
||||
|
||||
export const TRANSFORMATION_DEBUFFS_LIST = {
|
||||
snowball: 'salt',
|
||||
|
||||
@@ -530,6 +530,11 @@ const backgrounds = {
|
||||
misty_autumn_forest: { },
|
||||
autumn_bridge: { },
|
||||
},
|
||||
backgrounds122022: {
|
||||
branches_of_a_holiday_tree: { },
|
||||
inside_a_crystal: { },
|
||||
snowy_village: { },
|
||||
},
|
||||
timeTravelBackgrounds: {
|
||||
airship: {
|
||||
price: 1,
|
||||
|
||||
@@ -10,22 +10,26 @@ const gemsPromo = {
|
||||
|
||||
export const EVENTS = {
|
||||
noEvent: {
|
||||
start: '2022-11-30T20:00-05:00',
|
||||
start: '2022-11-27T20:00-05:00',
|
||||
end: '2022-12-20T08:00-05:00',
|
||||
season: 'normal',
|
||||
npcImageSuffix: '',
|
||||
},
|
||||
harvestFeast2022: {
|
||||
start: '2022-11-22T08:00-05:00',
|
||||
end: '2022-11-27T20:00-05:00',
|
||||
season: 'thanksgiving',
|
||||
npcImageSuffix: '_thanksgiving',
|
||||
},
|
||||
afterGala: {
|
||||
start: '2022-10-31T20:00-04:00',
|
||||
end: '2022-11-22T08:00-05:00',
|
||||
season: 'normal',
|
||||
npcImageSuffix: '',
|
||||
},
|
||||
bundle202211: {
|
||||
start: '2022-11-15T08:00-05:00',
|
||||
end: '2022-11-30T20:00-05:00',
|
||||
season: 'normal',
|
||||
npcImageSuffix: '',
|
||||
},
|
||||
afterGala: {
|
||||
start: '2022-10-31T20:00-04:00',
|
||||
end: '2022-11-15T08:00-05:00',
|
||||
season: 'normal',
|
||||
npcImageSuffix: '',
|
||||
},
|
||||
fall2022: {
|
||||
start: '2022-09-20T08:00-04:00',
|
||||
|
||||
@@ -404,6 +404,10 @@ const armor = {
|
||||
sheetGhostCostume: {
|
||||
con: 10,
|
||||
},
|
||||
jewelersApron: {
|
||||
int: 10,
|
||||
set: 'jewelers',
|
||||
},
|
||||
};
|
||||
|
||||
const body = {
|
||||
@@ -443,6 +447,10 @@ const eyewear = {
|
||||
comedyMask: {
|
||||
con: 10,
|
||||
},
|
||||
jewelersEyeLoupe: {
|
||||
per: 10,
|
||||
set: 'jewelers',
|
||||
},
|
||||
};
|
||||
|
||||
const head = {
|
||||
@@ -1118,6 +1126,10 @@ const shield = {
|
||||
con: 8,
|
||||
set: 'cookingImplements',
|
||||
},
|
||||
jewelersPliers: {
|
||||
str: 10,
|
||||
set: 'jewelers',
|
||||
},
|
||||
};
|
||||
|
||||
const headAccessory = {
|
||||
@@ -1556,6 +1568,10 @@ const weapon = {
|
||||
per: 8,
|
||||
set: 'cookingImplements',
|
||||
},
|
||||
finelyCutGem: {
|
||||
con: 10,
|
||||
set: 'jewelers',
|
||||
},
|
||||
};
|
||||
|
||||
forEach({
|
||||
|
||||
@@ -59,6 +59,7 @@ const armor = {
|
||||
202204: { },
|
||||
202207: { },
|
||||
202210: { },
|
||||
202212: { },
|
||||
301404: { },
|
||||
301703: { },
|
||||
301704: { },
|
||||
@@ -222,6 +223,7 @@ const headAccessory = {
|
||||
202105: { },
|
||||
202109: { },
|
||||
202203: { },
|
||||
202212: { },
|
||||
202205: { },
|
||||
301405: { },
|
||||
};
|
||||
@@ -253,6 +255,7 @@ const weapon = {
|
||||
202111: { twoHanded: true },
|
||||
202211: { twoHanded: true },
|
||||
202201: { },
|
||||
202212: { },
|
||||
202209: { },
|
||||
301404: { },
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
MIN_SHORTNAME_SIZE_FOR_CHALLENGES,
|
||||
PARTY_LIMIT_MEMBERS,
|
||||
MINIMUM_PASSWORD_LENGTH,
|
||||
MAXIMUM_PASSWORD_LENGTH,
|
||||
SUPPORTED_SOCIAL_NETWORKS,
|
||||
TAVERN_ID,
|
||||
MAX_MESSAGE_LENGTH,
|
||||
@@ -119,6 +120,7 @@ api.constants = {
|
||||
CHAT_FLAG_FROM_MOD,
|
||||
CHAT_FLAG_FROM_SHADOW_MUTE,
|
||||
MINIMUM_PASSWORD_LENGTH,
|
||||
MAXIMUM_PASSWORD_LENGTH,
|
||||
MAX_MESSAGE_LENGTH,
|
||||
MAX_GIFT_MESSAGE_LENGTH,
|
||||
MAX_LEVEL_HARD_CAP,
|
||||
|
||||
@@ -419,6 +419,11 @@ shops.getSeasonalGearBySet = function getSeasonalGearBySet (
|
||||
const itemInfo = getItemInfo(null, currentSet ? 'marketGear' : 'gear', gear, officialPinnedItems, language);
|
||||
itemInfo.locked = currentSet && user.stats.class !== gear.specialClass;
|
||||
|
||||
// gear that has previously been owned should be repurchaseable with gold
|
||||
if (user.items.gear.owned[gear.key] !== undefined) {
|
||||
itemInfo.currency = 'gold';
|
||||
}
|
||||
|
||||
return itemInfo;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -276,7 +276,7 @@ api.exportUserAvatarPng = {
|
||||
/**
|
||||
* @api {get} /export/inbox.html Export user private messages as HTML document
|
||||
* @apiName ExportUserPrivateMessages
|
||||
* @apiDescription This HTML export feature is not currently working (https://github.com/HabitRPG/habitica/issues/9489).
|
||||
*
|
||||
* @apiGroup DataExport
|
||||
*
|
||||
* @apiSuccess {HTML} File An html page of the user's private messages.
|
||||
|
||||
@@ -100,8 +100,11 @@ async function registerLocal (req, res, { isV3 = false }) {
|
||||
errorMessage: res.t('missingPassword'),
|
||||
equals: { options: [req.body.confirmPassword], errorMessage: res.t('passwordConfirmationMatch') },
|
||||
isLength: {
|
||||
options: { min: common.constants.MINIMUM_PASSWORD_LENGTH },
|
||||
errorMessage: res.t('minPasswordLength'),
|
||||
options: {
|
||||
min: common.constants.MINIMUM_PASSWORD_LENGTH,
|
||||
max: common.constants.MAXIMUM_PASSWORD_LENGTH,
|
||||
},
|
||||
errorMessage: res.t('passwordIssueLength'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const paymentConstants = {
|
||||
UNLIMITED_CUSTOMER_ID: 'habitrpg', // Users with the customerId have an unlimted free subscription
|
||||
GROUP_PLAN_CUSTOMER_ID: 'group-plan',
|
||||
GROUP_PLAN_PAYMENT_METHOD: 'Group Plan',
|
||||
GOOGLE_PAYMENT_METHOD: 'Google',
|
||||
IOS_PAYMENT_METHOD: 'Apple',
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO these files need to refactored.
|
||||
|
||||
import nconf from 'nconf';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
@@ -12,6 +14,8 @@ import { // eslint-disable-line import/no-cycle
|
||||
getUserInfo,
|
||||
sendTxn as txnEmail,
|
||||
} from '../email';
|
||||
import { paymentConstants } from './constants';
|
||||
import { cancelSubscription, createSubscription } from './subscriptions'; // eslint-disable-line import/no-cycle
|
||||
|
||||
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL');
|
||||
const JOINED_GROUP_PLAN = 'joined group plan';
|
||||
@@ -37,7 +41,8 @@ async function addSubscriptionToGroupUsers (group) {
|
||||
members = await User.find({ 'party._id': group._id }).select('_id purchased items auth profile.name notifications').exec();
|
||||
}
|
||||
|
||||
const promises = members.map(member => this.addSubToGroupUser(member, group));
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
const promises = members.map(member => addSubToGroupUser(member, group));
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
@@ -63,12 +68,12 @@ async function addSubToGroupUser (member, group) {
|
||||
// When changing customerIdsToIgnore or paymentMethodsToIgnore, the code blocks below for
|
||||
// the `group-member-join` email template will probably need to be changed.
|
||||
const customerIdsToIgnore = [
|
||||
this.constants.GROUP_PLAN_CUSTOMER_ID,
|
||||
this.constants.UNLIMITED_CUSTOMER_ID,
|
||||
paymentConstants.GROUP_PLAN_CUSTOMER_ID,
|
||||
paymentConstants.UNLIMITED_CUSTOMER_ID,
|
||||
];
|
||||
const paymentMethodsToIgnore = [
|
||||
this.constants.GOOGLE_PAYMENT_METHOD,
|
||||
this.constants.IOS_PAYMENT_METHOD,
|
||||
paymentConstants.GOOGLE_PAYMENT_METHOD,
|
||||
paymentConstants.IOS_PAYMENT_METHOD,
|
||||
];
|
||||
let previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE;
|
||||
const leader = await User.findById(group.leader).exec();
|
||||
@@ -104,7 +109,7 @@ async function addSubToGroupUser (member, group) {
|
||||
const memberPlan = member.purchased.plan;
|
||||
if (member.isSubscribed()) {
|
||||
const customerHasCancelledGroupPlan = (
|
||||
memberPlan.customerId === this.constants.GROUP_PLAN_CUSTOMER_ID
|
||||
memberPlan.customerId === paymentConstants.GROUP_PLAN_CUSTOMER_ID
|
||||
&& !member.hasNotCancelled()
|
||||
);
|
||||
const ignorePaymentPlan = paymentMethodsToIgnore.indexOf(memberPlan.paymentMethod) !== -1;
|
||||
@@ -127,13 +132,13 @@ async function addSubToGroupUser (member, group) {
|
||||
if ((ignorePaymentPlan || ignoreCustomerId) && !customerHasCancelledGroupPlan) {
|
||||
// member has been added to group plan but their subscription will not be changed
|
||||
// automatically so they need a special message in the email
|
||||
if (memberPlan.paymentMethod === this.constants.GOOGLE_PAYMENT_METHOD) {
|
||||
if (memberPlan.paymentMethod === paymentConstants.GOOGLE_PAYMENT_METHOD) {
|
||||
previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE;
|
||||
} else if (memberPlan.paymentMethod === this.constants.IOS_PAYMENT_METHOD) {
|
||||
} else if (memberPlan.paymentMethod === paymentConstants.IOS_PAYMENT_METHOD) {
|
||||
previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS;
|
||||
} else if (memberPlan.customerId === this.constants.UNLIMITED_CUSTOMER_ID) {
|
||||
} else if (memberPlan.customerId === paymentConstants.UNLIMITED_CUSTOMER_ID) {
|
||||
previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_LIFETIME_FREE;
|
||||
} else if (memberPlan.customerId === this.constants.GROUP_PLAN_CUSTOMER_ID) {
|
||||
} else if (memberPlan.customerId === paymentConstants.GROUP_PLAN_CUSTOMER_ID) {
|
||||
previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GROUP_PLAN;
|
||||
} else {
|
||||
// this triggers a generic message in the email template in case we forget
|
||||
@@ -183,7 +188,7 @@ async function addSubToGroupUser (member, group) {
|
||||
member.markModified('items.mounts');
|
||||
|
||||
data.user = member;
|
||||
await this.createSubscription(data);
|
||||
await createSubscription(data);
|
||||
|
||||
txnEmail(data.user, 'group-member-join', [
|
||||
{ name: 'LEADER', content: leader.profile.name },
|
||||
@@ -207,13 +212,14 @@ async function cancelGroupUsersSubscription (group) {
|
||||
members = await User.find({ 'party._id': group._id }).select('_id guilds purchased').exec();
|
||||
}
|
||||
|
||||
const promises = members.map(member => this.cancelGroupSubscriptionForUser(member, group));
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
const promises = members.map(member => cancelGroupSubscriptionForUser(member, group));
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
async function cancelGroupSubscriptionForUser (user, group, userWasRemoved = false) {
|
||||
if (user.purchased.plan.customerId !== this.constants.GROUP_PLAN_CUSTOMER_ID) return;
|
||||
if (user.purchased.plan.customerId !== paymentConstants.GROUP_PLAN_CUSTOMER_ID) return;
|
||||
|
||||
const userGroups = user.guilds.toObject();
|
||||
if (user.party._id) userGroups.push(user.party._id);
|
||||
@@ -241,7 +247,7 @@ async function cancelGroupSubscriptionForUser (user, group, userWasRemoved = fal
|
||||
{ name: 'LEADER', content: leader.profile.name },
|
||||
{ name: 'GROUP_NAME', content: group.name },
|
||||
]);
|
||||
await this.cancelSubscription({ user });
|
||||
await cancelSubscription({ user });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,11 @@ import { // eslint-disable-line import/no-cycle
|
||||
import { // eslint-disable-line import/no-cycle
|
||||
buyGems,
|
||||
} from './gems';
|
||||
import { paymentConstants } from './constants';
|
||||
|
||||
const api = {};
|
||||
|
||||
api.constants = {
|
||||
UNLIMITED_CUSTOMER_ID: 'habitrpg', // Users with the customerId have an unlimted free subscription
|
||||
GROUP_PLAN_CUSTOMER_ID: 'group-plan',
|
||||
GROUP_PLAN_PAYMENT_METHOD: 'Group Plan',
|
||||
GOOGLE_PAYMENT_METHOD: 'Google',
|
||||
IOS_PAYMENT_METHOD: 'Apple',
|
||||
};
|
||||
api.constants = paymentConstants;
|
||||
|
||||
api.addSubscriptionToGroupUsers = addSubscriptionToGroupUsers;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO these files need to refactored.
|
||||
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
@@ -19,6 +21,8 @@ import shared from '../../../common';
|
||||
import { sendNotification as sendPushNotification } from '../pushNotifications'; // eslint-disable-line import/no-cycle
|
||||
import calculateSubscriptionTerminationDate from './calculateSubscriptionTerminationDate';
|
||||
import { getCurrentEventList } from '../worldState'; // eslint-disable-line import/no-cycle
|
||||
import { paymentConstants } from './constants';
|
||||
import { addSubscriptionToGroupUsers, cancelGroupUsersSubscription } from './groupPayments'; // eslint-disable-line import/no-cycle
|
||||
|
||||
// @TODO: Abstract to shared/constant
|
||||
const JOINED_GROUP_PLAN = 'joined group plan';
|
||||
@@ -64,7 +68,7 @@ function _dateDiff (earlyDate, lateDate) {
|
||||
return moment(lateDate).diff(earlyDate, 'months', true);
|
||||
}
|
||||
|
||||
async function createSubscription (data) {
|
||||
async function prepareSubscriptionValues (data) {
|
||||
let recipient = data.gift ? data.gift.member : data.user;
|
||||
const block = shared.content.subscriptionBlocks[data.gift
|
||||
? data.gift.subscription.key
|
||||
@@ -88,7 +92,7 @@ async function createSubscription (data) {
|
||||
|
||||
if (group) {
|
||||
analytics.track(
|
||||
this.groupID,
|
||||
data.groupID,
|
||||
data.demographics,
|
||||
);
|
||||
}
|
||||
@@ -113,7 +117,7 @@ async function createSubscription (data) {
|
||||
groupId = group._id;
|
||||
recipient.purchased.plan.quantity = data.sub.quantity;
|
||||
|
||||
await this.addSubscriptionToGroupUsers(group);
|
||||
await addSubscriptionToGroupUsers(group);
|
||||
}
|
||||
|
||||
const { plan } = recipient.purchased;
|
||||
@@ -122,7 +126,10 @@ async function createSubscription (data) {
|
||||
if (plan.customerId && !plan.dateTerminated) { // User has active plan
|
||||
plan.extraMonths += months;
|
||||
} else {
|
||||
if (!recipientIsSubscribed || !plan.dateUpdated) plan.dateUpdated = today;
|
||||
if (!recipientIsSubscribed || !plan.dateUpdated) {
|
||||
plan.dateUpdated = today;
|
||||
}
|
||||
|
||||
if (moment(plan.dateTerminated).isAfter()) {
|
||||
plan.dateTerminated = moment(plan.dateTerminated).add({ months }).toDate();
|
||||
} else {
|
||||
@@ -131,9 +138,15 @@ async function createSubscription (data) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!plan.customerId) plan.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId
|
||||
if (!plan.customerId) {
|
||||
plan.customerId = 'Gift';
|
||||
}
|
||||
|
||||
// don't override existing customer, but all sub need a customerId
|
||||
} else {
|
||||
if (!plan.dateTerminated) plan.dateTerminated = today;
|
||||
if (!plan.dateTerminated) {
|
||||
plan.dateTerminated = today;
|
||||
}
|
||||
|
||||
Object.assign(plan, { // override plan with new values
|
||||
planId: block.key,
|
||||
@@ -153,22 +166,58 @@ async function createSubscription (data) {
|
||||
});
|
||||
|
||||
// allow non-override if a plan was previously used
|
||||
if (!plan.gemsBought) plan.gemsBought = 0;
|
||||
if (!plan.dateCreated) plan.dateCreated = today;
|
||||
if (!plan.mysteryItems) plan.mysteryItems = [];
|
||||
if (!plan.gemsBought) {
|
||||
plan.gemsBought = 0;
|
||||
}
|
||||
|
||||
if (!plan.dateCreated) {
|
||||
plan.dateCreated = today;
|
||||
}
|
||||
|
||||
if (!plan.mysteryItems) {
|
||||
plan.mysteryItems = [];
|
||||
}
|
||||
|
||||
if (data.subscriptionId) {
|
||||
plan.subscriptionId = data.subscriptionId;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
block,
|
||||
months,
|
||||
plan,
|
||||
recipient,
|
||||
autoRenews,
|
||||
group,
|
||||
groupId,
|
||||
itemPurchased,
|
||||
purchaseType,
|
||||
emailType,
|
||||
};
|
||||
}
|
||||
|
||||
async function createSubscription (data) {
|
||||
const {
|
||||
block,
|
||||
months,
|
||||
plan,
|
||||
recipient,
|
||||
autoRenews,
|
||||
group,
|
||||
groupId,
|
||||
itemPurchased,
|
||||
purchaseType,
|
||||
emailType,
|
||||
} = await prepareSubscriptionValues(data);
|
||||
|
||||
// Block sub perks
|
||||
const perks = Math.floor(months / 3);
|
||||
if (perks) {
|
||||
plan.consecutive.offset += months;
|
||||
plan.consecutive.gemCapExtra += perks * 5;
|
||||
if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25;
|
||||
await plan.updateHourglasses(data.user._id, perks, 'subscription_perks'); // one Hourglass every 3 months
|
||||
await plan.updateHourglasses(recipient._id, perks, 'subscription_perks'); // one Hourglass every 3 months
|
||||
}
|
||||
|
||||
if (recipient !== group) {
|
||||
@@ -178,7 +227,7 @@ async function createSubscription (data) {
|
||||
}
|
||||
|
||||
// @TODO: Create a factory pattern for use cases
|
||||
if (!data.gift && data.customerId !== this.constants.GROUP_PLAN_CUSTOMER_ID) {
|
||||
if (!data.gift && data.customerId !== paymentConstants.GROUP_PLAN_CUSTOMER_ID) {
|
||||
txnEmail(data.user, emailType);
|
||||
}
|
||||
|
||||
@@ -267,7 +316,7 @@ async function createSubscription (data) {
|
||||
promo: 'Winter',
|
||||
promoUsername: data.gift.member.auth.local.username,
|
||||
};
|
||||
await this.createSubscription(promoData);
|
||||
await createSubscription(promoData);
|
||||
}
|
||||
|
||||
if (data.gift.member.preferences.pushNotifications.giftedSubscription !== false) {
|
||||
@@ -334,7 +383,7 @@ async function cancelSubscription (data) {
|
||||
emailType = 'group-cancel-subscription';
|
||||
emailMergeData.push({ name: 'GROUP_NAME', content: group.name });
|
||||
|
||||
await this.cancelGroupUsersSubscription(group);
|
||||
await cancelGroupUsersSubscription(group);
|
||||
} else {
|
||||
// cancelling a user subscription
|
||||
plan = data.user.purchased.plan;
|
||||
@@ -344,12 +393,12 @@ async function cancelSubscription (data) {
|
||||
if (data.cancellationReason && data.cancellationReason === JOINED_GROUP_PLAN) sendEmail = false;
|
||||
}
|
||||
|
||||
if (plan.customerId === this.constants.GROUP_PLAN_CUSTOMER_ID) {
|
||||
if (plan.customerId === paymentConstants.GROUP_PLAN_CUSTOMER_ID) {
|
||||
sendEmail = false; // because group-member-cancel email has already been sent
|
||||
}
|
||||
|
||||
plan.dateTerminated = calculateSubscriptionTerminationDate(
|
||||
data.nextBill, plan, this.constants.GROUP_PLAN_CUSTOMER_ID,
|
||||
data.nextBill, plan, paymentConstants.GROUP_PLAN_CUSTOMER_ID,
|
||||
);
|
||||
|
||||
// clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated
|
||||
@@ -361,7 +410,9 @@ async function cancelSubscription (data) {
|
||||
await data.user.save();
|
||||
}
|
||||
|
||||
if (sendEmail) txnEmail(data.user, emailType, emailMergeData);
|
||||
if (sendEmail) {
|
||||
txnEmail(data.user, emailType, emailMergeData);
|
||||
}
|
||||
|
||||
if (group) {
|
||||
cancelType = 'group-unsubscribe';
|
||||
|
||||
@@ -150,21 +150,10 @@ function _setUpNewUser (user) {
|
||||
user.items.quests.dustbunnies = 1;
|
||||
user.purchased.background.violet = true;
|
||||
user.preferences.background = 'violet';
|
||||
if (moment().isBefore('2022-03-15T20:00-04:00')) {
|
||||
user.items.gear.owned.head_special_piDay = true;
|
||||
user.items.gear.equipped.head = 'head_special_piDay';
|
||||
user.items.gear.owned.shield_special_piDay = true;
|
||||
user.items.gear.equipped.shield = 'shield_special_piDay';
|
||||
user.items.food.Pie_Skeleton = 1;
|
||||
user.items.food.Pie_Base = 1;
|
||||
user.items.food.Pie_CottonCandyBlue = 1;
|
||||
user.items.food.Pie_CottonCandyPink = 1;
|
||||
user.items.food.Pie_Shade = 1;
|
||||
user.items.food.Pie_White = 1;
|
||||
user.items.food.Pie_Golden = 1;
|
||||
user.items.food.Pie_Zombie = 1;
|
||||
user.items.food.Pie_Desert = 1;
|
||||
user.items.food.Pie_Red = 1;
|
||||
if (moment().isBetween('2022-11-22T08:00-05:00', '2022-11-27T20:00-05:00')) {
|
||||
user.migration = '20221122_harvest_feast';
|
||||
user.items.pets['Turkey-Base'] = 5;
|
||||
user.items.currentPet = 'Turkey-Base';
|
||||
}
|
||||
|
||||
user.markModified('items achievements');
|
||||
|
||||
Reference in New Issue
Block a user