Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fdbfb9dc6 | |||
| 057a642baa | |||
| 6c522157a7 | |||
| ba9a1ab2a9 | |||
| 4767461c4f | |||
| 847c97dc8f | |||
| 215b26acac | |||
| e223e7821a | |||
| 8134fa7c00 | |||
| 84208f612e | |||
| 57e06334c0 | |||
| be695d25b3 | |||
| 77ee83f467 | |||
| 86556e346b | |||
| 2007a872c6 | |||
| a8348038de | |||
| 87bcd69979 | |||
| 8f8e84d0c7 | |||
| 2c18cb00cc | |||
| daa0fd18c0 | |||
| 5c555cbf88 | |||
| 7379c7b230 | |||
| c055537c38 | |||
| 7559feec8e | |||
| 43808696a8 | |||
| 72fb41c7e0 | |||
| 3bf18e09ed | |||
| 407a901883 | |||
| 81a008906b | |||
| 992a978923 | |||
| a8062ad615 | |||
| 781a904583 | |||
| d87946d912 | |||
| 7456ff2def | |||
| e0af620b40 | |||
| bb295551b5 | |||
| fce400f323 | |||
| c0ffb8b968 | |||
| 72539f9ba3 | |||
| dabd466719 | |||
| 8bf2304330 | |||
| 6937dc4e4e | |||
| 2917955ef0 | |||
| 55d13e44d4 | |||
| 90096f995f | |||
| 5c74c2b914 | |||
| 1f1a44e16f | |||
| a275109a3e | |||
| c65457690b | |||
| f740f12b97 | |||
| 9fd0bfae46 | |||
| bee23efbef | |||
| a504b18ce4 | |||
| f556b102c6 | |||
| ac62de7bd8 | |||
| 5ff3cc35a6 | |||
| 215e5e1c40 | |||
| 02ca96ea51 | |||
| e70ae4e9aa | |||
| e2bf8ae493 | |||
| 931a70a797 | |||
| e2d2a05315 | |||
| be041f734d | |||
| c430d2279c | |||
| ef592cf35f | |||
| f24cd10a79 | |||
| 2cd4e45016 | |||
| 8aaff7ae23 | |||
| 69a9fb89ef | |||
| e8eeb76cab | |||
| 2029739a1b | |||
| 5cef106ea5 | |||
| e096d7ac42 | |||
| 6db998e726 | |||
| 29c658b042 | |||
| 66710b8f38 | |||
| c77db3d625 | |||
| c947fa97d9 | |||
| b2b9702797 | |||
| e92503f032 | |||
| 8faa5b0582 | |||
| 95494c685b | |||
| 10978d46ab | |||
| 447eb6a0c4 | |||
| 3dec49b72c | |||
| 472d03f276 |
@@ -73,7 +73,7 @@ export default async function processUsers () {
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "5.41.0",
|
||||
"version": "5.43.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "habitica",
|
||||
"version": "5.41.0",
|
||||
"version": "5.43.3",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/register": "^7.22.15",
|
||||
"@google-analytics/data": "^4.12.1",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.2.3",
|
||||
"@slack/webhook": "^6.1.0",
|
||||
@@ -1975,17 +1974,6 @@
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/@google-analytics/data": {
|
||||
"version": "4.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@google-analytics/data/-/data-4.12.1.tgz",
|
||||
"integrity": "sha512-LzyrkVrnVUTYTmdmHayOZoroc+YA9GHEUrkSSuiXSmMSNbesuWy/MoTXugC1V7+8PCGqb2eQ1UtVVv/2BCAQYA==",
|
||||
"dependencies": {
|
||||
"google-gax": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/common": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-4.0.3.tgz",
|
||||
@@ -2329,6 +2317,7 @@
|
||||
"version": "1.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.8.tgz",
|
||||
"integrity": "sha512-vYVqYzHicDqyKB+NQhAc54I1QWCBLCrYG6unqOIcBTHx+7x8C9lcoLj3KVJXs2VB4lUbpWY+Kk9NipcbXYWmvg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
"@js-sdsl/ordered-map": "^4.4.2"
|
||||
@@ -2341,6 +2330,7 @@
|
||||
"version": "0.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz",
|
||||
"integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"long": "^5.0.0",
|
||||
@@ -2358,6 +2348,7 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
@@ -2372,6 +2363,7 @@
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
@@ -2385,6 +2377,7 @@
|
||||
"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,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
@@ -2395,12 +2388,14 @@
|
||||
"node_modules/@grpc/proto-loader/node_modules/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=="
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@grpc/proto-loader/node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
@@ -2409,6 +2404,7 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
@@ -2425,6 +2421,7 @@
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
@@ -2433,6 +2430,7 @@
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
@@ -2450,6 +2448,7 @@
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -2620,6 +2619,7 @@
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
|
||||
"integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/js-sdsl"
|
||||
@@ -2932,27 +2932,32 @@
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@protobufjs/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
||||
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
|
||||
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
|
||||
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
|
||||
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@protobufjs/fetch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
|
||||
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.1",
|
||||
"@protobufjs/inquire": "^1.1.0"
|
||||
@@ -2961,27 +2966,32 @@
|
||||
"node_modules/@protobufjs/float": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@protobufjs/inquire": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
||||
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
|
||||
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@protobufjs/pool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "4.6.0",
|
||||
@@ -3116,7 +3126,8 @@
|
||||
"node_modules/@types/caseless": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
|
||||
"integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg=="
|
||||
"integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.38",
|
||||
@@ -3219,7 +3230,8 @@
|
||||
"node_modules/@types/long": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
||||
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
|
||||
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.5",
|
||||
@@ -3269,6 +3281,7 @@
|
||||
"version": "2.48.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz",
|
||||
"integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/caseless": "*",
|
||||
"@types/node": "*",
|
||||
@@ -3280,6 +3293,7 @@
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
|
||||
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
@@ -3319,7 +3333,8 @@
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="
|
||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/triple-beam": {
|
||||
"version": "1.3.5",
|
||||
@@ -3529,6 +3544,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
@@ -6070,9 +6086,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -9346,6 +9363,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -11431,9 +11449,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -11597,6 +11616,7 @@
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.3.3.tgz",
|
||||
"integrity": "sha512-f4F2Y9X4+mqsrJuLZsuTljYuQpcBnQsCt9ScvZpdM8jGjqrcxyJi5JUiqtq0jtpdHVPzyit0N7f5t07e+kH5EA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "~1.10.3",
|
||||
"@grpc/proto-loader": "^0.7.0",
|
||||
@@ -11619,6 +11639,7 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
|
||||
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
@@ -11630,6 +11651,7 @@
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.6.0.tgz",
|
||||
"integrity": "sha512-bpOZVQV5gthH/jVCSuYuokRo2bTKOcuBiVWpjmTn6C5Agl5zclGfTljuGsQZxwwDBkli+YhZhP4TdlqTnhOezQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"extend": "^3.0.2",
|
||||
"https-proxy-agent": "^7.0.1",
|
||||
@@ -11645,6 +11667,7 @@
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
|
||||
"integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"gaxios": "^6.0.0",
|
||||
"json-bigint": "^1.0.0"
|
||||
@@ -11657,6 +11680,7 @@
|
||||
"version": "9.10.0",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.10.0.tgz",
|
||||
"integrity": "sha512-ol+oSa5NbcGdDqA+gZ3G3mev59OHBZksBTxY/tYwjtcp1H/scAFwJfSQU9/1RALoyZ7FslNbke8j4i3ipwlyuQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0",
|
||||
"ecdsa-sig-formatter": "^1.0.11",
|
||||
@@ -11673,6 +11697,7 @@
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
|
||||
"integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"gaxios": "^6.0.0",
|
||||
"jws": "^4.0.0"
|
||||
@@ -11685,6 +11710,7 @@
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
|
||||
"integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"agent-base": "^7.0.2",
|
||||
"debug": "4"
|
||||
@@ -11698,6 +11724,7 @@
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz",
|
||||
"integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
@@ -11720,6 +11747,7 @@
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
|
||||
"integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/request": "^2.48.8",
|
||||
"extend": "^3.0.2",
|
||||
@@ -11733,6 +11761,7 @@
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
|
||||
"integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"http-proxy-agent": "^5.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
@@ -11748,6 +11777,7 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
@@ -11759,6 +11789,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
@@ -14237,7 +14268,8 @@
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
|
||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
@@ -16660,6 +16692,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
@@ -17882,6 +17915,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.1.tgz",
|
||||
"integrity": "sha512-8awBvjO+FwkMd6gNoGFZyqkHZXCFd54CIYTb6De7dPaufGJ2XNW+QUNqbMr8MaAocMdb+KpsD4rxEOaTBDCffA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"protobufjs": "^7.2.5"
|
||||
},
|
||||
@@ -17894,6 +17928,7 @@
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz",
|
||||
"integrity": "sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "5.41.0",
|
||||
"version": "5.43.3",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/register": "^7.22.15",
|
||||
"@google-analytics/data": "^4.12.1",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.2.3",
|
||||
"@slack/webhook": "^6.1.0",
|
||||
@@ -106,8 +105,8 @@
|
||||
"start": "node --watch ./website/server/index.js",
|
||||
"start:simple": "node ./website/server/index.js",
|
||||
"debug": "node --watch --inspect ./website/server/index.js",
|
||||
"mongo:dev": "run-rs -v 7.0.23 -l ubuntu2204 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||
"mongo:test": "run-rs -v 7.0.23 -l ubuntu2204 --keep --dbpath mongodb-data-testing --number 1 --quiet",
|
||||
"mongo:dev": "run-rs -v 7.0.23 -l ubuntu2214 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||
"mongo:test": "run-rs -v 7.0.23 -l ubuntu2214 --keep --dbpath mongodb-data-testing --number 1 --quiet",
|
||||
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
||||
"apidoc": "gulp apidoc",
|
||||
"heroku-postbuild": ".heroku/report_deploy.sh"
|
||||
|
||||
@@ -47,6 +47,12 @@ describe('highlightMentions', () => {
|
||||
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
|
||||
});
|
||||
|
||||
it('highlights users with case-insensitive matching', async () => {
|
||||
const text = '@USER: message @User2 @USER3';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@USER](/profile/111): message [@User2](/profile/222) [@USER3](/profile/333)');
|
||||
});
|
||||
|
||||
it('doesn\'t highlight nonexisting users', async () => {
|
||||
const text = '@nouser message';
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import {
|
||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import { MAX_MESSAGE_LENGTH, CHAT_FLAG_FROM_SHADOW_MUTE } from '../../../../../website/common/script/constants';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
describe('POST /chat', () => {
|
||||
@@ -80,17 +80,20 @@ describe('POST /chat', () => {
|
||||
member.updateOne({ 'flags.chatRevoked': false });
|
||||
});
|
||||
|
||||
it('does not error when chat privileges are revoked when sending a message to a private guild', async () => {
|
||||
it('errors when chat privileges are revoked when sending a message to a private guild', async () => {
|
||||
await member.updateOne({
|
||||
'flags.chatRevoked': true,
|
||||
});
|
||||
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
await expect(member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not error when chat privileges are revoked when sending a message to a party', async () => {
|
||||
it('errors when chat privileges are revoked when sending a message to a party', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Party',
|
||||
@@ -106,9 +109,12 @@ describe('POST /chat', () => {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await privatePartyMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
await expect(privatePartyMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,7 +129,7 @@ describe('POST /chat', () => {
|
||||
member.updateOne({ 'flags.chatShadowMuted': false });
|
||||
});
|
||||
|
||||
it('creates a chat with zero flagCount when sending a message to a private guild', async () => {
|
||||
it('creates a chat with flagCount set when sending a message to a private guild', async () => {
|
||||
await member.updateOne({
|
||||
'flags.chatShadowMuted': true,
|
||||
});
|
||||
@@ -131,10 +137,10 @@ describe('POST /chat', () => {
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE);
|
||||
});
|
||||
|
||||
it('creates a chat with zero flagCount when sending a message to a party', async () => {
|
||||
it('creates a chat with flagCount set when sending a message to a party', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Party',
|
||||
@@ -153,7 +159,7 @@ describe('POST /chat', () => {
|
||||
const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,6 +244,18 @@ describe('POST /chat', () => {
|
||||
expect(groupMessages[0].id).to.exist;
|
||||
});
|
||||
|
||||
it('creates a chat with case-insensitive mentions', async () => {
|
||||
const originalUsername = member.auth.local.username;
|
||||
const uppercaseUsername = originalUsername.toUpperCase();
|
||||
const messageWithMentions = `hi @${uppercaseUsername}`;
|
||||
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: messageWithMentions });
|
||||
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
|
||||
|
||||
expect(newMessage.message.id).to.exist;
|
||||
expect(newMessage.message.text).to.include(`[@${uppercaseUsername}](/profile/${member._id})`);
|
||||
expect(groupMessages[0].id).to.exist;
|
||||
});
|
||||
|
||||
it('creates a chat with a max length of 3000 chars', async () => {
|
||||
const veryLongMessage = `
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.
|
||||
|
||||
@@ -61,6 +61,24 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fakes sending an invite if user is shadow muted', async () => {
|
||||
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const response = await inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||
});
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.not.have.nested.property('invitations.parties[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites a user to a group by username', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
@@ -209,6 +227,24 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fakes sending an invite if user is shadow muted', async () => {
|
||||
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const response = await inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.not.have.nested.property('invitations.parties[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites a user to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
@@ -281,6 +317,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fakes sending invite when inviter is shadow muted', async () => {
|
||||
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
|
||||
const res = await inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
emails: [testInvite],
|
||||
inviter: 'inviter name',
|
||||
});
|
||||
|
||||
const updatedUser = await inviterMuted.sync();
|
||||
|
||||
expect(res).to.exist;
|
||||
expect(updatedUser.invitesSent).to.eql(1);
|
||||
});
|
||||
|
||||
it('returns an error when invite is missing an email', async () => {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
emails: [{ name: 'test' }],
|
||||
@@ -405,6 +454,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fakes sending an invite if user is shadow muted', async () => {
|
||||
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
|
||||
const newUser = await generateUser();
|
||||
const invite = await inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [newUser._id],
|
||||
emails: [{ name: 'test', email: 'test@habitica.com' }],
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties[0]).to.not.exist;
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
|
||||
it('invites users to a group by uuid and email', async () => {
|
||||
const newUser = await generateUser();
|
||||
const invite = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('GET /inbox/messages', () => {
|
||||
it('returns four messages when using page-query ', async () => {
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
for (let i = 0; i < 50; i += 1) {
|
||||
promises.push(user.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('POST /members/send-private-message', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when to user has blocked the sender', async () => {
|
||||
it('returns error when recipient has blocked the sender', async () => {
|
||||
const receiver = await generateUser({ 'inbox.blocks': [userToSendMessage._id] });
|
||||
|
||||
await expect(userToSendMessage.post('/members/send-private-message', {
|
||||
@@ -56,7 +56,7 @@ describe('POST /members/send-private-message', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when sender has blocked to user', async () => {
|
||||
it('returns error when sender has blocked recipient', async () => {
|
||||
const receiver = await generateUser();
|
||||
const sender = await generateUser({ 'inbox.blocks': [receiver._id] });
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('POST /members/send-private-message', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when to user has opted out of messaging', async () => {
|
||||
it('returns error when recipient has opted out of messaging', async () => {
|
||||
const receiver = await generateUser({ 'inbox.optOut': true });
|
||||
|
||||
await expect(userToSendMessage.post('/members/send-private-message', {
|
||||
@@ -174,7 +174,7 @@ describe('POST /members/send-private-message', () => {
|
||||
expect(notification.data.excerpt).to.equal(messageExcerpt);
|
||||
});
|
||||
|
||||
it('allows admin to send when sender has blocked the admin', async () => {
|
||||
it('allows admin to send when recipient has blocked the admin', async () => {
|
||||
userToSendMessage = await generateUser({
|
||||
'permissions.moderator': true,
|
||||
});
|
||||
@@ -202,7 +202,7 @@ describe('POST /members/send-private-message', () => {
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
});
|
||||
|
||||
it('allows admin to send when to user has opted out of messaging', async () => {
|
||||
it('allows admin to send when recipient has opted out of messaging', async () => {
|
||||
userToSendMessage = await generateUser({
|
||||
'permissions.moderator': true,
|
||||
});
|
||||
@@ -229,4 +229,58 @@ describe('POST /members/send-private-message', () => {
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
});
|
||||
|
||||
describe('sender is shadow muted', () => {
|
||||
beforeEach(async () => {
|
||||
userToSendMessage = await generateUser({
|
||||
'flags.chatShadowMuted': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not save the message in the receiver inbox', async () => {
|
||||
const receiver = await generateUser();
|
||||
|
||||
const response = await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
expect(response.message.uuid).to.equal(receiver._id);
|
||||
|
||||
const updatedReceiver = await receiver.get('/user');
|
||||
const updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
const sendersMessageInReceiversInbox = _.find(
|
||||
updatedReceiver.inbox.messages,
|
||||
message => message.uuid === userToSendMessage._id && message.text === messageToSend,
|
||||
);
|
||||
|
||||
const sendersMessageInSendersInbox = _.find(
|
||||
updatedSender.inbox.messages,
|
||||
message => message.uuid === receiver._id && message.text === messageToSend,
|
||||
);
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.not.exist;
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
});
|
||||
|
||||
it('does not save the message message twice if recipient is sender', async () => {
|
||||
const response = await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: userToSendMessage._id,
|
||||
});
|
||||
|
||||
expect(response.message.uuid).to.equal(userToSendMessage._id);
|
||||
|
||||
const updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
const sendersMessageInSendersInbox = _.find(
|
||||
updatedSender.inbox.messages,
|
||||
message => message.uuid === userToSendMessage._id && message.text === messageToSend,
|
||||
);
|
||||
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
expect(Object.keys(updatedSender.inbox.messages).length).to.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('POST /user/auth/local/login', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
|
||||
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id, username: user.auth.local.username }),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -65,6 +65,52 @@ describe('POST /user/auth/social', () => {
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
|
||||
});
|
||||
|
||||
it('includes sanitized version of provided username', async () => {
|
||||
const response = await api.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
network,
|
||||
username: 'Google User Name',
|
||||
});
|
||||
|
||||
await expect(getProperty('users', response.id, 'auth.local.username')).to.eventually.equal('GoogleUserName');
|
||||
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.eventually.equal('googleusername');
|
||||
});
|
||||
|
||||
it('generates a random username if provided username contains only disallowed characters', async () => {
|
||||
const response = await api.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
network,
|
||||
username: 'Áîüè',
|
||||
});
|
||||
|
||||
await expect(getProperty('users', response.id, 'auth.local.username')).to.eventually.contain('hb-');
|
||||
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.eventually.contain('hb-');
|
||||
});
|
||||
|
||||
it('generates a random username if provided username contains a disallowed word', async () => {
|
||||
const response = await api.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
network,
|
||||
username: 'i am a TESTPLACEHOLDERSLURWORDHERE',
|
||||
});
|
||||
|
||||
await expect(getProperty('users', response.id, 'auth.local.username')).to.eventually.contain('hb-');
|
||||
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.eventually.contain('hb-');
|
||||
});
|
||||
|
||||
it('generates a random username if sanitized username conflicts with an extant user', async () => {
|
||||
user = await generateUser({ 'auth.local.username': 'GoogleUserName' });
|
||||
|
||||
const response = await api.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
network,
|
||||
username: 'Google User Name',
|
||||
});
|
||||
|
||||
await expect(getProperty('users', response.id, 'auth.local.username')).to.eventually.contain('hb-');
|
||||
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.eventually.contain('hb-');
|
||||
});
|
||||
|
||||
it('fails if allowRegister is false and user does not exist', async () => {
|
||||
await expect(api.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('GET /inbox/conversations', () => {
|
||||
it('returns five messages when using page-query ', async () => {
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
for (let i = 0; i < 50; i += 1) {
|
||||
promises.push(user.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"eslint-config-habitrpg": "6.2.0",
|
||||
"eslint-plugin-mocha": "5.3.0",
|
||||
"eslint-plugin-vue": "7.20.0",
|
||||
"ga-gtag": "^1.2.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"intro.js": "^7.2.0",
|
||||
@@ -38,7 +37,7 @@
|
||||
"timers-browserify": "^2.0.12",
|
||||
"uuid": "^9.0.1",
|
||||
"validator": "^13.9.0",
|
||||
"vite": "^6.0.0",
|
||||
"vite": "^6.3.6",
|
||||
"vite-plugin-compression2": "^1.3.3",
|
||||
"vue": "^2.7.10",
|
||||
"vue-fragment": "^1.6.0",
|
||||
@@ -5123,6 +5122,20 @@
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
@@ -5161,11 +5174,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ga-gtag": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ga-gtag/-/ga-gtag-1.2.0.tgz",
|
||||
"integrity": "sha512-j9gxutMdpGMdwaX1SzOG31Ddm+IGFjeNf+N3Z5g+BBpS8FSXOALlrM+ORIGc/QKszGJEDlw+6PfIsJZICsqsGQ=="
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
@@ -7126,6 +7134,21 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||
@@ -8528,9 +8551,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
||||
"version": "6.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"eslint-config-habitrpg": "6.2.0",
|
||||
"eslint-plugin-mocha": "5.3.0",
|
||||
"eslint-plugin-vue": "7.20.0",
|
||||
"ga-gtag": "^1.2.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"intro.js": "^7.2.0",
|
||||
@@ -42,7 +41,7 @@
|
||||
"timers-browserify": "^2.0.12",
|
||||
"uuid": "^9.0.1",
|
||||
"validator": "^13.9.0",
|
||||
"vite": "^6.0.0",
|
||||
"vite": "^6.3.6",
|
||||
"vite-plugin-compression2": "^1.3.3",
|
||||
"vue": "^2.7.10",
|
||||
"vue-fragment": "^1.6.0",
|
||||
|
||||
@@ -203,6 +203,9 @@ export default {
|
||||
|
||||
return response;
|
||||
}, error => { // Set up Error interceptors
|
||||
if (!error.response) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
if (error.response.status >= 400) {
|
||||
const isBanned = this.checkForBannedUser(error);
|
||||
if (isBanned === true) return null; // eslint-disable-line consistent-return
|
||||
|
||||
@@ -1055,6 +1055,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_elegant_palace {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_elegant_palace.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_enchanted_music_room {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_enchanted_music_room.png');
|
||||
width: 141px;
|
||||
@@ -1751,6 +1756,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_nighttime_street_with_shops {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_nighttime_street_with_shops.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_ocean_sunrise {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_ocean_sunrise.png');
|
||||
width: 141px;
|
||||
@@ -2437,6 +2447,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_winter_desert_with_saguaros {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_winter_desert_with_saguaros.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_winter_fireworks {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_winter_fireworks.png');
|
||||
width: 141px;
|
||||
@@ -29455,6 +29470,11 @@
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.back_armoire_harpsichord {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_armoire_harpsichord.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_clownsBowtie {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_clownsBowtie.png');
|
||||
width: 114px;
|
||||
@@ -29845,6 +29865,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_loneCowpokeOutfit {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_loneCowpokeOutfit.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_lunarArmor {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_lunarArmor.png');
|
||||
width: 90px;
|
||||
@@ -30480,6 +30505,11 @@
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.head_armoire_loneCowpokeHat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_loneCowpokeHat.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_lunarCrown {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_lunarCrown.png');
|
||||
width: 90px;
|
||||
@@ -30790,6 +30820,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_doubleBass {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_doubleBass.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_dragonTamerShield {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_dragonTamerShield.png');
|
||||
width: 90px;
|
||||
@@ -30995,6 +31030,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_prettyPinkGiftBox {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_prettyPinkGiftBox.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_ramHornShield {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_ramHornShield.png');
|
||||
width: 90px;
|
||||
@@ -31465,6 +31505,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_loneCowpokeOutfit {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_loneCowpokeOutfit.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_lunarArmor {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_lunarArmor.png');
|
||||
width: 90px;
|
||||
@@ -31760,6 +31805,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_bambooFlute {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_bambooFlute.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_barristerGavel {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_barristerGavel.png');
|
||||
width: 90px;
|
||||
@@ -32170,6 +32220,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_prettyPinkParasol {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_prettyPinkParasol.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_pushBroom {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_pushBroom.png');
|
||||
width: 114px;
|
||||
@@ -34060,6 +34115,46 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.back_mystery_202601 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_202601.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.back_mystery_202602 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_202602.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_mystery_202512 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202512.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_mystery_202512 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202512.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_mystery_202602 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202602.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_mystery_202512 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202512.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_mystery_202512 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202512.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_mystery_202601 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202601.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.back_mystery_201402 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_201402.png');
|
||||
width: 90px;
|
||||
@@ -38640,6 +38735,26 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_winter2026Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_winter2026Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_winter2026Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_winter2026Mage.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_winter2026Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_winter2026Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.broad_armor_special_winter2026Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_winter2026Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_yeti {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_yeti.png');
|
||||
width: 90px;
|
||||
@@ -38935,6 +39050,26 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_winter2026Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_winter2026Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_winter2026Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_winter2026Mage.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_winter2026Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_winter2026Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.head_special_winter2026Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_winter2026Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_yeti {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_yeti.png');
|
||||
width: 90px;
|
||||
@@ -39115,6 +39250,21 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_special_winter2026Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_winter2026Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_special_winter2026Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_winter2026Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.shield_special_winter2026Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_winter2026Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_special_yeti {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_yeti.png');
|
||||
width: 90px;
|
||||
@@ -39355,6 +39505,26 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_winter2026Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_winter2026Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_winter2026Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_winter2026Mage.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_winter2026Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_winter2026Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.slim_armor_special_winter2026Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_winter2026Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_yeti {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_yeti.png');
|
||||
width: 90px;
|
||||
@@ -39595,6 +39765,26 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_winter2026Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_winter2026Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_winter2026Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_winter2026Mage.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_winter2026Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_winter2026Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.weapon_special_winter2026Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_winter2026Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_yeti {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_yeti.png');
|
||||
width: 90px;
|
||||
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="378" height="176" viewBox="0 0 378 176" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0H378V174C378 175.105 377.105 176 376 176H1.99999C0.895423 176 0 175.105 0 174V0Z" fill="url(#paint0_linear_2257_239)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2257_239" x1="378" y1="0" x2="0" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#925CF3"/>
|
||||
<stop offset="1" stop-color="#34B5C1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 448 B |
@@ -0,0 +1,37 @@
|
||||
<svg width="48" height="96" viewBox="0 0 48 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.10104 12.0483C-2.82088 9.43721 -3.53422 6.57214 -5.6115 5.24584C-7.68877 3.91954 -9.89543 4.92709 -10.1422 6.808C-10.3891 8.68891 -9.06061 9.83066 -4.97737 13.9337C-3.81821 15.0985 -3.3812 14.6594 -3.10104 12.0483Z" stroke="#FFA624" stroke-width="4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.34089 15.2054C4.45116 13.6561 7.27707 12.8443 9.45877 13.9889C11.6405 15.1334 11.8754 17.5575 10.3778 18.7127C8.88016 19.868 7.23193 19.2828 1.65411 17.781C0.0706697 17.3546 0.230624 16.7548 2.34089 15.2054Z" stroke="#FFBE5D" stroke-width="4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.549002 12.0098C-3.61871 9.59194 -3.87667 15.8322 -2.20457 16.8023C-0.532473 17.7724 4.71671 14.4277 0.549002 12.0098Z" fill="#EE9109"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M-1.76917 16.0445L13.637 24.9825L9.18965 32.7229L-6.21656 23.785L-1.76917 16.0445Z" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M-6.90457 13.0652L3.36623 19.0238L-1.08116 26.7643L-11.352 20.8057L-6.90457 13.0652Z" fill="#FFBE5D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M-1.76917 16.0445L3.36623 19.0238L1.88377 21.604L-3.25163 18.6247L-1.76917 16.0445Z" fill="#FFA624"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M-6.21656 23.785L6.62195 31.2333L-3.75529 49.2944L-16.5938 41.8461L-6.21656 23.785Z" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.64886 25.2747L6.62195 31.2333L5.13948 33.8134L-5.13132 27.8548L-3.64886 25.2747Z" fill="#DDF3F3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.401307 24.1842L10.6721 30.1428L9.18965 32.7229L-1.08116 26.7643L0.401307 24.1842Z" fill="#DDF3F3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.7924 38.4607L17.9387 42.0519L21.31 40.5834L24.8838 41.4413L23.4225 38.0537L24.2762 34.4625L20.9049 35.9309L17.3311 35.0731L18.7924 38.4607Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.93867 71.2331L-4.79238 74.8243L-1.42111 73.3559L2.15271 74.2137L0.691383 70.8261L1.54509 67.2349L-1.82618 68.7033L-5.4 67.8455L-3.93867 71.2331Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.8949 25.3807L35.0583 29.8802L37.9424 26.2452L42.4202 25.0761L38.8028 22.178L37.6393 17.6786L34.7552 21.3135L30.2775 22.4826L33.8949 25.3807Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.2596 71.999L40.579 68.1435L45.9507 88.2881L31.6312 92.1436L26.2596 71.999Z" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9401 75.8545L26.2589 71.9966L31.6273 92.1421L17.3084 96L11.9401 75.8545Z" fill="#DDF3F3"/>
|
||||
<rect width="2.96589" height="20.8485" transform="matrix(0.965611 -0.25999 0.257652 0.966238 23.3957 72.7701)" fill="#FFA624"/>
|
||||
<rect width="2.96589" height="20.8485" transform="matrix(0.965611 -0.25999 0.257652 0.966238 26.2596 71.999)" fill="#FFBE5D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.9999 90.0369L30.8638 89.2658L31.6312 92.1436L28.7673 92.9147L27.9999 90.0369Z" fill="#EE9109"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3957 72.7701L26.2596 71.999L27.0269 74.8768L24.163 75.6479L23.3957 72.7701Z" fill="#EE9109"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9401 75.8545L23.3951 72.7682L24.162 75.6461L12.707 78.7325L11.9401 75.8545Z" fill="#C1E9E9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5443 93.1213L27.9999 90.0369L28.7673 92.9147L17.3117 95.9991L16.5443 93.1213Z" fill="#C1E9E9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.1235 71.2279L40.579 68.1435L41.3464 71.0213L29.8908 74.1057L29.1235 71.2279Z" fill="#DDF3F3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.7277 88.4947L45.1833 85.4103L45.9507 88.2881L34.4951 91.3725L33.7277 88.4947Z" fill="#DDF3F3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.8638 89.2658L33.7277 88.4947L34.4951 91.3725L31.6312 92.1436L30.8638 89.2658Z" fill="#FFA624"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.2596 71.999L29.1235 71.2279L29.8908 74.1057L27.0269 74.8768L26.2596 71.999Z" fill="#FFA624"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.5224 56.3076C25.8087 53.7812 24.0792 51.3933 21.6588 50.9455C19.2383 50.4977 17.5679 52.2625 18.0403 54.0994C18.5126 55.9363 20.17 56.4948 25.4855 58.7621C26.9945 59.4057 27.236 58.834 26.5224 56.3076Z" stroke="#FFA624" stroke-width="4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.745 57.1864C34.124 54.9555 36.4415 53.1391 38.8911 53.3791C41.3406 53.6191 42.4621 55.7782 41.5042 57.413C40.5463 59.0479 38.7999 59.1258 33.0684 59.8329C31.4413 60.0337 31.366 59.4173 32.745 57.1864Z" stroke="#FFBE5D" stroke-width="4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.8923 54.898C25.1267 54.225 27.2139 60.108 29.1258 60.378C31.0378 60.648 34.6579 55.571 29.8923 54.898Z" fill="#EE9109"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.247 59.5115L46.8635 61.9994L45.6255 70.8503L28.0091 68.3625L29.247 59.5115Z" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.6306 57.0236L29.247 59.5114L28.0091 68.3624L10.3927 65.8745L11.6306 57.0236Z" fill="#DDF3F3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L35.1192 60.3408L33.8813 69.1917L22.137 67.5332L23.3749 58.6822Z" fill="#FFBE5D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L29.247 59.5115L28.0091 68.3625L22.137 67.5332L23.3749 58.6822Z" fill="#FFA624"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.247 59.5115L35.1192 60.3408L34.7065 63.2911L28.8344 62.4618L29.247 59.5115Z" fill="#FFA624"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L29.247 59.5115L28.8344 62.4618L22.9622 61.6326L23.3749 58.6822Z" fill="#EE9109"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8053 62.9241L22.5496 64.5827L22.137 67.533L10.3927 65.8745L10.8053 62.9241Z" fill="#C1E9E9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.2939 66.2414L46.0382 67.9L45.6255 70.8503L33.8813 69.1917L34.2939 66.2414Z" fill="#DDF3F3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
@@ -30,12 +30,23 @@
|
||||
cursor: default;
|
||||
color: $gray-200;
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
background-color: $gray-700;
|
||||
background-color: transparent;
|
||||
border: 2px solid transparent;
|
||||
|
||||
box-shadow:
|
||||
0 1px 3px 0 rgba($black, 0.12),
|
||||
0 1px 2px 0 rgba($black, 0.24);
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
padding: 4px 12px;
|
||||
min-height: 32px;
|
||||
max-height: 32px;
|
||||
gap: 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
.svg {
|
||||
color: $gray-300;
|
||||
color: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,10 @@ h4 {
|
||||
background-color: $purple-300 !important;
|
||||
}
|
||||
|
||||
.bg-yellow-50 {
|
||||
background-color: $yellow-50 !important;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: $white !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g fill="#FFFFFF" fill-rule="nonzero">
|
||||
<polygon points="12.1973467 2 14 3.80265326 9.80187117 8 14 12.1973467 12.1973467 14 8 9.80187117 3.80265326 14 2 12.1973467 6.19812883 8 2 3.80265326 3.80265326 2 8 6.19812883"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 504 B |
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,29 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.1792 31.6843L46.8536 22.3769L23.918 28.6988L18.861 42.5218L44.341 58.5813L58.1792 31.6843Z" fill="#FF944C"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L46.1108 26.1328L36.2812 28.8422L46.6218 34.5148Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M30.2393 39.0304L26.4518 31.5515L36.2813 28.8422L30.2393 39.0304Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L36.2813 28.8422L30.2393 39.0304L46.6218 34.5148Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M53.8301 32.5279L46.1108 26.1328L46.6218 34.5148L53.8301 32.5279Z" fill="white"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M23.0309 41.0173L26.4518 31.5516L30.2393 39.0304L23.0309 41.0173Z" fill="#FA8537"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M53.8301 32.5279L46.6218 34.5148L43.0424 53.79L53.8301 32.5279Z" fill="#FA8537"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M23.0309 41.0173L30.2393 39.0304L43.0425 53.79L23.0309 41.0173Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L30.2393 39.0304L43.0425 53.79L46.6218 34.5148Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M50.555 4.15937L47.026 0.420004L38.7773 1.59601L36.4144 6.17539L44.5675 12.8919L50.555 4.15937Z" fill="#FFBE5D"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L46.6034 1.6924L43.0682 2.1964L46.414 4.62854Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M40.5221 5.46854L39.5331 2.7004L43.0682 2.1964L40.5221 5.46854Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L43.0683 2.1964L40.5221 5.46855L46.414 4.62854Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M49.0064 4.25894L46.6034 1.6924L46.414 4.62854L49.0064 4.25894Z" fill="white"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M37.9296 5.83815L39.5331 2.70041L40.5221 5.46855L37.9296 5.83815Z" fill="#FFA624"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M49.0064 4.25893L46.414 4.62853L44.3259 11.1688L49.0064 4.25893Z" fill="#FFA624"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M37.9297 5.83815L40.5221 5.46855L44.326 11.1688L37.9297 5.83815Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L40.5221 5.46855L44.326 11.1688L46.414 4.62854Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.2986 16.7775L24.6513 8.36623L11.1016 3.94533L4.07056 9.19883L11.614 25.6769L27.2986 16.7775Z" fill="#FF6165"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L23.0573 10.0026L17.2502 8.10789L20.5864 14.3719Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M10.908 11.2141L11.4432 6.21322L17.2502 8.10789L10.908 11.2141Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L17.2502 8.10789L10.9081 11.2141L20.5864 14.3719Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M24.8449 15.7613L23.0573 10.0026L20.5864 14.3719L24.8449 15.7613Z" fill="white"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M6.64955 9.82464L11.4432 6.21321L10.908 11.2141L6.64955 9.82464Z" fill="#F23035"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M24.8449 15.7613L20.5864 14.3719L12.5221 22.8464L24.8449 15.7613Z" fill="#F23035"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M6.64959 9.82464L10.9081 11.2141L12.5221 22.8463L6.64959 9.82464Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L10.9081 11.2141L12.5221 22.8463L20.5864 14.3719Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,29 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.82083 31.6843L17.1464 22.3769L40.082 28.6988L45.139 42.5218L19.659 58.5813L5.82083 31.6843Z" fill="#24CC8F"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L17.8892 26.1328L27.7188 28.8422L17.3782 34.5148Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M33.7607 39.0304L37.5482 31.5515L27.7187 28.8422L33.7607 39.0304Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L27.7187 28.8422L33.7607 39.0304L17.3782 34.5148Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M10.1699 32.5279L17.8892 26.1328L17.3782 34.5148L10.1699 32.5279Z" fill="white"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M40.9691 41.0173L37.5482 31.5516L33.7607 39.0304L40.9691 41.0173Z" fill="#1CA372"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M10.1699 32.5279L17.3782 34.5148L20.9576 53.79L10.1699 32.5279Z" fill="#1CA372"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M40.9691 41.0173L33.7607 39.0304L20.9575 53.79L40.9691 41.0173Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L33.7607 39.0304L20.9575 53.79L17.3782 34.5148Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.445 4.15937L16.974 0.420004L25.2227 1.59601L27.5856 6.17539L19.4325 12.8919L13.445 4.15937Z" fill="#925CF3"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L17.3966 1.6924L20.9318 2.1964L17.586 4.62854Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M23.4779 5.46854L24.4669 2.7004L20.9318 2.1964L23.4779 5.46854Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L20.9317 2.1964L23.4779 5.46855L17.586 4.62854Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M14.9936 4.25894L17.3966 1.6924L17.586 4.62854L14.9936 4.25894Z" fill="white"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M26.0704 5.83815L24.4669 2.70041L23.4779 5.46855L26.0704 5.83815Z" fill="#4F2A93"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M14.9936 4.25893L17.586 4.62853L19.6741 11.1688L14.9936 4.25893Z" fill="#4F2A93"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M26.0703 5.83815L23.4779 5.46855L19.674 11.1688L26.0703 5.83815Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L23.4779 5.46855L19.674 11.1688L17.586 4.62854Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.7014 16.7775L39.3487 8.36623L52.8984 3.94533L59.9294 9.19883L52.386 25.6769L36.7014 16.7775Z" fill="#50B5E9"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L40.9427 10.0026L46.7498 8.10789L43.4136 14.3719Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M53.092 11.2141L52.5568 6.21322L46.7498 8.10789L53.092 11.2141Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L46.7498 8.10789L53.0919 11.2141L43.4136 14.3719Z" fill="white"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M39.1551 15.7613L40.9427 10.0026L43.4136 14.3719L39.1551 15.7613Z" fill="white"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M57.3504 9.82464L52.5568 6.21321L53.092 11.2141L57.3504 9.82464Z" fill="#46A7D9"/>
|
||||
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M39.1551 15.7613L43.4136 14.3719L51.4779 22.8464L39.1551 15.7613Z" fill="#46A7D9"/>
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M57.3504 9.82464L53.0919 11.2141L51.4779 22.8463L57.3504 9.82464Z" fill="white"/>
|
||||
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L53.0919 11.2141L51.4779 22.8463L43.4136 14.3719Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -117,7 +117,7 @@ export default {
|
||||
closeWithAction () {
|
||||
this.close();
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: 'achievements' });
|
||||
this.$router.push(`/profile/${this.$store.state.user.data._id}#achievements`);
|
||||
}, 200);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -311,7 +311,7 @@
|
||||
<input
|
||||
id="passwordInput"
|
||||
v-model="password"
|
||||
class="form-control input-with-error"
|
||||
class="form-control dark input-with-error"
|
||||
type="password"
|
||||
:placeholder="$t('password')"
|
||||
:class="{'input-invalid': passwordInvalid, 'input-valid': passwordValid}"
|
||||
@@ -323,7 +323,7 @@
|
||||
{{ $t('minPasswordLength') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group mb-4">
|
||||
<label
|
||||
v-once
|
||||
for="confirmPasswordInput"
|
||||
@@ -331,7 +331,7 @@
|
||||
<input
|
||||
id="confirmPasswordInput"
|
||||
v-model="passwordConfirm"
|
||||
class="form-control input-with-error"
|
||||
class="form-control dark input-with-error"
|
||||
type="password"
|
||||
:placeholder="$t('confirmPasswordPlaceholder')"
|
||||
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
|
||||
@@ -344,13 +344,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="btn btn-info"
|
||||
:enabled="!resetPasswordSetNewOneData.hasError"
|
||||
<button
|
||||
class="btn btn-info w-100"
|
||||
:disabled="!password || !passwordConfirm
|
||||
|| password !== passwordConfirm || resetPasswordSetNewOneData.hasError"
|
||||
@click="resetPasswordSetNewOneLink()"
|
||||
>
|
||||
{{ $t('setNewPass') }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -672,7 +673,7 @@ export default {
|
||||
|
||||
this.login();
|
||||
},
|
||||
async forgotPasswordLink () {
|
||||
forgotPasswordLink: debounce(async function forgotPassLink () {
|
||||
if (!this.username) {
|
||||
window.alert(this.$t('missingEmail')); // eslint-disable-line no-alert
|
||||
return;
|
||||
@@ -683,7 +684,7 @@ export default {
|
||||
});
|
||||
|
||||
window.alert(this.$t('newPassSent')); // eslint-disable-line no-alert
|
||||
},
|
||||
}, 500),
|
||||
async resetPasswordSetNewOneLink () {
|
||||
if (!this.password) {
|
||||
window.alert(this.$t('missingNewPassword')); // eslint-disable-line no-alert
|
||||
|
||||
@@ -43,9 +43,11 @@ export default {
|
||||
const AUTH_SETTINGS = localStorage.getItem(LOCALSTORAGE_AUTH_KEY);
|
||||
const parseSettings = JSON.parse(AUTH_SETTINGS);
|
||||
const userId = parseSettings ? parseSettings.auth.apiId : '';
|
||||
const username = this.$store?.state?.user?.data?.auth?.local?.username || '';
|
||||
|
||||
return this.$t('accountSuspended', {
|
||||
userId,
|
||||
username,
|
||||
communityManagerEmail: COMMUNITY_MANAGER_EMAIL,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
@update-challenge="updateChallenge"
|
||||
/>
|
||||
<close-challenge-modal
|
||||
:members="members"
|
||||
:challenge-id="challenge._id"
|
||||
:prize="challenge.prize"
|
||||
:flag-count="challenge.flagCount"
|
||||
@@ -72,32 +71,40 @@
|
||||
</div>
|
||||
<div class="col-12 col-md-6 text-right">
|
||||
<div
|
||||
class="box member-count"
|
||||
class="box member-count p-2"
|
||||
@click="showMemberModal()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon member-icon"
|
||||
v-html="icons.memberIcon"
|
||||
></div>
|
||||
{{ challenge.memberCount }}
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('participantsTitle') }}
|
||||
<div class="box-content">
|
||||
<div class="icon-number-row">
|
||||
<div
|
||||
class="svg-icon member-icon"
|
||||
v-html="icons.memberIcon"
|
||||
></div>
|
||||
<span class="number">{{ challenge.memberCount }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('participantsTitle') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div
|
||||
class="svg-icon gem-icon"
|
||||
v-html="icons.gemIcon"
|
||||
></div>
|
||||
{{ challenge.prize || 0 }}
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('prize') }}
|
||||
<div class="box prize-count p-2">
|
||||
<div class="box-content">
|
||||
<div class="icon-number-row">
|
||||
<div
|
||||
class="svg-icon gem-icon"
|
||||
v-html="icons.gemIcon"
|
||||
></div>
|
||||
<span class="number">{{ challenge.prize || 0 }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('prize') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -304,7 +311,6 @@
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
padding: 1em;
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
@@ -314,22 +320,88 @@
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
vertical-align: bottom;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&.member-count:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.box-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-number-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.1em;
|
||||
|
||||
.number {
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
margin-right: .2em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.details {
|
||||
font-size: 12px;
|
||||
margin-top: 0.4em;
|
||||
color: $gray-200;
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
line-height: 1.15;
|
||||
word-break: break-word;
|
||||
max-height: 2.3em;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
&.member-count {
|
||||
.icon-number-row {
|
||||
.svg-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
font-size: 11px;
|
||||
line-height: 1.1;
|
||||
max-height: 2.2em;
|
||||
}
|
||||
}
|
||||
|
||||
&.prize-count {
|
||||
.icon-number-row {
|
||||
.svg-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
font-size: 11px;
|
||||
line-height: 1.1;
|
||||
max-height: 2.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +696,6 @@ export default {
|
||||
this.members = [];
|
||||
},
|
||||
closeChallenge () {
|
||||
this.initialMembersLoad();
|
||||
this.$root.$emit('bv::show::modal', 'close-challenge-modal');
|
||||
},
|
||||
edit () {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
id="close-challenge-modal"
|
||||
:title="$t('endChallenge')"
|
||||
size="md"
|
||||
:hide-header="false"
|
||||
>
|
||||
<div
|
||||
slot="modal-header"
|
||||
@@ -15,6 +16,9 @@
|
||||
>
|
||||
{{ $t('endChallenge') }}
|
||||
</h2>
|
||||
<close-x
|
||||
@close="$root.$emit('bv::hide::modal', 'close-challenge-modal')"
|
||||
/>
|
||||
</div>
|
||||
<div class="row text-center">
|
||||
<span
|
||||
@@ -28,28 +32,67 @@
|
||||
class="col-12"
|
||||
>
|
||||
<div class="col-12">
|
||||
<div class="support-habitica">
|
||||
<!-- @TODO: Add challenge achievement badge here-->
|
||||
<div class="badge-section">
|
||||
<div
|
||||
class="gems-left"
|
||||
v-html="icons.gemsOrange"
|
||||
></div>
|
||||
<div
|
||||
class="challenge-badge"
|
||||
v-html="icons.endChallengeBadge"
|
||||
></div>
|
||||
<div
|
||||
class="gems-right"
|
||||
v-html="icons.gemsPurple"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<strong v-once>{{ $t('selectChallengeWinnersDescription') }}</strong>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<member-search-dropdown
|
||||
:text="winnerText"
|
||||
:members="members"
|
||||
:challenge-id="challengeId"
|
||||
@member-selected="selectMember"
|
||||
/>
|
||||
<div class="col-12 search-input-container">
|
||||
<div class="search-input-wrapper">
|
||||
<div
|
||||
class="search-icon"
|
||||
v-html="icons.search"
|
||||
></div>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
class="search-input"
|
||||
type="text"
|
||||
placeholder="@Username"
|
||||
@input="searchMembers"
|
||||
@focus="showResults = true"
|
||||
@blur="handleBlur"
|
||||
>
|
||||
<div
|
||||
v-if="showResults && filteredMembers.length > 0"
|
||||
class="search-results"
|
||||
>
|
||||
<div
|
||||
v-for="member in filteredMembers"
|
||||
:key="member._id"
|
||||
class="search-result-item"
|
||||
@mousedown="selectMember(member)"
|
||||
>
|
||||
{{ getMemberDisplayName(member) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary"
|
||||
class="btn award-winner-btn"
|
||||
:class="{'has-winner': winner._id}"
|
||||
:disabled="!winner._id"
|
||||
@click="closeChallenge"
|
||||
>
|
||||
{{ $t('awardWinners') }}
|
||||
<span>{{ $t('awardWinners') }}</span>
|
||||
<div
|
||||
class="gem-icon"
|
||||
v-html="icons.gem"
|
||||
></div>
|
||||
<span>{{ prize }} {{ prize === 1 ? $t('gem') : $t('gems') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
@@ -60,14 +103,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<strong v-once>{{ $t('doYouWantedToDeleteChallenge') }}</strong>
|
||||
<strong
|
||||
v-once
|
||||
class="delete-challenge-text"
|
||||
>{{ $t('doYouWantedToDeleteChallenge') }}</strong>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="col-12 refund-text"
|
||||
>
|
||||
{{ $t('deleteChallengeRefundDescription') }}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-danger"
|
||||
class="btn btn-danger delete-challenge-btn"
|
||||
@click="deleteChallenge()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon color delete-icon"
|
||||
v-html="icons.deleteIcon"
|
||||
></div>
|
||||
{{ $t('deleteChallenge') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -82,6 +138,7 @@
|
||||
|
||||
<style lang='scss'>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/button.scss';
|
||||
|
||||
#close-challenge-modal {
|
||||
h2 {
|
||||
@@ -94,26 +151,190 @@
|
||||
|
||||
.header-wrap {
|
||||
width: 100%;
|
||||
padding-top: 2em;
|
||||
padding-top: 32px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.support-habitica {
|
||||
background-image: url('@/assets/svg/for-css/support-habitica-gems.svg?raw');
|
||||
width: 325px;
|
||||
height: 89px;
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-input-container {
|
||||
margin-top: 1em !important;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
position: relative;
|
||||
width: 384px;
|
||||
margin: 0 auto;
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-55%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: $gray-200;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding-left: 36px;
|
||||
padding-right: 12px;
|
||||
border: 1px solid $gray-400;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.2s ease, border-width 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: 2px solid $purple-400;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
.search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: $white;
|
||||
border: 1px solid $gray-400;
|
||||
border-top: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.search-result-item {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background-color: $purple-600;
|
||||
color: $purple-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delete-challenge-text {
|
||||
color: $maroon-50;
|
||||
}
|
||||
|
||||
.refund-text {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
color: $gray-50;
|
||||
margin-top: 0.5em !important;
|
||||
}
|
||||
|
||||
.delete-challenge-btn {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.delete-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.award-winner-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 32px;
|
||||
padding: 4px 12px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:not(:disabled) {
|
||||
background-color: $white;
|
||||
color: $gray-200;
|
||||
border: 1px solid $gray-400;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
|
||||
&.has-winner {
|
||||
background-color: $purple-200;
|
||||
color: $white;
|
||||
border-color: $purple-200;
|
||||
}
|
||||
|
||||
&:hover:not(.has-winner) {
|
||||
background-color: $gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
.gem-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: $gems-color;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1.5rem;
|
||||
margin: -24px auto 0;
|
||||
padding: 0.5rem 0;
|
||||
|
||||
.gems-left, .gems-right {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.challenge-badge {
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer, .modal-header {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.footer-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.col-12 {
|
||||
margin-top: 2em;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.col-12:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.or {
|
||||
@@ -123,21 +344,41 @@
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
font-weight: bold;
|
||||
color: $gray-100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
||||
import debounce from 'lodash/debounce';
|
||||
import searchIcon from '@/assets/svg/for-css/search.svg?raw';
|
||||
import deleteIcon from '@/assets/svg/delete.svg?raw';
|
||||
import gemIcon from '@/assets/svg/gem.svg?raw';
|
||||
import endChallengeBadge from '@/assets/svg/for-css/end_challenge_badge.svg?raw';
|
||||
import gemsOrange from '@/assets/svg/for-css/orange100_red100_yellow100_gems.svg?raw';
|
||||
import gemsPurple from '@/assets/svg/for-css/purple200_green10_blue100_gems.svg?raw';
|
||||
import closeX from '@/components/ui/closeX';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
memberSearchDropdown,
|
||||
closeX,
|
||||
},
|
||||
props: ['challengeId', 'members', 'prize', 'flagCount'],
|
||||
props: ['challengeId', 'prize', 'flagCount'],
|
||||
data () {
|
||||
return {
|
||||
winner: {},
|
||||
searchTerm: '',
|
||||
showResults: false,
|
||||
filteredMembers: [],
|
||||
isSearching: false,
|
||||
icons: Object.freeze({
|
||||
search: searchIcon,
|
||||
deleteIcon,
|
||||
gem: gemIcon,
|
||||
endChallengeBadge,
|
||||
gemsOrange,
|
||||
gemsPurple,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -149,9 +390,58 @@ export default {
|
||||
return this.flagCount > 0;
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.searchMembersDebounced = debounce(this.performSearch, 500);
|
||||
},
|
||||
methods: {
|
||||
searchMembers () {
|
||||
if (!this.searchTerm) {
|
||||
this.filteredMembers = [];
|
||||
this.isSearching = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSearching = true;
|
||||
this.searchMembersDebounced();
|
||||
},
|
||||
async performSearch () {
|
||||
if (!this.searchTerm) {
|
||||
this.filteredMembers = [];
|
||||
this.isSearching = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const searchTerm = this.searchTerm.replace('@', '');
|
||||
|
||||
try {
|
||||
const members = await this.$store.dispatch('members:getChallengeMembers', {
|
||||
challengeId: this.challengeId,
|
||||
searchTerm,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
|
||||
this.filteredMembers = members.slice(0, 10);
|
||||
} catch (err) {
|
||||
this.filteredMembers = [];
|
||||
} finally {
|
||||
this.isSearching = false;
|
||||
}
|
||||
},
|
||||
getMemberDisplayName (member) {
|
||||
if (member.auth?.local?.username) {
|
||||
return `@${member.auth.local.username}`;
|
||||
}
|
||||
return member.profile?.name || '';
|
||||
},
|
||||
selectMember (member) {
|
||||
this.winner = member;
|
||||
this.searchTerm = this.getMemberDisplayName(member);
|
||||
this.showResults = false;
|
||||
},
|
||||
handleBlur () {
|
||||
setTimeout(() => {
|
||||
this.showResults = false;
|
||||
}, 200);
|
||||
},
|
||||
async closeChallenge () {
|
||||
this.challenge = await this.$store.dispatch('challenges:selectChallengeWinner', {
|
||||
|
||||
@@ -52,17 +52,21 @@
|
||||
<div
|
||||
v-if="!group.purchased.plan.dateTerminated
|
||||
&& group.purchased.plan.paymentMethod === 'Stripe'"
|
||||
class="btn btn-primary"
|
||||
class="btn btn-primary mb-3"
|
||||
@click="redirectToStripeEdit({groupId: group.id})"
|
||||
>
|
||||
{{ $t('subUpdateCard') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!group.purchased.plan.dateTerminated"
|
||||
class="btn btn-sm btn-danger"
|
||||
@click="cancelSubscriptionConfirm({group: group})"
|
||||
>
|
||||
{{ $t('cancelGroupSub') }}
|
||||
<div v-if="!group.purchased.plan.dateTerminated">
|
||||
<div class="small gray-50 mb-3" v-once>
|
||||
{{ $t('groupPlanBillingFYIShort') }}
|
||||
</div>
|
||||
<div
|
||||
class="btn btn-sm btn-danger"
|
||||
@click="cancelSubscriptionConfirm({group: group})"
|
||||
>
|
||||
{{ $t('cancelGroupSub') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -82,9 +82,7 @@
|
||||
<select-translated-array
|
||||
:items="[
|
||||
'groupParentChildren',
|
||||
'groupCouple',
|
||||
'groupFriends',
|
||||
'groupCoworkers',
|
||||
'groupManager',
|
||||
'groupTeacher'
|
||||
]"
|
||||
|
||||
@@ -218,13 +218,19 @@
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
// somehow the browser felt like setting this 398px instead
|
||||
// now its fixed to 400 :)
|
||||
width: 400px;
|
||||
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
@media (max-width: 589px) {
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (max-width: 353px) {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.quest-col {
|
||||
::v-deep {
|
||||
.item-wrapper {
|
||||
@@ -251,6 +257,28 @@
|
||||
::v-deep & {
|
||||
.modal-dialog {
|
||||
width: 448px !important;
|
||||
max-width: calc(100vw - 20px);
|
||||
margin: 0.5rem auto;
|
||||
display: flex;
|
||||
|
||||
@media (max-width: 468px) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@media (max-width: 353px) {
|
||||
width: 100% !important;
|
||||
margin: 0.25rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 300px) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
class="banner d-flex align-items-center justify-content-between py-3 px-4"
|
||||
id="privacy-banner"
|
||||
v-if="!hidden"
|
||||
id="privacy-banner"
|
||||
class="banner d-flex align-items-center justify-content-between py-3 px-4"
|
||||
>
|
||||
<p
|
||||
class="mr-3 mb-0"
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
<template>
|
||||
<div
|
||||
class="notification d-flex flex-column justify-content-center text-center"
|
||||
class="notification d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<strong
|
||||
v-once
|
||||
class="mx-auto mb-2"
|
||||
<img
|
||||
src="@/assets/images/gifts_start.svg"
|
||||
class="gift-start"
|
||||
alt=""
|
||||
>
|
||||
{{ $t('g1g1') }}
|
||||
</strong>
|
||||
<small
|
||||
v-once
|
||||
class="mx-4 mb-3"
|
||||
>
|
||||
{{ $t('g1g1Details') }}
|
||||
</small>
|
||||
<div
|
||||
class="btn-secondary mx-auto d-flex"
|
||||
@click="showSelectUser()"
|
||||
>
|
||||
<div
|
||||
<div class="content-wrapper d-flex flex-column justify-content-center text-center">
|
||||
<strong
|
||||
v-once
|
||||
class="m-auto"
|
||||
class="mx-auto mb-2"
|
||||
>
|
||||
{{ $t('g1g1') }}
|
||||
</strong>
|
||||
<small
|
||||
v-once
|
||||
class="mx-4 mb-3"
|
||||
>
|
||||
{{ $t('g1g1Details') }}
|
||||
</small>
|
||||
<button
|
||||
class="btn btn-secondary mx-auto"
|
||||
@click="showSelectUser()"
|
||||
>
|
||||
{{ $t('sendGift') }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<img
|
||||
src="@/assets/images/gifts_start.svg"
|
||||
class="gift-end"
|
||||
alt=""
|
||||
>
|
||||
<div
|
||||
class="notification-remove"
|
||||
class="close-x"
|
||||
@click.stop="remove()"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
class="svg-icon svg-close"
|
||||
v-html="icons.close"
|
||||
></div>
|
||||
</div>
|
||||
@@ -41,51 +47,89 @@
|
||||
<style lang='scss' scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
small, strong {
|
||||
small {
|
||||
color: $white;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
line-height: 1.714;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $white;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
line-height: 1.714;
|
||||
}
|
||||
|
||||
.notification {
|
||||
background-image: url('@/assets/images/g1g1-notif.png');
|
||||
background-image: url('@/assets/images/gifts_bg.svg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 10rem;
|
||||
padding: 3rem;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification-remove {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
right: 24px;
|
||||
top: 24px;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
width: 5.75rem;
|
||||
min-height: 1.5rem;
|
||||
border-radius: 2px;
|
||||
border-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
.gift-start {
|
||||
height: 96px;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.gift-end {
|
||||
height: 96px;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) scaleX(-1);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.close-x {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
|
||||
&:hover .svg-close {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.svg-close {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import closeIcon from '@/assets/svg/close-teal.svg?raw';
|
||||
import { mapActions } from '@/libs/store';
|
||||
import closeIcon from '@/assets/svg/close-white.svg?raw';
|
||||
|
||||
export default {
|
||||
props: ['notification'],
|
||||
props: ['notification', 'eventKey'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
@@ -94,11 +138,11 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
readNotification: 'notifications:readNotification',
|
||||
}),
|
||||
remove () {
|
||||
this.readNotification({ notificationId: this.notification.id });
|
||||
if (this.eventKey) {
|
||||
window.localStorage.setItem(`hide-g1g1-${this.eventKey}`, 'true');
|
||||
}
|
||||
this.$emit('notification-removed');
|
||||
},
|
||||
showSelectUser () {
|
||||
this.$root.$emit('bv::show::modal', 'select-user-modal');
|
||||
|
||||
@@ -71,7 +71,7 @@ export default {
|
||||
props: ['notification', 'canRemove'],
|
||||
methods: {
|
||||
action () {
|
||||
this.$router.push({ name: 'achievements' });
|
||||
this.$router.push(`/profile/${this.$store.state.user.data._id}#achievements`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
action () {
|
||||
this.$router.push({ name: 'stats' });
|
||||
this.$router.push(`/profile/${this.$store.state.user.data._id}#stats`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
v-if="showOnboardingGuide"
|
||||
:never-seen="hasSpecialBadge"
|
||||
/>
|
||||
<gift-one-get-one-notification
|
||||
v-if="shouldShowG1g1"
|
||||
:notification="g1g1Notification"
|
||||
:event-key="g1g1EventKey"
|
||||
@notification-removed="handleG1g1Removed"
|
||||
/>
|
||||
<component
|
||||
:is="notification.type"
|
||||
v-for="notification in notifications"
|
||||
@@ -114,6 +120,7 @@
|
||||
<script>
|
||||
import * as quests from '@/../../common/script/content/quests';
|
||||
import { hasCompletedOnboarding } from '@/../../common/script/libs/onboarding';
|
||||
import find from 'lodash/find';
|
||||
import { mapState, mapActions } from '@/libs/store';
|
||||
import notificationsIcon from '@/assets/svg/notifications.svg?raw';
|
||||
import MenuDropdown from '../ui/customMenuDropdown';
|
||||
@@ -151,6 +158,7 @@ export default {
|
||||
CARD_RECEIVED,
|
||||
CHALLENGE_INVITATION,
|
||||
GIFT_ONE_GET_ONE,
|
||||
GiftOneGetOneNotification: GIFT_ONE_GET_ONE,
|
||||
GROUP_TASK_ASSIGNED,
|
||||
GROUP_TASK_CLAIMED,
|
||||
GROUP_TASK_NEEDS_WORK,
|
||||
@@ -178,17 +186,14 @@ export default {
|
||||
hasSpecialBadge: false,
|
||||
quests,
|
||||
openStatus: undefined,
|
||||
g1g1Hidden: false,
|
||||
actionableNotifications: [
|
||||
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
||||
'QUEST_INVITATION',
|
||||
],
|
||||
// A list of notifications handled by this component,
|
||||
// listed in the order they should appear in the notifications panel.
|
||||
// NOTE: Those not listed here won't be shown in the notification panel!
|
||||
handledNotifications: [
|
||||
'NEW_STUFF',
|
||||
'ITEM_RECEIVED',
|
||||
'GIFT_ONE_GET_ONE',
|
||||
'GROUP_TASK_NEEDS_WORK',
|
||||
'GUILD_INVITATION',
|
||||
'PARTY_INVITATION',
|
||||
@@ -207,7 +212,10 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
notificationsOrder () {
|
||||
// Returns a map of NOTIFICATION_TYPE -> POSITION
|
||||
const orderMap = {};
|
||||
@@ -286,9 +294,9 @@ export default {
|
||||
|
||||
return notifications;
|
||||
},
|
||||
// The total number of notification, shown inside the dropdown
|
||||
notificationsCount () {
|
||||
return this.notifications.length;
|
||||
const g1g1Count = this.shouldShowG1g1 ? 1 : 0;
|
||||
return this.notifications.length + g1g1Count;
|
||||
},
|
||||
hasUnseenNotifications () {
|
||||
return this.notifications.some(notification => (notification.seen === false));
|
||||
@@ -299,6 +307,30 @@ export default {
|
||||
showOnboardingGuide () {
|
||||
return !hasCompletedOnboarding(this.user);
|
||||
},
|
||||
currentG1g1Event () {
|
||||
return find(this.currentEventList, event => event.promo === 'g1g1');
|
||||
},
|
||||
g1g1EventKey () {
|
||||
if (!this.currentG1g1Event || !this.currentG1g1Event.start) return null;
|
||||
const startDate = new Date(this.currentG1g1Event.start);
|
||||
return `${startDate.getFullYear()}-${startDate.getMonth()}`;
|
||||
},
|
||||
shouldShowG1g1 () {
|
||||
if (!this.currentG1g1Event) return false;
|
||||
const eventKey = this.g1g1EventKey;
|
||||
if (eventKey && window.localStorage.getItem(`hide-g1g1-${eventKey}`) === 'true') {
|
||||
return false;
|
||||
}
|
||||
return !this.g1g1Hidden;
|
||||
},
|
||||
g1g1Notification () {
|
||||
return {
|
||||
type: 'GIFT_ONE_GET_ONE',
|
||||
id: `g1g1-event-${this.currentG1g1Event?.start || 'default'}`,
|
||||
data: {},
|
||||
seen: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
|
||||
@@ -364,6 +396,9 @@ export default {
|
||||
isActionable (notification) {
|
||||
return this.actionableNotifications.indexOf(notification.type) !== -1;
|
||||
},
|
||||
handleG1g1Removed () {
|
||||
this.g1g1Hidden = true;
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -176,7 +176,12 @@ export default {
|
||||
}
|
||||
},
|
||||
showProfile (startingPage) {
|
||||
this.$router.push({ name: startingPage });
|
||||
const userId = this.$store.state.user.data._id;
|
||||
let path = `/profile/${userId}`;
|
||||
if (startingPage !== 'profile') {
|
||||
path += `#${startingPage}`;
|
||||
}
|
||||
this.$router.push(path);
|
||||
},
|
||||
toLearnMore () {
|
||||
this.$router.push({ name: 'subscription' });
|
||||
|
||||
@@ -454,17 +454,14 @@ export default {
|
||||
},
|
||||
isUserMentioned () {
|
||||
const message = this.msg;
|
||||
|
||||
if (message.highlight) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { user } = this;
|
||||
const displayName = user.profile.name;
|
||||
const { username } = user.auth.local;
|
||||
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
|
||||
message.highlight = new RegExp(pattern, 'i').test(message.text);
|
||||
|
||||
if (!username) return false;
|
||||
const usernamePattern = new RegExp(`@${escapeRegExp(username)}(?:\\b|(?=[^a-zA-Z0-9_]))`, 'i');
|
||||
message.highlight = usernamePattern.test(message.text);
|
||||
return message.highlight;
|
||||
},
|
||||
flagCountDescription () {
|
||||
@@ -494,6 +491,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
mapProfileLinksToModal () {
|
||||
if (!this.$refs?.markdownContainer) {
|
||||
return;
|
||||
}
|
||||
const links = this.$refs.markdownContainer.getElementsByTagName('a');
|
||||
for (let i = 0; i < links.length; i += 1) {
|
||||
let link = links[i].pathname;
|
||||
|
||||
@@ -328,6 +328,8 @@ export default {
|
||||
alreadyReadNotification,
|
||||
nextCron: null,
|
||||
handledNotifications,
|
||||
isInitialLoadComplete: false,
|
||||
pendingRebirthNotification: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -453,6 +455,18 @@ export default {
|
||||
|
||||
return this.runYesterDailies();
|
||||
},
|
||||
async showPendingRebirthModal () {
|
||||
if (this.pendingRebirthNotification) {
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
|
||||
await axios.post('/api/v4/notifications/read', {
|
||||
notificationIds: [this.pendingRebirthNotification.id],
|
||||
});
|
||||
|
||||
this.pendingRebirthNotification = null;
|
||||
}
|
||||
},
|
||||
showDeathModal () {
|
||||
this.playSound('Death');
|
||||
this.$root.$emit('bv::show::modal', 'death');
|
||||
@@ -661,6 +675,18 @@ export default {
|
||||
this.showLevelUpNotifications(this.user.stats.lvl);
|
||||
}
|
||||
this.handleUserNotifications(this.user.notifications);
|
||||
|
||||
this.isInitialLoadComplete = true;
|
||||
|
||||
const hasRebirthConfirmationFlag = localStorage.getItem('show-rebirth-confirmation') === 'true';
|
||||
|
||||
if (hasRebirthConfirmationFlag) {
|
||||
localStorage.removeItem('show-rebirth-confirmation');
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
} else {
|
||||
this.showPendingRebirthModal();
|
||||
}
|
||||
},
|
||||
async handleUserNotifications (after) {
|
||||
if (this.$store.state.isRunningYesterdailies) return;
|
||||
@@ -700,8 +726,15 @@ export default {
|
||||
this.$root.$emit('habitica:won-challenge', notification);
|
||||
break;
|
||||
case 'REBIRTH_ACHIEVEMENT':
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
if (localStorage.getItem('show-rebirth-confirmation') === 'true') {
|
||||
markAsRead = false;
|
||||
} else if (!this.isInitialLoadComplete) {
|
||||
this.pendingRebirthNotification = notification;
|
||||
markAsRead = false;
|
||||
} else {
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
}
|
||||
break;
|
||||
case 'STREAK_ACHIEVEMENT':
|
||||
this.text(`${this.$t('streaks')}: ${this.user.achievements.streak}`, () => {
|
||||
|
||||
@@ -197,9 +197,7 @@
|
||||
<select-translated-array
|
||||
:items="[
|
||||
'groupParentChildren',
|
||||
'groupCouple',
|
||||
'groupFriends',
|
||||
'groupCoworkers',
|
||||
'groupManager',
|
||||
'groupTeacher'
|
||||
]"
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
box-shadow: 0 1px 2px 0 rgba($black, 0.2);
|
||||
z-index: 9;
|
||||
height: 3rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-width: 683px) {
|
||||
height: auto;
|
||||
min-height: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
@@ -23,6 +29,19 @@
|
||||
padding: 0.75rem;
|
||||
|
||||
color: $gray-50;
|
||||
white-space: nowrap;
|
||||
|
||||
@media (max-width: 683px) {
|
||||
padding: 0.5rem;
|
||||
font-size: 13px;
|
||||
flex: 1 1 auto;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
padding: 0.5rem 0.4rem;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $purple-300;
|
||||
|
||||
@@ -105,7 +105,7 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
privacyConsent: true,
|
||||
privacyConsent: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -189,6 +189,7 @@
|
||||
>
|
||||
</p>
|
||||
<div
|
||||
v-if="paymentMethodLogo.icon"
|
||||
class="svg svg-icon mb-4"
|
||||
:class="paymentMethodLogo.class"
|
||||
v-html="paymentMethodLogo.icon"
|
||||
@@ -205,6 +206,13 @@
|
||||
<div>{{ $t('subUpdateCard') }}</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
v-if="!hasGroupPlan"
|
||||
class="small text-center mb-4"
|
||||
>
|
||||
{{ $t('subscriptionBillingFYIShort') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="purchasedPlanExtraMonthsDetails.months > 0"
|
||||
class="extra-months green-10 py-2 px-3 mb-4"
|
||||
@@ -407,6 +415,13 @@
|
||||
<div class="purple-bar my-auto"></div>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-center mt-3">
|
||||
<div
|
||||
v-once
|
||||
v-if="!hasSubscription"
|
||||
class="small gray-100 w-50 text-center mb-5"
|
||||
>
|
||||
{{ $t('subscriptionBillingFYI') }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gift-box mb-2"
|
||||
@@ -631,7 +646,7 @@
|
||||
background-color: $purple-400;
|
||||
height: 1px;
|
||||
width: 50%;
|
||||
max-width: 432px;
|
||||
max-width: 417px;
|
||||
}
|
||||
|
||||
.purple-gradient {
|
||||
@@ -654,6 +669,12 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 12px;
|
||||
line-height: 1.67;
|
||||
max-width: 874px;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
border-radius: 8px;
|
||||
width: 192px;
|
||||
|
||||
@@ -12,14 +12,12 @@
|
||||
class="staff col-6 p-0"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<router-link
|
||||
<div
|
||||
class="title"
|
||||
:to="{'name': 'userProfile', 'params': {'userId': user.uuid}}"
|
||||
>
|
||||
{{ user.name }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div
|
||||
v-if="user.type === 'Staff'"
|
||||
class="svg-icon staff-icon ml-1"
|
||||
v-html="icons.tierStaff"
|
||||
></div>
|
||||
|
||||
@@ -269,7 +269,13 @@
|
||||
|
||||
.modal-dialog {
|
||||
width: 448px;
|
||||
max-width: calc(100vw - 20px);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
||||
@media (max-width: 468px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-dialog {
|
||||
@@ -346,7 +352,23 @@
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
width: 448px;
|
||||
width: 100%;
|
||||
max-width: 448px;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (max-width: 468px) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 300px) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
@@ -564,7 +586,7 @@
|
||||
|
||||
.limitedTime {
|
||||
height: 32px;
|
||||
width: 446px;
|
||||
width: 100%;
|
||||
font-size: 0.75rem;
|
||||
margin: 24px 0 0 0;
|
||||
background-color: $purple-300;
|
||||
@@ -829,10 +851,17 @@ export default {
|
||||
- ownedMounts
|
||||
- ownedItems;
|
||||
|
||||
if (
|
||||
petsRemaining < 0
|
||||
&& !window.confirm(this.$t('purchasePetItemConfirm', { itemText: this.item.text })) // eslint-disable-line no-alert
|
||||
) return;
|
||||
if (petsRemaining < 0) {
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:purchase-confirm', {
|
||||
message: this.$t('purchasePetItemConfirm', { itemText: this.item.text }),
|
||||
currency: this.item.currency,
|
||||
cost: this.item.value * this.selectedAmountToBuy,
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.item.purchaseType === 'customization') {
|
||||
@@ -844,15 +873,23 @@ export default {
|
||||
this.purchased(this.item.text);
|
||||
} else {
|
||||
const shouldConfirmPurchase = this.item.currency === 'gems' || this.item.currency === 'hourglasses';
|
||||
if (
|
||||
shouldConfirmPurchase
|
||||
&& !this.confirmPurchase(this.item.currency, this.item.value * this.selectedAmountToBuy)
|
||||
) {
|
||||
return;
|
||||
if (shouldConfirmPurchase) {
|
||||
const confirmed = await this.confirmPurchase(
|
||||
this.item.currency,
|
||||
this.item.value * this.selectedAmountToBuy,
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.genericPurchase) {
|
||||
this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
|
||||
await this.purchased(this.item.text);
|
||||
if (this.item.key === 'rebirth_orb') {
|
||||
localStorage.setItem('show-rebirth-confirmation', 'true');
|
||||
}
|
||||
await this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
|
||||
if (this.item.key !== 'rebirth_orb') {
|
||||
await this.purchased(this.item.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,22 @@
|
||||
|
||||
.modal-dialog {
|
||||
width: 448px;
|
||||
max-width: calc(100vw - 20px);
|
||||
display: flex;
|
||||
|
||||
@media (max-width: 468px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 300px) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="purchase-confirm-modal"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
modal-class="purchase-confirm-modal"
|
||||
centered
|
||||
>
|
||||
<div class="modal-content-wrapper">
|
||||
<div class="top-bar"></div>
|
||||
<div class="modal-body-content">
|
||||
<div
|
||||
class="currency-chip"
|
||||
:class="currency"
|
||||
>
|
||||
<span
|
||||
class="svg-icon icon-24"
|
||||
v-html="icons[currency]"
|
||||
></span>
|
||||
<span class="cost-value">{{ cost }}</span>
|
||||
</div>
|
||||
<h2 class="modal-title">
|
||||
{{ $t('confirmPurchase') }}
|
||||
</h2>
|
||||
<p class="modal-subtitle">
|
||||
{{ confirmationMessage }}
|
||||
</p>
|
||||
<div class="button-wrapper">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="confirm()"
|
||||
>
|
||||
{{ $t('confirm') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn-cancel"
|
||||
@click="cancel()"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import svgGem from '@/assets/svg/gem.svg?raw';
|
||||
import svgHourglass from '@/assets/svg/hourglass.svg?raw';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
confirmationMessage: '',
|
||||
currency: 'gems',
|
||||
cost: 0,
|
||||
resolveCallback: null,
|
||||
icons: Object.freeze({
|
||||
gems: svgGem,
|
||||
hourglasses: svgHourglass,
|
||||
}),
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:purchase-confirm', config => {
|
||||
this.confirmationMessage = config.message;
|
||||
this.currency = config.currency || 'gems';
|
||||
this.cost = config.cost || 0;
|
||||
this.resolveCallback = config.resolve;
|
||||
this.$root.$emit('bv::show::modal', 'purchase-confirm-modal');
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica:purchase-confirm');
|
||||
},
|
||||
methods: {
|
||||
confirm () {
|
||||
if (this.resolveCallback) {
|
||||
this.resolveCallback(true);
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
cancel () {
|
||||
if (this.resolveCallback) {
|
||||
this.resolveCallback(false);
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'purchase-confirm-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
::v-deep .purchase-confirm-modal {
|
||||
.modal-dialog {
|
||||
max-width: 330px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
height: 8px;
|
||||
background-color: $purple-300;
|
||||
}
|
||||
|
||||
.modal-body-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.currency-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 40px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
|
||||
&.gems {
|
||||
color: $gems-color;
|
||||
background-color: rgba($green-10, 0.15);
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
background-color: rgba($blue-10, 0.15);
|
||||
}
|
||||
|
||||
.icon-24 {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
color: $purple-300;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $purple-300;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -163,8 +163,33 @@
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
margin-top: 8%;
|
||||
width: 448px !important;
|
||||
max-width: calc(100vw - 20px);
|
||||
display: flex;
|
||||
|
||||
@media (max-width: 468px) {
|
||||
width: 100% !important;
|
||||
margin: 3rem auto 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 353px) {
|
||||
margin: 2.5rem auto 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-dialog {
|
||||
left: -8px;
|
||||
top: -8px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 300px) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -485,8 +510,12 @@ export default {
|
||||
this.selectedAmountToBuy = 1;
|
||||
this.$emit('change', $event);
|
||||
},
|
||||
buyItem () {
|
||||
if (!this.confirmPurchase(this.item.currency, this.item.value * this.selectedAmountToBuy)) {
|
||||
async buyItem () {
|
||||
const confirmed = await this.confirmPurchase(
|
||||
this.item.currency,
|
||||
this.item.value * this.selectedAmountToBuy,
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
this.makeGenericPurchase(this.item, 'buyQuestModal', this.selectedAmountToBuy);
|
||||
|
||||
@@ -120,9 +120,9 @@
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'heyeilatan', realName: 'Natalie'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'CuriousMagpie'}) }})
|
||||
- Web Developer
|
||||
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'phillipthelen'}) }})
|
||||
- Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'redphoenix', realName: 'Vicky'}) }}
|
||||
@@ -133,10 +133,6 @@
|
||||
{{ $t('commGuideAKA', {habitName: 'Beffymaroo', realName: 'Beth'}) }}
|
||||
- Art, Community Management, Many Hats
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Sabe'}) }}
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Apollo', realName: 'Tressley'}) }}
|
||||
- Designer
|
||||
@@ -146,8 +142,12 @@
|
||||
- Mobile Designer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
|
||||
- Mobile Developer
|
||||
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Kalista'}) }}
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'fizself', realName: 'Hafiz'}) }}
|
||||
- Developer
|
||||
</li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara013')"></p>
|
||||
@@ -156,7 +156,7 @@
|
||||
<em>
|
||||
Lemoness, lefnire, Slappybag, litenull, Shaner, Bobbyroberts99, wc8,
|
||||
Breadstrings, Megan, Blade, Daniel the Bard, deilann, shanaqui, Nakonana,
|
||||
Dewines, Alys, Fox_town, MaybeSteveRogers, and Cantras.
|
||||
Dewines, Alys, Fox_town, MaybeSteveRogers, Cantras, and heyeilatan.
|
||||
</em>
|
||||
</p>
|
||||
<h2 id="final">
|
||||
|
||||
@@ -45,6 +45,9 @@
|
||||
<p class="gray-200">
|
||||
{{ $t('billedMonthly') }}
|
||||
</p>
|
||||
<small class="gray-200">
|
||||
{{ $t('groupPlanBillingFYI') }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="top-right"></div>
|
||||
<div class="d-flex justify-content-between align-items-middle w-100 gap-72 mb-100">
|
||||
@@ -114,6 +117,9 @@
|
||||
<p class="gray-200">
|
||||
{{ $t('billedMonthly') }}
|
||||
</p>
|
||||
<small class="gray-200">
|
||||
{{ $t('groupPlanBillingFYI') }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="bot-right"></div>
|
||||
</div>
|
||||
@@ -174,6 +180,11 @@
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 12px;
|
||||
line-height: 1.67;
|
||||
}
|
||||
|
||||
// Major layout elements
|
||||
|
||||
.bottom-banner {
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
<privacy-banner
|
||||
class="privacy-banner"
|
||||
/>
|
||||
<div class="bg-purple-300 white">
|
||||
<div class="bg-purple-300 white pt-5">
|
||||
<div>
|
||||
<div
|
||||
id="intro-signup"
|
||||
>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="d-flex justify-content-center pb-5 mb-5">
|
||||
<div class="w-33 mr-5 mt-5">
|
||||
<img
|
||||
src="@/assets/images/home/home-main@3x.png"
|
||||
|
||||
@@ -64,9 +64,11 @@
|
||||
<li>sexual orientation; and</li>
|
||||
<li>information collected from a known child.</li>
|
||||
</ul>
|
||||
<p><strong>
|
||||
NOTE: Please do not provide us “sensitive personal information” or “sensitive personal data”, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary.
|
||||
</strong></p>
|
||||
<p>
|
||||
<strong>
|
||||
NOTE: Please do not provide us “sensitive personal information” or “sensitive personal data”, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary.
|
||||
</strong>
|
||||
</p>
|
||||
<h3 id="section_1_1">
|
||||
1.1 Information You Provide Directly
|
||||
</h3>
|
||||
@@ -617,7 +619,7 @@
|
||||
7. General Audience Services
|
||||
</h2>
|
||||
<p>
|
||||
The Service is intended for users 18 years or older; you are not permitted to access or use the Service if you are younger than 18. We do not knowingly collect personal information from children under the age of 18 through the Service. We encourage parents and legal guardians to monitor their children’s Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 18 has provided personal information to us, please contact us at <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>, and we will delete that information from our databases.
|
||||
The Service is intended for users 18 years or older; you are not permitted to access or use the Service if you are younger than 18. We do not knowingly collect personal information from children under the age of 18 through the Service. We encourage parents and legal guardians to monitor their children’s Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 18 has provided personal information to us, please contact us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>, and we will delete that information from our databases.
|
||||
</p>
|
||||
|
||||
<h2 id="section_8">
|
||||
@@ -708,7 +710,7 @@
|
||||
|
||||
<p><strong><u>Nevada Residents</u></strong></p>
|
||||
<p>
|
||||
Nevada residents may opt out of the sale of certain “covered information” collected by operators of websites or online services. We currently do not sell covered information, as “sale” is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>.
|
||||
Nevada residents may opt out of the sale of certain “covered information” collected by operators of websites or online services. We currently do not sell covered information, as “sale” is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>.
|
||||
</p>
|
||||
<p><strong><u>Notice to United Kingdom/European/Switzerland Residents.</u></strong></p>
|
||||
<p>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<router-view />
|
||||
</div>
|
||||
<div
|
||||
id="bottom-background"
|
||||
v-if="loginFlow"
|
||||
id="bottom-background"
|
||||
class="bg-purple-300"
|
||||
>
|
||||
<div class="seamless_mountains_demo_repeat"></div>
|
||||
@@ -31,7 +31,10 @@
|
||||
id="bottom-wrap"
|
||||
class="purple-4"
|
||||
>
|
||||
<div id="bottom-background" v-if="!loginFlow">
|
||||
<div
|
||||
v-if="!loginFlow"
|
||||
id="bottom-background"
|
||||
>
|
||||
<div class="seamless_mountains_demo_repeat"></div>
|
||||
<div class="midground_foreground_extended2"></div>
|
||||
</div>
|
||||
@@ -104,9 +107,10 @@
|
||||
footer, footer a {
|
||||
background: transparent;
|
||||
color: $purple-500;
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@@ -117,10 +121,6 @@
|
||||
border-top-color: $purple-100;
|
||||
}
|
||||
|
||||
.donate-text {
|
||||
color: $purple-500;
|
||||
}
|
||||
|
||||
.logo {
|
||||
color: $purple-300;
|
||||
}
|
||||
@@ -129,42 +129,27 @@
|
||||
color: $purple-500;
|
||||
}
|
||||
|
||||
.social .d-flex:hover {
|
||||
a {
|
||||
color: $white;
|
||||
}
|
||||
svg {
|
||||
fill: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.social-circle {
|
||||
background: $purple-50;
|
||||
color: $purple-500;
|
||||
|
||||
.instagram svg {
|
||||
svg {
|
||||
background-color: $purple-50;
|
||||
fill: $purple-500;
|
||||
&:hover {
|
||||
fill: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.bluesky svg {
|
||||
background-color: $purple-50;
|
||||
fill: $purple-500;
|
||||
&:hover {
|
||||
fill: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.facebook svg {
|
||||
background-color: $purple-50;
|
||||
fill: $purple-500;
|
||||
&:hover {
|
||||
fill: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.tumblr svg {
|
||||
background-color: $purple-50;
|
||||
fill: $purple-500;
|
||||
&:hover {
|
||||
fill: $white;
|
||||
}
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-contribute {
|
||||
background: $white;
|
||||
box-shadow: none;
|
||||
@@ -274,7 +259,8 @@ export default {
|
||||
return 'purple-footer';
|
||||
},
|
||||
loginFlow () {
|
||||
return ['login', 'register', 'username'].indexOf(this.$route.name) !== -1;
|
||||
const loginRoutes = ['forgotPassword', 'login', 'register', 'resetPassword', 'username'];
|
||||
return loginRoutes.indexOf(this.$route.name) !== -1;
|
||||
},
|
||||
showContentWrap () {
|
||||
return this.$route.name !== 'news';
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
BY PURCHASING PREMIUM YOU EXPRESSLY UNDERSTAND AND AGREE TO OUR REFUND POLICY:
|
||||
</p>
|
||||
<p>
|
||||
YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href='mailto:admin@habitica.com'>ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD).
|
||||
YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href="mailto:admin@habitica.com">ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD).
|
||||
</p>
|
||||
<p>
|
||||
FOR ANY CUSTOMER WHO PURCHASED PREMIUM IN APPLE INC.'s APP STORE ("APP STORE"), PLEASE CONTACT APPLE INC.'s SUPPORT TEAM: <a
|
||||
|
||||
@@ -1,65 +1,45 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="broken-task-modal"
|
||||
title="Broken Challenge"
|
||||
size="sm"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
modal-class="broken-task-confirm-modal"
|
||||
centered
|
||||
>
|
||||
<div
|
||||
v-if="brokenChallengeTask && brokenChallengeTask.challenge"
|
||||
class="modal-body"
|
||||
class="modal-content-wrapper"
|
||||
>
|
||||
<div
|
||||
v-if="brokenChallengeTask.challenge.broken === 'TASK_DELETED'
|
||||
|| brokenChallengeTask.challenge.broken === 'CHALLENGE_TASK_NOT_FOUND'"
|
||||
>
|
||||
<h2>{{ $t('brokenTask') }}</h2>
|
||||
<div>
|
||||
<div class="top-bar"></div>
|
||||
<div class="modal-body-content">
|
||||
<div
|
||||
class="icon-wrapper"
|
||||
v-html="icons.alertIcon"
|
||||
></div>
|
||||
<h2 class="modal-title">
|
||||
{{ modalTitle }}
|
||||
</h2>
|
||||
<p class="modal-subtitle">
|
||||
{{ modalSubtitle }}
|
||||
</p>
|
||||
<div class="button-wrapper">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="unlink('keep')"
|
||||
@click="keepAction()"
|
||||
>
|
||||
{{ $t('keepIt') }}
|
||||
{{ keepButtonText }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="removeTask(obj)"
|
||||
@click="removeAction()"
|
||||
>
|
||||
{{ $t('removeIt') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="brokenChallengeTask.challenge.broken === 'CHALLENGE_DELETED'">
|
||||
<h2>{{ $t('brokenChallenge') }}</h2>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="unlink('keep-all')"
|
||||
>
|
||||
{{ $t('keepTasks') }}
|
||||
{{ removeButtonText }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="unlink('remove-all')"
|
||||
class="btn-cancel"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('removeTasks') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="brokenChallengeTask.challenge.broken === 'CHALLENGE_CLOSED'">
|
||||
<h2 v-html="$t('challengeCompleted', {user: brokenChallengeTask.challenge.winner})"></h2>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="unlink('keep-all')"
|
||||
>
|
||||
{{ $t('keepTasks') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="unlink('remove-all')"
|
||||
>
|
||||
{{ $t('removeTasks') }}
|
||||
{{ $t('cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,23 +47,175 @@
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-body {
|
||||
padding-bottom: 2em;
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
::v-deep .broken-task-confirm-modal {
|
||||
.modal-dialog {
|
||||
max-width: 330px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
height: 8px;
|
||||
background-color: $maroon-100;
|
||||
}
|
||||
|
||||
.modal-body-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
margin-top: 40px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
::v-deep svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
path {
|
||||
fill: #DE3F3F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
color: $maroon-100;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $purple-300;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapActions } from '@/libs/store';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import alertIcon from '@/assets/svg/for-css/alert.svg?raw';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
data () {
|
||||
return {
|
||||
brokenChallengeTask: {},
|
||||
icons: Object.freeze({
|
||||
alertIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
brokenType () {
|
||||
return this.brokenChallengeTask.challenge?.broken;
|
||||
},
|
||||
isSingleTask () {
|
||||
return this.brokenType === 'TASK_DELETED'
|
||||
|| this.brokenType === 'CHALLENGE_TASK_NOT_FOUND';
|
||||
},
|
||||
brokenChallengeTaskCount () {
|
||||
if (!this.brokenChallengeTask.challenge?.id) return 0;
|
||||
const challengeId = this.brokenChallengeTask.challenge.id;
|
||||
const tasksData = this.$store.state.tasks.data;
|
||||
let count = 0;
|
||||
['habits', 'dailys', 'todos', 'rewards'].forEach(type => {
|
||||
if (tasksData[type]) {
|
||||
count += tasksData[type].filter(
|
||||
t => t.challenge && t.challenge.id === challengeId,
|
||||
).length;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
},
|
||||
modalTitle () {
|
||||
if (this.isSingleTask) {
|
||||
return this.$t('brokenTask');
|
||||
}
|
||||
if (this.brokenType === 'CHALLENGE_CLOSED') {
|
||||
return this.$t('challengeCompleted');
|
||||
}
|
||||
return this.$t('brokenChallenge');
|
||||
},
|
||||
modalSubtitle () {
|
||||
if (this.isSingleTask) {
|
||||
return this.$t('brokenTaskDescription');
|
||||
}
|
||||
if (this.brokenType === 'CHALLENGE_CLOSED') {
|
||||
return this.$t('challengeCompletedDescription', { user: this.brokenChallengeTask.challenge?.winner });
|
||||
}
|
||||
return this.$t('brokenChallengeDescription');
|
||||
},
|
||||
keepButtonText () {
|
||||
if (this.isSingleTask) {
|
||||
return this.$t('keepIt');
|
||||
}
|
||||
return this.$t('keepTasks');
|
||||
},
|
||||
removeButtonText () {
|
||||
if (this.isSingleTask) {
|
||||
return this.$t('removeIt');
|
||||
}
|
||||
return this.$t('removeTasks');
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('handle-broken-task', task => {
|
||||
this.brokenChallengeTask = { ...task };
|
||||
@@ -99,8 +231,36 @@ export default {
|
||||
unlinkOneTask: 'tasks:unlinkOneTask',
|
||||
unlinkAllTasks: 'tasks:unlinkAllTasks',
|
||||
}),
|
||||
keepAction () {
|
||||
if (this.isSingleTask) {
|
||||
this.unlink('keep');
|
||||
} else {
|
||||
this.unlink('keep-all');
|
||||
}
|
||||
},
|
||||
async removeAction () {
|
||||
if (this.isSingleTask) {
|
||||
await this.removeTask();
|
||||
} else {
|
||||
await this.unlink('remove-all');
|
||||
}
|
||||
},
|
||||
async unlink (keepOption) {
|
||||
if (keepOption.indexOf('-all') !== -1) {
|
||||
if (keepOption === 'remove-all') {
|
||||
const count = this.brokenChallengeTaskCount;
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:delete-task-confirm', {
|
||||
title: count === 1 ? this.$t('deleteTask') : this.$t('deleteXTasks', { count }),
|
||||
description: this.$t('brokenChallengeTaskCount', { count }),
|
||||
message: this.$t('confirmDeleteTasks'),
|
||||
buttonText: count === 1 ? this.$t('deleteTask') : this.$t('deleteXTasks', { count }),
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
await this.unlinkAllTasks({
|
||||
challengeId: this.brokenChallengeTask.challenge.id,
|
||||
keep: keepOption,
|
||||
@@ -122,8 +282,14 @@ export default {
|
||||
});
|
||||
this.close();
|
||||
},
|
||||
removeTask () {
|
||||
if (!window.confirm('Are you sure you want to delete this task?')) return; // eslint-disable-line no-alert
|
||||
async removeTask () {
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:delete-task-confirm', {
|
||||
message: this.$t('sureDelete'),
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
this.destroyTask(this.brokenChallengeTask);
|
||||
this.close();
|
||||
},
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
ref="tasksList"
|
||||
class="sortable-tasks"
|
||||
:disabled="activeFilter.label === 'scheduled' || !canBeDragged()"
|
||||
scrollSensitivity="64"
|
||||
scroll-sensitivity="64"
|
||||
:delay-on-touch-only="true"
|
||||
:delay="100"
|
||||
@update="taskSorted"
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="delete-task-confirm-modal"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
modal-class="delete-confirm-modal"
|
||||
centered
|
||||
>
|
||||
<div class="modal-content-wrapper">
|
||||
<div class="top-bar"></div>
|
||||
<div class="modal-body-content">
|
||||
<div
|
||||
class="icon-wrapper"
|
||||
v-html="icons.alertIcon"
|
||||
></div>
|
||||
<h2 class="modal-title">
|
||||
{{ displayTitle }}
|
||||
</h2>
|
||||
<p
|
||||
v-if="description"
|
||||
class="modal-description"
|
||||
>
|
||||
{{ description }}
|
||||
</p>
|
||||
<p class="modal-subtitle">
|
||||
{{ confirmationMessage }}
|
||||
</p>
|
||||
<div class="button-wrapper">
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="confirm()"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
<button
|
||||
class="btn-cancel"
|
||||
@click="cancel()"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import alertIcon from '@/assets/svg/for-css/alert.svg?raw';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
confirmationMessage: '',
|
||||
taskType: '',
|
||||
description: '',
|
||||
customTitle: '',
|
||||
customButtonText: '',
|
||||
resolveCallback: null,
|
||||
icons: Object.freeze({
|
||||
alertIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
displayTitle () {
|
||||
if (this.customTitle) return this.customTitle;
|
||||
return this.$t('deleteType', { type: this.taskType });
|
||||
},
|
||||
buttonText () {
|
||||
if (this.customButtonText) return this.customButtonText;
|
||||
return this.displayTitle;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:delete-task-confirm', config => {
|
||||
this.confirmationMessage = config.message;
|
||||
this.taskType = config.taskType || '';
|
||||
this.description = config.description || '';
|
||||
this.customTitle = config.title || '';
|
||||
this.customButtonText = config.buttonText || '';
|
||||
this.resolveCallback = config.resolve;
|
||||
this.$root.$emit('bv::show::modal', 'delete-task-confirm-modal');
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica:delete-task-confirm');
|
||||
},
|
||||
methods: {
|
||||
confirm () {
|
||||
if (this.resolveCallback) {
|
||||
this.resolveCallback(true);
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
cancel () {
|
||||
if (this.resolveCallback) {
|
||||
this.resolveCallback(false);
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'delete-task-confirm-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
::v-deep .delete-confirm-modal {
|
||||
.modal-dialog {
|
||||
max-width: 330px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
height: 8px;
|
||||
background-color: $maroon-100;
|
||||
}
|
||||
|
||||
.modal-body-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
margin-top: 40px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
::v-deep svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
path {
|
||||
fill: #DE3F3F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
color: $maroon-100;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-description {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.modal-description + .modal-subtitle {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $purple-300;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1177,9 +1177,16 @@ export default {
|
||||
moveToBottom () {
|
||||
this.$emit('moveTo', this.task, 'bottom');
|
||||
},
|
||||
destroy () {
|
||||
async destroy () {
|
||||
const type = this.$t(this.task.type);
|
||||
if (!window.confirm(this.$t('sureDeleteType', { type }))) return; // eslint-disable-line no-alert
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:delete-task-confirm', {
|
||||
message: this.$t('sureDeleteType', { type }),
|
||||
taskType: type,
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
this.destroyTask(this.task);
|
||||
this.$emit('taskDestroyed', this.task);
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary d-flex align-items-center justify-content-center"
|
||||
:class="{disabled: !canSave}"
|
||||
:class="{'btn-disabled': !canSave}"
|
||||
type="button"
|
||||
@click="submit()"
|
||||
>
|
||||
@@ -150,25 +150,25 @@
|
||||
<button
|
||||
type="button"
|
||||
class="habit-option-container no-transition
|
||||
d-flex flex-column justify-content-center align-items-center"
|
||||
d-flex flex-column justify-content-center align-items-center"
|
||||
:class="!task.up ? cssClass('habit-control-disabled') : ''"
|
||||
:disabled="challengeAccessRequired"
|
||||
@click="toggleUpDirection()"
|
||||
>
|
||||
<div
|
||||
class="habit-option-button no-transition
|
||||
d-flex justify-content-center align-items-center mb-2"
|
||||
d-flex justify-content-center align-items-center mb-2"
|
||||
:class="task.up ? cssClass('bg') : ''"
|
||||
>
|
||||
<div
|
||||
class="habit-option-icon svg-icon no-transition"
|
||||
:class="task.up ? '' : 'disabled'"
|
||||
:class="task.up ? '' : 'icon-disabled'"
|
||||
v-html="icons.positive"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="habit-option-label no-transition"
|
||||
:class="task.up ? cssClass('icon') : 'disabled'"
|
||||
:class="task.up ? cssClass('icon') : 'label-disabled'"
|
||||
>
|
||||
{{ $t('positive') }}
|
||||
</div>
|
||||
@@ -176,25 +176,25 @@
|
||||
<button
|
||||
type="button"
|
||||
class="habit-option-container no-transition
|
||||
d-flex flex-column justify-content-center align-items-center"
|
||||
d-flex flex-column justify-content-center align-items-center"
|
||||
:class="!task.down ? cssClass('habit-control-disabled') : ''"
|
||||
:disabled="challengeAccessRequired"
|
||||
@click="toggleDownDirection()"
|
||||
>
|
||||
<div
|
||||
class="habit-option-button no-transition
|
||||
d-flex justify-content-center align-items-center mb-2"
|
||||
d-flex justify-content-center align-items-center mb-2"
|
||||
:class="task.down ? cssClass('bg') : ''"
|
||||
>
|
||||
<div
|
||||
class="habit-option-icon no-transition svg-icon negative mx-auto"
|
||||
:class="task.down ? '' : 'disabled'"
|
||||
:class="task.down ? '' : 'icon-disabled'"
|
||||
v-html="icons.negative"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="habit-option-label no-transition"
|
||||
:class="task.down ? cssClass('icon') : 'disabled'"
|
||||
:class="task.down ? cssClass('icon') : 'label-disabled'"
|
||||
>
|
||||
{{ $t('negative') }}
|
||||
</div>
|
||||
@@ -382,6 +382,45 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showStatAssignment"
|
||||
class="stat-assignment option mt-3"
|
||||
>
|
||||
<div class="form-group row">
|
||||
<label
|
||||
v-once
|
||||
class="col-12 mb-1"
|
||||
>{{ $t('assignedStat') }}</label>
|
||||
<div class="col-12">
|
||||
<div class="stat-dropdown-container">
|
||||
<select-list
|
||||
:items="statOptions"
|
||||
:value="task.attribute"
|
||||
key-prop="key"
|
||||
active-key-prop="key"
|
||||
@select="task.attribute = $event.key"
|
||||
>
|
||||
<template #item="{ item, button }">
|
||||
<div class="stat-option-content">
|
||||
<span
|
||||
class="stat-option-title"
|
||||
:class="item.key"
|
||||
>
|
||||
{{ $t(item.label) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!button"
|
||||
class="stat-option-description"
|
||||
>
|
||||
{{ $t(item.description) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</select-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="task.type === 'habit' && !groupId"
|
||||
class="option mt-3"
|
||||
@@ -591,8 +630,8 @@
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary btn-footer
|
||||
d-flex align-items-center justify-content-center"
|
||||
:class="{disabled: !canSave}"
|
||||
d-flex align-items-center justify-content-center"
|
||||
:class="{'btn-disabled': !canSave}"
|
||||
type="button"
|
||||
@click="submit()"
|
||||
>
|
||||
@@ -881,12 +920,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
.btn-disabled {
|
||||
background-color: $white;
|
||||
border: 2px solid transparent;
|
||||
color: $gray-200;
|
||||
line-height: 1.714;
|
||||
box-shadow: 0px 1px 3px 0px rgba(26, 24, 29, 0.12), 0px 1px 2px 0px rgba(26, 24, 29, 0.24);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
|
||||
&:focus {
|
||||
background-color: $white;
|
||||
@@ -909,6 +950,87 @@
|
||||
.streak-addon path {
|
||||
fill: $gray-200;
|
||||
}
|
||||
|
||||
.stat-dropdown-container {
|
||||
.select-list {
|
||||
.selectListItem {
|
||||
margin-bottom: 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.selectListItem .dropdown-item {
|
||||
padding: 8px 16px !important;
|
||||
height: auto !important;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba($purple-600, 0.25) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.stat-option-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.stat-option-title {
|
||||
font-weight: normal;
|
||||
color: $gray-50;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-option-content {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
.stat-option-title {
|
||||
display: block;
|
||||
font-family: Roboto;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
text-transform: capitalize;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.str {
|
||||
color: $maroon-100;
|
||||
}
|
||||
|
||||
&.int {
|
||||
color: $blue-50;
|
||||
}
|
||||
|
||||
&.con {
|
||||
color: $yellow-5;
|
||||
}
|
||||
|
||||
&.per {
|
||||
color: $purple-300;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-option-description {
|
||||
display: block;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: $gray-100;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -948,7 +1070,7 @@
|
||||
height: 10px;
|
||||
color: $white;
|
||||
|
||||
&.disabled {
|
||||
&.icon-disabled {
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
@@ -962,7 +1084,7 @@
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
|
||||
&.disabled {
|
||||
&.label-disabled {
|
||||
color: $gray-100;
|
||||
font-weight: normal;
|
||||
}
|
||||
@@ -1018,10 +1140,9 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.disabled .input-group-text {
|
||||
.input-group-outer.disabled .input-group-text {
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -1036,6 +1157,7 @@ import SelectMulti from './modal-controls/selectMulti';
|
||||
import selectDifficulty from '@/components/tasks/modal-controls/selectDifficulty';
|
||||
import selectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray';
|
||||
import lockableLabel from '@/components/tasks/modal-controls/lockableLabel';
|
||||
import selectList from '@/components/ui/selectList';
|
||||
|
||||
import syncTask from '../../mixins/syncTask';
|
||||
|
||||
@@ -1059,6 +1181,7 @@ export default {
|
||||
selectTranslatedArray,
|
||||
toggleCheckbox,
|
||||
lockableLabel,
|
||||
selectList,
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
@@ -1092,6 +1215,12 @@ export default {
|
||||
con: 'constitution',
|
||||
per: 'perception',
|
||||
},
|
||||
statOptions: [
|
||||
{ key: 'str', label: 'strength', description: 'strTaskText' },
|
||||
{ key: 'int', label: 'intelligence', description: 'intTaskText' },
|
||||
{ key: 'con', label: 'constitution', description: 'conTaskText' },
|
||||
{ key: 'per', label: 'perception', description: 'perTaskText' },
|
||||
],
|
||||
calendarHighlights: { dates: [new Date()] },
|
||||
};
|
||||
},
|
||||
@@ -1185,6 +1314,12 @@ export default {
|
||||
selectedTags () {
|
||||
return this.getTagsFor(this.task);
|
||||
},
|
||||
showStatAssignment () {
|
||||
return this.task.type !== 'reward'
|
||||
&& !this.groupId
|
||||
&& this.user.preferences.automaticAllocation === true
|
||||
&& this.user.preferences.allocationMode === 'taskbased';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
task () {
|
||||
@@ -1303,9 +1438,16 @@ export default {
|
||||
}
|
||||
this.$root.$emit('bv::hide::modal', 'task-modal');
|
||||
},
|
||||
destroy () {
|
||||
async destroy () {
|
||||
const type = this.$t(this.task.type);
|
||||
if (!window.confirm(this.$t('sureDeleteType', { type }))) return; // eslint-disable-line no-alert
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:delete-task-confirm', {
|
||||
message: this.$t('sureDeleteType', { type }),
|
||||
taskType: type,
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
this.destroyTask(this.task);
|
||||
this.$emit('taskDestroyed', this.task);
|
||||
this.$root.$emit('bv::hide::modal', 'task-modal');
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<template #button-content>
|
||||
<slot
|
||||
name="item"
|
||||
:item="selected || placeholder"
|
||||
:item="selectedItem || placeholder"
|
||||
:button="true"
|
||||
>
|
||||
<!-- Fallback content -->
|
||||
@@ -134,6 +134,14 @@ export default {
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedItem () {
|
||||
if (this.activeKeyProp) {
|
||||
return this.items.find(item => item[this.activeKeyProp] === this.selected);
|
||||
}
|
||||
return this.items.find(item => item === this.selected);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getKeyProp (item) {
|
||||
return this.keyProp ? item[this.keyProp] : item.key || item.identifier;
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
type="checkbox"
|
||||
:checked="isChecked"
|
||||
:value="value"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
@change="handleChange"
|
||||
>
|
||||
<label
|
||||
class="toggle-switch-label"
|
||||
@@ -116,7 +116,7 @@
|
||||
.toggle-switch-inner:before {
|
||||
content: "";
|
||||
padding-left: 10px;
|
||||
background-color: $green-10;
|
||||
background-color: $green-50;
|
||||
}
|
||||
|
||||
.toggle-switch-inner:after {
|
||||
|
||||
@@ -1126,7 +1126,12 @@ export default {
|
||||
this.loadUser();
|
||||
this.oldTitle = this.$store.state.title;
|
||||
this.handleExternalLinks();
|
||||
this.selectPage(this.startingPage);
|
||||
// Check if there's a hash in the URL to determine the starting page
|
||||
let pageToSelect = this.startingPage;
|
||||
if (window.location.hash && (window.location.hash === '#stats' || window.location.hash === '#achievements')) {
|
||||
pageToSelect = window.location.hash.substring(1);
|
||||
}
|
||||
this.selectPage(pageToSelect);
|
||||
this.$root.$on('habitica:report-profile-result', () => {
|
||||
this.loadUser();
|
||||
});
|
||||
@@ -1211,10 +1216,15 @@ export default {
|
||||
},
|
||||
selectPage (page) {
|
||||
this.selectedPage = page || 'profile';
|
||||
window.history.replaceState(null, null, '');
|
||||
const profileUserId = this.userId || this.userLoggedIn._id;
|
||||
let newPath = `/profile/${profileUserId}`;
|
||||
if (page !== 'profile') {
|
||||
newPath += `#${page}`;
|
||||
}
|
||||
window.history.replaceState(null, null, newPath);
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('user'),
|
||||
subSection: this.$t(this.startingPage),
|
||||
subSection: this.$t(page),
|
||||
});
|
||||
},
|
||||
getNextIncentive () {
|
||||
|
||||
@@ -3,14 +3,10 @@ import isEqual from 'lodash/isEqual';
|
||||
import keys from 'lodash/keys';
|
||||
import pick from 'lodash/pick';
|
||||
import amplitude from 'amplitude-js';
|
||||
import { gtag, install } from 'ga-gtag';
|
||||
import Vue from 'vue';
|
||||
import getStore from '@/store';
|
||||
|
||||
const AMPLITUDE_KEY = import.meta.env.AMPLITUDE_KEY;
|
||||
const DEBUG_ENABLED = import.meta.env.DEBUG_ENABLED === 'true';
|
||||
const GA_ID = import.meta.env.GA_ID;
|
||||
const IS_PRODUCTION = import.meta.env.NODE_ENV === 'production';
|
||||
const REQUIRED_FIELDS = ['eventCategory', 'eventAction'];
|
||||
|
||||
let analyticsLoading = false;
|
||||
@@ -19,7 +15,7 @@ let analyticsReady = false;
|
||||
function _getConsentedUser () {
|
||||
const store = getStore();
|
||||
const user = store.state.user.data;
|
||||
if (!user?.preferences?.analyticsConsent || navigator.globalPrivacyControl) {
|
||||
if (!user?.preferences?.analyticsConsent) {
|
||||
return false;
|
||||
}
|
||||
return user;
|
||||
@@ -69,10 +65,6 @@ function _gatherUserStats (properties) {
|
||||
export function safeSetup (userId) {
|
||||
if (analyticsLoading || analyticsReady) return;
|
||||
analyticsLoading = true;
|
||||
install(GA_ID, {
|
||||
debug_mode: DEBUG_ENABLED || !IS_PRODUCTION,
|
||||
user_id: userId,
|
||||
});
|
||||
amplitude.getInstance().init(AMPLITUDE_KEY, userId);
|
||||
analyticsReady = true;
|
||||
analyticsLoading = false;
|
||||
@@ -90,7 +82,6 @@ export function track (properties, options = {}) {
|
||||
// Track events on the server by default
|
||||
if (trackOnClient === true) {
|
||||
amplitude.getInstance().logEvent(properties.eventAction, properties);
|
||||
gtag('event', properties.eventAction, properties);
|
||||
} else {
|
||||
const store = getStore();
|
||||
store.dispatch('analytics:trackEvent', properties);
|
||||
@@ -105,7 +96,6 @@ export function updateUser (properties = {}) {
|
||||
// Use nextTick to avoid blocking the UI
|
||||
Vue.nextTick(() => {
|
||||
_gatherUserStats(properties);
|
||||
gtag('set', 'user_properties', properties);
|
||||
forEach(properties, (value, key) => {
|
||||
const identify = new amplitude.Identify().set(key, value);
|
||||
amplitude.getInstance().identify(identify);
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import habiticaMarkdown from 'habitica-markdown/withMentions';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
|
||||
export default function renderWithMentions (text, user) {
|
||||
if (!text) return null;
|
||||
const env = { userName: user.auth.local.username, displayName: user.profile.name };
|
||||
return habiticaMarkdown.render(String(text), env);
|
||||
const env = { userName: user.auth.local.username };
|
||||
let html = habiticaMarkdown.render(String(text), env);
|
||||
|
||||
if (user.auth.local.username) {
|
||||
const username = escapeRegExp(user.auth.local.username);
|
||||
const regex = new RegExp(`(<span class="at-text">@)(${username})(</span>)`, 'gi');
|
||||
html = html.replace(regex, (match, p1, p2, p3) => `${p1.replace('at-text', 'at-text at-highlight')}${p2}${p3}`);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ export default [
|
||||
uuid: '61b2c855-0a30-444c-bcc6-1cac876460b0',
|
||||
},
|
||||
{
|
||||
name: 'heyeilatan',
|
||||
name: 'fizself',
|
||||
type: 'Staff',
|
||||
uuid: 'f4e5c6da-0617-48bf-b3bd-9f97636774a8',
|
||||
uuid: 'e39ea3eb-28d2-48da-8568-7a5b0e64498e',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -39,7 +39,15 @@ export default {
|
||||
};
|
||||
|
||||
const purchaseForKey = currencyToPurchaseForKey[currency];
|
||||
return window.confirm(this.$t(purchaseForKey, { cost })); // eslint-disable-line no-alert
|
||||
|
||||
return new Promise(resolve => {
|
||||
this.$root.$emit('habitica:purchase-confirm', {
|
||||
message: this.$t(purchaseForKey, { cost }),
|
||||
currency,
|
||||
cost,
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -679,7 +679,7 @@ import NotificationMixins from '@/mixins/notifications';
|
||||
|
||||
// extract to a shared path
|
||||
const CONVERSATIONS_PER_PAGE = 10;
|
||||
const PM_PER_PAGE = 10;
|
||||
const PM_PER_PAGE = 50;
|
||||
|
||||
const UI_STATES = Object.freeze({
|
||||
LOADING: 'LOADING',
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
flex: 0 0 732px;
|
||||
flex: 0 0 751px;
|
||||
max-width: unset;
|
||||
|
||||
::v-deep {
|
||||
|
||||
@@ -217,8 +217,18 @@ export default {
|
||||
}
|
||||
},
|
||||
async changeClassAndClose () {
|
||||
if (!this.classDisabled && !window.confirm(this.$t('changeClassConfirmCost'))) {
|
||||
return;
|
||||
if (!this.classDisabled) {
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:purchase-confirm', {
|
||||
message: this.$t('changeClassConfirmCost'),
|
||||
currency: 'gems',
|
||||
cost: 3,
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.$root.$once('bv::hide::modal', () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<tr>
|
||||
<td colspan="3"
|
||||
<td
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
colspan="3"
|
||||
>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3
|
||||
@@ -18,8 +19,9 @@
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3"
|
||||
<td
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
colspan="3"
|
||||
>
|
||||
<h3
|
||||
v-once
|
||||
@@ -33,6 +35,21 @@
|
||||
v-html="$t('privacySettingsOverview') + ' ' + $t('learnMorePrivacy')"
|
||||
>
|
||||
</p>
|
||||
<div
|
||||
v-if="gpcEnabled"
|
||||
class="mx-4 px-3 py-2 mb-4 gpc-alert d-flex align-items-center black bg-yellow-50"
|
||||
>
|
||||
<div
|
||||
class="svg svg-icon mr-2"
|
||||
v-html="icons.alert"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="gpc-message"
|
||||
v-html="gpcInfo"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-center"
|
||||
>
|
||||
@@ -44,8 +61,8 @@
|
||||
{{ $t('performanceAnalytics') }}
|
||||
</label>
|
||||
<toggle-switch
|
||||
class="mb-auto"
|
||||
v-model="user.preferences.analyticsConsent"
|
||||
class="mb-auto"
|
||||
@change="prefToggled()"
|
||||
/>
|
||||
</div>
|
||||
@@ -91,6 +108,29 @@
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.gpc-alert {
|
||||
border-radius: 4px;
|
||||
line-height: 1.714;
|
||||
|
||||
.gpc-message {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
::v-deep a {
|
||||
color: $black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 16px;
|
||||
opacity: 0.75;
|
||||
|
||||
::v-deep svg path {
|
||||
fill: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mb-28p {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
@@ -110,24 +150,43 @@ import ToggleSwitch from '@/components/ui/toggleSwitch.vue';
|
||||
import { GenericUserPreferencesMixin } from '@/pages/settings/components/genericUserPreferencesMixin';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import { mapState } from '@/libs/store';
|
||||
import alert from '@/assets/svg/for-css/alert.svg?raw';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SaveCancelButtons,
|
||||
ToggleSwitch,
|
||||
},
|
||||
mixins: [
|
||||
GenericUserPreferencesMixin,
|
||||
InlineSettingMixin,
|
||||
],
|
||||
components: {
|
||||
SaveCancelButtons,
|
||||
ToggleSwitch,
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
alert,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
gpcEnabled () {
|
||||
return navigator.globalPrivacyControl;
|
||||
},
|
||||
gpcInfo () {
|
||||
const gpcUrl = 'https://globalprivacycontrol.org/';
|
||||
if (this.user.preferences.analyticsConsent) {
|
||||
return this.$t('gpcPlusAnalytics', { url: gpcUrl });
|
||||
}
|
||||
return this.$t('gpcWarning', { url: gpcUrl });
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
finalize () {
|
||||
this.setUserPreference('analyticsConsent');
|
||||
localStorage.setItem('analyticsConsent', this.user.preferences.analyticsConsent);
|
||||
this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues = false;
|
||||
},
|
||||
prefToggled () {
|
||||
@@ -135,7 +194,10 @@ export default {
|
||||
this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues = newVal;
|
||||
},
|
||||
resetControls () {
|
||||
this.user.preferences.analyticsConsent = !this.user.preferences.analyticsConsent;
|
||||
if (this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues) {
|
||||
this.user.preferences.analyticsConsent = !this.user.preferences.analyticsConsent;
|
||||
this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<bug-report-success-modal v-if="isUserLoaded" />
|
||||
<external-link-modal />
|
||||
<birthday-modal />
|
||||
<purchase-confirm-modal v-if="isUserLoaded" />
|
||||
<delete-task-confirm-modal v-if="isUserLoaded" />
|
||||
<template v-if="isUserLoaded">
|
||||
<privacy-banner />
|
||||
<chat-banner />
|
||||
@@ -138,6 +140,8 @@ import paymentsSuccessModal from '@/components/payments/successModal';
|
||||
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
|
||||
import subCanceledModal from '@/components/payments/canceledModal';
|
||||
import externalLinkModal from '@/components/externalLinkModal.vue';
|
||||
import purchaseConfirmModal from '@/components/shops/purchaseConfirmModal.vue';
|
||||
import deleteTaskConfirmModal from '@/components/tasks/deleteTaskConfirmModal.vue';
|
||||
|
||||
import spellsMixin from '@/mixins/spells';
|
||||
import {
|
||||
@@ -172,6 +176,8 @@ export default {
|
||||
bugReportModal,
|
||||
bugReportSuccessModal,
|
||||
externalLinkModal,
|
||||
purchaseConfirmModal,
|
||||
deleteTaskConfirmModal,
|
||||
},
|
||||
mixins: [notifications, spellsMixin],
|
||||
data () {
|
||||
@@ -263,11 +269,12 @@ export default {
|
||||
this.$store.dispatch('tasks:fetchUserTasks'),
|
||||
]).then(() => {
|
||||
this.$store.state.isUserLoaded = true;
|
||||
const analyticsConsent = localStorage.getItem('analyticsConsent');
|
||||
if (analyticsConsent !== null
|
||||
&& analyticsConsent !== this.user.preferences.analyticsConsent
|
||||
) {
|
||||
this.$store.dispatch('user:set', { 'preferences.analyticsConsent': analyticsConsent });
|
||||
let analyticsConsent = localStorage.getItem('analyticsConsent');
|
||||
if (analyticsConsent !== null) {
|
||||
analyticsConsent = analyticsConsent === 'true';
|
||||
if (analyticsConsent !== this.user.preferences.analyticsConsent) {
|
||||
this.$store.dispatch('user:set', { 'preferences.analyticsConsent': analyticsConsent });
|
||||
}
|
||||
}
|
||||
if (window && window['habitica-i18n']) {
|
||||
if (this.user.preferences.language === window['habitica-i18n'].language.code) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import { DEPRECATED_ROUTES } from '@/router/deprecated-routes';
|
||||
|
||||
// NOTE: when adding a page make sure to implement the `common:setTitle` action
|
||||
|
||||
const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'@/components/auth/registerLoginReset');
|
||||
const Logout = () => import(/* webpackChunkName: "auth" */'@/components/auth/logout');
|
||||
|
||||
// Hall
|
||||
@@ -79,17 +78,15 @@ const router = new VueRouter({
|
||||
// in the route component to set a specific subtitle for the page.
|
||||
routes: [
|
||||
{ name: 'logout', path: '/logout', component: Logout },
|
||||
{
|
||||
name: 'resetPassword', path: '/reset-password', component: RegisterLoginReset, meta: { requiresLogin: false },
|
||||
}, {
|
||||
name: 'forgotPassword', path: '/forgot-password', component: RegisterLoginReset, meta: { requiresLogin: false },
|
||||
},
|
||||
{ name: 'tasks', path: '/', component: UserTasks },
|
||||
{
|
||||
name: 'userProfile',
|
||||
path: '/profile/:userId',
|
||||
props: true,
|
||||
},
|
||||
{ name: 'profile', path: '/user/profile' },
|
||||
{ name: 'stats', path: '/user/stats' },
|
||||
{ name: 'achievements', path: '/user/achievements' },
|
||||
{
|
||||
path: '/inventory',
|
||||
component: InventoryContainer,
|
||||
@@ -369,6 +366,10 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (to.params.startingPage !== undefined) {
|
||||
startingPage = to.params.startingPage;
|
||||
}
|
||||
// Check if there's a hash in the URL for stats or achievements
|
||||
if (to.hash === '#stats' || to.hash === '#achievements') {
|
||||
startingPage = to.hash.substring(1);
|
||||
}
|
||||
if (from.name === null) {
|
||||
store.state.postLoadModal = `profile/${to.params.userId}`;
|
||||
return next({ name: 'tasks' });
|
||||
@@ -389,10 +390,18 @@ router.beforeEach(async (to, from, next) => {
|
||||
}
|
||||
|
||||
if ((to.name === 'stats' || to.name === 'achievements' || to.name === 'profile') && from.name !== null) {
|
||||
const userId = store.state.user.data._id;
|
||||
let redirectPath = `/profile/${userId}`;
|
||||
if (to.name === 'stats') {
|
||||
redirectPath += '#stats';
|
||||
} else if (to.name === 'achievements') {
|
||||
redirectPath += '#achievements';
|
||||
}
|
||||
router.app.$emit('habitica:show-profile', {
|
||||
userId,
|
||||
startingPage: to.name,
|
||||
fromPath: from.path,
|
||||
toPath: to.path,
|
||||
toPath: redirectPath,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,9 @@ export const STATIC_ROUTES = {
|
||||
{
|
||||
name: 'features', path: 'features', component: FeaturesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'forgotPassword', path: '/forgot-password', component: RegisterLoginReset, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
@@ -90,6 +93,9 @@ export const STATIC_ROUTES = {
|
||||
{
|
||||
name: 'register', path: '/register', component: RegisterLoginReset, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'resetPassword', path: '/reset-password', component: RegisterLoginReset, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'terms', path: 'terms', component: TermsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { authAsCredentialsState, LOCALSTORAGE_AUTH_KEY } from '@/libs/auth';
|
||||
|
||||
const GA_ID = import.meta.env.GA_ID;
|
||||
|
||||
function saveLocalDataAuth (store, apiId, apiToken) {
|
||||
const credentialsObj = {
|
||||
auth: {
|
||||
@@ -123,9 +121,6 @@ export async function appleAuth (store, params) {
|
||||
export function logout (store, options = {}) {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
if (window.gtag) {
|
||||
window.gtag('config', GA_ID, { user_id: null });
|
||||
}
|
||||
const query = options.redirectToLogin === true ? '?redirectToLogin=true' : '';
|
||||
window.location.href = `/logout-server${query}`;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import Vue from 'vue';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
|
||||
export async function getChat (store, payload) {
|
||||
const response = await axios.get(`/api/v4/groups/${payload.groupId}/chat`);
|
||||
const response = await axios.get(`/api/v4/groups/${payload.groupId}/chat?limit=400`);
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ describe('renderWithMentions', () => {
|
||||
expect(result).to.be.null;
|
||||
});
|
||||
|
||||
test('highlights displayname', () => {
|
||||
test('does not highlight displayname to prevent impersonation', () => {
|
||||
const text = 'hello @displayedUser with text after';
|
||||
|
||||
const result = renderMarkdown(text, user('user', 'displayedUser'));
|
||||
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
||||
expect(result).to.contain('<span class="at-text">@displayedUser</span>');
|
||||
expect(result).to.not.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
||||
});
|
||||
|
||||
test('highlights username', () => {
|
||||
@@ -56,7 +56,8 @@ describe('renderWithMentions', () => {
|
||||
|
||||
const result = renderMarkdown(plainText, user('use', 'mentions'));
|
||||
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@mentions</span>');
|
||||
expect(result).to.contain('<span class="at-text">@mentions</span>');
|
||||
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>');
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@use</span>');
|
||||
expect(result).to.contain('<span class="at-text">@mail</span>');
|
||||
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
|
||||
|
||||
@@ -26,7 +26,6 @@ const envVars = [
|
||||
'EMAILS_COMMUNITY_MANAGER_EMAIL',
|
||||
'EMAILS_TECH_ASSISTANCE_EMAIL',
|
||||
'EMAILS_PRESS_ENQUIRY_EMAIL',
|
||||
'GA_ID',
|
||||
'STRIPE_PUB_KEY',
|
||||
'GOOGLE_CLIENT_ID',
|
||||
'APPLE_AUTH_CLIENT_ID',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"achievement": "Достижения",
|
||||
"achievement": "Постижение",
|
||||
"onwards": "Напред!",
|
||||
"levelup": "Изпълнявайки целите си в истинския живот, Вие се качихте ниво и здравето Ви беше запълнено!",
|
||||
"reachedLevel": "Достигнахте Ниво <%= level %>",
|
||||
@@ -106,5 +106,25 @@
|
||||
"achievementSeasonalSpecialist": "Сезонен експерт",
|
||||
"achievementRedLetterDayText": "Са събрали всички Червени животни.",
|
||||
"achievementSeeingRedModalText": "Събрали сте всички Червени любимци!",
|
||||
"achievementSkeletonCrewText": "Събрали са всички Скелетни животни."
|
||||
"achievementSkeletonCrewText": "Събрали са всички Скелетни животни.",
|
||||
"achievementVioletsAreBlueText": "Събра всички сини пет-ове Памучен бонбон.",
|
||||
"achievementWildBlueYonderModalText": "Ти укроти всички маунт-ове Памучен бонбон синьо!",
|
||||
"achievementWildBlueYonderText": "Укроти всички маунт-ове Памучен бонбон синьо.",
|
||||
"achievementVioletsAreBlue": "Розите са червени, Теменужките са сини",
|
||||
"achievementVioletsAreBlueModalText": "Ти събра (или колекционира) всички домашни любимци (или пет-ове) от серията Памучен бонбон синьо!",
|
||||
"achievementWildBlueYonder": "Дивото синьо небе",
|
||||
"achievementSeasonalSpecialistText": "Завърши всички сезонни куестове от пролетта и зимата: Лов на яйца (Egg Hunt), Дядо Коледа-ловец (Trapper Santa) и Намери мечето (Find the Cub)!",
|
||||
"achievementSeasonalSpecialistModalText": "Вие завършихте всичките сезонни куестове!",
|
||||
"achievementDomesticatedModalText": "Ти събра (или колекционира) всички опитомени домашни любимци (пет-ове)!",
|
||||
"achievementDomesticatedText": "Излюпи (или Отгледа) всички стандартни цветове на опитомени домашни любимци (пет-ове): пор, морско свинче, петел, летящо прасе), плъх, заек, кон и крава!",
|
||||
"achievementShadyCustomerText": "Събра всички пет-ове Сянка.",
|
||||
"achievementShadyCustomerModalText": "Събра всички пет-ове Сянка.",
|
||||
"achievementShadyCustomer": "Сенчест тип",
|
||||
"achievementDomesticated": "И-Я–И–Я–ЙО",
|
||||
"achievementZodiacZookeeper": "Пазител на Зодиака",
|
||||
"achievementShadeOfItAll": "В сянката на света",
|
||||
"achievementShadeOfItAllText": "Опитоми всички сенчести коне.",
|
||||
"achievementZodiacZookeeperText": "Излюпи всички стандартни животни(базов цвят) от зодиака: Плъх, Крава, Заек, Змия, Овца, Маймуна, Кокошка, Вълк, Тигър, Летящо прасе, и Дракон!",
|
||||
"achievementZodiacZookeeperModalText": "Събрахте всички животни от зодиака!",
|
||||
"achievementBirdsOfAFeather": "От една порода"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"challenge": "Предизвикателство",
|
||||
"challengeDetails": "Предизвикателствата са обществени събития, в които играчите се състезават и печелят награди като изпълняват няколко свързани по някакъв начин задачи.",
|
||||
"brokenChaLink": "Повредена връзка на предизвикателство",
|
||||
"brokenTask": "Повредена връзка на предизвикателство: тази задача е била част от предизвикателство, но е била премахната от него. Какво бихте искали да направите?",
|
||||
"keepIt": "Запазване",
|
||||
"removeIt": "Премахване",
|
||||
"brokenChallenge": "Повредена връзка на предизвикателство: тази задача е била част от предизвикателство, но то (или групата) е било изтрито. Какво бихте искали да направите с останалите задачи?",
|
||||
|
||||
@@ -88,7 +88,6 @@
|
||||
"success": "Готово!",
|
||||
"classGear": "Снаряжение за класа",
|
||||
"classGearText": "Поздравления за избора на клас! Добавих новата Ви основна екипировка в инвентара Ви. Погледнете по-долу, за да я екипирате!",
|
||||
"autoAllocate": "Автоматично разпределяне",
|
||||
"spells": "Умения",
|
||||
"skillsTitle": "Умения",
|
||||
"toDo": "Задача",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"challenge": "Výzva",
|
||||
"challengeDetails": "Výzvy jsou komunitní události, ve kterých hráči soutěží a získávají odměny za plnění úkolů.",
|
||||
"brokenChaLink": "Nefunkční odkaz na výzvu",
|
||||
"brokenTask": "Nefunkční odkaz na výzvu: tento úkol byl součástí výzvy, ale byl z ní odstraněn. Co chceš dělat?",
|
||||
"keepIt": "Ponechat",
|
||||
"removeIt": "Odstranit",
|
||||
"brokenChallenge": "Nefunkční odkaz na výzvu: tento úkol byl součástí výzvy, ale ta (nebo skupina, která ji vytvořila) byla odstraněna. Co chceš dělat s osiřelými úkoly?",
|
||||
|
||||
@@ -88,7 +88,6 @@
|
||||
"success": "Úspěch!",
|
||||
"classGear": "Vybavení pro tvé povolání",
|
||||
"classGearText": "Gratuluji k vybrání povolání! Přidal jsem ti základní zbraň do tvého inventáře. Podívej se dolů a vybav se!",
|
||||
"autoAllocate": "Připisovat automaticky",
|
||||
"spells": "Dovednosti",
|
||||
"skillsTitle": "<%= classStr %> Dovednosti",
|
||||
"toDo": "úkol",
|
||||
|
||||
@@ -121,7 +121,6 @@
|
||||
"yesterDailiesCallToAction": "Začít můj nový den!",
|
||||
"sessionOutdated": "Tvá relace je zastaralá. Prosím, zkus ji obnovit nebo synchronizovat.",
|
||||
"errorTemporaryItem": "Tento předmět je dočasný a nemůže být připnut.",
|
||||
"sureDeleteType": "Chceš tento <%= type %> opravdu smazat?",
|
||||
"deleteTaskType": "Tento <%= type %> smazat",
|
||||
"addNotes": "Přidej poznámky",
|
||||
"addATitle": "Přidej název",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"challenge": "Udfordring",
|
||||
"challengeDetails": "Udfordringer er fællesskabsevents, i hvilke spillere konkurrerer og vinder belønninger ved at fuldføre en gruppe relaterede opgaver.",
|
||||
"brokenChaLink": "Defekt udfordringslink",
|
||||
"brokenTask": "Defekt udfordringslink: denne opgave var en del af en udfordring, men er blevet fjernet fra den. Hvad vil du gøre?",
|
||||
"keepIt": "Behold den",
|
||||
"removeIt": "Fjern den",
|
||||
"brokenChallenge": "Defekt udfordringslink: denne opgave var en del af en udfordring, men udfordringen (eller gruppen) er blevet fjernet. Hvad vil du gøre med de gruppeløse opgaver?",
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"allocatePerPop": "Tilføj et point til Opfattelse",
|
||||
"allocateInt": "Point tilføjet til Intelligens:",
|
||||
"allocateIntPop": "Tilføj et point til Intelligens",
|
||||
"noMoreAllocate": "Nu hvor du har nået niveau 100, vil du ikke optjene flere Egenskabspoint. Du kan fortsætte, eller starte et nyt eventyr fra niveau 1 ved at bruge <a href='http://habitica.fandom.com/wiki/Orb_of_Rebirth' target='_blank'>Genfødselskuglen</a>!",
|
||||
"noMoreAllocate": "Nu hvor du har nået niveau 100, vil du ikke optjene flere Egenskabspoint. Du kan fortsætte, eller starte et nyt eventyr fra niveau 1 ved at bruge<a href='/shops/market'>Genfødselskugle</a>.",
|
||||
"stats": "Egenskaber",
|
||||
"strength": "Styrke",
|
||||
"strText": "Styrke forøger chancen for tilfældige \"fuldtræffere\" og giver et boost til Guld, Erfaring, og chancen for at finde genstande fra dem. Det hjælper også med at påføre bossmonstre skade.",
|
||||
@@ -183,5 +183,10 @@
|
||||
"chatCastSpellParty": "<%= username %> kaster <%= spell %> for holdet.",
|
||||
"notEnoughGold": "Ikke nok guld.",
|
||||
"purchasePetItemConfirm": "Dette køb vil overskride det antal genstande du skal bruge for at klække alle mulige <%= itemText %> Kæledyr. Er du sikker?",
|
||||
"purchaseForGold": "Køb for <%= cost %> Guld?"
|
||||
"purchaseForGold": "Køb for <%= cost %> Guld?",
|
||||
"skins": "Hudfarve",
|
||||
"titleFacialHair": "Ansigts hår",
|
||||
"titleHaircolor": "Hårfarve",
|
||||
"titleHairbase": "Hår stil",
|
||||
"strTaskText": "Øg chancen for fuldtræffer slag og skade ved at færdiggøre opgaver. Dette øger også skade mod missions monstrene."
|
||||
}
|
||||
|
||||
@@ -13,5 +13,16 @@
|
||||
"faqQuestion28": "Kan jeg sætte mine Daglige på pause, hvis jeg har brug for en pause?",
|
||||
"webFaqAnswer28": "Ja! Knappen \"Pause Damage\" kan findes i Indstillinger. Det vil forhindre dig i at miste HP for mistede Daglige. Dette er nyttigt, hvis du er på ferie, har brug for et hvil, eller du af en eller anden grund måske har brug for en pause. Hvis du deltager i en Quest, vil dine egne afventende fremskridt blive sat på pause, men du vil stadig tage skade fra dit partimedlems missede Daglige.\n\nFor at sætte specifikke Daglige på pause, kan du redigere tidsplanen, så den forfalder hver 0. dag, indtil du er klar til at genstarte den.",
|
||||
"faqQuestion29": "Hvordan gendanner jeg HP?",
|
||||
"webFaqAnswer29": "Du kan genvinde 15 HP ved at købe en Health Potion fra din belønningskolonne for 25 guld. Derudover vil du altid genvinde fuld HP, når du stiger i niveau!"
|
||||
"webFaqAnswer29": "Du kan genvinde 15 HP ved at købe en Health Potion fra din belønningskolonne for 25 guld. Derudover vil du altid genvinde fuld HP, når du stiger i niveau!",
|
||||
"webFaqAnswer34": "Dyr kan lide mad som matcher deres farve, med undtagelse af basis dyr som alle kan lide det samme mad, uanset farve. Du kan se hvilken specifik mad de forskellige dyr kan lide, herunder: \n\n * Basis dyr kan lide kød\n * Hvide dyr kan lide mælk\n * Ørken dyr kan lide kartofler\n * Røde dyr kan lide jordbær\n * Skygge dyr kan lide chokolade\n * Skelet dyr kan lide fisk\n * Zombie dyr kan lide kød\n * Lyserøde candyfloss dyr kan lide lyserød candyfloss\n * Blå candyfloss dyr kan lide blå candyfloss\n * Gyldne dyr kan lide honning",
|
||||
"faqQuestion30": "Hvad sker der når jeg løber tør for SP?",
|
||||
"webFaqAnswer30": "Hvis din SP når ned på nul, mister du et level - det levels egenskabspoint, alt dit guld, og et stykke udstyr som kan købes igen. Du kan genvinde SP ved at færdiggøre opgaver, og ved at gå op i level igen.",
|
||||
"faqQuestion31": "Hvorfor mistede jeg SP ved at gennemføre en ikke-negativ opgave?",
|
||||
"webFaqAnswer31": "Hvis du gennemfører en opgave og mister SP du ikke burde have mistet, er der opstået en forsinkelse imens serveren synkroniserede ændringer mellem dine enheder. Fx. Hvis du bruger Guld, Magisk energi, eller mister SP på din telefon og efterfølgende gennemfører en opgave på hjemmesiden, mangler serveren bare at synkronisere dine enheder.",
|
||||
"faqQuestion32": "Hvordan vælger jeg klassering?",
|
||||
"webFaqAnswer32": "Alle spillere starter i kriger-klassen indtil de når level 10. Når du når level 10 får du valget mellem at skifte klassering eller fortsætte som kriger.\n\nHver klasse har forskelligt udstyr og færdigheder. Hvis du ikke har lyst til at skifte klassering, kan du trykke på “fravælg”. Hvis du vælger ikke at vælge klassering, kan du altid aktivere klasseringssystemet i indstillingerne senere.\n\nHvis du gerne vil skifte klasse efter level 10, kan du gøre dette ved at bruge en genfødselskugle. Genfødselskuglen vil være mulig at købe på markedet for 6 ædelsten når du når level 50, eller få den gratis ved at nå level 100.\n\nAlternativt, kan du på ethvert tidspunkt ændre klassering i indstillinger for 3 ædelsen. Dette nulstiller ikke dit level, som ellers sker ved brug af genfødselskuglen. Det vil derimod give dig mulighed for at omrokere de egenskabspoint du har samlet sammen ved at gå op i level, til at matche din nye klasse.",
|
||||
"faqQuestion33": "Hvad er den blå bjælke som kommer efter level 10?",
|
||||
"webFaqAnswer33": "Efter du låser op for klasseringssystemet kan du også låse op for færdigheder, som kræver at Magisk energi benyttes. Magisk energi bestemmes ud fra din intelligensstatus, og kan justeres med færdigheder og visse udstyr.",
|
||||
"faqQuestion34": "Hvilken slags mad kan mit dyr lide?",
|
||||
"faqQuestion35": "Mit dyr forsvandt da jeg fodrede det! Hvad skete der?"
|
||||
}
|
||||
|
||||
@@ -24,34 +24,34 @@
|
||||
"invalidEmail": "Det kræver en valid emailadresse for at få nulstillet dit kodeord.",
|
||||
"login": "Log ind",
|
||||
"logout": "Log Ud",
|
||||
"marketing1Header": "Forbedr dine vaner ved at spille et spil",
|
||||
"marketing1Lead1Title": "Dit liv, rollespillet",
|
||||
"marketing1Lead1": "Habitica er et computerspil, der hjælper med at forbedre dine vaner i virkeligheden. Det gør dit liv til et spil ved at lave alle dine opgaver (Vaner, Daglige og To-Do's) indtil små monstre, du skal besejre. Jo bedre du er til dette, desto større fremskridt vil du gøre i spillet. Hvis du begår fejl i livet, vil din karakter miste fremskridt i spillet.",
|
||||
"marketing1Lead2Title": "Få Lækkert Udstyr",
|
||||
"marketing1Lead2": "Forbedr dine vaner for at ændre din avatar. Vis det fede udstyr du har tjent!",
|
||||
"marketing1Lead3Title": "Find Tilfældige Præmier",
|
||||
"marketing1Header": "Forbedr dine vaner med spil, et level ad gangen!",
|
||||
"marketing1Lead1Title": "Gør dit liv til et spil",
|
||||
"marketing1Lead1": "Habitica er den perfekte app for alle der har svært ved at færdiggøre sin to-do liste. Vi bruger genkendelige spillemekanismer, såsom at belønne dig med Guld, Erfarring og genstande, for at hjælpe dig med at føle mere produktiv og samtidig øge følelsen af præstation, når du færdiggøre en opgave. Jo bedre du er til at færdiggøre dine opgaver, jo længere kommer du i spillet.",
|
||||
"marketing1Lead2Title": "Udstyr dig med stil",
|
||||
"marketing1Lead2": "Saml svær, udrustning og meget andet med Guld du tjener ved at gennemføre opgaver. Med mulighed for at indsamle, og vælge, mellem hundredevis af forskellige genstande, løber du aldrig tør for sammensætninger. Optimer for niveau, stil, eller begge! ",
|
||||
"marketing1Lead3Title": "Bliv belønnet for din indsats",
|
||||
"marketing1Lead3": "For nogle er det chancen, der motiverer dem: et system der hedder \"stokastiske belønninger.\" Habitica har plads til alle slags måder at forbedre eller straffe sig selv på: positive, negative, forudsigelige og tilfældige.",
|
||||
"marketing2Header": "Kæmp med venner, deltag i interessegrupper",
|
||||
"marketing2Header": "Slå dig sammen med dine venner",
|
||||
"marketing2Lead1Title": "Social produktivitet",
|
||||
"marketing2Lead1": "Selvom du selvfølgelig kan spille Habitica selv, bliver det først virkelig godt når I begynder at samarbejde, konkurrere og holde hinanden ansvarlige. Den mest effektive del af ethvert selvforbedringsprogram er social ansvarlighed, og hvad er et bedre miljø for ansvarlighed og konkurrence end et computerspil?",
|
||||
"marketing2Lead2Title": "Bekæmp monstre",
|
||||
"marketing2Lead2Title": "Bekæmp monstre i opgaver",
|
||||
"marketing2Lead2": "Hvad er et rollespil uden slåskampe? Bekæmp monstre med dit hold. Monstre er \"super-ansvarligheds-mode\" - en dag, du ikke træner, er en dag, hvor monstret skader *alle!*",
|
||||
"marketing2Lead3Title": "Udfordr Hinanden",
|
||||
"marketing2Lead3Title": "Udfordr hinanden",
|
||||
"marketing2Lead3": "Udfordringer lader dig konkurrere med venner og fremmede. Den, der er bedst i slutningen af en udfordring vinder særlige præmier.",
|
||||
"marketing3Header": "Apps og Udvidelser",
|
||||
"marketing3Header": "Forskellige måder at bruge Habitica på",
|
||||
"marketing3Lead1": "**iPhone & Android** apps lader dig klare dine ting på farten. Vi ved, at det nogen gange er for meget at skulle logge ind på websiden for at klikke på knapper.",
|
||||
"marketing3Lead2Title": "Integrationer",
|
||||
"marketing3Lead2Title": "Udviklet af fællesskabet",
|
||||
"marketing3Lead2": "Andre **tredjepartsværktøjer** kan binde Habitica sammen med andre dele af dit liv. Vores API muliggør integrationer som [Chrome Extension](https://chrome.google.com/webstore/detail/habitica/pidkmpibnnnhneohdgjclfdjpijggmjj?hl=en-US), med hvilken du mister point ved at bruge unyttige hjemmesider, og optjener point når du browser de nyttige i stedet. [Se mere her](https://habitica.fandom.com/wiki/Extensions,_Add-Ons,_and_Customizations).",
|
||||
"marketing4Header": "Organisatorisk brug",
|
||||
"marketing4Header": "Udover hverdagens pligter",
|
||||
"marketing4Lead1": "Uddannelse er en af de bedste områder at bruge spilelementer. Vi ved alle, hvordan studerende nærmest er limet til deres telefon disse dage, så brug dette! Sæt dine elever til at kæmpe mod hinanden som hyggelig konkurrence. Beløn god opførsel med sjældne præmier. Se deres karakterer og opførsel blive forbedret.",
|
||||
"marketing4Lead1Title": "Brug af Spilelementer i Undervisning",
|
||||
"marketing4Lead1Title": "Spilelementer i undervisning",
|
||||
"marketing4Lead2": "Prisen for sundhedssektoren stiger, og der må ske noget. Hundredevis af programmer er bygget til at reducere udgifter og forbedre velvære. Vi tror på, at Habitica kan bane vejen for en sundere livsstil.",
|
||||
"marketing4Lead2Title": "Brug af Spilelementer i Sundhed og Velvære",
|
||||
"marketing4Lead3-1": "Vil du gøre dit liv til et spil?",
|
||||
"marketing4Lead2Title": "Spilelementer i sundhed og velvære",
|
||||
"marketing4Lead3-1": "Klar til at udrette noget imens du har det sjovt?",
|
||||
"marketing4Lead3-2": "Interesseret i at lede en gruppe for uddannelse, velvære og mere?",
|
||||
"marketing4Lead3Title": "Spilificér Alt",
|
||||
"mobileAndroid": "Android",
|
||||
"mobileIOS": "iOS",
|
||||
"marketing4Lead3Title": "Start din rejse!",
|
||||
"mobileAndroid": "Android App",
|
||||
"mobileIOS": "iOS App",
|
||||
"oldNews": "Nyheder",
|
||||
"setNewPass": "Sæt nyt kodeord",
|
||||
"password": "Kodeord",
|
||||
@@ -110,7 +110,7 @@
|
||||
"missingPassword": "Manglende kodeord.",
|
||||
"missingNewPassword": "Manglende nyt kodeord.",
|
||||
"invalidEmailDomain": "Du kan ikke registrere med emails med følgende domæner: <%= domains %>",
|
||||
"wrongPassword": "Forkert kodeord.",
|
||||
"wrongPassword": "Forkert kodeord. Hvis du har glemt dit kodeord, tryk på “Glemt kodeord”.",
|
||||
"incorrectDeletePhrase": "Skriv venligst <%= magicWord %> med store bogstaver for at slette din konto.",
|
||||
"notAnEmail": "Ugyldig e-mailadresse.",
|
||||
"emailTaken": "E-mailadressen er allerede brugt til en konto.",
|
||||
@@ -121,8 +121,8 @@
|
||||
"usernameTaken": "Brugernavn allerede taget.",
|
||||
"passwordConfirmationMatch": "Kodeord og godkendelse er ikke ens.",
|
||||
"passwordResetPage": "Nulstil kodeord",
|
||||
"passwordReset": "Hvis vi har din email på lager, vil instruktioner til opsætning af nyt kodeord være sendt til din email.",
|
||||
"invalidLoginCredentialsLong": "Åh-åh - din emailaddresse/brugernavn eller kodeord er forkert.\n- Vær sikker på du har indtastet korrekt. Der gøres forskel på store og små bogstaver.\n- Du kan have brugt Facebook eller Google til at oprette din konto, ikke email, så dobbeltcheck ved at prøve dem.\n- Hvis du har glemt dit kodeord, så klik på \"Glemt kodeord\".",
|
||||
"passwordReset": "Hvis din E-mail eller brugernavn allerede findes i vores system, vil instruktioner til opsætning af nyt kodeord være sendt til din email.",
|
||||
"invalidLoginCredentialsLong": "Åh-åh - din e-mailadresse, brugernavn eller kodeord er forkert.\nPrøv igen, eller klik på \"Glemt kodeord\".",
|
||||
"invalidCredentials": "Der er ingen konto med disse legitimationsoplysninger.",
|
||||
"accountSuspended": "Denne konto, bruger-ID \"<%= userId %>\", er blevet blokeret for at bryde Retningslinjerne for fællesskabet (https://habitica.com/static/community-guidelines) eller vores Vilkår og betingelser (https://habitica.com/static/terms). For at få detaljer eller bede om at få blokaden ophævet, så send venligst en email til vores Community Manager på <%= communityManagerEmail %>, eller bed din forælder eller værge om at gøre det. Inkluder venligst dit @Brugernavn i emailen.",
|
||||
"accountSuspendedTitle": "Kontoen er blevet suspenderet",
|
||||
@@ -132,14 +132,14 @@
|
||||
"invalidReqParams": "Ugyldige anmodningsparametre.",
|
||||
"memberIdRequired": "\"member\" skal være et gyldigt Unikt Bruger-ID.",
|
||||
"heroIdRequired": "\"heroID\" skal være et gyldigt Unikt Bruger-ID.",
|
||||
"cannotFulfillReq": "Din anmodning kan ikke udføres. Kontakt admin@habitica.com hvis fejlen fortsætter.",
|
||||
"cannotFulfillReq": "Denne e-mail er allerede taget i brug. Prøv at logge ind eller brug en anden e-mail til registrering. Hvis du har brug for hjælp, kontakt os på admin@habitica.com",
|
||||
"modelNotFound": "Denne model findes ikke.",
|
||||
"signUpWithSocial": "Tilmeld med <%= social %>",
|
||||
"signUpWithSocial": "Fortsæt med <%= social %>",
|
||||
"loginWithSocial": "Log in med<%= social %>",
|
||||
"confirmPassword": "Bekræft kodeord",
|
||||
"usernameLimitations": "Brugernavn skal være 1 til 20 tegn, kun bruge bogstaver fra a til z, tal fra 0 til 9, bindestreg eller bundstreg (_), og kan ikke inkludere nogle upassende ord.",
|
||||
"usernameLimitations": "Brugernavn kan ændres på alle tidspunkter. Det skal være 1 til 20 tegn, kun indeholde bogstaver fra a til z, tal fra 0 til 9, bindestreg eller understreg (_)",
|
||||
"usernamePlaceholder": "fx HabitRabbit",
|
||||
"emailPlaceholder": "e.g., rabbit@example.com",
|
||||
"emailPlaceholder": "Fx., kanin@eksempel.com",
|
||||
"passwordPlaceholder": "e.g., ******************",
|
||||
"confirmPasswordPlaceholder": "Sikr dig at det er det samme kodeord!",
|
||||
"joinHabitica": "Opret bruger",
|
||||
@@ -169,10 +169,22 @@
|
||||
"joinMany": "Tilslut dig over <%= userCountInMillions %> millioner andre, der har det sjovt imens de opnår deres mål!",
|
||||
"joinToday": "Tilmeld dig Habitica i dag",
|
||||
"signup": "Tilmeld dig",
|
||||
"getStarted": "Kom i gang!",
|
||||
"getStarted": "Kom i gang",
|
||||
"mobileApps": "Mobile apps",
|
||||
"learnMore": "Lær mere",
|
||||
"communityInstagram": "Instagram",
|
||||
"minPasswordLength": "Kodeord skal bestå af 8 eller flere tegn.",
|
||||
"enterHabitica": "Spil Habitica"
|
||||
"enterHabitica": "Spil Habitica",
|
||||
"footerProduct": "Produkt",
|
||||
"marketing4Lead3Button": "Start i dag",
|
||||
"incorrectResetPhrase": "Skriv <%= magicWord %> i store bogstaver for at nulstille din konto.",
|
||||
"minPasswordLengthLogin": "Dit kodeord er minimum 8 karakterer langt.",
|
||||
"enterValidEmail": "Indtast venligst en gyldig e-mailadresse.",
|
||||
"translateHabitica": "Oversæt Habitica",
|
||||
"whatToCallYou": "Hvad skal vi kalde dig?",
|
||||
"acceptPrivacyTOS": "Bekræfter du at du er over 18 år gammel, og har læst og accepteret vores <a href='/static/terms' target='_blank'>Terms of Services</a> og <a href='/static/privacy' target='_blank'>Privatlivspolitik</a>",
|
||||
"emailBlockedRegistration": "Denne e-mail er blokeret, og kan ikke benyttes til registrering. Hvis der er sket en fejl, kontakt os på admin@habitica.com",
|
||||
"emailUsernamePlaceholder": "Fx., vanedyr eller kanin@eksempel.com",
|
||||
"marketing3Lead1Title": "Android & iOS apps",
|
||||
"socialAlreadyExists": "Dette login benyttes allerede af en eksisterende Habitica konto."
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@
|
||||
"success": "Succes!",
|
||||
"classGear": "Klasseudstyr",
|
||||
"classGearText": "Tillykke med at vælge klasse! Jeg har tilføjet dit nye basisudstyr til dit inventar. Tag et kig forneden og tag det på!",
|
||||
"autoAllocate": "Tildel automatisk",
|
||||
"spells": "Evner",
|
||||
"skillsTitle": "<%= classStr %> Evner",
|
||||
"toDo": "To Do",
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
"userData": "Brugerdata",
|
||||
"exportUserData": "Eksportér brugerdata:",
|
||||
"export": "Eksportér",
|
||||
"xml": "(XML)",
|
||||
"json": "(JSON)",
|
||||
"xml": "XML",
|
||||
"json": "JSON",
|
||||
"customDayStart": "Brugerdefineret starttidspunkt",
|
||||
"sureChangeCustomDayStartTime": "Er du sikker på, at du vil ændre dit brugerdefinerede starttidspunkt? Dine Daglige vil blive opdateret næste gang du bruger Habitica efter <%= time %>. Vær sikker på, at du har udført dine Daglige før da!",
|
||||
"customDayStartHasChanged": "Dit brugerdefinerede starttidspunkt er ændret.",
|
||||
@@ -46,7 +46,7 @@
|
||||
"newUsername": "Nyt brugernavn",
|
||||
"dangerZone": "Farezone",
|
||||
"resetText1": "ADVARSEL! Dette nulstiller mange dele af din konto. Vi fraråder på det kraftigste dette, men nogen finder det brugbart i begyndelsen efter at have eksperimenteret med Habitica i et kort stykke tid.",
|
||||
"resetText2": "Du vil miste alle niveauer, Guld, og Erfaringspoint. Alle dine Opgaver (bortset fra dem fra Udfordringer) vil blive slettet permanent, og du vil miste al deres historik. Du vil miste alt dit udstyr, undtagen gratis gave-udstyr eller Mystiske abonnentsgenstande. Du vil være i stand til at købe alle de mistede genstand igen, inklusiv tidsbegrænset udstyr (du skal være den korrekte klasse for at købe klasse-begrænset udstyr). Du vil beholde din nuværende klasse, præstationer og dine kæle- og ridedyr. Du ville måske foretrække at bruge en Genfødselskugle i stedet. Det er en meget sikrere mulighed, og du vil beholde dine opgaver og udstyr.",
|
||||
"resetText2": "En anden mulighed er at bruge en <b>Genfødselskugle</b>, hvilket vil nulstille alt, men du vil beholde dine opgaver og dit udstyr.",
|
||||
"deleteLocalAccountText": "Er du sikker? Dette vil slette din konto for evigt, og den kan aldrig gendannes! Du skal registrere en ny konto for at kunne bruge Habitica igen. Ædelsten du har brugt eller har på lager vil ikke blive refunderet. Hvis du er fuldstændig sikker, så skriv dit kodeord i boksen herunder.",
|
||||
"deleteSocialAccountText": "Er du sikker? Dette vil slette din brugerkonto for evigt, og den kan aldrig gendannes! Du vil være nødt til at oprette en ny konto for at bruge Habitica igen. Ædelsten i en Klanbank eller som er blevet brugt vil ikke blive refunderet. Hvis du virkelig er helt sikker, så indtast \"<%= magicWord %>\" i tekstboksen nedenunder.",
|
||||
"API": "API",
|
||||
@@ -176,5 +176,24 @@
|
||||
"account": "Konto",
|
||||
"loginMethods": "Login metoder",
|
||||
"character": "Karakter",
|
||||
"siteLanguage": "Webstedets sprog"
|
||||
"siteLanguage": "Webstedets sprog",
|
||||
"showLevelUpModal": "Når man går et level op",
|
||||
"showHatchPetModal": "Når man udruger et dyr",
|
||||
"showRaisePetModal": "Når man opgradere et dyr til ridedyr",
|
||||
"showStreakModal": "Når man modtager en streak præstation",
|
||||
"baileyAnnouncement": "Seneste Bailey meddelelse",
|
||||
"view": "Vis",
|
||||
"feedbackPlaceholder": "Tilføj til feedback",
|
||||
"downloadCSV": "Download CSV",
|
||||
"downloadAs": "Download As",
|
||||
"yourUserData": "Dine brugerdata",
|
||||
"taskHistory": "Opgave historie",
|
||||
"resetTextLocal": "Hvis du er helt sikker, indsæt dit kodeord i tekstfeltet nedenfor.",
|
||||
"yourUserDataDisclaimer": "Her kan du downloade en kopi af din opgavehistorie og dine bruger data.",
|
||||
"useridCopied": "BrugerID er kopieret til udklipsholderen.",
|
||||
"developerMode": "Udviklingsmode",
|
||||
"api": "API",
|
||||
"currentPass": "Nuværende kodeord",
|
||||
"resetDetail1": "Du vil miste alle dine levels, dit Guld, og Erfaringspoint.",
|
||||
"resetDetail2": "Du vil forblive i din valgte klasse, og beholde dine præstationer, dyr og ridedyr."
|
||||
}
|
||||
|
||||
@@ -921,5 +921,14 @@
|
||||
"backgroundInsideForestWitchsCottageNotes": "Webe Zauber in der Hütte der Waldhexe.",
|
||||
"backgroundCastleKeepWithBannersText": "Burghalle mit Bannern",
|
||||
"backgroundCastleKeepWithBannersNotes": "Singe Geschichten von Heldentaten in einer Burghalle mit Bannern.",
|
||||
"backgrounds112025": "SET 138: Veröffentlicht im November 2025"
|
||||
"backgrounds112025": "SET 138: Veröffentlicht im November 2025",
|
||||
"backgrounds122025": "SET 139: Veröffentlicht im Dezember 2025",
|
||||
"backgroundNighttimeStreetWithShopsNotes": "Genieße das warme Leuchten einer Nächtlichen Straße mit Geschäften.",
|
||||
"backgroundNighttimeStreetWithShopsText": "Nächtliche Straße mit Geschäften",
|
||||
"backgrounds012026": "SET 140: Veröffentlicht im Januar 2026",
|
||||
"backgrounds022026": "SET 141: Veröffentlicht im Februar 2026",
|
||||
"backgroundElegantPalaceText": "Eleganter Palast",
|
||||
"backgroundElegantPalaceNotes": "Bewundere die farbenfrohen Hallen eines Eleganten Palastes.",
|
||||
"backgroundWinterDesertWithSaguarosText": "Winter-Wüste mit Kakteen",
|
||||
"backgroundWinterDesertWithSaguarosNotes": "Atme die kalte Luft Wunder Winter-Wüste mit Kakteen."
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
"challenge": "Herausforderung",
|
||||
"challengeDetails": "Herausforderungen sind Gemeinschaftsereignisse, an denen Spieler teilnehmen und Preise gewinnen können, indem sie die dazugehörigen Aufgaben erledigen.",
|
||||
"brokenChaLink": "Toter Herausforderungs-Link",
|
||||
"brokenTask": "Toter Herausforderungs-Link: Diese Aufgabe war Teil einer Herausforderung, aber ist mittlerweile entfernt worden. Was möchtest Du tun?",
|
||||
"keepIt": "Behalten",
|
||||
"removeIt": "Entfernen",
|
||||
"brokenChallenge": "Toter Herausforderungs-Link: Diese Aufgabe war Teil einer Herausforderung, aber die Herausforderung (oder Gruppe) wurde gelöscht. Was möchtest Du mit den verwaisten Aufgaben tun?",
|
||||
"challengeCompleted": "Diese Herausforderung ist beendet, und gewonnen hat <span class=\"badge\"><%- user %></span>! Was soll mit den verwaisten Aufgaben geschehen?",
|
||||
"brokenChallenge": "Defekter Herausforderungslink",
|
||||
"challengeCompleted": "Herausforderung abgeschlossen!",
|
||||
"unsubChallenge": "Toter Herausforderungs-Link: Diese Aufgabe war Teil einer Herausforderung, aber Du hast aufgehört an dieser Herausforderungs teilzunehmen. Was soll mit den verwaisten Aufgaben geschehen?",
|
||||
"challenges": "Herausforderungen",
|
||||
"endDate": "Endet",
|
||||
@@ -108,5 +107,10 @@
|
||||
"resetFlagCount": "Markierungszähler zurücksetzen",
|
||||
"cannotClone": "Diese Herausforderung kann nicht dupliziert werden, weil einer oder mehrere Spieler sie als unangemessen gemeldet haben. Einer der Mitarbeiter wird dich in Kürze mit Anweisungen kontaktieren. Wenn mehr als 48 Stunden vergangen sind, und du nichts von ihnen gehört hast, schicke bitte eine Email an admin@habitica.com, um Unterstützung zu erhalten.",
|
||||
"resetFlags": "Markierungen zurücksetzen",
|
||||
"messageChallengeFlagOfficial": "Offizielle Herausforderungen können nicht gemeldet werden."
|
||||
"messageChallengeFlagOfficial": "Offizielle Herausforderungen können nicht gemeldet werden.",
|
||||
"deleteChallengeRefundDescription": "Wenn du diese Herausforderung löschst, bekommst du den Preis in Edelsteinen erstattet und die Aufgaben der Herausforderung verbleiben auf der Aufgabentafel der Teilnehmer.",
|
||||
"brokenTaskDescription": "Diese Aufgabe war Teil einer Herausforderung, wurde jedoch daraus entfernt. Was möchtest Du tun?",
|
||||
"brokenChallengeDescription": "Diese Aufgabe war Teil einer Herausforderung, aber die Herausforderung (oder Gruppe) wurde gelöscht. Was soll mit den verwaisten Aufgaben geschehen?",
|
||||
"challengeCompletedDescription": "Gewonnen hat <%- user %>! Was soll mit den verwaisten Aufgaben geschehen?",
|
||||
"brokenTask": "Defekter Link zur Herausforderung"
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"allocatePerPop": "Erhöhe Wahrnehmung um einen Punkt",
|
||||
"allocateInt": "Zugewiesene Intelligenzpunkte:",
|
||||
"allocateIntPop": "Erhöhe Intelligenz um einen Punkt",
|
||||
"noMoreAllocate": "Jetzt, nach dem Erreichen von Level 100, wirst Du keine weiteren Attributpunkte erhalten. Du kannst weiter aufsteigen oder ein neues Abenteuer auf Level 1 anfangen, indem Du die <a href='/shops/market'>Sphäre der Wiedergeburt</a> benutzt!",
|
||||
"noMoreAllocate": "Nachdem du nun Level 100 erreicht hast, erhältst du keine weiteren Attributpunkte mehr. Du kannst weiter aufsteigen oder mit der <a href=‚/shops/market‘>Sphäre der Wiedergeburt</a> ein neues Abenteuer auf Level 1 beginnen.",
|
||||
"stats": "Attributwerte",
|
||||
"strength": "Stärke",
|
||||
"strText": "Stärke erhöht die Wahrscheinlichkeit zufälliger \"kritischer Treffer\" und die Rate mit der durch sie Gold, Beute und Erfahrung gewonnen wird. Und hilft auch dabei, Boss-Monstern Schaden zuzufügen.",
|
||||
@@ -114,12 +114,12 @@
|
||||
"unallocated": "Freie Attributspunkte",
|
||||
"autoAllocation": "Verteilungsmuster",
|
||||
"autoAllocationPop": "Verteilt gemäß Deiner Einstellungen Punkte auf Deine Attribute, wenn Du ein Level aufsteigst.",
|
||||
"evenAllocation": "Attributspunkte gleichmäßig verteilen",
|
||||
"evenAllocationPop": "Weist jedem Statuswert die gleiche Anzahl an Punkten zu.",
|
||||
"evenAllocation": "Gleichmäßig verteilen",
|
||||
"evenAllocationPop": "Weist jedem Attribut die gleiche Anzahl von Punkten zu.",
|
||||
"classAllocation": "Punkte anhand der Klasse verteilen",
|
||||
"classAllocationPop": "Weist den Statuswerten die wichtig für Deine Klasse sind, mehr Punkte zu.",
|
||||
"taskAllocation": "Verteile Punkte abhängig von Aufgaben-Aktivität",
|
||||
"taskAllocationPop": "Verteilt Punkte abhängig davon, welche Kategorien die Aufgaben haben, die Du überwiegend erledigst: Stärke, Intelligenz, Ausdauer oder Wahrnehmung.",
|
||||
"classAllocationPop": "Weist den Attributen die wichtig für Deine Klasse sind, mehr Punkte zu.",
|
||||
"taskAllocation": "Verteile Punkte abhängig von Aufgabenaktivität",
|
||||
"taskAllocationPop": "Verteilt Punkte basierend auf den Kategorien Stärke, Intelligenz, Konstitution und Wahrnehmung, die mit den von Dir erledigten Aufgaben verbunden sind.",
|
||||
"distributePoints": "Verteile freie Punkte automatisch",
|
||||
"distributePointsPop": "Verteilt alle freien Attributpunkte gemäß Deinem gewählten Verteilungsmuster.",
|
||||
"warriorText": "Krieger verursachen mehr und stärkere \"kritische Treffer\", die zufällige Boni auf Gold, Erfahrung und Beute beim Erfüllen einer Aufgabe geben. Sie sind auch sehr stark gegen Bossmonster. Spiele einen Krieger, wenn Dich die Chance auf Belohnungen im Lottogewinn-Stil besonders reizt und Du besonders effektiv gegen Bossmonster sein willst!",
|
||||
@@ -191,5 +191,14 @@
|
||||
"titleHaircolor": "Haarfarben",
|
||||
"titleFacialHair": "Bärte",
|
||||
"titleHairbase": "Frisuren",
|
||||
"customizations": "Individualisierungen"
|
||||
"customizations": "Individualisierungen",
|
||||
"strTaskText": "Erhöht die kritische Trefferchance und den Schaden beim Erfüllen von Aufgaben. Erhöht außerdem den Schaden, der Quest-Bossen zugefügt wird.",
|
||||
"conTaskText": "Reduziert den Schaden, der durch verpasste tägliche Aufgaben und negative Gewohnheiten verursacht wird. Reduziert nicht den Schaden durch Quest-Bosse.",
|
||||
"autoAllocate": "Automatische Zuweisung",
|
||||
"pointsAvailable": "Verfügbare Punkte",
|
||||
"allocationMethod": "Zuteilungsmethode",
|
||||
"assignedStat": "Zugewiesener Wert",
|
||||
"intTaskText": "Erhöht die durch Aufgaben gesammelte Erfahrung. Erhöht außerdem deine Manakapazität und Manaregenerationsrate.",
|
||||
"perTaskText": "Erhöht die Drop-Chance für Gegenstände, die tägliche Drop-Obergrenze für Gegenstände, die Serienboni für Aufgaben und das beim Abschließen von Aufgaben verdiente Gold.",
|
||||
"statAllocationInfo": "Mit jedem Level erhältst Du einen Punkt, den Du einem Attribut Deiner Wahl zuweisen kannst. Du kannst die Zuweisung manuell vornehmen oder es dem Spiel überlassen, indem Du eine der Optionen zur automatischen Zuweisung wählst."
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"dontDespair": "Nicht verzweifeln!",
|
||||
"deathPenaltyDetails": "Du hast ein Level, Dein Gold und ein Stück Ausrüstung verloren, aber Du kannst alles durch harte Arbeit wieder zurückbekommen! Viel Glück -- Du schaffst das.",
|
||||
"refillHealthTryAgain": "Lebenspunkte wiederherstellen & Nochmal versuchen",
|
||||
"dyingOftenTips": "Passiert das öfters? <a href='https://habitica.fandom.com/wiki/Death_Mechanics#Strategies_for_Staying_Alive' target='_blank'>Hier gibt es einige Tipps!</a>",
|
||||
"dyingOftenTips": "Passiert das öfters? <a href='/static/faq#prevent-damage' target='_blank'>Hier gibt es einige Tipps!</a>",
|
||||
"losingHealthWarning": "Vorsicht - Du verlierst Lebenspunkte!",
|
||||
"losingHealthWarning2": "Lass Deine Lebenspunkte nicht auf null fallen! Sollte das passieren wirst Du ein Level, Dein Gold und einen Gegenstand Deiner Ausrüstung verlieren.",
|
||||
"toRegainHealth": "Um Deine Lebenspunkte wiederherzustellen:",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"faqQuestion29": "Wie kann ich HP wiederherstellen?",
|
||||
"webFaqAnswer29": "Du kannst 15 HP wiederherstellen, indem du einen Gesundheitstrank in deiner Belohnungsspalte für 25 Gold kaufst. Außerdem erhältst du immer volle HP, wenn du auflevelst!",
|
||||
"faqQuestion30": "Was passiert, wenn ich keine HP mehr habe?",
|
||||
"webFaqAnswer30": "Wenn deine HP Null erreichen, verlierst du eine Stufe, dein gesamtes Gold und ein Ausrüstungsstück, das du wieder kaufen kannst.",
|
||||
"webFaqAnswer30": "Wenn deine HP Null erreichen, verlierst du ein Level, dessen Attribut-Punkt, dein gesamtes Gold und ein Ausrüstungsstück, das du wieder kaufen kannst. Du kannst dich wieder aufbauen, indem du Aufgaben erledigst und dich wieder hochlevelst.",
|
||||
"faqQuestion31": "Warum habe ich HP verloren, wenn ich mit einer nicht-negativen Aufgabe interagiere?",
|
||||
"faqQuestion32": "Wie kann ich eine Klasse wählen?",
|
||||
"faqQuestion33": "Was ist der blaue Balken, der nach Level 10 erscheint?",
|
||||
@@ -243,5 +243,7 @@
|
||||
"subscriptionDetail470": "Gruppenabonnentenvorteile verhalten sich genauso wie die eines wiederkehrenden 1-Monats-Abonnements. Du erhältst eine Mystische Sanduhr am Anfang jedes Monats und die Anzahl an Edelsteinen, die du jeden Monat auf dem Marktplatz kaufen kannst, wird sich erhöhen bis zu einem Limit von 50.",
|
||||
"subscriptionPara3": "Wir hoffen, dass dieser neue Rhythmus besser vorhersagbar ist, mehr Zugang zur fantastischen Gegenstandauswahl im Laden des Zeitreisenden ermöglicht und noch mehr Motivation bietet, jeden Monat Fortschritte an deinen Aufgaben zu machen!",
|
||||
"faqQuestion67": "Was sind die Klassen in Habitica?",
|
||||
"webFaqAnswer67": "Klassen sind verschiedene Rollen, die dein Charakter spielen kann. Jede Klasse bietet ihre eigene Reihe von einzigartigen Vorteilen und Fähigkeiten beim Aufsteigen auf höhere Level. Diese Fähigkeiten können das Bearbeiten deiner Aufgaben ergänzen oder dabei helfen, deine Party beim Abschließen von Quests zu unterstützen.\n\nDeine Klasse bestimmt auch, welche Ausrüstung für dich in den Belohnungen, im Marktplatz und im Jahreszeitenmarkt zum Kauf erhältlich ist.\n\nHier ist eine Zusammenfassung jeder Klasse, um dir dabei zu helfen, diejenige zu wählen, welche am besten zu deinem Spielstil passt:\n#### **Krieger**\n* Die Krieger verursachen hohen Schaden bei Bossen und haben eine hohe Chance für kritische Treffer beim Abschließen von Aufgaben, was dich mit extra Erfahrung und Gold belohnt.\n* Stärke ist ihr primäres Attribut, welches den Schaden erhöht, den sie verursachen.\n* Ausdauer ist ihr sekundäres Attribut, welches den Schaden verringert, den sie erhalten.\n* Die Fähigkeiten der Krieger erhöhen die Ausdauer und Stärke der Gruppenmitglieder.\n* Erwäge, einen Krieger zu spielen, wenn du es liebst, Bosse zu bekämpfen und auch ein wenig Schutz möchtest, wenn du gelegentlich Aufgaben versäumst.\n#### **Heiler**\n* Die Heiler haben eine starke Verteidigung und können sich selbst, sowie Gruppenmitglieder, heilen.\n* Ausdauer ist ihr primäres Attribut, welches ihre Heilungen verstärkt und den Schaden, den sie erhalten, verringert.\n* Intelligenz ist ihr sekundäres Attribut, welches ihr Mana und ihre Erfahrung erhöht.\n* Die Fähigkeiten der Heiler bewirken, dass ihre Aufgaben weniger rot werden und erhöhen die Ausdauer der Gruppenmitglieder.\n* Erwäge, einen Heiler zu spielen, wenn du oft Aufgaben versäumst, und die Fähigkeit benötigst, dich selbst und deine Gruppenmitglieder zu heilen. Heiler erreichen schnell neue Level.\n#### **Magier**\n* Die Magier gewinnen schnell neue Level und viel Mana, und verursachen Schaden bei Bossen in Quests.\n* Intelligenz ist ihr primäres Attribut, welches ihr Mana und ihre Erfahrung erhöht.\n* Wahrnehmung ist ihr sekundäres Attribut, welches ihr gefundenes Gold und ihre gefundenen Gegenstände vermehrt.\n* Die Fähigkeiten der Magier bewirken, dass ihre Aufgaben Strähnen eingefroren werden, stellen das Mana ihrer Gruppenmitglieder wieder her, und erhöhen ihre Intelligenz.\n* Erwäge, einen Magier zu spielen, wenn du durch das schnelle Erreichen neuer Level und das Beisteuern von Schaden in Boss Quests motiviert wirst.\n#### **Schurke**\n* Die Schurken bekommen die meisten erbeuteten Gegenstände und das meiste Gold beim Erledigen von Aufgaben und haben eine höhere Chance, kritische Treffer zu erzielen, was ihnen noch mehr Erfahrung und Gold beschert.\n* Wahrnehmung ist ihr primäres Attribut, welches ihr gefundenes Gold und ihre gefundenen Gegenstände vermehrt.\n* Stärke ist ihr sekundäres Attribut, welches den Schaden erhöht, den sie verursachen.\n* Die Fähigkeiten der Schurken helfen ihnen, versäumten Tagesaufgaben auszuweichen, Gold zu klauen und die Wahrnehmung ihrer Gruppenmitglieder zu erhöhen.\n* Erwäge, einen Schurken zu spielen, wenn du durch Belohnungen sehr motiviert wirst."
|
||||
"webFaqAnswer67": "Klassen sind verschiedene Rollen, die dein Charakter spielen kann. Jede Klasse bietet ihre eigene Reihe von einzigartigen Vorteilen und Fähigkeiten beim Aufsteigen auf höhere Level. Diese Fähigkeiten können das Bearbeiten deiner Aufgaben ergänzen oder dabei helfen, deine Party beim Abschließen von Quests zu unterstützen.\n\nDeine Klasse bestimmt auch, welche Ausrüstung für dich in den Belohnungen, im Marktplatz und im Jahreszeitenmarkt zum Kauf erhältlich ist.\n\nHier ist eine Zusammenfassung jeder Klasse, um dir dabei zu helfen, diejenige zu wählen, welche am besten zu deinem Spielstil passt:\n#### **Krieger**\n* Die Krieger verursachen hohen Schaden bei Bossen und haben eine hohe Chance für kritische Treffer beim Abschließen von Aufgaben, was dich mit extra Erfahrung und Gold belohnt.\n* Stärke ist ihr primäres Attribut, welches den Schaden erhöht, den sie verursachen.\n* Ausdauer ist ihr sekundäres Attribut, welches den Schaden verringert, den sie erhalten.\n* Die Fähigkeiten der Krieger erhöhen die Ausdauer und Stärke der Gruppenmitglieder.\n* Erwäge, einen Krieger zu spielen, wenn du es liebst, Bosse zu bekämpfen und auch ein wenig Schutz möchtest, wenn du gelegentlich Aufgaben versäumst.\n#### **Heiler**\n* Die Heiler haben eine starke Verteidigung und können sich selbst, sowie Gruppenmitglieder, heilen.\n* Ausdauer ist ihr primäres Attribut, welches ihre Heilungen verstärkt und den Schaden, den sie erhalten, verringert.\n* Intelligenz ist ihr sekundäres Attribut, welches ihr Mana und ihre Erfahrung erhöht.\n* Die Fähigkeiten der Heiler bewirken, dass ihre Aufgaben weniger rot werden und erhöhen die Ausdauer der Gruppenmitglieder.\n* Erwäge, einen Heiler zu spielen, wenn du oft Aufgaben versäumst, und die Fähigkeit benötigst, dich selbst und deine Gruppenmitglieder zu heilen. Heiler erreichen schnell neue Level.\n#### **Magier**\n* Die Magier gewinnen schnell neue Level und viel Mana, und verursachen Schaden bei Bossen in Quests.\n* Intelligenz ist ihr primäres Attribut, welches ihr Mana und ihre Erfahrung erhöht.\n* Wahrnehmung ist ihr sekundäres Attribut, welches ihr gefundenes Gold und ihre gefundenen Gegenstände vermehrt.\n* Die Fähigkeiten der Magier bewirken, dass ihre Aufgaben Strähnen eingefroren werden, stellen das Mana ihrer Gruppenmitglieder wieder her, und erhöhen ihre Intelligenz.\n* Erwäge, einen Magier zu spielen, wenn du durch das schnelle Erreichen neuer Level und das Beisteuern von Schaden in Boss Quests motiviert wirst.\n#### **Schurke**\n* Die Schurken bekommen die meisten erbeuteten Gegenstände und das meiste Gold beim Erledigen von Aufgaben und haben eine höhere Chance, kritische Treffer zu erzielen, was ihnen noch mehr Erfahrung und Gold beschert.\n* Wahrnehmung ist ihr primäres Attribut, welches ihr gefundenes Gold und ihre gefundenen Gegenstände vermehrt.\n* Stärke ist ihr sekundäres Attribut, welches den Schaden erhöht, den sie verursachen.\n* Die Fähigkeiten der Schurken helfen ihnen, versäumten Tagesaufgaben auszuweichen, Gold zu klauen und die Wahrnehmung ihrer Gruppenmitglieder zu erhöhen.\n* Erwäge, einen Schurken zu spielen, wenn du durch Belohnungen sehr motiviert wirst.",
|
||||
"faqQuestion68": "Wie kann ich den Verlust von HP verhindern?",
|
||||
"webFaqAnswer68": "Wenn du häufig LP verlierst, probiere diese Tipps aus:\n\n– Pausiere deine täglichen Aufgaben. Die Schaltfläche „Schaden pausieren“ in den Einstellungen verhindert, dass du HP für verpasste Aufgaben verlierst.\n– Passe den Zeitplan deiner täglichen Aufgaben an. Indem du sie so einstellst, dass sie nie fällig sind, kannst du sie trotzdem abschließen und Belohnungen erhalten, ohne HP zu verlieren.\n– Versuche, Klassenfertigkeiten einzusetzen:\n– Schurken können „Schleichen“ einsetzen, um Schaden durch verpasste tägliche Aufgaben zu vermeiden.\n– Krieger können „Gewaltschlag“ einsetzen, um die Röte einer täglichen Aufgabe zu verringern und so den erlittenen Schaden beim Verpassen zu reduzieren.\n– Heiler können „Brennende Helle“ einsetzen, um die Röte einer täglichen Aufgabe zu verringern und so den erlittenen Schaden beim Verpassen zu reduzieren"
|
||||
}
|
||||
|
||||
@@ -28,16 +28,16 @@
|
||||
"marketing1Lead1Title": "Mache dein Leben zum Spiel",
|
||||
"marketing1Lead1": "Habitica ist die perfekte App, für alle die Probleme mit ToDo-Listen haben. Wir verwenden bekannte Spiel-Mechaniken wie Belohnungen in Gold, XP und Gegenstände, die dir dabei helfen, dich produktiver zu fühlen und dein Erfolgserlebnis zu steigern, wenn du Aufgaben vollendest. Je besser Du Dich dabei anstellst, umso weiter kommst Du im Spiel.",
|
||||
"marketing1Lead2Title": "Rüsten dich mit Stil aus",
|
||||
"marketing1Lead2": "Sammele Schwerter, Rüstungen und vieles mehr mit Gold, welches du beim Vollenden von Aufgaben verdienst. Mit hunderten von Ausrüstungsstücken, die du sammeln und auswählen kannst, werden dir nie Kombinationen zum Ausprobieren ausgehen. Optimiere deine Statistik, deinen Style oder beides zusammen! ",
|
||||
"marketing1Lead2": "Sammele Schwerter, Rüstungen und vieles mehr mit dem Gold, das du beim Vollenden von Aufgaben verdienst. Mit hunderten von Stücken zum Sammeln, aus denen du auswählen kannst, werden dir die Kombinationen zum Ausprobieren nie ausgehen. Optimiere deine Werte, deinen Style oder beides! ",
|
||||
"marketing1Lead3Title": "Verdiene Belohnungen für deine Bemühungen",
|
||||
"marketing1Lead3": "Etwas zu haben, auf das man sich freuen kann, mag den Unterschied ausmachen, ob man eine Aufgabe erledigt oder ob sie einen wochenlang quält. Wenn das Leben keine Belohnung bietet ist Habitica für dich da! Du wirst für jede Aufgabe belohnt, aber Überraschungen gibt es an jeder Ecke - also mach weiter so! ",
|
||||
"marketing2Header": "Schließe dich mit Freunden zusammen",
|
||||
"marketing2Header": "Verbünde dich mit Freunden",
|
||||
"marketing2Lead1Title": "Soziale Produktivität",
|
||||
"marketing2Lead1": "Hole dir einen Motivationsschub, indem du mit anderen zusammenarbeitest, konkurrierst und interagierst! Habitica wurde entwickelt, um den effektivsten Teil eines jeden Selbstverbesserungsprogramms zu nutzen: soziale Verantwortung.",
|
||||
"marketing2Lead2Title": "Bekämpfe Monster in Quests",
|
||||
"marketing2Lead2": "Nimm dich einer unserer hunderten von Quests mit einer Gruppe von Freunden an, um dich ins Getümmel zu stürzen. Die Monster der Quests bringen deine Verantwortlichkeit auf die Spitze. Wenn du vergisst, Zahnseide zu benutzen, schadet das allen!",
|
||||
"marketing2Lead3Title": "Fordert einander heraus",
|
||||
"marketing2Lead3": "Nimm an Herausforderungen teil, die von unserer Community erstellt wurden und erhalte zusammengestellte Aufgabenlisten, die deinen Interessen und Zielen entsprechen. Gib dein Bestes dabei, um den Edelsteinpreis zu wetteifern, der dem Gewinner verliehen wird!",
|
||||
"marketing2Lead3": "Nimm an Herausforderungen teil, die von unserer Community erstellt wurden, und erhalte handverlesene Aufgabenlisten, die deinen Interessen und Zielen entsprechen. Gib dein Bestes, um den Edelsteinpreis zu gewinnen!",
|
||||
"marketing3Header": "Weitere Möglichkeiten, Habitica zu nutzen",
|
||||
"marketing3Lead1": "Du kannst Habitica auf deinem Android- oder iOS-Gerät nutzen, um Aufgaben überall abzuhaken. Schau dir unsere preisgekrönten Apps an, um einen neuen Ansatz zur Erledigung von Aufgaben zu finden.",
|
||||
"marketing3Lead2Title": "Open-Source Community",
|
||||
@@ -124,7 +124,7 @@
|
||||
"passwordReset": "Wenn wir Deine E-Mail-Adresse oder Deinen Benutzernamen kennen, wurden Anweisungen zum Passwort-Zurücksetzen dorthin verschickt.",
|
||||
"invalidLoginCredentialsLong": "Deine E-Mail-Adresse, deine Benutzername oder Passwort sind nicht korrekt. Bitte versuche es erneut oder wähle \"Passwort vergessen.\"",
|
||||
"invalidCredentials": "Es gibt kein Konto, das diese Anmeldedaten verwendet.",
|
||||
"accountSuspended": "Dieser Account, Benutzer-ID \"<%= userId %>\", wurde gesperrt wegen Verletzung der Community-Richtlinien (https://habitica.com/static/community-guidelines) oder der Allgemeinen Geschäftsbedingungen (https://habitica.com/static/terms). Für genauere Angaben oder um die Aufgebung der Sperre zu erbitten, kontaktiere bitte unseren Community Manager unter <%= communityManagerEmail %> oder bitte Deine Eltern oder Erziehungsberechtigten ihm zu schreiben. Bitte nenne Deine @Benutzer-ID in der E-Mail.",
|
||||
"accountSuspended": "Dieser Account \"<%= userId %>\", wurde gesperrt. Für weitere Informationen oder um Widerspruch einzulegen, sende bitte eine E-Mail an admin@habitica.com mit deinem Habitica-Benutzernamen oder User-ID.",
|
||||
"accountSuspendedTitle": "Dieser Account wurde suspendiert",
|
||||
"unsupportedNetwork": "Dieses Netzwerk wird aktuell nicht unterstützt.",
|
||||
"cantDetachSocial": "Der Account hat nur noch diese Authentifizierung, sie kann nicht getrennt werden.",
|
||||
@@ -132,12 +132,12 @@
|
||||
"invalidReqParams": "Ungültige Anfrageparameter.",
|
||||
"memberIdRequired": "\"member\" muss eine gültige UUID sein.",
|
||||
"heroIdRequired": "\"herold\" muss eine gültige UUID sein.",
|
||||
"cannotFulfillReq": "Bitte gib eine gültige E-Mail-Adresse ein. Schreibe eine E-Mail an admin@habitica.com, falls dieser Fehler weiterhin auftritt.",
|
||||
"cannotFulfillReq": "Diese Mailadresse ist bereits in Gebrauch. Du kannst versuchen dich einzuloggen oder eine andere Mailadresse zur Registrierung verwenden. Wende dich an admin@habitica.com, falls du Hilfe benötigst.",
|
||||
"modelNotFound": "Diese Vorlage existiert nicht.",
|
||||
"signUpWithSocial": "Mit <%= social %> fortfahren",
|
||||
"loginWithSocial": "Mit <%= social %> anmelden",
|
||||
"confirmPassword": "Passwort bestätigen",
|
||||
"usernameLimitations": "Benutzernamen können jederzeit geändert werden. Sie müssen zwischen 1 und 20 Zeichen lang sein und dürfen nur Buchstaben von A bis Z, Zahlen von 0 bis 9, Bindestriche und Unterstriche beinhalten.",
|
||||
"usernameLimitations": "Benutzernamen können jederzeit geändert werden. Sie müssen zwischen 1 und 20 Zeichen lang sein und dürfen nur Buchstaben von a bis z, Zahlen von 0 bis 9, Bindestriche und Unterstriche beinhalten.",
|
||||
"usernamePlaceholder": "z.B., HabitRabbit",
|
||||
"emailPlaceholder": "z.B., gryphon@beispiel.com",
|
||||
"passwordPlaceholder": "z.B., ******************",
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
"weaponSpecialFallRogueText": "Silberner Pflock",
|
||||
"weaponSpecialFallRogueNotes": "Befördert Untote dauerhaft ins Jenseits. Notfalls auch gegen Werwölfe einsetzbar - Vielseitigkeit kann nie schaden. Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2014 Herbstausrüstung.",
|
||||
"weaponSpecialFallWarriorText": "Krabbige Klaue der Wissenschaft",
|
||||
"weaponSpecialFallWarriorNotes": "Diese Krabbige Klaue ist technisch auf dem neusten Stand! Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2014 Sommerausrüstung.",
|
||||
"weaponSpecialFallWarriorNotes": "Diese Krabbige Klaue ist technisch auf dem neusten Stand! Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2014 Herbstausrüstung.",
|
||||
"weaponSpecialFallMageText": "Fliegender Besen",
|
||||
"weaponSpecialFallMageNotes": "Dieser fliegende Besen ist schneller als ein Drache! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limitierte Ausgabe 2014 Herbstausrüstung.",
|
||||
"weaponSpecialFallHealerText": "Skarabäus-Zauberstab",
|
||||
@@ -526,7 +526,7 @@
|
||||
"armorSpecialFall2015RogueText": "Geflügelte Kampfrüstung",
|
||||
"armorSpecialFall2015RogueNotes": "Flieg in den Kampf! Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2015 Herbstausrüstung.",
|
||||
"armorSpecialFall2015WarriorText": "Vogelscheuchenrüstung",
|
||||
"armorSpecialFall2015WarriorNotes": "Obwohl sie nur mit Stroh ausgestopft ist, ist diese Rüstung extrem rüstig! Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2015 Herbstausrüstung.",
|
||||
"armorSpecialFall2015WarriorNotes": "Zwar nur mit Stroh ausgestopft, ist diese Rüstung doch extrem rüstig! Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe 2015 Herbstausrüstung.",
|
||||
"armorSpecialFall2015MageText": "Genähte Roben",
|
||||
"armorSpecialFall2015MageNotes": "Jede Masche dieser Rüstung schimmert mit Zauberei. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe 2015 Herbstausrüstung.",
|
||||
"armorSpecialFall2015HealerText": "Roben des Tränkebrauers",
|
||||
@@ -1873,7 +1873,7 @@
|
||||
"weaponSpecialFall2019WarriorText": "Krallen-Kriegsgabel",
|
||||
"weaponSpecialFall2019RogueNotes": "Ob Du ein Orchester dirigierst oder eine Arie singst, dieses Hilfsmittel hält Deine Hände frei für dramatische Gesten! Erhöht Stärke um <%= str %>. Limitierte Ausgabe 2019 Herbstausrüstung.",
|
||||
"weaponSpecialFall2019RogueText": "Notenständer",
|
||||
"headSpecialFall2019RogueNotes": "Hast Du diese Kopfbedeckung in einer Auktion vermeintlich verwünschter Kostümstücke oder auf dem Speicher eines extentrischen Großelternteils gefunden? Welchen Ursprungs auch immer, ihr Alter und ihre Abnutzung tragen zu Deiner mystischen Aura bei. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2019 Herbstausrüstung.",
|
||||
"headSpecialFall2019RogueNotes": "Hast Du diese Kopfbedeckung in einer Auktion vermeintlich verwünschter Kostümstücke oder auf dem Speicher eines exzentrischen Großelternteils gefunden? Welchen Ursprungs auch immer, ihr Alter und ihre Abnutzung tragen zu Deiner mystischen Aura bei. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2019 Herbstausrüstung.",
|
||||
"headSpecialFall2019RogueText": "Antiker Opernhut",
|
||||
"armorMystery201909Notes": "Deine robuste Außenschale ist schützend, aber es ist angeraten nach Eichhörnchen Ausschau zu halten... Gewährt keinen Attributionsbonus. Abonnentengegenstand, September 2019.",
|
||||
"armorMystery201909Text": "Affabler Eichelanzug",
|
||||
@@ -2409,7 +2409,7 @@
|
||||
"weaponSpecialFall2021MageNotes": "Wissen sucht Wissen. Geformt aus Erinnerungen und Sehnsüchten giert diese furchteinflößende Hand nach mehr. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limitierte Ausgabe 2021 Herbstausrüstung.",
|
||||
"weaponSpecialFall2021HealerText": "Beschwörungszauberstab",
|
||||
"headArmoireHeraldsCapNotes": "Dieser Herolds-Hut kommt mit einer flotten Feder. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Herolds Set (Gegenstand 2 von 4).",
|
||||
"armorSpecialFall2021RogueNotes": "Es hat einen Totenkopf, eine Ledertunika und Metallnieten! Großartig! Allerdings bietet es kein luftdichtes Siegel gegen Pampe! Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2021 Herbstausrüstung.",
|
||||
"armorSpecialFall2021RogueNotes": "Es hat einen Totenkopf, eine Ledertunika und Metallnieten! Großartig! Allerdings bietet es kein luftdichtes Siegel gegen Pampe! Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2021 Herbstausrüstung.",
|
||||
"armorSpecialFall2021WarriorText": "Formaler Wollanzug",
|
||||
"armorSpecialFall2021WarriorNotes": "Ein atemberaubender Anzug, perfekt für die Überquerung von Brücken in stockdunkler Nacht. Erhöht Ausdauer um <%= con %>. Limiterte Ausgabe 2021, Herbstausrüstung.",
|
||||
"armorSpecialFall2021MageText": "Robe der abgrundtiefen Finsternis",
|
||||
@@ -2800,7 +2800,7 @@
|
||||
"armorMystery202406Text": "Phantom-Seeräuber Kleidung",
|
||||
"headMystery202406Text": "Phantom-Seeräuber Hut",
|
||||
"eyewearMystery202406Text": "Phantom-Seeräuber Maske",
|
||||
"weaponArmoirePaintbrushNotes": "Ein Ruck purer Inspiration durchdringt dich, wenn du diesen Frabpinsel aufhebst, und ermöglicht dir, alles zu malen, was du dir vorstellen kannst. Erhöht Intelligenz um <%= int %>.Verzauberter Schrank: Malerset (Gegenstand 3 von 4).",
|
||||
"weaponArmoirePaintbrushNotes": "Ein Ruck purer Inspiration durchdringt Dich, wenn Du diesen Frabpinsel aufhebst, und ermöglicht Dir, alles zu malen, was Du Dir vorstellen kannst. Erhöht Intelligenz um <%= int %>.Verzauberter Schrank: Malerset (Gegenstand 3 von 4).",
|
||||
"weaponArmoirePaintbrushText": "Farbpinsel",
|
||||
"weaponArmoireMopText": "Mopp",
|
||||
"weaponArmoireCleaningClothText": "Putzlappen",
|
||||
@@ -3074,7 +3074,7 @@
|
||||
"headSpecialFall2024RogueText": "Schwarze Katzenmaske",
|
||||
"headSpecialFall2024HealerText": "Space Invader-Maske",
|
||||
"headSpecialWinter2025HealerNotes": "Es ist nicht nötig, sie zu entwirren, da sie bereits die Form eines Hutes haben. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Winterausrüstung 2024-2025.",
|
||||
"headSpecialWinter2025MageText": "Aurorahut",
|
||||
"headSpecialWinter2025MageText": "Aurora Kopfschmuck",
|
||||
"headSpecialWinter2025MageNotes": "Dieser Hut ist mehr als nur ein schicker Fascinator, er lässt dich wie das Polarlicht selbst aussehen. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Winterausrüstung 2024-2025.",
|
||||
"headSpecialSummer2024MageText": "Seeanemonen-Hut",
|
||||
"headSpecialSummer2024HealerText": "Seeschneckenhaus",
|
||||
@@ -3437,5 +3437,38 @@
|
||||
"shieldSpecialFall2025WarriorText": "Sasquatch Schild",
|
||||
"shieldSpecialFall2025WarriorNotes": "Verschaffe dir etwas mehr Zeit zum Nachdenken und Planen, indem du dich vor deinen nächsten Tagesaufgaben abschirmst. Erhöht die Konstitution um <%= con %>. Limitierte Auflage Herbst 2025 Ausrüstung.",
|
||||
"shieldSpecialFall2025HealerNotes": "Verschaffe dir etwas mehr Zeit, um Vorräte zu sammeln, indem du dich vor deinen Aufgaben abschirmst. Erhöht die Konstitution um <%= con %>. Limitierte Ausgabe Herbst 2025 Ausrüstung.",
|
||||
"shieldArmoireSoftOrangePillowNotes": "Der vorbereitete Krieger packt für jede Expedition ein Kissen ein. Mach dich bereit, neue Verpflichtungen zu übernehmen ... sogar während du ein Nickerchen machst. Erhöht Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Kleiderschrank: Orangenes Loungewear-Set (Gegenstand 3 von 3)."
|
||||
"shieldArmoireSoftOrangePillowNotes": "Der vorbereitete Krieger packt für jede Expedition ein Kissen ein. Mach dich bereit, neue Verpflichtungen zu übernehmen ... sogar während du ein Nickerchen machst. Erhöht Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Kleiderschrank: Orangenes Loungewear-Set (Gegenstand 3 von 3).",
|
||||
"bodyMystery202509Text": "Schal des windgepeitschten Wanderers",
|
||||
"armorSpecialFall2025RogueNotes": "Ein hartes und schmales Ziel in dieser saisonalen Rüstung ist am schwersten zu treffen. Erhöht die Wahrnehmung um <%= per %>. Limitierte Ausgabe Herbst 2025 Ausrüstung.",
|
||||
"weaponSpecialWinter2026WarriorText": "Raureif Sense",
|
||||
"weaponSpecialWinter2026WarriorNotes": "Sensen helfen beim Schneiden, Ernten und beim Abdecken großer Bereiche - alles Dinge, die du beim Verfeinern von Aufgabenlisten brauchst. Erhöht Stärke um <%= str %>. Limitierte Ausgabe Winterausrüstung 2025-2026.",
|
||||
"weaponSpecialWinter2026RogueText": "Skistock",
|
||||
"weaponSpecialWinter2026RogueNotes": "Stistöcke helfen dir dabei, Balance, Stabilität und Timing zu wahren - alles Dinge, die du brauchst, um wirklich produktiv zu sein. Erhöht Stärke um <%= str %>. Limitierte Ausgabe Winterausrüstung 2025-2026.",
|
||||
"weaponSpecialWinter2026HealerText": "Polarstab",
|
||||
"weaponSpecialWinter2026HealerNotes": "Stäbe dienen als Stütze, Stabilitätshilfe und zur Richtungsfindung - alles Dinge, die dir beim Bezwingen einer Aufgabenliste wirklich helfen. Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Winterausrüstung 2025-2026.",
|
||||
"weaponSpecialWinter2026MageNotes": "Kandelaber helfen, indem sie mehrere Kerzen gleichzeitig halten - folge diesem Beispiel, wenn du das nächste Mal multitasken musst. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limitierte Ausgabe Winterausrüstung 2025-2026.",
|
||||
"weaponSpecialWinter2026MageText": "Kandelaber Stab",
|
||||
"weaponMystery202512Text": "Klinge des Keks-Champions",
|
||||
"weaponMystery202512Notes": "Ein glänzendes Schwert, gegossen aus Zucker, Minze und arkanen Zaubern. Gewährt keinen Attributbonus. Dezember 2025 Abonnentengegenstand.",
|
||||
"weaponArmoireBambooFluteNotes": "Hwhoooo! Hu-whooooo! Versammle Deine Gruppe zu einer Meditationssitzung oder einem Selbstfürsorge-Nickerchen, während ihr euch zu den Klängen dieser Bambusflöte entspannt. Erhöht Konstitution und Intelligenz um jeweils <%= attrs %>. Verzauberter Schrank: Musikinstrumentenset 2 (Artikel 2 von 3)",
|
||||
"armorSpecialWinter2026WarriorNotes": "Eiszapfen brechen und gleiten bei jedem Schritt auf dem Weg zum Abschluss Deiner täglichen Aufgaben. Erhöht die Konstitution um <%= con %>. Limitierte Auflage Winter 2025-2026 Ausrüstung.",
|
||||
"armorMystery202512Notes": "Bereit für den Kampf mit diesem Schild, das sowohl süß als auch stark ist. Verleiht keinen Vorteil. Dezember 2025 Abonnenten-Gegenstand.",
|
||||
"armorSpecialWinter2026HealerNotes": "Wie eine natürliche Lichtshow wirst Du auf Deinem Weg zum Abschluss Deiner täglichen Aufgaben umwerfend aussehen. Erhöht die Konstitution um <%= con %>. Limitierte Auflage Winter 2025-2026 Ausrüstung.",
|
||||
"armorArmoireLoneCowpokeOutfitText": "Outfit eines einsamen Cowboys",
|
||||
"armorArmoireLoneCowpokeOutfitNotes": "Halt, halt! Möchtest Du ein Zeichen setzen, wenn Du als geheimnisvoller Fremder in die Stadt reitest, bereit, produktiv zu sein? Hier ist das perfekte Outfit, komplett mit Gamaschen und einer glänzenden, silbernen Gürtelschnalle. Erhöht die Konstitution um <%= con %>. Verzauberter Kleiderschrank: Set „Einsamer Cowboy“ (Artikel 2 von 2)",
|
||||
"headSpecialWinter2026WarriorNotes": "Behalte Deinen Fokus und Deine Konzentration bei, während Du dich in dieser Saison größere Ziele setzen. Erhöht die Kraft um <%= str %>. Limitierte Auflage 2025-2026 Winterausrüstung.",
|
||||
"weaponMystery202601Text": "Aegis des Winters",
|
||||
"weaponMystery202601Notes": "Ein eisiger Blasenschild, der magischen Schutz vor gegnerischen Elementen gewährt. Verleiht keinen Vorteil. Januar 2026 Abonnenten-Gegenstand.",
|
||||
"weaponArmoireBambooFluteText": "Bambusflöte",
|
||||
"weaponArmoirePrettyPinkParasolText": "Hübscher rosa Sonnenschirm",
|
||||
"weaponArmoirePrettyPinkParasolNotes": "Hübsch und praktisch ist die beste Kombination. Und für eine besonders beeindruckende Präsentation dreh diesen Sonnenschirm einfach einmal um! Erhöht alle Werte um jeweils <%= attrs %>. Verzauberter Schrank: Pretty in Pink-Set (Artikel 1 von 2)",
|
||||
"armorSpecialWinter2026WarriorText": "Raureif-Sensenmann-Anzug",
|
||||
"armorSpecialWinter2026RogueText": "Skianzug und Skier",
|
||||
"armorSpecialWinter2026RogueNotes": "Rase schnell die Pisten hinunter, um Deine täglichen Aufgaben zu erledigen. Erhöht die Wahrnehmung um <%= pro %>. Limitierte Auflage Winter 2025-2026 Ausrüstung.",
|
||||
"armorSpecialWinter2026HealerText": "Polar-Mantel",
|
||||
"armorSpecialWinter2026MageText": "Mitternachtskerzen-Gewand",
|
||||
"armorSpecialWinter2026MageNotes": "Gleite geschmeidig wie Wachs über Deinen Weg, um Deine täglichen Aufgaben zu erledigen. Erhöht die Intelligenz um <%= int %>. Limitierte Auflage Winter 2025-2026 Ausrüstung.",
|
||||
"armorMystery202512Text": "Keks-Champion-Rüstung",
|
||||
"headSpecialWinter2026WarriorText": "Frostsichel-Helm",
|
||||
"headSpecialWinter2026RogueText": "Skimaske und Schutzbrille"
|
||||
}
|
||||
|
||||