mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-10-27 11:12:28 +01:00
Compare commits
89 Commits
v5.36.2
...
fiz/ToS-st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd06148422 | ||
|
|
a0b179561b | ||
|
|
9a1fb18959 | ||
|
|
876d5a67d6 | ||
|
|
3078af8f2a | ||
|
|
dad1440138 | ||
|
|
12773d539e | ||
|
|
ae4130b108 | ||
|
|
ad0614282e | ||
|
|
2ea0b64603 | ||
|
|
bd1aa1e417 | ||
|
|
5a7704aed7 | ||
|
|
7c49b845d6 | ||
|
|
1ee172139d | ||
|
|
6447b9ab4b | ||
|
|
5c414099d9 | ||
|
|
5e8e1179aa | ||
|
|
7e86a62624 | ||
|
|
1ba9dda0ed | ||
|
|
2feadd6125 | ||
|
|
227e5ceaa8 | ||
|
|
f77ab5a3ab | ||
|
|
1916faf647 | ||
|
|
80ecb5cef1 | ||
|
|
75c36e6622 | ||
|
|
78330c975a | ||
|
|
95266f6cb3 | ||
|
|
e9b2c1b51a | ||
|
|
2a2bea07ab | ||
|
|
ea60ddbf4c | ||
|
|
1c2ca0e478 | ||
|
|
ef2b7eb928 | ||
|
|
3d16387a61 | ||
|
|
93b7770eaa | ||
|
|
a9f84d3307 | ||
|
|
efe0b3cd9e | ||
|
|
96731da380 | ||
|
|
0c5dd5d8b5 | ||
|
|
2f943a22e6 | ||
|
|
666184d7e4 | ||
|
|
17d22dda3f | ||
|
|
d1a18c121d | ||
|
|
836d7f3991 | ||
|
|
ace9c3c46a | ||
|
|
068640311e | ||
|
|
f26d2a59ae | ||
|
|
03c7e9172e | ||
|
|
6fdc072ec3 | ||
|
|
e68661c04b | ||
|
|
4f567592ea | ||
|
|
63c9b7a894 | ||
|
|
eaec39188e | ||
|
|
ba6940eb81 | ||
|
|
f8a3e4d673 | ||
|
|
2727da6f6c | ||
|
|
fa97852e38 | ||
|
|
2c7da25a25 | ||
|
|
9a072e3e76 | ||
|
|
823b339d27 | ||
|
|
fe98d9485d | ||
|
|
407e1bb560 | ||
|
|
98a6535dc3 | ||
|
|
9948e8ee44 | ||
|
|
bce07ec357 | ||
|
|
836807aa1e | ||
|
|
ebbcbef6d5 | ||
|
|
ccc6c9867f | ||
|
|
20d31ed8c8 | ||
|
|
39ff6cbe05 | ||
|
|
1bf2efa885 | ||
|
|
4b45a6389c | ||
|
|
5ba7d2395e | ||
|
|
972f23e235 | ||
|
|
9f599b0c8e | ||
|
|
b937c2df0b | ||
|
|
9c4396027a | ||
|
|
2bab20d032 | ||
|
|
cb2ee670e3 | ||
|
|
b65d23d535 | ||
|
|
007cdf0ca2 | ||
|
|
1e4799bac6 | ||
|
|
47222445ad | ||
|
|
126b382da1 | ||
|
|
ec78831a81 | ||
|
|
9bfb2afd9c | ||
|
|
389124b83f | ||
|
|
eb25330296 | ||
|
|
29892ff5e3 | ||
|
|
99a31b322a |
@@ -1,11 +0,0 @@
|
||||
import gulp from 'gulp';
|
||||
import nodemon from 'gulp-nodemon';
|
||||
|
||||
import pkg from '../package.json';
|
||||
|
||||
gulp.task('nodemon', done => {
|
||||
nodemon({
|
||||
script: pkg.main,
|
||||
});
|
||||
done();
|
||||
});
|
||||
@@ -49,12 +49,6 @@ function integrationTestCommand (testDir) {
|
||||
}
|
||||
|
||||
/* Test task definitions */
|
||||
gulp.task('test:nodemon', gulp.series(done => {
|
||||
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
|
||||
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
|
||||
done();
|
||||
}, 'nodemon'));
|
||||
|
||||
gulp.task('test:prepare:mongo', cb => {
|
||||
const mongooseOptions = getDefaultConnectionOptions();
|
||||
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
||||
|
||||
@@ -21,7 +21,6 @@ if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-e
|
||||
require('./gulp/gulp-build'); // eslint-disable-line global-require
|
||||
require('./gulp/gulp-console'); // eslint-disable-line global-require
|
||||
require('./gulp/gulp-sprites'); // eslint-disable-line global-require
|
||||
require('./gulp/gulp-start'); // eslint-disable-line global-require
|
||||
require('./gulp/gulp-tests'); // eslint-disable-line global-require
|
||||
require('./gulp/gulp-transifex-test'); // eslint-disable-line global-require
|
||||
require('gulp').task('default', gulp.series('test')); // eslint-disable-line global-require
|
||||
|
||||
Submodule habitica-images updated: 142936a93a...aa72332019
@@ -37,7 +37,7 @@ let consoleStamp = require('console-stamp');
|
||||
consoleStamp(console);
|
||||
|
||||
// Initialize configuration
|
||||
require('../../website/server/libs/api-v3/setupNconf')();
|
||||
require('../../website/server/libs/api-v3/setupNconf').default();
|
||||
|
||||
let MONGODB_OLD = nconf.get('MONGODB_OLD');
|
||||
let MONGODB_NEW = nconf.get('MONGODB_NEW');
|
||||
|
||||
@@ -32,7 +32,7 @@ let moment = require('moment');
|
||||
consoleStamp(console);
|
||||
|
||||
// Initialize configuration
|
||||
require('../../website/server/libs/api-v3/setupNconf')();
|
||||
require('../../website/server/libs/api-v3/setupNconf').default();
|
||||
|
||||
let MONGODB_OLD = nconf.get('MONGODB_OLD');
|
||||
let MONGODB_NEW = nconf.get('MONGODB_NEW');
|
||||
|
||||
@@ -6,7 +6,7 @@ require('@babel/register'); // eslint-disable-line import/no-extraneous-dependen
|
||||
function setUpServer () {
|
||||
const nconf = require('nconf'); // eslint-disable-line global-require, no-unused-vars
|
||||
const mongoose = require('mongoose'); // eslint-disable-line global-require, no-unused-vars
|
||||
const setupNconf = require('../website/server/libs/setupNconf'); // eslint-disable-line global-require
|
||||
const setupNconf = require('../website/server/libs/setupNconf').default; // eslint-disable-line global-require
|
||||
|
||||
setupNconf();
|
||||
|
||||
|
||||
610
package-lock.json
generated
610
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "5.36.2",
|
||||
"version": "5.38.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "habitica",
|
||||
"version": "5.36.2",
|
||||
"version": "5.38.2",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
@@ -43,7 +43,6 @@
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-filter": "^7.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-nodemon": "^2.5.0",
|
||||
"gulp.spritesmith": "^6.13.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"helmet": "^4.6.0",
|
||||
@@ -55,12 +54,11 @@
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-recur": "^1.0.7",
|
||||
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
|
||||
"mongoose": "^8.9.5",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.12.1",
|
||||
"node-gcm": "^1.0.5",
|
||||
"nodemon": "^2.0.20",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.5.3",
|
||||
"passport-facebook": "^3.0.0",
|
||||
@@ -87,7 +85,7 @@
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.8.2",
|
||||
"chai": "^4.3.7",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
@@ -151,14 +149,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -542,14 +532,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -1699,9 +1681,10 @@
|
||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
|
||||
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
|
||||
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@@ -1786,231 +1769,6 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
|
||||
"integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
|
||||
"integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
|
||||
"integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
|
||||
"integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
|
||||
"integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
|
||||
"integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
|
||||
"integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
|
||||
"integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
|
||||
@@ -2026,96 +1784,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
|
||||
"integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
||||
@@ -4170,6 +3838,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -4200,6 +3876,41 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/nodemon": {
|
||||
"version": "2.0.22",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
|
||||
"integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2",
|
||||
"debug": "^3.2.7",
|
||||
"ignore-by-default": "^1.0.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"semver": "^5.7.1",
|
||||
"simple-update-notifier": "^1.0.7",
|
||||
"supports-color": "^5.5.0",
|
||||
"touch": "^3.1.0",
|
||||
"undefsafe": "^2.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"nodemon": "bin/nodemon.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nodemon"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/nodemon/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
@@ -4214,6 +3925,36 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/simple-update-notifier": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
|
||||
"dependencies": {
|
||||
"semver": "~7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/simple-update-notifier/node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/apidoc/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
@@ -4787,10 +4528,11 @@
|
||||
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -6193,7 +5935,7 @@
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
@@ -7013,15 +6755,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/coa/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/coa/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -7115,6 +6848,7 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
@@ -8993,14 +8727,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-habitrpg/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-habitrpg/node_modules/ignore": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||
@@ -10507,7 +10233,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"devOptional": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/filename-reserved-regex": {
|
||||
"version": "2.0.0",
|
||||
@@ -11593,24 +11319,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-watcher/node_modules/fsevents": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-watcher/node_modules/glob-parent": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
||||
@@ -12300,16 +12008,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/gulp-nodemon": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/gulp-nodemon/-/gulp-nodemon-2.5.0.tgz",
|
||||
"integrity": "sha512-vXfaP72xo2C6XOaXrNcLEM3QqDJ1x21S3x97U4YtzN2Rl2kH57++aFkAVxe6BafGRSTxs/xVfE/jNNlCv5Ym2Q==",
|
||||
"dependencies": {
|
||||
"colors": "^1.2.1",
|
||||
"gulp": "^4.0.0",
|
||||
"nodemon": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/gulp.spritesmith": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/gulp.spritesmith/-/gulp.spritesmith-6.13.0.tgz",
|
||||
@@ -12542,6 +12240,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
|
||||
@@ -15335,15 +15041,6 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@@ -15407,9 +15104,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/moment-recur": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/moment-recur/-/moment-recur-1.0.7.tgz",
|
||||
"integrity": "sha512-jpBQn6H62gCnEYjtYdLfK/VdPZHEXo1t8RrVItHVVS67SRvByyJBNBa3WQSGTe+8H0smcYO/79FYTA9LGMVdQw==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "git+ssh://git@github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
|
||||
"integrity": "sha512-LaqXV3izeVQjG0EKAEQQWdneMiff5JR6oBKcfgl0uFOTeaFzLKG62psk7r2VE7RTBdhYigt6KNaLZR7GdWPEIA==",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"moment": "<3.0.0"
|
||||
}
|
||||
@@ -15949,7 +15647,7 @@
|
||||
"version": "2.18.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
|
||||
"integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==",
|
||||
"devOptional": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanomatch": {
|
||||
"version": "1.2.13",
|
||||
@@ -16312,68 +16010,6 @@
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "2.0.22",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
|
||||
"integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2",
|
||||
"debug": "^3.2.7",
|
||||
"ignore-by-default": "^1.0.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"semver": "^5.7.1",
|
||||
"simple-update-notifier": "^1.0.7",
|
||||
"supports-color": "^5.5.0",
|
||||
"touch": "^3.1.0",
|
||||
"undefsafe": "^2.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"nodemon": "bin/nodemon.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nodemon"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/noop-logger": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
|
||||
@@ -18200,9 +17836,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
||||
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -19213,15 +18850,6 @@
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/run-rs/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/run-rs/node_modules/mongodb": {
|
||||
"version": "3.6.12",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.12.tgz",
|
||||
@@ -19745,25 +19373,6 @@
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
|
||||
"dependencies": {
|
||||
"semver": "~7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier/node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/sinon": {
|
||||
"version": "15.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz",
|
||||
@@ -20790,15 +20399,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/svgo/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/svgo/node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
|
||||
17
package.json
17
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "5.36.2",
|
||||
"version": "5.38.2",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
@@ -38,7 +38,6 @@
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-filter": "^7.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-nodemon": "^2.5.0",
|
||||
"gulp.spritesmith": "^6.13.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"helmet": "^4.6.0",
|
||||
@@ -50,12 +49,11 @@
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-recur": "^1.0.7",
|
||||
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
|
||||
"mongoose": "^8.9.5",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.12.1",
|
||||
"node-gcm": "^1.0.5",
|
||||
"nodemon": "^2.0.20",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.5.3",
|
||||
"passport-facebook": "^3.0.0",
|
||||
@@ -100,23 +98,22 @@
|
||||
"test:sanity": "nyc --silent --no-clean mocha test/sanity --recursive",
|
||||
"test:common": "nyc --silent --no-clean mocha test/common --recursive",
|
||||
"test:content": "nyc --silent --no-clean mocha test/content --recursive",
|
||||
"test:nodemon": "gulp test:nodemon",
|
||||
"coverage": "nyc report --reporter=html --report-dir coverage/results; open coverage/results/index.html",
|
||||
"sprites": "gulp sprites:compile",
|
||||
"client:dev": "cd website/client && npm run serve",
|
||||
"client:build": "cd website/client && npm run build",
|
||||
"client:unit": "cd website/client && npm run test:unit",
|
||||
"start": "gulp nodemon",
|
||||
"start": "node --watch ./website/server/index.js",
|
||||
"start:simple": "node ./website/server/index.js",
|
||||
"debug": "gulp nodemon --inspect",
|
||||
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||
"mongo:test": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data-testing --number 1 --quiet",
|
||||
"debug": "node --watch --inspect ./website/server/index.js",
|
||||
"mongo:dev": "run-rs -v 7.0.23 -l ubuntu2404 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||
"mongo:test": "run-rs -v 7.0.23 -l ubuntu2404 --keep --dbpath mongodb-data-testing --number 1 --quiet",
|
||||
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
||||
"apidoc": "gulp apidoc",
|
||||
"heroku-postbuild": ".heroku/report_deploy.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.8.2",
|
||||
"chai": "^4.3.7",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
|
||||
@@ -71,15 +71,14 @@ async function deleteHabiticaData (user, email) {
|
||||
}
|
||||
|
||||
async function processEmailAddress (email) {
|
||||
const emailRegex = new RegExp(`^${email}$`, 'i');
|
||||
const localUsers = await User.find(
|
||||
{ 'auth.local.email': emailRegex },
|
||||
{ 'auth.local.email': email },
|
||||
{ _id: 1, apiToken: 1, auth: 1 },
|
||||
).exec();
|
||||
|
||||
const socialUsers = await User.find(
|
||||
{
|
||||
'auth.local.email': { $not: emailRegex },
|
||||
'auth.local.email': { $ne: email },
|
||||
$or: [
|
||||
{ 'auth.facebook.emails.value': email },
|
||||
{ 'auth.google.emails.value': email },
|
||||
|
||||
@@ -8,7 +8,17 @@ const TASK_VALUE_CHANGE_FACTOR = 0.9747;
|
||||
const MIN_TASK_VALUE = -47.27;
|
||||
|
||||
async function updateTeamTasks (team) {
|
||||
if (team.purchased.plan.dateTerminated) {
|
||||
const dateTerminated = new Date(team.purchased.plan.dateTerminated);
|
||||
if (dateTerminated < new Date()) {
|
||||
team.purchased.plan.customerId = undefined;
|
||||
team.markModified('purchased.plan');
|
||||
return team.save();
|
||||
}
|
||||
}
|
||||
|
||||
const toSave = [];
|
||||
|
||||
let teamLeader = await User.findOne({ _id: team.leader }, 'preferences').exec();
|
||||
|
||||
if (!teamLeader) { // why would this happen?
|
||||
@@ -93,12 +103,7 @@ async function updateTeamTasks (team) {
|
||||
export default async function processTeamsCron () {
|
||||
const activeTeams = await Group.find({
|
||||
'purchased.plan.customerId': { $exists: true },
|
||||
$or: [
|
||||
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
||||
{ 'purchased.plan.dateTerminated': null },
|
||||
{ 'purchased.plan.dateTerminated': { $gt: new Date() } },
|
||||
],
|
||||
}).exec();
|
||||
}, { cron: 1, leader: 1, purchased: 1 }).exec();
|
||||
|
||||
const cronPromises = activeTeams.map(updateTeamTasks);
|
||||
return Promise.all(cronPromises);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
import {
|
||||
generateRes,
|
||||
generateReq,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
import { authWithHeaders as authWithHeadersFactory } from '../../../../website/server/middlewares/auth';
|
||||
|
||||
const authPath = '../../../../website/server/middlewares/auth';
|
||||
|
||||
describe('auth middleware', () => {
|
||||
let res; let req; let
|
||||
@@ -16,6 +19,7 @@ describe('auth middleware', () => {
|
||||
|
||||
describe('auth with headers', () => {
|
||||
it('allows to specify a list of user field that we do not want to load', done => {
|
||||
const authWithHeadersFactory = requireAgain(authPath).authWithHeaders;
|
||||
const authWithHeaders = authWithHeadersFactory({
|
||||
userFieldsToExclude: ['items'],
|
||||
});
|
||||
@@ -35,6 +39,7 @@ describe('auth middleware', () => {
|
||||
});
|
||||
|
||||
it('makes sure some fields are always included', done => {
|
||||
const authWithHeadersFactory = requireAgain(authPath).authWithHeaders;
|
||||
const authWithHeaders = authWithHeadersFactory({
|
||||
userFieldsToExclude: [
|
||||
'items', 'auth.timestamps',
|
||||
@@ -60,5 +65,57 @@ describe('auth middleware', () => {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('errors with InvalidCredentialsError and code when token is wrong', done => {
|
||||
const authWithHeadersFactory = requireAgain(authPath).authWithHeaders;
|
||||
const authWithHeaders = authWithHeadersFactory({ userFieldsToExclude: [] });
|
||||
|
||||
req.headers['x-api-user'] = user._id;
|
||||
req.headers['x-api-key'] = 'totally-wrong-token';
|
||||
|
||||
authWithHeaders(req, res, err => {
|
||||
expect(err).to.exist;
|
||||
expect(err.name).to.equal('InvalidCredentialsError');
|
||||
expect(err.code).to.equal('invalid_credentials');
|
||||
expect(err.message).to.equal(res.t('invalidCredentials'));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when ENFORCE_CLIENT_HEADER is true', () => {
|
||||
let authFactory;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox.stub(nconf, 'get').withArgs('ENFORCE_CLIENT_HEADER').returns('true');
|
||||
authFactory = requireAgain(authPath).authWithHeaders;
|
||||
});
|
||||
|
||||
it('errors with missingClientHeader when x-client header is not present', done => {
|
||||
const authWithHeaders = authFactory({ userFieldsToExclude: [] });
|
||||
|
||||
req.headers['x-api-user'] = user._id;
|
||||
req.headers['x-api-key'] = user;
|
||||
authWithHeaders(req, res, err => {
|
||||
expect(err).to.exist;
|
||||
expect(err.name).to.equal('BadRequest');
|
||||
expect(err.message).to.equal(res.t('missingClientHeader'));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows request to pass when x-client header is present', done => {
|
||||
const authWithHeaders = authFactory({ userFieldsToExclude: [] });
|
||||
|
||||
req.headers['x-api-user'] = user._id;
|
||||
req.headers['x-api-key'] = user.apiToken;
|
||||
req.headers['x-client'] = 'habitica-web';
|
||||
|
||||
authWithHeaders(req, res, err => {
|
||||
if (err) return done(err);
|
||||
expect(res.locals.user).to.exist;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
206
test/api/unit/middlewares/blocker.test.js
Normal file
206
test/api/unit/middlewares/blocker.test.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
import {
|
||||
generateRes,
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
import { Forbidden } from '../../../../website/server/libs/errors';
|
||||
import { apiError } from '../../../../website/server/libs/apiError';
|
||||
import { model as Blocker } from '../../../../website/server/models/blocker';
|
||||
|
||||
function checkIPBlockedErrorThrown (next) {
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(apiError('ipAddressBlocked'));
|
||||
expect(calledWith[0] instanceof Forbidden).to.equal(true);
|
||||
}
|
||||
|
||||
function checkClientBlockedErrorThrown (next) {
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(apiError('clientBlocked'));
|
||||
expect(calledWith[0] instanceof Forbidden).to.equal(true);
|
||||
}
|
||||
|
||||
function checkErrorNotThrown (next) {
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
}
|
||||
|
||||
describe('Blocker middleware', () => {
|
||||
const pathToBlocker = '../../../../website/server/middlewares/blocker';
|
||||
|
||||
let res; let req; let next;
|
||||
|
||||
beforeEach(() => {
|
||||
res = generateRes();
|
||||
req = generateReq();
|
||||
next = generateNext();
|
||||
});
|
||||
|
||||
describe('Blocking IPs', () => {
|
||||
it('is disabled when the env var is not defined', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(undefined);
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('is disabled when the env var is an empty string', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('');
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('is disabled when the env var contains comma separated empty strings', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(' , , ');
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('does not throw when the ip does not match', () => {
|
||||
req.ip = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2');
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('does not throw when the blocker IP does not match', async () => {
|
||||
req.ip = '192.168.1.1';
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
if (event === 'change') {
|
||||
callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: '192.168.1.2' } });
|
||||
}
|
||||
},
|
||||
});
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('does not throw when a client is blocked', async () => {
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
if (event === 'change') {
|
||||
callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: '192.168.1.1' } });
|
||||
}
|
||||
},
|
||||
});
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('throws when the blocker IP is blocked', async () => {
|
||||
req.ip = '192.168.1.1';
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
if (event === 'change') {
|
||||
callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: '192.168.1.1' } });
|
||||
}
|
||||
},
|
||||
});
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkIPBlockedErrorThrown(next);
|
||||
});
|
||||
|
||||
it('throws when the ip is blocked', () => {
|
||||
req.ip = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1');
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkIPBlockedErrorThrown(next);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Blocking clients', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('');
|
||||
req.headers['x-client'] = 'test-client';
|
||||
});
|
||||
it('is disabled when no clients are blocked', () => {
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('does not throw when the client does not match', async () => {
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
if (event === 'change') {
|
||||
callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'another-client' } });
|
||||
}
|
||||
},
|
||||
});
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('throws when the client is blocked', async () => {
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
if (event === 'change') {
|
||||
callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'test-client' } });
|
||||
}
|
||||
},
|
||||
});
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkClientBlockedErrorThrown(next);
|
||||
});
|
||||
|
||||
it('does not throw when an ip is blocked', async () => {
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
if (event === 'change') {
|
||||
callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: 'test-client' } });
|
||||
}
|
||||
},
|
||||
});
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('updates the list when data changes', async () => {
|
||||
let blockCallback;
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
blockCallback = callback;
|
||||
if (event === 'change') {
|
||||
callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'another-client' } });
|
||||
}
|
||||
},
|
||||
});
|
||||
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||
attachBlocker(req, res, next);
|
||||
checkErrorNotThrown(next);
|
||||
blockCallback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'test-client' } });
|
||||
attachBlocker(req, res, next);
|
||||
expect(next).to.have.been.calledTwice;
|
||||
const calledWith = next.getCall(1).args;
|
||||
expect(calledWith[0].message).to.equal(apiError('clientBlocked'));
|
||||
expect(calledWith[0] instanceof Forbidden).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,76 +0,0 @@
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
import {
|
||||
generateRes,
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
import { Forbidden } from '../../../../website/server/libs/errors';
|
||||
import { apiError } from '../../../../website/server/libs/apiError';
|
||||
|
||||
function checkErrorThrown (next) {
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(apiError('ipAddressBlocked'));
|
||||
expect(calledWith[0] instanceof Forbidden).to.equal(true);
|
||||
}
|
||||
|
||||
function checkErrorNotThrown (next) {
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
}
|
||||
|
||||
describe('ipBlocker middleware', () => {
|
||||
const pathToIpBlocker = '../../../../website/server/middlewares/ipBlocker';
|
||||
|
||||
let res; let req; let next;
|
||||
|
||||
beforeEach(() => {
|
||||
res = generateRes();
|
||||
req = generateReq();
|
||||
next = generateNext();
|
||||
});
|
||||
|
||||
it('is disabled when the env var is not defined', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(undefined);
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('is disabled when the env var is an empty string', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('is disabled when the env var contains comma separated empty strings', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(' , , ');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('does not throw when the ip does not match', () => {
|
||||
req.ip = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('throws when the ip is blocked', () => {
|
||||
req.ip = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorThrown(next);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,13 @@
|
||||
import moment from 'moment';
|
||||
import requireAgain from 'require-again';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import { model as NewsPost } from '../../../../website/server/models/newsPost';
|
||||
import { model as Group } from '../../../../website/server/models/group';
|
||||
import { model as Blocker } from '../../../../website/server/models/blocker';
|
||||
import common from '../../../../website/common';
|
||||
|
||||
const pathToUserSchema = '../../../../website/server/models/user/schema';
|
||||
|
||||
describe('User Model', () => {
|
||||
describe('.toJSON()', () => {
|
||||
it('keeps user._tmp when calling .toJSON', () => {
|
||||
@@ -912,4 +916,73 @@ describe('User Model', () => {
|
||||
expect(user.toJSON().flags.newStuff).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validates email', () => {
|
||||
it('does not throw an error for a valid email', () => {
|
||||
const user = new User();
|
||||
user.auth.local.email = 'hello@example.com';
|
||||
const errors = user.validateSync();
|
||||
expect(errors.errors['auth.local.email']).to.not.exist;
|
||||
});
|
||||
|
||||
it('throws an error if email is not valid', () => {
|
||||
const user = new User();
|
||||
user.auth.local.email = 'invalid-email';
|
||||
const errors = user.validateSync();
|
||||
expect(errors.errors['auth.local.email'].message).to.equal(common.i18n.t('invalidEmail'));
|
||||
});
|
||||
|
||||
it('throws an error if email is using a restricted domain', () => {
|
||||
const user = new User();
|
||||
user.auth.local.email = 'scammer@habitica.com';
|
||||
const errors = user.validateSync();
|
||||
expect(errors.errors['auth.local.email'].message).to.equal(common.i18n.t('invalidEmailDomain', { domains: 'habitica.com, habitrpg.com' }));
|
||||
});
|
||||
|
||||
it('throws an error if email was blocked specifically', () => {
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: 'blocked@example.com' } });
|
||||
},
|
||||
});
|
||||
const schema = requireAgain(pathToUserSchema).UserSchema;
|
||||
const valid = schema.paths['auth.local.email'].options.validate.every(v => v.validator('blocked@example.com'));
|
||||
expect(valid).to.equal(false);
|
||||
});
|
||||
|
||||
it('throws an error if email domain was blocked', () => {
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: '@example.com' } });
|
||||
},
|
||||
});
|
||||
const schema = requireAgain(pathToUserSchema).UserSchema;
|
||||
const valid = schema.paths['auth.local.email'].options.validate.every(v => v.validator('blocked@example.com'));
|
||||
expect(valid).to.equal(false);
|
||||
});
|
||||
|
||||
it('throws an error if user portion of email was blocked', () => {
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: 'blocked@' } });
|
||||
},
|
||||
});
|
||||
const schema = requireAgain(pathToUserSchema).UserSchema;
|
||||
const valid = schema.paths['auth.local.email'].options.validate.every(v => v.validator('blocked@example.com'));
|
||||
expect(valid).to.equal(false);
|
||||
});
|
||||
|
||||
it('does not throw an error if email is not blocked', () => {
|
||||
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||
on: (event, callback) => {
|
||||
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: '@example.com' } });
|
||||
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: 'blocked@' } });
|
||||
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: 'bad@test.com' } });
|
||||
},
|
||||
});
|
||||
const schema = requireAgain(pathToUserSchema).UserSchema;
|
||||
const valid = schema.paths['auth.local.email'].options.validate.every(v => v.validator('good@test.com'));
|
||||
expect(valid).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,6 +238,28 @@ describe('POST /user/auth/reset-password-set-new-one', () => {
|
||||
expect(isPassValid).to.equal(true);
|
||||
});
|
||||
|
||||
it('changes the apiToken on password reset', async () => {
|
||||
const user = await generateUser();
|
||||
const previousToken = user.apiToken;
|
||||
|
||||
const code = encrypt(JSON.stringify({
|
||||
userId: user._id,
|
||||
expiresAt: moment().add({ days: 1 }),
|
||||
}));
|
||||
await user.updateOne({
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
await api.post(`${endpoint}`, {
|
||||
newPassword: 'my new password',
|
||||
confirmPassword: 'my new password',
|
||||
code,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.apiToken).to.not.eql(previousToken);
|
||||
});
|
||||
|
||||
it('renders the success page and convert the password from sha1 to bcrypt', async () => {
|
||||
const user = await generateUser();
|
||||
|
||||
|
||||
@@ -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 }),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -27,11 +27,30 @@ describe('PUT /user/auth/update-password', async () => {
|
||||
newPassword,
|
||||
confirmPassword: newPassword,
|
||||
});
|
||||
expect(response).to.eql({});
|
||||
|
||||
expect(response).to.exist;
|
||||
expect(response.apiToken).to.exist;
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.hashed_password).to.not.eql(previousHashedPassword);
|
||||
});
|
||||
|
||||
it('should change the apiToken on password change', async () => {
|
||||
const previousToken = user.apiToken;
|
||||
const response = await user.put(ENDPOINT, {
|
||||
password,
|
||||
newPassword,
|
||||
confirmPassword: newPassword,
|
||||
});
|
||||
|
||||
const newToken = response.apiToken;
|
||||
expect(newToken).to.exist;
|
||||
|
||||
await user.sync();
|
||||
expect(user.apiToken).to.eql(newToken);
|
||||
expect(user.apiToken).to.not.eql(previousToken);
|
||||
});
|
||||
|
||||
it('returns an error when confirmPassword does not match newPassword', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
password,
|
||||
|
||||
@@ -133,21 +133,21 @@ describe('Content Schedule', () => {
|
||||
});
|
||||
|
||||
it('sets the end date for a gala', () => {
|
||||
const date = new Date('2024-05-20');
|
||||
const date = new Date('2024-05-31');
|
||||
const matchers = getAllScheduleMatchingGroups(date);
|
||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2024-06-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2024-06-01T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||
});
|
||||
|
||||
it('sets the end date for a winter gala', () => {
|
||||
const date = new Date('2024-12-22');
|
||||
const date = new Date('2025-02-28');
|
||||
const matchers = getAllScheduleMatchingGroups(date);
|
||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-01T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||
});
|
||||
|
||||
it('sets the end date in new year for a winter gala', () => {
|
||||
const date = new Date('2025-01-04');
|
||||
const date = new Date('2025-02-28');
|
||||
const matchers = getAllScheduleMatchingGroups(date);
|
||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-01T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||
});
|
||||
|
||||
it('uses correct date for first hours of the month', () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('Shop Featured Items', () => {
|
||||
});
|
||||
|
||||
it('contains the current premium hatching potions', () => {
|
||||
clock = Sinon.useFakeTimers(new Date('2024-04-08'));
|
||||
clock = Sinon.useFakeTimers(new Date('2024-04-09'));
|
||||
const items = featuredItems.market();
|
||||
expect(_.find(items, item => item.path === 'premiumHatchingPotions.Porcelain')).to.exist;
|
||||
});
|
||||
|
||||
@@ -19,6 +19,6 @@ const sinonStubPromise = require('sinon-stub-promise');
|
||||
sinonStubPromise(global.sinon);
|
||||
global.sandbox = sinon.createSandbox();
|
||||
|
||||
const setupNconf = require('../../website/server/libs/setupNconf');
|
||||
const setupNconf = require('../../website/server/libs/setupNconf').default;
|
||||
|
||||
setupNconf('./config.json.example');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
const nconf = require('nconf');
|
||||
const mongoose = require('mongoose');
|
||||
const setupNconf = require('../../website/server/libs/setupNconf');
|
||||
const setupNconf = require('../../website/server/libs/setupNconf').default;
|
||||
|
||||
// fix further imports of require/import syntaxes
|
||||
require('@babel/register');
|
||||
|
||||
@@ -3,6 +3,7 @@ module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
'habitrpg/lib/vue',
|
||||
@@ -39,7 +40,4 @@ module.exports = {
|
||||
order: ['template', 'style', 'script'],
|
||||
}],
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset',
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
],
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
<link rel="shortcut icon" sizes="192x192" href="/static/icons/favicon_192x192.png">
|
||||
<link rel="mask-icon" href="/static/icons/favicon.ico">
|
||||
<meta property="og:image" content="/static/emails/images/meta-image.png" />
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading-screen">
|
||||
@@ -28,10 +29,9 @@
|
||||
</div>
|
||||
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
|
||||
<script type="text/javascript" src="//cloudfront.loggly.com/js/loggly.tracker-latest.min.js" async></script>
|
||||
<!-- Translations -->
|
||||
<script type='text/javascript' src='/api/v4/i18n/browser-script'></script>
|
||||
<script type='text/javascript' src='/api/v4/i18n/browser-script' vite-ignore></script>
|
||||
</body>
|
||||
</html>
|
||||
11833
website/client/package-lock.json
generated
11833
website/client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,28 +3,26 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit --require ./tests/unit/helpers.js",
|
||||
"lint": "vue-cli-service lint .",
|
||||
"lint-no-fix": "vue-cli-service lint --no-fix .",
|
||||
"serve": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "vitest run",
|
||||
"test:unit:watch": "vitest watch",
|
||||
"lint": "eslint --ext .js,.vue --ignore-path ../../.gitignore --fix .",
|
||||
"lint-no-fix": "eslint --ext .js,.vue --no-fix src",
|
||||
"postinstall": "node ./scripts/npm-postinstall.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"@vue/cli-plugin-router": "^5.0.8",
|
||||
"@vue/cli-plugin-unit-mocha": "^5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@froxz/vite-plugin-s3": "^1.6.0",
|
||||
"@vitejs/plugin-vue2": "^2.3.3",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^8.21.3",
|
||||
"assert": "^2.1.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^0.28.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"core-js": "^3.33.1",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-habitrpg": "6.2.0",
|
||||
"eslint-plugin-mocha": "5.3.0",
|
||||
@@ -34,31 +32,34 @@
|
||||
"intro.js": "^7.2.0",
|
||||
"jquery": "^3.7.1",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^14.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-locales-webpack-plugin": "^1.2.0",
|
||||
"nconf": "^0.12.1",
|
||||
"sass": "^1.63.4",
|
||||
"sass-loader": "^14.1.1",
|
||||
"sinon": "^17.0.1",
|
||||
"stopword": "^2.0.8",
|
||||
"timers-browserify": "^2.0.12",
|
||||
"uuid": "^9.0.1",
|
||||
"validator": "^13.9.0",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-compression2": "^1.3.3",
|
||||
"vue": "^2.7.10",
|
||||
"vue-fragment": "^1.6.0",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-template-babel-compiler": "^2.0.0",
|
||||
"vue-template-compiler": "^2.7.10",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
||||
"@vitest/browser": "^3.0.5",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"chai": "^5.1.0",
|
||||
"inspectpack": "^4.7.1",
|
||||
"jsdom": "^26.0.0",
|
||||
"mocha": "^11.1.0",
|
||||
"playwright": "^1.50.1",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"vitest": "^3.0.5",
|
||||
"webpack": "^5.94.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@
|
||||
</div>
|
||||
<snackbars />
|
||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||
<user-main v-else />
|
||||
<div v-else>
|
||||
<user-main />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
#loading-screen-inapp {
|
||||
#melior {
|
||||
@@ -90,7 +92,7 @@
|
||||
</style>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.modal-backdrop {
|
||||
opacity: .9 !important;
|
||||
@@ -108,16 +110,16 @@ import axios from 'axios';
|
||||
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import { mapState } from '@/libs/store';
|
||||
import userMain from '@/pages/user-main';
|
||||
import snackbars from '@/components/snackbars/notifications';
|
||||
import { LOCALSTORAGE_AUTH_KEY } from '@/libs/auth';
|
||||
|
||||
const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
|
||||
const COMMUNITY_MANAGER_EMAIL = import.meta.env.EMAILS_COMMUNITY_MANAGER_EMAIL;
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
snackbars,
|
||||
userMain,
|
||||
userMain: () => import('@/pages/user-main'),
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -221,11 +223,10 @@ export default {
|
||||
|
||||
const errorData = error.response.data;
|
||||
const errorMessage = errorData.message || errorData;
|
||||
const errorCode = errorData.error;
|
||||
|
||||
// Check for conditions to reset the user auth
|
||||
// TODO use a specific error like NotificationNotFound instead of checking for the string
|
||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
||||
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
|
||||
// If 'invalid_credentials' signaled, force logout
|
||||
if (error.response.status === 401 && errorCode === 'invalid_credentials') {
|
||||
this.$store.dispatch('auth:logout', { redirectToLogin: true });
|
||||
return null;
|
||||
}
|
||||
@@ -268,16 +269,29 @@ export default {
|
||||
const loadingScreen = document.getElementById('loading-screen');
|
||||
if (loadingScreen) document.body.removeChild(loadingScreen);
|
||||
|
||||
if (this.isStaticPage || !this.isUserLoggedIn) {
|
||||
this.hideLoadingScreen();
|
||||
// Check if we need to show password change success message
|
||||
if (sessionStorage.getItem('passwordChangeSuccess') === 'true') {
|
||||
sessionStorage.removeItem('passwordChangeSuccess');
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: this.$t('passwordSuccess'),
|
||||
type: 'success',
|
||||
timeout: true,
|
||||
});
|
||||
}
|
||||
|
||||
this.$router.onReady(() => {
|
||||
if (this.isStaticPage || !this.isUserLoggedIn) {
|
||||
this.hideLoadingScreen();
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
hideLoadingScreen () {
|
||||
this.loading = false;
|
||||
},
|
||||
checkForBannedUser (error) {
|
||||
const AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
|
||||
const AUTH_SETTINGS = localStorage.getItem(LOCALSTORAGE_AUTH_KEY);
|
||||
const parseSettings = JSON.parse(AUTH_SETTINGS);
|
||||
const errorMessage = error.response.data.message;
|
||||
|
||||
@@ -301,4 +315,3 @@ export default {
|
||||
</script>
|
||||
|
||||
<style src="@/assets/scss/index.scss" lang="scss"></style>
|
||||
<style src="@/assets/scss/sprites.scss" lang="scss"></style>
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.Mount_Head_Gryphon-Gryphatrice, .Mount_Body_Gryphon-Gryphatrice {
|
||||
.Mount_Head_Gryphon-Gryphatrice, .Mount_Body_Gryphon-Gryphatrice, .Mount_Head_Dragon-Hydra, .Mount_Body_Dragon-Hydra {
|
||||
width: 135px;
|
||||
height: 135px;
|
||||
}
|
||||
@@ -190,6 +190,14 @@
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Mount-Body-Gryphatrice.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Mount_Head_Dragon-Hydra {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Dragon-Hydra.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Mount_Body_Dragon-Hydra {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Dragon-Hydra.gif") no-repeat;
|
||||
}
|
||||
|
||||
.background_airship, .background_clocktower, .background_steamworks {
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
|
||||
@@ -2001,6 +2001,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_sirens_lair {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_sirens_lair.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_slimy_swamp {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_slimy_swamp.png');
|
||||
width: 141px;
|
||||
@@ -2176,11 +2181,21 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_summer_seashore {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_summer_seashore.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_sunken_ship {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_sunken_ship.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_sunny_street_with_shops {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_sunny_street_with_shops.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_sunset_meadow {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_sunset_meadow.png');
|
||||
width: 141px;
|
||||
@@ -29545,6 +29560,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_beekeepersSuit {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_beekeepersSuit.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_blueMoonShozoku {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_blueMoonShozoku.png');
|
||||
width: 114px;
|
||||
@@ -29675,6 +29695,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_flyFishingWaders {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_flyFishingWaders.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_funnyFoolCostume {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_funnyFoolCostume.png');
|
||||
width: 114px;
|
||||
@@ -29880,6 +29905,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_redWaistcoat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_redWaistcoat.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_robeOfDiamonds {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_robeOfDiamonds.png');
|
||||
width: 114px;
|
||||
@@ -30145,6 +30175,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_beekeepersHat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_beekeepersHat.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_bigWig {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_bigWig.png');
|
||||
width: 90px;
|
||||
@@ -30285,6 +30320,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_flyFishingHat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_flyFishingHat.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_frostedHelm {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_frostedHelm.png');
|
||||
width: 114px;
|
||||
@@ -30505,6 +30545,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_redNewsieHat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_redNewsieHat.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_regalCrown {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_regalCrown.png');
|
||||
width: 114px;
|
||||
@@ -30645,6 +30690,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_beekeepersHive {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_beekeepersHive.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_birthdayBanner {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_birthdayBanner.png');
|
||||
width: 114px;
|
||||
@@ -30745,6 +30795,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_flyFishingRod {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_flyFishingRod.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_gardenersSpade {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_gardenersSpade.png');
|
||||
width: 114px;
|
||||
@@ -31090,6 +31145,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_beekeepersSuit {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_beekeepersSuit.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_blueMoonShozoku {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_blueMoonShozoku.png');
|
||||
width: 114px;
|
||||
@@ -31220,6 +31280,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_flyFishingWaders {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_flyFishingWaders.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_funnyFoolCostume {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_funnyFoolCostume.png');
|
||||
width: 114px;
|
||||
@@ -31425,6 +31490,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_redWaistcoat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_redWaistcoat.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_robeOfDiamonds {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_robeOfDiamonds.png');
|
||||
width: 114px;
|
||||
@@ -31660,6 +31730,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_beekeepersSmoker {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_beekeepersSmoker.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_blueKite {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_blueKite.png');
|
||||
width: 114px;
|
||||
@@ -35560,6 +35635,36 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.back_mystery_202506 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_202506.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_mystery_202506 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202506.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.back_mystery_202507 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_202507.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.head_mystery_202507 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202507.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_mystery_202508 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202508.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.weapon_mystery_202508 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202508.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.broad_armor_mystery_301404 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png');
|
||||
width: 90px;
|
||||
@@ -37105,6 +37210,26 @@
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.broad_armor_special_summer2025Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2025Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_summer2025Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2025Mage.png');
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.broad_armor_special_summer2025Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2025Rogue.png');
|
||||
width: 114px;
|
||||
height: 108px;
|
||||
}
|
||||
.broad_armor_special_summer2025Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2025Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -37335,6 +37460,26 @@
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.head_special_summer2025Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2025Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_summer2025Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2025Mage.png');
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.head_special_summer2025Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2025Rogue.png');
|
||||
width: 114px;
|
||||
height: 108px;
|
||||
}
|
||||
.head_special_summer2025Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2025Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -37510,6 +37655,21 @@
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.shield_special_summer2025Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2025Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_special_summer2025Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2025Rogue.png');
|
||||
width: 114px;
|
||||
height: 108px;
|
||||
}
|
||||
.shield_special_summer2025Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2025Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -37730,6 +37890,26 @@
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.slim_armor_special_summer2025Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2025Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_summer2025Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2025Mage.png');
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.slim_armor_special_summer2025Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2025Rogue.png');
|
||||
width: 114px;
|
||||
height: 108px;
|
||||
}
|
||||
.slim_armor_special_summer2025Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2025Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -37950,6 +38130,26 @@
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.weapon_special_summer2025Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2025Healer.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_summer2025Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2025Mage.png');
|
||||
width: 114px;
|
||||
height: 117px;
|
||||
}
|
||||
.weapon_special_summer2025Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2025Rogue.png');
|
||||
width: 114px;
|
||||
height: 108px;
|
||||
}
|
||||
.weapon_special_summer2025Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2025Warrior.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summerHealer.png');
|
||||
width: 90px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.featured-label {
|
||||
width: auto;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
$grid-gutter-width: 24px;
|
||||
|
||||
// Bootstrap and its default variables
|
||||
@import 'node_modules/bootstrap/scss/bootstrap';
|
||||
@import '~/bootstrap/scss/bootstrap';
|
||||
|
||||
// Bootstrap Vue styles
|
||||
@import 'node_modules/bootstrap-vue/dist/bootstrap-vue';
|
||||
@import '~/bootstrap-vue/dist/bootstrap-vue';
|
||||
@@ -316,3 +316,9 @@
|
||||
line-height: 2;
|
||||
padding: 2px 2px;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
h1 {
|
||||
margin-top: 0px;
|
||||
|
||||
@@ -61,13 +61,13 @@ input, textarea, input.form-control, textarea.form-control {
|
||||
|
||||
&.input-valid {
|
||||
padding-right: 27px;
|
||||
background-image: url(~@/assets/svg/for-css/check.svg);
|
||||
background-image: url(@/assets/svg/for-css/check.svg);
|
||||
background-size: 1rem;
|
||||
}
|
||||
|
||||
&.input-invalid {
|
||||
padding-right: 40px;
|
||||
background-image: url(~@/assets/svg/for-css/alert.svg);
|
||||
background-image: url(@/assets/svg/for-css/alert.svg);
|
||||
background-size: 16px 16px;
|
||||
border-color: $red-100 !important;
|
||||
}
|
||||
@@ -239,7 +239,7 @@ $bg-disabled-control: $gray-10;
|
||||
&:checked~.custom-control-label::after {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-image: url(~@/assets/svg/for-css/checkbox-white.svg);
|
||||
background-image: url(@/assets/svg/for-css/checkbox-white.svg);
|
||||
background-size: 13px 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
}
|
||||
|
||||
.iconalert-success::before {
|
||||
background-image: url(~@/assets/svg/for-css/checkbox-white.svg);
|
||||
background-image: url(@/assets/svg/for-css/checkbox-white.svg);
|
||||
background-size: 13px 10px;
|
||||
background-color: #1ca372;
|
||||
}
|
||||
|
||||
.iconalert-warning::before, .iconalert-error::before {
|
||||
background-image: url(~@/assets/svg/for-css/alert-white.svg);
|
||||
background-image: url(@/assets/svg/for-css/alert-white.svg);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.modal {
|
||||
z-index: 1350;
|
||||
|
||||
@@ -46,13 +46,11 @@
|
||||
|
||||
.background {
|
||||
background-repeat: repeat-x;
|
||||
|
||||
height:216px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@@ -67,6 +65,13 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.shop-message {
|
||||
position: relative;
|
||||
height: 76px;
|
||||
margin: 71px auto;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.npc {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.container-fluid.static-view {
|
||||
margin: 5em 2em 0 2em;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<!-- @TODO i18n. How to setup the strings with the router-link inside?-->
|
||||
<img
|
||||
:class="retiredChatPage ? 'mt-5' : 'image-404'"
|
||||
src="~@/assets/images/404.png"
|
||||
src="@/assets/images/404.png"
|
||||
>
|
||||
<div v-if="retiredChatPage">
|
||||
<h1>
|
||||
@@ -48,7 +48,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
h1, .static-wrapper h1 {
|
||||
color: $purple-200;
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.btn-primary:active {
|
||||
border: 2px solid $purple-400 !important;
|
||||
@@ -193,10 +193,10 @@
|
||||
import Avatar from '../avatar';
|
||||
import { mapState } from '@/libs/store';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import warriorIcon from '@/assets/svg/warrior.svg';
|
||||
import rogueIcon from '@/assets/svg/rogue.svg';
|
||||
import healerIcon from '@/assets/svg/healer.svg';
|
||||
import wizardIcon from '@/assets/svg/wizard.svg';
|
||||
import warriorIcon from '@/assets/svg/warrior.svg?raw';
|
||||
import rogueIcon from '@/assets/svg/rogue.svg?raw';
|
||||
import healerIcon from '@/assets/svg/healer.svg?raw';
|
||||
import wizardIcon from '@/assets/svg/wizard.svg?raw';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
h2 {
|
||||
color: $purple-200;
|
||||
@@ -100,7 +100,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import closeIcon from '@/assets/svg/close.svg';
|
||||
import closeIcon from '@/assets/svg/close.svg?raw';
|
||||
import Sprite from '@/components/ui/sprite.vue';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/mixins.scss';
|
||||
@import '@/assets/scss/mixins.scss';
|
||||
|
||||
#generic-achievement {
|
||||
@include centeredModal();
|
||||
@@ -61,7 +61,7 @@
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
@@ -98,7 +98,7 @@
|
||||
<script>
|
||||
import achievements from '@/../../common/script/content/achievements';
|
||||
import { mapState } from '@/libs/store';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import svgClose from '@/assets/svg/close.svg?raw';
|
||||
import Sprite from '@/components/ui/sprite.vue';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -58,7 +58,7 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
#level-up {
|
||||
.modal-content {
|
||||
@@ -157,8 +157,8 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
|
||||
import Avatar from '../avatar';
|
||||
import Sprite from '@/components/ui/sprite';
|
||||
import { mapState } from '@/libs/store';
|
||||
import starGroup from '@/assets/svg/star-group.svg';
|
||||
import sparkles from '@/assets/svg/sparkles-left.svg';
|
||||
import starGroup from '@/assets/svg/star-group.svg?raw';
|
||||
import sparkles from '@/assets/svg/sparkles-left.svg?raw';
|
||||
|
||||
const levelQuests = {
|
||||
15: 'atom1',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</h2>
|
||||
<img
|
||||
class="onboarding-complete-banner d-block"
|
||||
src="~@/assets/images/onboarding-complete-banner@2x.png"
|
||||
src="@/assets/images/onboarding-complete-banner@2x.png"
|
||||
>
|
||||
<p
|
||||
v-once
|
||||
@@ -59,7 +59,7 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
h2 {
|
||||
color: $purple-200;
|
||||
@@ -100,7 +100,7 @@ button {
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import svgClose from '@/assets/svg/close.svg?raw';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
|
||||
@@ -97,9 +97,9 @@ import { mapState } from '@/libs/store';
|
||||
import Sprite from '@/components/ui/sprite';
|
||||
|
||||
export default {
|
||||
components: [
|
||||
components: {
|
||||
Sprite,
|
||||
],
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
maxHealth,
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
#won-challenge {
|
||||
.modal-body {
|
||||
@@ -96,7 +96,7 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.purple {
|
||||
color: $purple-300;
|
||||
@@ -146,9 +146,9 @@
|
||||
<script>
|
||||
import habiticaMarkdown from 'habitica-markdown';
|
||||
import closeIcon from '@/components/shared/closeIcon';
|
||||
import sparkles from '@/assets/svg/star-group.svg';
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
import stars from '@/assets/svg/sparkles-left.svg';
|
||||
import sparkles from '@/assets/svg/star-group.svg?raw';
|
||||
import gem from '@/assets/svg/gem.svg?raw';
|
||||
import stars from '@/assets/svg/sparkles-left.svg?raw';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="row standard-page col-12 d-flex justify-content-center">
|
||||
<div class="admin-panel-content">
|
||||
<h1>Admin Panel</h1>
|
||||
<h1>{{ $t("adminPanel") }}</h1>
|
||||
<form
|
||||
class="form-inline"
|
||||
@submit.prevent="searchUsers(userIdentifier)"
|
||||
@@ -72,7 +72,7 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: 'Admin Panel',
|
||||
section: this.$t('adminPanel'),
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
@@ -55,7 +55,7 @@
|
||||
<script>
|
||||
import VueRouter from 'vue-router';
|
||||
import { mapState } from '@/libs/store';
|
||||
import LoadingSpinner from '../ui/loadingSpinner';
|
||||
import LoadingSpinner from '../../ui/loadingSpinner';
|
||||
|
||||
const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
||||
|
||||
@@ -38,12 +38,17 @@
|
||||
>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input
|
||||
:id="permission.key"
|
||||
v-model="hero.permissions[permission.key]"
|
||||
:disabled="!hasPermission(user, permission.key)"
|
||||
:disabled="!hasPermission(user, permission.key)
|
||||
|| (hero.permissions.fullAccess && permission.key !== 'fullAccess')"
|
||||
class="custom-control-input"
|
||||
type="checkbox"
|
||||
>
|
||||
<label class="custom-control-label">
|
||||
<label
|
||||
class="custom-control-label"
|
||||
:for="permission.key"
|
||||
>
|
||||
{{ permission.name }}<br>
|
||||
<small class="text-secondary">{{ permission.description }}</small>
|
||||
</label>
|
||||
@@ -124,7 +129,10 @@
|
||||
value="Save"
|
||||
class="btn btn-primary mt-1"
|
||||
>
|
||||
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||
<b
|
||||
v-if="hasUnsavedChanges"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</div>
|
||||
@@ -147,7 +155,7 @@ import markdownDirective from '@/directives/markdown';
|
||||
import saveHero from '../mixins/saveHero';
|
||||
|
||||
import { mapState } from '@/libs/store';
|
||||
import { userStateMixin } from '../../../mixins/userState';
|
||||
import { userStateMixin } from '../../../../mixins/userState';
|
||||
|
||||
const permissionList = [
|
||||
{
|
||||
@@ -175,6 +183,11 @@ const permissionList = [
|
||||
name: 'Challenge Admin',
|
||||
description: 'Can create official habitica challenges and admin all challenges',
|
||||
},
|
||||
{
|
||||
key: 'accessControl',
|
||||
name: 'Access Control',
|
||||
description: 'Can manage IP-Address, Client and E-Mail blockers',
|
||||
},
|
||||
{
|
||||
key: 'coupons',
|
||||
name: 'Coupon Creator',
|
||||
@@ -126,7 +126,7 @@
|
||||
@click="changeApiToken()"
|
||||
>
|
||||
Change API Token
|
||||
</a>
|
||||
</a>
|
||||
<div
|
||||
v-if="tokenModified"
|
||||
>
|
||||
@@ -46,7 +46,7 @@
|
||||
:
|
||||
<span :class="{ ownedItem: !item.neverOwned }">{{ item.text }}</span>
|
||||
</span>
|
||||
- {{ itemType }}.{{item.key}} - <i> {{ item.set }}</i>
|
||||
- {{ itemType }}.{{ item.key }} - <i> {{ item.set }}</i>
|
||||
|
||||
<div
|
||||
v-if="item.modified"
|
||||
@@ -16,9 +16,9 @@
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
:has-unsaved-changes="hasUnsavedChanges([hero.flags, unModifiedHero.flags],
|
||||
[hero.auth, unModifiedHero.auth],
|
||||
[hero.balance, unModifiedHero.balance],
|
||||
[hero.secret, unModifiedHero.secret])"
|
||||
[hero.auth, unModifiedHero.auth],
|
||||
[hero.balance, unModifiedHero.balance],
|
||||
[hero.secret, unModifiedHero.secret])"
|
||||
/>
|
||||
|
||||
<subscription-and-perks
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
<contributor-details
|
||||
:hero="hero"
|
||||
:hasUnsavedChanges="hasUnsavedChanges(
|
||||
:has-unsaved-changes="hasUnsavedChanges(
|
||||
[hero.contributor, unModifiedHero.contributor],
|
||||
[hero.permissions, unModifiedHero.permissions],
|
||||
[hero.secret, unModifiedHero.secret],
|
||||
@@ -149,7 +149,7 @@ import Achievements from './achievements.vue';
|
||||
import UserHistory from './userHistory.vue';
|
||||
import Stats from './stats.vue';
|
||||
|
||||
import { userStateMixin } from '../../../mixins/userState';
|
||||
import { userStateMixin } from '../../../../mixins/userState';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -32,38 +32,43 @@
|
||||
></p>
|
||||
</div>
|
||||
<div v-if="userHasParty">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Party ID
|
||||
</label>
|
||||
<strong class="col-sm-9 col-form-label">
|
||||
{{ groupPartyData._id }}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Estimated Member Count
|
||||
</label>
|
||||
<strong class="col-sm-9 col-form-label">
|
||||
{{ groupPartyData.memberCount }}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Leader
|
||||
</label>
|
||||
<strong class="col-sm-9 col-form-label">
|
||||
<span v-if="userIsPartyLeader">User is the party leader</span>
|
||||
<span v-else>Party leader is
|
||||
<router-link :to="{'name': 'userProfile', 'params': {'userId': groupPartyData.leader}}">
|
||||
{{ groupPartyData.leader }}
|
||||
</router-link>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
class="btn btn-danger"
|
||||
@click="removeFromParty()">Remove from Party</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Party ID
|
||||
</label>
|
||||
<strong class="col-sm-9 col-form-label">
|
||||
{{ groupPartyData._id }}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Estimated Member Count
|
||||
</label>
|
||||
<strong class="col-sm-9 col-form-label">
|
||||
{{ groupPartyData.memberCount }}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Leader
|
||||
</label>
|
||||
<strong class="col-sm-9 col-form-label">
|
||||
<span v-if="userIsPartyLeader">User is the party leader</span>
|
||||
<span v-else>Party leader is
|
||||
<router-link
|
||||
:to="{'name': 'userProfile', 'params': {'userId': groupPartyData.leader}}"
|
||||
>
|
||||
{{ groupPartyData.leader }}
|
||||
</router-link>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
class="btn btn-danger"
|
||||
@click="removeFromParty()"
|
||||
>
|
||||
Remove from Party
|
||||
</div>
|
||||
</div>
|
||||
<strong v-else>User is not in a party.</strong>
|
||||
<div class="subsection-start">
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveHero({hero: {
|
||||
_id: hero._id,
|
||||
flags: hero.flags,
|
||||
balance: hero.balance,
|
||||
auth: hero.auth,
|
||||
secret: hero.secret,
|
||||
}, msg: 'Privileges or Gems or Moderation Notes'})">
|
||||
<form
|
||||
@submit.prevent="saveHero({hero: {
|
||||
_id: hero._id,
|
||||
flags: hero.flags,
|
||||
balance: hero.balance,
|
||||
auth: hero.auth,
|
||||
secret: hero.secret,
|
||||
}, msg: 'Privileges or Gems or Moderation Notes'})"
|
||||
>
|
||||
<div class="card mt-2">
|
||||
<div class="card-header">
|
||||
<h3
|
||||
@@ -14,9 +16,12 @@
|
||||
@click="expand = !expand"
|
||||
>
|
||||
Privileges, Gem Balance
|
||||
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
|
||||
Unsaved changes
|
||||
</b>
|
||||
<b
|
||||
v-if="hasUnsavedChanges && !expand"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
@@ -133,7 +138,10 @@
|
||||
value="Save"
|
||||
class="btn btn-primary mt-1"
|
||||
>
|
||||
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||
<b
|
||||
v-if="hasUnsavedChanges"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</div>
|
||||
@@ -19,7 +19,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.about-row {
|
||||
margin-left: 0px;
|
||||
@@ -8,9 +8,12 @@
|
||||
@click="expand = !expand"
|
||||
>
|
||||
Stats
|
||||
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
|
||||
Unsaved changes
|
||||
</b>
|
||||
<b
|
||||
v-if="hasUnsavedChanges && !expand"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
@@ -18,47 +21,60 @@
|
||||
class="card-body"
|
||||
>
|
||||
<stats-row
|
||||
v-model="hero.stats.hp"
|
||||
label="Health"
|
||||
color="red-label"
|
||||
:max="maxHealth"
|
||||
v-model="hero.stats.hp" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.exp"
|
||||
label="Experience"
|
||||
color="yellow-label"
|
||||
min="0"
|
||||
:max="maxFieldHardCap"
|
||||
v-model="hero.stats.exp" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.mp"
|
||||
label="Mana"
|
||||
color="blue-label"
|
||||
min="0"
|
||||
:max="maxFieldHardCap"
|
||||
v-model="hero.stats.mp" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.lvl"
|
||||
label="Level"
|
||||
step="1"
|
||||
min="0"
|
||||
:max="maxLevelHardCap"
|
||||
v-model="hero.stats.lvl" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.gp"
|
||||
label="Gold"
|
||||
min="0"
|
||||
:max="maxFieldHardCap"
|
||||
v-model="hero.stats.gp" />
|
||||
/>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">Selected Class</label>
|
||||
<div class="col-sm-9">
|
||||
<select
|
||||
id="selectedClass"
|
||||
v-model="hero.stats.class"
|
||||
class="form-control"
|
||||
:disabled="hero.stats.lvl < 10"
|
||||
>
|
||||
<option value="warrior">Warrior</option>
|
||||
<option value="wizard">Mage</option>
|
||||
<option value="healer">Healer</option>
|
||||
<option value="rogue">Rogue</option>
|
||||
</select>
|
||||
id="selectedClass"
|
||||
v-model="hero.stats.class"
|
||||
class="form-control"
|
||||
:disabled="hero.stats.lvl < 10"
|
||||
>
|
||||
<option value="warrior">
|
||||
Warrior
|
||||
</option>
|
||||
<option value="wizard">
|
||||
Mage
|
||||
</option>
|
||||
<option value="healer">
|
||||
Healer
|
||||
</option>
|
||||
<option value="rogue">
|
||||
Rogue
|
||||
</option>
|
||||
</select>
|
||||
<small>
|
||||
When changing class, players usually need stat points deallocated as well.
|
||||
</small>
|
||||
@@ -67,50 +83,59 @@
|
||||
|
||||
<h3>Stat Points</h3>
|
||||
<stats-row
|
||||
v-model="hero.stats.points"
|
||||
label="Unallocated"
|
||||
min="0"
|
||||
step="1"
|
||||
:max="maxStatPoints"
|
||||
v-model="hero.stats.points" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.str"
|
||||
label="Strength"
|
||||
color="red-label"
|
||||
min="0"
|
||||
:max="maxStatPoints"
|
||||
step="1"
|
||||
v-model="hero.stats.str" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.int"
|
||||
label="Intelligence"
|
||||
color="blue-label"
|
||||
min="0"
|
||||
:max="maxStatPoints"
|
||||
step="1"
|
||||
v-model="hero.stats.int" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.per"
|
||||
label="Perception"
|
||||
color="purple-label"
|
||||
min="0"
|
||||
:max="maxStatPoints"
|
||||
step="1"
|
||||
v-model="hero.stats.per" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.con"
|
||||
label="Constitution"
|
||||
color="yellow-label"
|
||||
min="0"
|
||||
:max="maxStatPoints"
|
||||
step="1"
|
||||
v-model="hero.stats.con" />
|
||||
/>
|
||||
<div class="form-group row">
|
||||
<div class="offset-sm-3 col-sm-9">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning btn-sm"
|
||||
@click="deallocateStatPoints">
|
||||
@click="deallocateStatPoints"
|
||||
>
|
||||
Deallocate all stat points
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row" v-if="statPointsIncorrect">
|
||||
<div
|
||||
v-if="statPointsIncorrect"
|
||||
class="form-group row"
|
||||
>
|
||||
<div class="offset-sm-3 col-sm-9 text-danger">
|
||||
Error: Sum of stat points should equal the users level
|
||||
</div>
|
||||
@@ -118,35 +143,40 @@
|
||||
|
||||
<h3>Buffs</h3>
|
||||
<stats-row
|
||||
v-model="hero.stats.buffs.str"
|
||||
label="Strength"
|
||||
color="red-label"
|
||||
min="0"
|
||||
step="1"
|
||||
v-model="hero.stats.buffs.str" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.buffs.int"
|
||||
label="Intelligence"
|
||||
color="blue-label"
|
||||
min="0"
|
||||
step="1"
|
||||
v-model="hero.stats.buffs.int" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.buffs.per"
|
||||
label="Perception"
|
||||
color="purple-label"
|
||||
min="0"
|
||||
step="1"
|
||||
v-model="hero.stats.buffs.per" />
|
||||
/>
|
||||
<stats-row
|
||||
v-model="hero.stats.buffs.con"
|
||||
label="Constitution"
|
||||
color="yellow-label"
|
||||
min="0"
|
||||
step="1"
|
||||
v-model="hero.stats.buffs.con" />
|
||||
/>
|
||||
<div class="form-group row">
|
||||
<div class="offset-sm-3 col-sm-9">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning btn-sm"
|
||||
@click="resetBuffs">
|
||||
@click="resetBuffs"
|
||||
>
|
||||
Reset Buffs
|
||||
</button>
|
||||
</div>
|
||||
@@ -161,7 +191,10 @@
|
||||
value="Save"
|
||||
class="btn btn-primary mt-1"
|
||||
>
|
||||
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||
<b
|
||||
v-if="hasUnsavedChanges"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</div>
|
||||
@@ -170,7 +203,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.about-row {
|
||||
margin-left: 0px;
|
||||
@@ -189,7 +222,7 @@ import markdownDirective from '@/directives/markdown';
|
||||
import saveHero from '../mixins/saveHero';
|
||||
|
||||
import { mapState } from '@/libs/store';
|
||||
import { userStateMixin } from '../../../mixins/userState';
|
||||
import { userStateMixin } from '../../../../mixins/userState';
|
||||
|
||||
import StatsRow from './stats-row';
|
||||
|
||||
@@ -6,49 +6,71 @@
|
||||
}, msg: 'Subscription Perks' })"
|
||||
>
|
||||
<div class="card mt-2">
|
||||
<div class="card-header"
|
||||
@click="expand = !expand">
|
||||
<div
|
||||
class="card-header"
|
||||
@click="expand = !expand"
|
||||
>
|
||||
<h3
|
||||
class="mb-0 mt-0"
|
||||
:class="{ 'open': expand }"
|
||||
>
|
||||
Subscription, Monthly Perks
|
||||
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
|
||||
Unsaved changes
|
||||
</b>
|
||||
<b
|
||||
v-if="hasUnsavedChanges && !expand"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
v-if="expand"
|
||||
class="card-body"
|
||||
>
|
||||
<div
|
||||
<div
|
||||
class="form-group row"
|
||||
>
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Payment method:
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input v-model="hero.purchased.plan.paymentMethod"
|
||||
<input
|
||||
v-if="!isRegularPaymentMethod"
|
||||
v-model="hero.purchased.plan.paymentMethod"
|
||||
class="form-control"
|
||||
type="text"
|
||||
v-if="!isRegularPaymentMethod"
|
||||
>
|
||||
<select
|
||||
<select
|
||||
v-else
|
||||
v-model="hero.purchased.plan.paymentMethod"
|
||||
class="form-control"
|
||||
type="text"
|
||||
>
|
||||
<option value="groupPlan">Group Plan</option>
|
||||
<option value="Stripe">Stripe</option>
|
||||
<option value="Apple">Apple</option>
|
||||
<option value="Google">Google</option>
|
||||
<option value="Amazon Payments">Amazon</option>
|
||||
<option value="PayPal">PayPal</option>
|
||||
<option value="Gift">Gift</option>
|
||||
<option value="">Clear out</option>
|
||||
</select>
|
||||
v-model="hero.purchased.plan.paymentMethod"
|
||||
class="form-control"
|
||||
type="text"
|
||||
>
|
||||
<option value="groupPlan">
|
||||
Group Plan
|
||||
</option>
|
||||
<option value="Stripe">
|
||||
Stripe
|
||||
</option>
|
||||
<option value="Apple">
|
||||
Apple
|
||||
</option>
|
||||
<option value="Google">
|
||||
Google
|
||||
</option>
|
||||
<option value="Amazon Payments">
|
||||
Amazon
|
||||
</option>
|
||||
<option value="PayPal">
|
||||
PayPal
|
||||
</option>
|
||||
<option value="Gift">
|
||||
Gift
|
||||
</option>
|
||||
<option value="">
|
||||
Clear out
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -58,25 +80,40 @@
|
||||
Payment schedule:
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input v-model="hero.purchased.plan.planId"
|
||||
<input
|
||||
v-if="!isRegularPlanId"
|
||||
v-model="hero.purchased.plan.planId"
|
||||
class="form-control"
|
||||
type="text"
|
||||
v-if="!isRegularPlanId"
|
||||
>
|
||||
<select
|
||||
<select
|
||||
v-else
|
||||
v-model="hero.purchased.plan.planId"
|
||||
class="form-control"
|
||||
type="text"
|
||||
>
|
||||
<option value="basic_earned">Monthly recurring</option>
|
||||
<option value="basic_3mo">3 Months recurring</option>
|
||||
<option value="basic_6mo">6 Months recurring</option>
|
||||
<option value="basic_12mo">12 Months recurring</option>
|
||||
<option value="group_monthly">Group Plan (legacy)</option>
|
||||
<option value="group_plan_auto">Group Plan (auto)</option>
|
||||
<option value="">Clear out</option>
|
||||
</select>
|
||||
v-model="hero.purchased.plan.planId"
|
||||
class="form-control"
|
||||
type="text"
|
||||
>
|
||||
<option value="basic_earned">
|
||||
Monthly recurring
|
||||
</option>
|
||||
<option value="basic_3mo">
|
||||
3 Months recurring
|
||||
</option>
|
||||
<option value="basic_6mo">
|
||||
6 Months recurring
|
||||
</option>
|
||||
<option value="basic_12mo">
|
||||
12 Months recurring
|
||||
</option>
|
||||
<option value="group_monthly">
|
||||
Group Plan (legacy)
|
||||
</option>
|
||||
<option value="group_plan_auto">
|
||||
Group Plan (auto)
|
||||
</option>
|
||||
<option value="">
|
||||
Clear out
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -86,43 +123,50 @@
|
||||
Customer ID:
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
v-model="hero.purchased.plan.customerId"
|
||||
class="form-control"
|
||||
type="text"
|
||||
>
|
||||
<input
|
||||
v-model="hero.purchased.plan.customerId"
|
||||
class="form-control"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row"
|
||||
v-if="hero.purchased.plan.planId === 'group_plan_auto'">
|
||||
<div
|
||||
v-if="hero.purchased.plan.planId === 'group_plan_auto'"
|
||||
class="form-group row"
|
||||
>
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Group Plan Memberships:
|
||||
</label>
|
||||
<div class="col-sm-9 col-form-label">
|
||||
<loading-spinner
|
||||
v-if="!groupPlans"
|
||||
dark-color=true
|
||||
/>
|
||||
v-if="!groupPlans"
|
||||
dark-color="true"
|
||||
/>
|
||||
<b
|
||||
v-else-if="groupPlans.length === 0"
|
||||
class="text-danger col-form-label"
|
||||
v-else-if="groupPlans.length === 0"
|
||||
class="text-danger col-form-label"
|
||||
>User is not part of an active group plan!</b>
|
||||
<div
|
||||
v-else
|
||||
v-for="group in groupPlans"
|
||||
v-else
|
||||
:key="group._id"
|
||||
class="card mb-2">
|
||||
class="card mb-2"
|
||||
>
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">{{ group.name }}
|
||||
<h6 class="card-title">
|
||||
{{ group.name }}
|
||||
<small class="float-right">{{ group._id }}</small>
|
||||
</h6>
|
||||
<p class="card-text">
|
||||
<strong>Leader: </strong>
|
||||
<a
|
||||
v-if="group.leader !== hero._id"
|
||||
@click="switchUser(group.leader)"
|
||||
>{{ group.leader }}</a>
|
||||
<strong v-else class="text-success">This user</strong>
|
||||
<a
|
||||
v-if="group.leader !== hero._id"
|
||||
@click="switchUser(group.leader)"
|
||||
>{{ group.leader }}</a>
|
||||
<strong
|
||||
v-else
|
||||
class="text-success"
|
||||
>This user</strong>
|
||||
</p>
|
||||
<p class="card-text">
|
||||
<strong>Members: </strong> {{ group.memberCount }}
|
||||
@@ -190,16 +234,21 @@
|
||||
<strong class="input-group-text">
|
||||
{{ dateFormat(hero.purchased.plan.dateTerminated) }}
|
||||
</strong>
|
||||
<a class="btn btn-danger"
|
||||
href="#"
|
||||
<a
|
||||
v-if="!hero.purchased.plan.dateTerminated && hero.purchased.plan.planId"
|
||||
v-b-modal.sub_termination_modal
|
||||
v-if="!hero.purchased.plan.dateTerminated && hero.purchased.plan.planId">
|
||||
class="btn btn-danger"
|
||||
href="#"
|
||||
>
|
||||
Terminate
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<small v-if="!hero.purchased.plan.dateTerminated
|
||||
&& hero.purchased.plan.planId" class="text-success">
|
||||
<small
|
||||
v-if="!hero.purchased.plan.dateTerminated
|
||||
&& hero.purchased.plan.planId"
|
||||
class="text-success"
|
||||
>
|
||||
The subscription does not have a termination date and is active.
|
||||
</small>
|
||||
</div>
|
||||
@@ -235,11 +284,13 @@
|
||||
step="any"
|
||||
>
|
||||
<div class="input-group-append">
|
||||
<a class="btn btn-warning"
|
||||
<a
|
||||
v-if="hero.purchased.plan.dateTerminated && hero.purchased.plan.extraMonths > 0"
|
||||
class="btn btn-warning"
|
||||
@click="applyExtraMonths"
|
||||
v-if="hero.purchased.plan.dateTerminated && hero.purchased.plan.extraMonths > 0">
|
||||
>
|
||||
Apply Credit
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-secondary">
|
||||
@@ -339,19 +390,24 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row"
|
||||
v-if="!isConvertingToGroupPlan && hero.purchased.plan.planId !== 'group_plan_auto'">
|
||||
<div
|
||||
v-if="!isConvertingToGroupPlan && hero.purchased.plan.planId !== 'group_plan_auto'"
|
||||
class="form-group row"
|
||||
>
|
||||
<div class="offset-sm-3 col-sm-9">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary btn-sm"
|
||||
@click="beginGroupPlanConvert">
|
||||
@click="beginGroupPlanConvert"
|
||||
>
|
||||
Begin converting to group plan subscription
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row"
|
||||
v-if="isConvertingToGroupPlan">
|
||||
<div
|
||||
v-if="isConvertingToGroupPlan"
|
||||
class="form-group row"
|
||||
>
|
||||
<label class="col-sm-3 col-form-label">
|
||||
Group Plan group ID:
|
||||
</label>
|
||||
@@ -374,25 +430,40 @@
|
||||
class="btn btn-primary mt-1"
|
||||
@click="saveClicked"
|
||||
>
|
||||
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||
<b
|
||||
v-if="hasUnsavedChanges"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
<b-modal id="sub_termination_modal" title="Set Termination Date">
|
||||
<b-modal
|
||||
id="sub_termination_modal"
|
||||
title="Set Termination Date"
|
||||
>
|
||||
<p>
|
||||
You can set the sub benefit termination date to today or to the last
|
||||
day of the current billing cycle. Any extra subscription credit will
|
||||
then be processed and automatically added onto the selected date.
|
||||
</p>
|
||||
<template #modal-footer>
|
||||
<div class="mt-3 btn btn-secondary" @click="$bvModal.hide('sub_termination_modal')">
|
||||
<div
|
||||
class="mt-3 btn btn-secondary"
|
||||
@click="$bvModal.hide('sub_termination_modal')"
|
||||
>
|
||||
Close
|
||||
</div>
|
||||
<div class="mt-3 btn btn-danger" @click="terminateSubscription()">
|
||||
<div
|
||||
class="mt-3 btn btn-danger"
|
||||
@click="terminateSubscription()"
|
||||
>
|
||||
Set to Today
|
||||
</div>
|
||||
<div class="mt-3 btn btn-danger" @click="terminateSubscription(todayWithRemainingCycle)">
|
||||
<div
|
||||
class="mt-3 btn btn-danger"
|
||||
@click="terminateSubscription(todayWithRemainingCycle)"
|
||||
>
|
||||
Set to {{ todayWithRemainingCycle.utc().format('MM/DD/YYYY') }}
|
||||
</div>
|
||||
</template>
|
||||
@@ -401,7 +472,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.input-group-append {
|
||||
width: auto;
|
||||
@@ -420,15 +491,15 @@
|
||||
import isUUID from 'validator/es/lib/isUUID';
|
||||
import moment from 'moment';
|
||||
import { getPlanContext } from '@/../../common/script/cron';
|
||||
import subscriptionBlocks from '@/../../common/script/content/subscriptionBlocks';
|
||||
import saveHero from '../mixins/saveHero';
|
||||
import subscriptionBlocks from '../../../../../common/script/content/subscriptionBlocks';
|
||||
import LoadingSpinner from '@/components/ui/loadingSpinner';
|
||||
|
||||
export default {
|
||||
mixins: [saveHero],
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
},
|
||||
mixins: [saveHero],
|
||||
props: {
|
||||
hero: {
|
||||
type: Object,
|
||||
@@ -22,8 +22,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PurchaseHistoryTable from '../../ui/purchaseHistoryTable.vue';
|
||||
import { userStateMixin } from '../../../mixins/userState';
|
||||
import PurchaseHistoryTable from '../../../ui/purchaseHistoryTable.vue';
|
||||
import { userStateMixin } from '../../../../mixins/userState';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -150,7 +150,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.page-header.btn-flat {
|
||||
background: transparent;
|
||||
@@ -180,7 +180,7 @@
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { userStateMixin } from '../../../mixins/userState';
|
||||
import { userStateMixin } from '../../../../mixins/userState';
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
@@ -13,9 +13,12 @@
|
||||
@click="expand = !expand"
|
||||
>
|
||||
User Profile
|
||||
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
|
||||
Unsaved changes
|
||||
</b>
|
||||
<b
|
||||
v-if="hasUnsavedChanges && !expand"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
@@ -66,7 +69,10 @@
|
||||
value="Save"
|
||||
class="btn btn-primary mt-1"
|
||||
>
|
||||
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||
<b
|
||||
v-if="hasUnsavedChanges"
|
||||
class="text-warning float-right"
|
||||
>
|
||||
Unsaved changes
|
||||
</b>
|
||||
</div>
|
||||
@@ -86,7 +92,7 @@ import markdownDirective from '@/directives/markdown';
|
||||
import saveHero from '../mixins/saveHero';
|
||||
|
||||
import { mapState } from '@/libs/store';
|
||||
import { userStateMixin } from '../../../mixins/userState';
|
||||
import { userStateMixin } from '../../../../mixins/userState';
|
||||
|
||||
function resetData (self) {
|
||||
self.expand = false;
|
||||
133
website/client/src/components/admin/blocker/blocker_form.vue
Normal file
133
website/client/src/components/admin/blocker/blocker_form.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div style="display: contents">
|
||||
<td>
|
||||
<select
|
||||
v-model="blocker.type"
|
||||
class="form-control"
|
||||
@change="onTypeChanged"
|
||||
>
|
||||
<option value="ipaddress">
|
||||
IP-Address
|
||||
</option>
|
||||
<option value="client">
|
||||
Client Identifier
|
||||
</option>
|
||||
<option value="email">
|
||||
E-Mail
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
v-model="blocker.area"
|
||||
class="form-control"
|
||||
>
|
||||
<option value="full">
|
||||
Full
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
v-model="blocker.value"
|
||||
class="form-control"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
:class="{ 'is-invalid input-invalid': !isValid }"
|
||||
@input="validateValue"
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
v-model="blocker.reason"
|
||||
class="form-control"
|
||||
>
|
||||
</td>
|
||||
<td
|
||||
colspan="3"
|
||||
class="text-right"
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:disabled="!isValid"
|
||||
:class="{ disabled: !isValid }"
|
||||
@click="$emit('save', blocker)"
|
||||
>
|
||||
<span>Save</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="$emit('cancel')"
|
||||
>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
</td>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.btn-primary.disabled {
|
||||
background: #4F2A93;
|
||||
color: white;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import isIP from 'validator/es/lib/isIP';
|
||||
|
||||
export default {
|
||||
name: 'BlockerForm',
|
||||
props: {
|
||||
isNew: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
blocker: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
type: '',
|
||||
area: '',
|
||||
value: '',
|
||||
reason: '',
|
||||
}),
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isValid: false,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.validateValue();
|
||||
},
|
||||
methods: {
|
||||
onTypeChanged () {
|
||||
if (this.blocker.type === 'email') {
|
||||
this.blocker.area = 'full';
|
||||
}
|
||||
this.validateValue();
|
||||
},
|
||||
validateValue () {
|
||||
if (this.blocker.type === 'ipaddress') {
|
||||
this.validateValueAsIpAddress();
|
||||
} else if (this.blocker.type === 'client') {
|
||||
this.validateValueAsClient();
|
||||
} else if (this.blocker.type === 'email') {
|
||||
this.validateValueAsEmail();
|
||||
}
|
||||
},
|
||||
validateValueAsEmail () {
|
||||
const emailRegex = /^([a-zA-Z0-9._%+-]*)@(?:[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})?$/;
|
||||
this.isValid = emailRegex.test(this.blocker.value) && this.blocker.value.length > 3;
|
||||
},
|
||||
validateValueAsIpAddress () {
|
||||
this.isValid = isIP(this.blocker.value);
|
||||
},
|
||||
validateValueAsClient () {
|
||||
this.isValid = this.blocker.value.length > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
238
website/client/src/components/admin/blocker/index.vue
Normal file
238
website/client/src/components/admin/blocker/index.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="row standard-page col-12 d-flex justify-content-center">
|
||||
<div class="blocker-content">
|
||||
<h1>
|
||||
Blockers
|
||||
<button
|
||||
class="btn btn-primary float-right"
|
||||
@click="showCreateForm = true"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
</h1>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Type <span
|
||||
id="type_tooltip"
|
||||
class="info-icon"
|
||||
>?</span>
|
||||
<b-tooltip
|
||||
target="type_tooltip"
|
||||
>
|
||||
<b>IP-Address</b> - Block access for a specific IP-Address
|
||||
<br>
|
||||
<br>
|
||||
<b>Client</b> - Block access for a client based on the "x-client" header.
|
||||
<br>
|
||||
<br>
|
||||
<b>E-Mail</b> - Blocks e-mails from being used for signup.
|
||||
</b-tooltip>
|
||||
</th>
|
||||
<th>
|
||||
Area <span
|
||||
id="area_tooltip"
|
||||
class="info-icon"
|
||||
>?</span>
|
||||
<b-tooltip
|
||||
target="area_tooltip"
|
||||
>
|
||||
<b>Full</b> - Block access to the entire site.
|
||||
<br>
|
||||
<br>
|
||||
<b>Payments</b> - Block access to any payment related functionality.
|
||||
</b-tooltip>
|
||||
</th>
|
||||
<th>Value</th>
|
||||
<th>Reason</th>
|
||||
<th>Source</th>
|
||||
<th>Created at</th>
|
||||
<th class="btncol"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="showCreateForm">
|
||||
<BlockerForm
|
||||
:is-new="true"
|
||||
:blocker="newBlocker"
|
||||
@save="createBlocker"
|
||||
@cancel="showCreateForm = false"
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="blocker in blockers"
|
||||
:key="blocker._id"
|
||||
>
|
||||
<BlockerForm
|
||||
v-if="blocker._id === editedBlockerId"
|
||||
:blocker="blocker"
|
||||
@save="saveBlocker(blocker)"
|
||||
@cancel="editedBlockerId = null"
|
||||
/>
|
||||
<template v-else>
|
||||
<td>{{ getTypeName(blocker.type) }}</td>
|
||||
<td>{{ getAreaName(blocker.area) }}</td>
|
||||
<td>{{ blocker.value }}</td>
|
||||
<td>{{ blocker.reason || "--" }}</td>
|
||||
<td>{{ blocker.blockSource }}</td>
|
||||
<td>{{ blocker.createdAt }}</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
@click="editBlocker(blocker._id)"
|
||||
>
|
||||
<span
|
||||
v-once
|
||||
class="svg-icon icon-16"
|
||||
v-html="icons.editIcon"
|
||||
></span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="deleteBlocker(blocker._id)"
|
||||
>
|
||||
<span
|
||||
v-once
|
||||
class="svg-icon icon-16"
|
||||
v-html="icons.deleteIcon"
|
||||
></span>
|
||||
</button>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.blocker-content {
|
||||
flex: 0 0 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.4rem 0.75rem;
|
||||
}
|
||||
|
||||
.btncol {
|
||||
width: 123px;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
font-size: 0.8rem;
|
||||
color: $purple-400;
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
background-color: $gray-500;
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.info-icon:hover {
|
||||
background-color: $purple-400;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import editIcon from '@/assets/svg/edit.svg?raw';
|
||||
import deleteIcon from '@/assets/svg/delete.svg?raw';
|
||||
import BlockerForm from './blocker_form.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BlockerForm,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showCreateForm: false,
|
||||
newBlocker: {
|
||||
type: '',
|
||||
area: 'full',
|
||||
value: '',
|
||||
reason: '',
|
||||
},
|
||||
blockers: [],
|
||||
editedBlockerId: null,
|
||||
icons: Object.freeze({
|
||||
editIcon,
|
||||
deleteIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('siteBlockers'),
|
||||
});
|
||||
this.loadBlockers();
|
||||
},
|
||||
methods: {
|
||||
async loadBlockers () {
|
||||
this.blockers = await this.$store.dispatch('blockers:getBlockers');
|
||||
},
|
||||
editBlocker (id) {
|
||||
this.editedBlockerId = id;
|
||||
},
|
||||
async saveBlocker (blocker) {
|
||||
await this.$store.dispatch('blockers:updateBlocker', { blocker });
|
||||
this.editedBlockerId = null;
|
||||
this.loadBlockers();
|
||||
},
|
||||
async deleteBlocker (blockerId) {
|
||||
if (!window.confirm('Are you sure you want to delete this blocker?')) {
|
||||
return;
|
||||
}
|
||||
await this.$store.dispatch('blockers:deleteBlocker', { blockerId });
|
||||
this.loadBlockers();
|
||||
},
|
||||
async createBlocker (blocker) {
|
||||
await this.$store.dispatch('blockers:createBlocker', { blocker });
|
||||
this.showCreateForm = false;
|
||||
this.newBlocker = {
|
||||
type: '',
|
||||
area: 'full',
|
||||
value: '',
|
||||
reason: '',
|
||||
};
|
||||
this.loadBlockers();
|
||||
},
|
||||
|
||||
getTypeName (type) {
|
||||
switch (type) {
|
||||
case 'ipaddress':
|
||||
return 'IP-Address';
|
||||
case 'email':
|
||||
return 'E-Mail';
|
||||
case 'client':
|
||||
return 'Client Identifier';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
},
|
||||
getAreaName (area) {
|
||||
switch (area) {
|
||||
case 'full':
|
||||
return 'Full';
|
||||
case 'payments':
|
||||
return 'Payments';
|
||||
default:
|
||||
return area;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
40
website/client/src/components/admin/container.vue
Normal file
40
website/client/src/components/admin/container.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<secondary-menu class="col-12">
|
||||
<router-link
|
||||
v-if="hasPermission(user, 'userSupport')"
|
||||
class="nav-link"
|
||||
:to="{name: 'adminPanel'}"
|
||||
>
|
||||
{{ $t('adminPanel') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="hasPermission(user, 'accessControl')"
|
||||
class="nav-link"
|
||||
:to="{name: 'blockers'}"
|
||||
>
|
||||
{{ $t('siteBlockers') }}
|
||||
</router-link>
|
||||
</secondary-menu><div class="col-12">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import SecondaryMenu from '@/components/secondaryMenu';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SecondaryMenu,
|
||||
},
|
||||
mixins: [
|
||||
userStateMixin,
|
||||
],
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -276,9 +276,9 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="time-travel"
|
||||
v-if="TIME_TRAVEL_ENABLED && user?.permissions?.fullAccess"
|
||||
:key="lastTimeJump"
|
||||
class="time-travel"
|
||||
>
|
||||
<a
|
||||
class="btn btn-secondary mr-1"
|
||||
@@ -299,7 +299,7 @@
|
||||
@click="resetTime()"
|
||||
>
|
||||
Reset
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
class="btn btn-secondary mr-1"
|
||||
@@ -403,7 +403,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
.footer-row {
|
||||
margin: 0;
|
||||
flex: 0 1 auto;
|
||||
@@ -838,12 +838,12 @@ import moment from 'moment';
|
||||
import Vue from 'vue';
|
||||
|
||||
// images
|
||||
import melior from '@/assets/svg/melior.svg';
|
||||
import bluesky from '@/assets/svg/bluesky.svg';
|
||||
import facebook from '@/assets/svg/facebook.svg';
|
||||
import instagram from '@/assets/svg/instagram.svg';
|
||||
import tumblr from '@/assets/svg/tumblr.svg';
|
||||
import heart from '@/assets/svg/heart.svg';
|
||||
import melior from '@/assets/svg/melior.svg?raw';
|
||||
import bluesky from '@/assets/svg/bluesky.svg?raw';
|
||||
import facebook from '@/assets/svg/facebook.svg?raw';
|
||||
import instagram from '@/assets/svg/instagram.svg?raw';
|
||||
import tumblr from '@/assets/svg/tumblr.svg?raw';
|
||||
import heart from '@/assets/svg/heart.svg?raw';
|
||||
|
||||
// components & modals
|
||||
import { mapState } from '@/libs/store';
|
||||
@@ -851,12 +851,14 @@ import buyGemsModal from './payments/buyGemsModal.vue';
|
||||
import reportBug from '@/mixins/reportBug.js';
|
||||
import { worldStateMixin } from '@/mixins/worldState';
|
||||
|
||||
const DEBUG_ENABLED = process.env.DEBUG_ENABLED === 'true'; // eslint-disable-line no-process-env
|
||||
const TIME_TRAVEL_ENABLED = process.env.TIME_TRAVEL_ENABLED === 'true'; // eslint-disable-line no-process-env
|
||||
const DEBUG_ENABLED = import.meta.env.DEBUG_ENABLED === 'true';
|
||||
const TIME_TRAVEL_ENABLED = import.meta.env.TIME_TRAVEL_ENABLED === 'true';
|
||||
|
||||
let sinon;
|
||||
if (TIME_TRAVEL_ENABLED) {
|
||||
// eslint-disable-next-line global-require
|
||||
sinon = await import('sinon');
|
||||
if (import.meta.env.TIME_TRAVEL_ENABLED === 'true') {
|
||||
(async () => {
|
||||
sinon = await import('sinon');
|
||||
})();
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.form {
|
||||
margin: 0 auto;
|
||||
@@ -227,8 +227,8 @@ import debounce from 'lodash/debounce';
|
||||
import isEmail from 'validator/es/lib/isEmail';
|
||||
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
|
||||
import { setUpAxios, buildAppleAuthUrl } from '@/libs/auth';
|
||||
import googleIcon from '@/assets/svg/google.svg';
|
||||
import appleIcon from '@/assets/svg/apple_black.svg';
|
||||
import googleIcon from '@/assets/svg/google.svg?raw';
|
||||
import appleIcon from '@/assets/svg/apple_black.svg?raw';
|
||||
|
||||
export default {
|
||||
name: 'AuthForm',
|
||||
@@ -290,7 +290,7 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
hello.init({
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||
google: import.meta.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -220,7 +220,6 @@
|
||||
v-if="forgotPassword"
|
||||
id="forgot-form"
|
||||
@submit.prevent="handleSubmit"
|
||||
@keyup.enter="handleSubmit"
|
||||
>
|
||||
<div class="text-center">
|
||||
<div>
|
||||
@@ -268,12 +267,11 @@
|
||||
v-if="resetPasswordSetNewOne"
|
||||
id="reset-password-set-new-one-form"
|
||||
@submit.prevent="handleSubmit"
|
||||
@keyup.enter="handleSubmit"
|
||||
>
|
||||
<div class="text-center">
|
||||
<div>
|
||||
<div
|
||||
class="svg-icon habitica-logo color"
|
||||
class="svg-icon habitica-logo"
|
||||
v-html="icons.habiticaIcon"
|
||||
></div>
|
||||
</div>
|
||||
@@ -355,7 +353,7 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
@media only screen and (min-height: 1080px) {
|
||||
.bottom-wrap-register {
|
||||
@@ -491,7 +489,7 @@
|
||||
|
||||
#top-background {
|
||||
.seamless_stars_varied_opacity_repeat {
|
||||
background-image: url('~@/assets/images/auth/seamless_stars_varied_opacity.png');
|
||||
background-image: url('@/assets/images/auth/seamless_stars_varied_opacity.png');
|
||||
background-repeat: repeat-x;
|
||||
position: absolute;
|
||||
height: 500px;
|
||||
@@ -510,7 +508,7 @@
|
||||
position: relative;
|
||||
|
||||
.seamless_mountains_demo_repeat {
|
||||
background-image: url('~@/assets/images/auth/seamless_mountains_demo.png');
|
||||
background-image: url('@/assets/images/auth/seamless_mountains_demo.png');
|
||||
background-repeat: repeat-x;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
@@ -520,7 +518,7 @@
|
||||
}
|
||||
|
||||
.midground_foreground_extended2 {
|
||||
background-image: url('~@/assets/images/auth/midground_foreground_extended2.png');
|
||||
background-image: url('@/assets/images/auth/midground_foreground_extended2.png');
|
||||
position: relative;
|
||||
width: 1500px;
|
||||
max-width: 100%;
|
||||
@@ -611,11 +609,11 @@ import isEmail from 'validator/es/lib/isEmail';
|
||||
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
|
||||
import { buildAppleAuthUrl } from '../../libs/auth';
|
||||
import sanitizeRedirect from '@/mixins/sanitizeRedirect';
|
||||
import exclamation from '@/assets/svg/exclamation.svg';
|
||||
import gryphon from '@/assets/svg/gryphon.svg';
|
||||
import habiticaIcon from '@/assets/svg/logo-horizontal.svg';
|
||||
import googleIcon from '@/assets/svg/google.svg';
|
||||
import appleIcon from '@/assets/svg/apple_black.svg';
|
||||
import exclamation from '@/assets/svg/exclamation.svg?raw';
|
||||
import gryphon from '@/assets/svg/gryphon.svg?raw';
|
||||
import habiticaIcon from '@/assets/svg/logo-horizontal.svg?raw';
|
||||
import googleIcon from '@/assets/svg/google.svg?raw';
|
||||
import appleIcon from '@/assets/svg/apple_black.svg?raw';
|
||||
|
||||
export default {
|
||||
mixins: [sanitizeRedirect],
|
||||
@@ -726,9 +724,13 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.forgotPassword = this.$route.path.startsWith('/forgot-password');
|
||||
|
||||
if (this.forgotPassword) {
|
||||
if (this.$route.query.email) {
|
||||
this.username = this.$route.query.email;
|
||||
}
|
||||
}
|
||||
hello.init({
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||
google: import.meta.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.avatar {
|
||||
width: 141px;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.bottom-banner {
|
||||
background: linear-gradient(114.26deg, $purple-300 0%, $purple-200 100%);
|
||||
@@ -55,7 +55,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import sparkles from '@/assets/svg/sparkles-left.svg';
|
||||
import sparkles from '@/assets/svg/sparkles-left.svg?raw';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
import gold from '@/assets/svg/gold.svg';
|
||||
import gem from '@/assets/svg/gem.svg?raw';
|
||||
import gold from '@/assets/svg/gold.svg?raw';
|
||||
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
||||
import Sprite from '@/components/ui/sprite.vue';
|
||||
|
||||
@@ -72,7 +72,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.customize-options {
|
||||
width: 100%;
|
||||
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.sub-menu {
|
||||
display: flex;
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
|
||||
<script>
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import { LOCALSTORAGE_AUTH_KEY } from '@/libs/auth';
|
||||
|
||||
const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
|
||||
const COMMUNITY_MANAGER_EMAIL = import.meta.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
@@ -39,12 +40,14 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
bannedMessage () {
|
||||
const AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
|
||||
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,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
h2 {
|
||||
color: $white;
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
h2 {
|
||||
color: $white;
|
||||
@@ -134,7 +134,7 @@ label {
|
||||
|
||||
<script>
|
||||
import closeIcon from '@/components/shared/closeIcon';
|
||||
import checkCircleIcon from '@/assets/svg/check_circle.svg';
|
||||
import checkCircleIcon from '@/assets/svg/check_circle.svg?raw';
|
||||
import { MODALS } from '@/libs/consts';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
h1 {
|
||||
color: $purple-200;
|
||||
@@ -380,9 +380,9 @@ import sidebarSection from '../sidebarSection';
|
||||
import userLink from '../userLink';
|
||||
import groupLink from '../groupLink';
|
||||
|
||||
import gemIcon from '@/assets/svg/gem.svg';
|
||||
import memberIcon from '@/assets/svg/member-icon.svg';
|
||||
import calendarIcon from '@/assets/svg/calendar.svg';
|
||||
import gemIcon from '@/assets/svg/gem.svg?raw';
|
||||
import memberIcon from '@/assets/svg/member-icon.svg?raw';
|
||||
import calendarIcon from '@/assets/svg/calendar.svg?raw';
|
||||
|
||||
const TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge'];
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
// Have to use this, because v-markdown creates p element in h3. Scoping doesn't work with it.
|
||||
.challenge-title > p {
|
||||
display: -webkit-box;
|
||||
@@ -127,7 +127,7 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.challenge {
|
||||
background-color: $white;
|
||||
@@ -377,14 +377,14 @@ import categoryTags from '../categories/categoryTags';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import gemIcon from '@/assets/svg/gem.svg';
|
||||
import memberIcon from '@/assets/svg/member-icon.svg';
|
||||
import calendarIcon from '@/assets/svg/calendar.svg';
|
||||
import habitIcon from '@/assets/svg/habit.svg';
|
||||
import todoIcon from '@/assets/svg/todo.svg';
|
||||
import dailyIcon from '@/assets/svg/daily.svg';
|
||||
import rewardIcon from '@/assets/svg/reward.svg';
|
||||
import officialIcon from '@/assets/svg/official.svg';
|
||||
import gemIcon from '@/assets/svg/gem.svg?raw';
|
||||
import memberIcon from '@/assets/svg/member-icon.svg?raw';
|
||||
import calendarIcon from '@/assets/svg/calendar.svg?raw';
|
||||
import habitIcon from '@/assets/svg/habit.svg?raw';
|
||||
import todoIcon from '@/assets/svg/todo.svg?raw';
|
||||
import dailyIcon from '@/assets/svg/daily.svg?raw';
|
||||
import rewardIcon from '@/assets/svg/reward.svg?raw';
|
||||
import officialIcon from '@/assets/svg/official.svg?raw';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
#challenge-modal {
|
||||
h5 {
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
#close-challenge-modal {
|
||||
h2 {
|
||||
@@ -98,7 +98,7 @@
|
||||
}
|
||||
|
||||
.support-habitica {
|
||||
background-image: url('~@/assets/svg/for-css/support-habitica-gems.svg');
|
||||
background-image: url('@/assets/svg/for-css/support-habitica-gems.svg?raw');
|
||||
width: 325px;
|
||||
height: 89px;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.header-row {
|
||||
@@ -122,7 +122,7 @@ import challengeModal from './challengeModal';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
import positiveIcon from '@/assets/svg/positive.svg?raw';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.no-challenge-section {
|
||||
padding: 2em;
|
||||
@@ -84,7 +84,7 @@ import markdownDirective from '@/directives/markdown';
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import challengeItem from './challengeItem';
|
||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||
import challengeIcon from '@/assets/svg/challenge.svg?raw';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.header-row {
|
||||
@@ -150,8 +150,8 @@ import challengeModal from './challengeModal';
|
||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
|
||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
import challengeIcon from '@/assets/svg/challenge.svg?raw';
|
||||
import positiveIcon from '@/assets/svg/positive.svg?raw';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.modal-body {
|
||||
padding: 0px 8px 0px 8px;
|
||||
@@ -207,8 +207,8 @@ import { mapState } from '@/libs/store';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import svgReport from '@/assets/svg/report.svg';
|
||||
import svgClose from '@/assets/svg/close.svg?raw';
|
||||
import svgReport from '@/assets/svg/report.svg?raw';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/tiers.scss';
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/tiers.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.autocomplete-results {
|
||||
padding: .5em;
|
||||
@@ -74,16 +74,16 @@
|
||||
<script>
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import styleHelper from '@/mixins/styleHelper';
|
||||
import tier1 from '@/assets/svg/tier-1.svg';
|
||||
import tier2 from '@/assets/svg/tier-2.svg';
|
||||
import tier3 from '@/assets/svg/tier-3.svg';
|
||||
import tier4 from '@/assets/svg/tier-4.svg';
|
||||
import tier5 from '@/assets/svg/tier-5.svg';
|
||||
import tier6 from '@/assets/svg/tier-6.svg';
|
||||
import tier7 from '@/assets/svg/tier-7.svg';
|
||||
import tier8 from '@/assets/svg/tier-mod.svg';
|
||||
import tier9 from '@/assets/svg/tier-staff.svg';
|
||||
import tierNPC from '@/assets/svg/tier-npc.svg';
|
||||
import tier1 from '@/assets/svg/tier-1.svg?raw';
|
||||
import tier2 from '@/assets/svg/tier-2.svg?raw';
|
||||
import tier3 from '@/assets/svg/tier-3.svg?raw';
|
||||
import tier4 from '@/assets/svg/tier-4.svg?raw';
|
||||
import tier5 from '@/assets/svg/tier-5.svg?raw';
|
||||
import tier6 from '@/assets/svg/tier-6.svg?raw';
|
||||
import tier7 from '@/assets/svg/tier-7.svg?raw';
|
||||
import tier8 from '@/assets/svg/tier-mod.svg?raw';
|
||||
import tier9 from '@/assets/svg/tier-staff.svg?raw';
|
||||
import tierNPC from '@/assets/svg/tier-npc.svg?raw';
|
||||
|
||||
export default {
|
||||
mixins: [styleHelper],
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.avatar {
|
||||
width: 10%;
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.modal-body {
|
||||
padding: 0px 8px 0px 8px;
|
||||
@@ -199,7 +199,7 @@
|
||||
import notifications from '@/mixins/notifications';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import svgClose from '@/assets/svg/close.svg?raw';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
|
||||
@@ -572,7 +572,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
$dialogMarginTop: 56px;
|
||||
$userCreationBgHeight: 105px;
|
||||
@@ -671,7 +671,7 @@
|
||||
}
|
||||
|
||||
.user-creation-bg {
|
||||
background-image: url('~@/assets/creator/creator-hills-bg.png');
|
||||
background-image: url('@/assets/creator/creator-hills-bg.png');
|
||||
height: $userCreationBgHeight;
|
||||
width: 219px;
|
||||
margin: 0 auto;
|
||||
@@ -1001,18 +1001,18 @@ import hairSettings from './avatarModal/hair-settings';
|
||||
import extraSettings from './avatarModal/extra-settings';
|
||||
import closeX from './ui/closeX';
|
||||
|
||||
import logoPurple from '@/assets/svg/logo-purple.svg';
|
||||
import bodyIcon from '@/assets/svg/body.svg';
|
||||
import accessoriesIcon from '@/assets/svg/accessories.svg';
|
||||
import skinIcon from '@/assets/svg/skin.svg';
|
||||
import hairIcon from '@/assets/svg/hair.svg';
|
||||
import backgroundsIcon from '@/assets/svg/backgrounds.svg';
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
import hourglass from '@/assets/svg/hourglass.svg';
|
||||
import gold from '@/assets/svg/gold.svg';
|
||||
import arrowRight from '@/assets/svg/arrow_right.svg';
|
||||
import arrowLeft from '@/assets/svg/arrow_left.svg';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import logoPurple from '@/assets/svg/logo-purple.svg?raw';
|
||||
import bodyIcon from '@/assets/svg/body.svg?raw';
|
||||
import accessoriesIcon from '@/assets/svg/accessories.svg?raw';
|
||||
import skinIcon from '@/assets/svg/skin.svg?raw';
|
||||
import hairIcon from '@/assets/svg/hair.svg?raw';
|
||||
import backgroundsIcon from '@/assets/svg/backgrounds.svg?raw';
|
||||
import gem from '@/assets/svg/gem.svg?raw';
|
||||
import hourglass from '@/assets/svg/hourglass.svg?raw';
|
||||
import gold from '@/assets/svg/gold.svg?raw';
|
||||
import arrowRight from '@/assets/svg/arrow_right.svg?raw';
|
||||
import arrowLeft from '@/assets/svg/arrow_left.svg?raw';
|
||||
import svgClose from '@/assets/svg/close.svg?raw';
|
||||
import { avatarEditorUtilities } from '../mixins/avatarEditUtilities';
|
||||
import Sprite from './ui/sprite';
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
#external-link-modal {
|
||||
&.modal {
|
||||
@@ -174,8 +174,8 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import exclamationIcon from '@/assets/svg/exclamation.svg';
|
||||
import closeIcon from '@/assets/svg/new-close.svg';
|
||||
import exclamationIcon from '@/assets/svg/exclamation.svg?raw';
|
||||
import closeIcon from '@/assets/svg/new-close.svg?raw';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
.face-avatar {
|
||||
width: 36px;
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
h2 {
|
||||
color: $purple-300;
|
||||
@@ -201,7 +201,7 @@
|
||||
|
||||
</style>
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/mixins.scss';
|
||||
@import '@/assets/scss/mixins.scss';
|
||||
#create-group {
|
||||
.modal-dialog {
|
||||
max-width: 448px;
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
></div>
|
||||
<img
|
||||
class="task-columns"
|
||||
src="~@/assets/images/group-plans/task-columns.png"
|
||||
srcset="~@/assets/images/group-plans/task-columns@2x.png 2x,
|
||||
~@/assets/images/group-plans/task-columns@3x.png 3x"
|
||||
src="@/assets/images/group-plans/task-columns.png"
|
||||
srcset="@/assets/images/group-plans/task-columns@2x.png 2x,
|
||||
@/assets/images/group-plans/task-columns@3x.png 3x"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
@@ -96,7 +96,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
#group-plans-update {
|
||||
.modal-content {
|
||||
border-top-left-radius: 10px;
|
||||
@@ -125,7 +125,7 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
h2 {
|
||||
color: $white;
|
||||
line-height: 28px;
|
||||
@@ -183,8 +183,8 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import closeIcon from '@/assets/svg/close.svg';
|
||||
import sparkles from '@/assets/svg/sparkles-left.svg';
|
||||
import closeIcon from '@/assets/svg/close.svg?raw';
|
||||
import sparkles from '@/assets/svg/sparkles-left.svg?raw';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
|
||||
@@ -125,8 +125,8 @@
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/create-task.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/create-task.scss';
|
||||
|
||||
h1 {
|
||||
color: $purple-300;
|
||||
@@ -190,13 +190,13 @@ import toggleSwitch from '@/components/ui/toggleSwitch';
|
||||
|
||||
import sync from '../../mixins/sync';
|
||||
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
import filterIcon from '@/assets/svg/filter.svg';
|
||||
import deleteIcon from '@/assets/svg/delete.svg';
|
||||
import habitIcon from '@/assets/svg/habit.svg';
|
||||
import dailyIcon from '@/assets/svg/daily.svg';
|
||||
import todoIcon from '@/assets/svg/todo.svg';
|
||||
import rewardIcon from '@/assets/svg/reward.svg';
|
||||
import positiveIcon from '@/assets/svg/positive.svg?raw';
|
||||
import filterIcon from '@/assets/svg/filter.svg?raw';
|
||||
import deleteIcon from '@/assets/svg/delete.svg?raw';
|
||||
import habitIcon from '@/assets/svg/habit.svg?raw';
|
||||
import dailyIcon from '@/assets/svg/daily.svg?raw';
|
||||
import todoIcon from '@/assets/svg/todo.svg?raw';
|
||||
import rewardIcon from '@/assets/svg/reward.svg?raw';
|
||||
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
@@ -212,8 +212,8 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/variables.scss';
|
||||
@import '@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/variables.scss';
|
||||
|
||||
.chat-actions {
|
||||
margin-top: 1em;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user