Merge branch 'develop' into release

This commit is contained in:
Sabe Jones 2019-12-19 16:55:10 -06:00
commit be107f02d3
303 changed files with 28278 additions and 21288 deletions

View file

@ -10,7 +10,7 @@ async function syncChallengeToMembers (challenges) {
const promises = [];
users.forEach(user => {
promises.push(challenge.syncToUser(user));
promises.push(challenge.syncTasksToUser(user));
promises.push(challenge.save());
promises.push(user.save());
});

View file

@ -0,0 +1,107 @@
/*
* Award Onboarding Achievements for existing users
*/
/* eslint-disable no-console */
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20191218_onboarding_achievements';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const set = {};
set.migration = MIGRATION_NAME;
const hasPet = Object.keys(user.items.pets).find(petKey => {
const pet = user.items.pets[petKey];
if (pet >= 5) return true;
return false;
});
if (hasPet) {
set['achievements.hatchedPet'] = true;
}
const hasFedPet = Object.keys(user.items.pets).find(petKey => {
const pet = user.items.pets[petKey];
if (pet > 5) return true;
return false;
});
if (hasFedPet) {
set['achievements.fedPet'] = true;
}
const hasGear = Object.keys(user.items.gear.owned).find(gearKey => {
const gear = user.items.gear.owned[gearKey];
if (gear === true && gearKey.indexOf('_special_') === -1) return true;
return false;
});
if (hasGear) {
set['achievements.purchasedEquipment'] = true;
}
const hasTask = Object.keys(user.tasksOrder).find(tasksOrderType => {
const order = user.tasksOrder[tasksOrderType];
if (order && order.length > 0) return true;
return false;
});
if (hasTask) {
set['achievements.createdTask'] = true;
}
const hasExperience = user.stats && user.stats.exp && user.stats.exp > 0;
if (hasTask && hasExperience) {
set['achievements.completedTask'] = true;
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.update({ _id: user._id }, { $set: set }).exec();
}
module.exports = async function processUsers () { // eslint-disable-line import/no-commonjs
const query = {
migration: { $ne: MIGRATION_NAME },
};
const fields = {
_id: 1,
stats: 1,
items: 1,
achievements: 1,
tasksOrder: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(100)
.sort({ _id: 1 })
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

316
package-lock.json generated
View file

@ -13,14 +13,14 @@
}
},
"@babel/core": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.4.tgz",
"integrity": "sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==",
"version": "7.7.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.5.tgz",
"integrity": "sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw==",
"requires": {
"@babel/code-frame": "^7.5.5",
"@babel/generator": "^7.7.4",
"@babel/helpers": "^7.7.4",
"@babel/parser": "^7.7.4",
"@babel/parser": "^7.7.5",
"@babel/template": "^7.7.4",
"@babel/traverse": "^7.7.4",
"@babel/types": "^7.7.4",
@ -33,6 +33,11 @@
"source-map": "^0.5.0"
},
"dependencies": {
"@babel/parser": {
"version": "7.7.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz",
"integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig=="
},
"convert-source-map": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
@ -152,9 +157,9 @@
}
},
"@babel/helper-module-transforms": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.4.tgz",
"integrity": "sha512-ehGBu4mXrhs0FxAqN8tWkzF8GSIGAiEumu4ONZ/hD9M88uHcD+Yu2ttKfOCgwzoesJOJrtQh7trI5YPbRtMmnA==",
"version": "7.7.5",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz",
"integrity": "sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw==",
"requires": {
"@babel/helper-module-imports": "^7.7.4",
"@babel/helper-simple-access": "^7.7.4",
@ -490,21 +495,21 @@
}
},
"@babel/plugin-transform-modules-amd": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.4.tgz",
"integrity": "sha512-/542/5LNA18YDtg1F+QHvvUSlxdvjZoD/aldQwkq+E3WCkbEjNSN9zdrOXaSlfg3IfGi22ijzecklF/A7kVZFQ==",
"version": "7.7.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz",
"integrity": "sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ==",
"requires": {
"@babel/helper-module-transforms": "^7.7.4",
"@babel/helper-module-transforms": "^7.7.5",
"@babel/helper-plugin-utils": "^7.0.0",
"babel-plugin-dynamic-import-node": "^2.3.0"
}
},
"@babel/plugin-transform-modules-commonjs": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.4.tgz",
"integrity": "sha512-k8iVS7Jhc367IcNF53KCwIXtKAH7czev866ThsTgy8CwlXjnKZna2VHwChglzLleYrcHz1eQEIJlGRQxB53nqA==",
"version": "7.7.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz",
"integrity": "sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==",
"requires": {
"@babel/helper-module-transforms": "^7.7.4",
"@babel/helper-module-transforms": "^7.7.5",
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/helper-simple-access": "^7.7.4",
"babel-plugin-dynamic-import-node": "^2.3.0"
@ -573,9 +578,9 @@
}
},
"@babel/plugin-transform-regenerator": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.4.tgz",
"integrity": "sha512-e7MWl5UJvmPEwFJTwkBlPmqixCtr9yAASBqff4ggXTNicZiwbF8Eefzm6NVgfiBp7JdAGItecnctKTgH44q2Jw==",
"version": "7.7.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz",
"integrity": "sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw==",
"requires": {
"regenerator-transform": "^0.14.0"
}
@ -640,9 +645,9 @@
}
},
"@babel/preset-env": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.7.4.tgz",
"integrity": "sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g==",
"version": "7.7.6",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.7.6.tgz",
"integrity": "sha512-k5hO17iF/Q7tR9Jv8PdNBZWYW6RofxhnxKjBMc0nG4JTaWvOTiPoO/RLFwAKcA4FpmuBFm6jkoqaRJLGi0zdaQ==",
"requires": {
"@babel/helper-module-imports": "^7.7.4",
"@babel/helper-plugin-utils": "^7.0.0",
@ -672,8 +677,8 @@
"@babel/plugin-transform-function-name": "^7.7.4",
"@babel/plugin-transform-literals": "^7.7.4",
"@babel/plugin-transform-member-expression-literals": "^7.7.4",
"@babel/plugin-transform-modules-amd": "^7.7.4",
"@babel/plugin-transform-modules-commonjs": "^7.7.4",
"@babel/plugin-transform-modules-amd": "^7.7.5",
"@babel/plugin-transform-modules-commonjs": "^7.7.5",
"@babel/plugin-transform-modules-systemjs": "^7.7.4",
"@babel/plugin-transform-modules-umd": "^7.7.4",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.7.4",
@ -681,7 +686,7 @@
"@babel/plugin-transform-object-super": "^7.7.4",
"@babel/plugin-transform-parameters": "^7.7.4",
"@babel/plugin-transform-property-literals": "^7.7.4",
"@babel/plugin-transform-regenerator": "^7.7.4",
"@babel/plugin-transform-regenerator": "^7.7.5",
"@babel/plugin-transform-reserved-words": "^7.7.4",
"@babel/plugin-transform-shorthand-properties": "^7.7.4",
"@babel/plugin-transform-spread": "^7.7.4",
@ -691,7 +696,7 @@
"@babel/plugin-transform-unicode-regex": "^7.7.4",
"@babel/types": "^7.7.4",
"browserslist": "^4.6.0",
"core-js-compat": "^3.1.1",
"core-js-compat": "^3.4.7",
"invariant": "^2.2.2",
"js-levenshtein": "^1.1.3",
"semver": "^5.5.0"
@ -746,9 +751,9 @@
}
},
"@google-cloud/common": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.2.3.tgz",
"integrity": "sha512-lvw54mGKn8VqVIy2NzAk0l5fntBFX4UwQhHk6HaqkyCQ7WBl5oz4XhzKMtMilozF/3ObPcDogqwuyEWyZ6rnQQ==",
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.2.5.tgz",
"integrity": "sha512-Iw6LJj7V8XEVFDORCHC/I+YVmK98KmZSa8z10O4h2Wpkzlk/Sjj8Ruz4QJHawj7GwuWjQQ47O2Z4JECXf1S3ag==",
"requires": {
"@google-cloud/projectify": "^1.0.0",
"@google-cloud/promisify": "^1.0.0",
@ -762,22 +767,22 @@
}
},
"@google-cloud/projectify": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.2.tgz",
"integrity": "sha512-WnkGxvk4U1kAJpoS/Ehk+3MZXVW+XHHhwc/QyD6G8Za4xml3Fv+NRn/bYffl1TxSg+gE0N0mj9Shgc7e8+fl8A=="
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz",
"integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg=="
},
"@google-cloud/promisify": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.3.tgz",
"integrity": "sha512-Rufgfl3TnkIil3CjsH33Q6093zeoVqyqCdvtvgHuCqRJxCZYfaVPIyr8JViMeLTD4Ja630pRKKZVSjKggoVbNg=="
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz",
"integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ=="
},
"@google-cloud/trace-agent": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-4.2.3.tgz",
"integrity": "sha512-BezSjercoxF7CAjklEwlJ9w7sS7lrGcMKCiyfdGq9cl7ZlsrYU2FnO3x8aDXQicnfaoHEeBFvAcR4ADhoGZamQ==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-4.2.4.tgz",
"integrity": "sha512-XCiEQ2D241RvYmMyeX/Q4My6NGAnSNjZZ/ff82MTGLc12iBxvYD3zMU/Yx6XtcByHIaSFqmCexK1hZzkALHjyQ==",
"requires": {
"@google-cloud/common": "^2.0.0",
"@opencensus/propagation-stackdriver": "0.0.18",
"@opencensus/propagation-stackdriver": "0.0.19",
"builtin-modules": "^3.0.0",
"console-log-level": "^1.4.0",
"continuation-local-storage": "^3.2.1",
@ -824,9 +829,9 @@
}
},
"@opencensus/core": {
"version": "0.0.18",
"resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.18.tgz",
"integrity": "sha512-PgRQXLyb3bLi8Z6pQct9erYFRdnYAZNQXAEVPf6Xq6IMkZaH20wiOTNNPxEckjI31mq5utgstAbwOn4gJiPjBQ==",
"version": "0.0.19",
"resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.19.tgz",
"integrity": "sha512-Y5QXa7vggMU0+jveLcworfX9jNnztix7x1NraAV0uGkTp4y46HrFl0DnNcnNxUDvBu/cYeWRwlmhiWlr9+adOQ==",
"requires": {
"continuation-local-storage": "^3.2.1",
"log-driver": "^1.2.7",
@ -843,11 +848,11 @@
}
},
"@opencensus/propagation-stackdriver": {
"version": "0.0.18",
"resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.18.tgz",
"integrity": "sha512-BLwfszIGAfqN2mqGf/atfEu84cWeoLM/YuXGfXDO1iDN2k5GXz4QFyhS8sz5l63HtsYuQqFuV+Ze7ZM0NvJp2A==",
"version": "0.0.19",
"resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.19.tgz",
"integrity": "sha512-TTL9KIOkvTpd+DT2gj3R3JP7XqOAf69ab/wzxIwpBlFqfRiIFBkOALyC/Gy4pKooAe5DemDhXZuRtIa0PgfoZQ==",
"requires": {
"@opencensus/core": "^0.0.18",
"@opencensus/core": "^0.0.19",
"hex2dec": "^1.0.1",
"uuid": "^3.2.1"
}
@ -1707,25 +1712,44 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"aws-sdk": {
"version": "2.578.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.578.0.tgz",
"integrity": "sha512-QOot7ha8J+w+AQf1UNzpGpbcZtCaK/mqjenG177ybm2nvm00a4PKa5dz/kF/bYi2qMx9yJmiQ17kn32Q5ar8Kg==",
"version": "2.590.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.590.0.tgz",
"integrity": "sha512-cdH3B/IuEuds84zRvi52WEW0UXOjsyl9hEkdbS2Uo8P7pWepaKTOZ3BKIUsy/lXen0nHcRWDCHeUkBwNW1Q2bg==",
"requires": {
"buffer": "^4.9.1",
"events": "^1.1.1",
"ieee754": "^1.1.13",
"jmespath": "^0.15.0",
"querystring": "^0.2.0",
"sax": "^1.2.1",
"url": "^0.10.3",
"uuid": "^3.3.2",
"xml2js": "^0.4.19"
"buffer": "4.9.1",
"events": "1.1.1",
"ieee754": "1.1.13",
"jmespath": "0.15.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"uuid": "3.3.2",
"xml2js": "0.4.19"
},
"dependencies": {
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
},
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
}
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
}
}
},
@ -2488,13 +2512,13 @@
"dev": true
},
"browserslist": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.3.tgz",
"integrity": "sha512-jWvmhqYpx+9EZm/FxcZSbUZyDEvDTLDi3nSAKbzEkyWvtI0mNSmUosey+5awDW1RUlrgXbQb5A6qY1xQH9U6MQ==",
"version": "4.8.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz",
"integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==",
"requires": {
"caniuse-lite": "^1.0.30001010",
"electron-to-chromium": "^1.3.306",
"node-releases": "^1.1.40"
"caniuse-lite": "^1.0.30001015",
"electron-to-chromium": "^1.3.322",
"node-releases": "^1.1.42"
}
},
"bson": {
@ -2503,9 +2527,9 @@
"integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg=="
},
"buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
@ -2641,9 +2665,9 @@
"integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
},
"caniuse-lite": {
"version": "1.0.30001012",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001012.tgz",
"integrity": "sha512-7RR4Uh04t9K1uYRWzOJmzplgEOAXbfK72oVNokCdMzA67trrhPzy93ahKk1AWHiA0c58tD2P+NHqxrA8FZ+Trg=="
"version": "1.0.30001015",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz",
"integrity": "sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ=="
},
"capture-stack-trace": {
"version": "1.0.1",
@ -3319,11 +3343,11 @@
"dev": true
},
"core-js-compat": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.4.2.tgz",
"integrity": "sha512-W0Aj+LM3EAxxjD0Kp2o4be8UlnxIZHNupBv2znqrheR4aY2nOn91794k/xoSp+SxqqriiZpTsSwBtZr60cbkwQ==",
"version": "3.4.8",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.4.8.tgz",
"integrity": "sha512-l3WTmnXHV2Sfu5VuD7EHE2w7y+K68+kULKt5RJg8ZJk3YhHF1qLD4O8v8AmNq+8vbOwnPFFDvds25/AoEvMqlQ==",
"requires": {
"browserslist": "^4.7.3",
"browserslist": "^4.8.2",
"semver": "^6.3.0"
},
"dependencies": {
@ -3432,9 +3456,9 @@
}
},
"csv-stringify": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.3.3.tgz",
"integrity": "sha512-q8Qj+/lN74LRmG7Mg0LauE5WcnJOD5MEGe1gI57IYJCB61KWuEbAFHm1uIPDkI26aqElyBB57SlE2GGwq2EY5A=="
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.3.4.tgz",
"integrity": "sha512-w3sjZh/b5xvN1NeWPbMBnvW+Q4D+cCoAk/2J0C/DqJKV3dHqseQGzP/BsdpqbIBl5UTFQxHgHkSUu5aiMFT62g=="
},
"currently-unhandled": {
"version": "0.4.1",
@ -4088,9 +4112,9 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"electron-to-chromium": {
"version": "1.3.314",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.314.tgz",
"integrity": "sha512-IKDR/xCxKFhPts7h+VaSXS02Z1mznP3fli1BbXWXeN89i2gCzKraU8qLpEid8YzKcmZdZD3Mly3cn5/lY9xsBQ=="
"version": "1.3.322",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz",
"integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA=="
},
"emitter-listener": {
"version": "1.1.2",
@ -4247,9 +4271,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"eslint": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.7.1.tgz",
"integrity": "sha512-UWzBS79pNcsDSxgxbdjkmzn/B6BhsXMfUaOHnNwyE8nD+Q6pyT96ow2MccVayUTV4yMid4qLhMiQaywctRkBLA==",
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.7.2.tgz",
"integrity": "sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng==",
"requires": {
"@babel/code-frame": "^7.0.0",
"ajv": "^6.10.0",
@ -6026,9 +6050,9 @@
}
},
"gaxios": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.1.0.tgz",
"integrity": "sha512-Gtpb5sdQmb82sgVkT2GnS2n+Kx4dlFwbeMYcDlD395aEvsLCSQXJJcHt7oJ2LrGxDEAeiOkK79Zv2A8Pzt6CFg==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.2.0.tgz",
"integrity": "sha512-54Y7s3yvtEO9CZ0yBVQHI5fzS7TzkjlnuLdDEkeyL1SNYMv877VofvA56E/C3dvj3rS7GFiyMWl833Qrr+nrkg==",
"requires": {
"abort-controller": "^3.0.0",
"extend": "^3.0.2",
@ -6344,9 +6368,9 @@
}
},
"google-auth-library": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.5.1.tgz",
"integrity": "sha512-zCtjQccWS/EHYyFdXRbfeSGM/gW+d7uMAcVnvXRnjBXON5ijo6s0nsObP0ifqileIDSbZjTlLtgo+UoN8IFJcg==",
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.6.1.tgz",
"integrity": "sha512-qPbg/KxZao9DxPcvP9QmKFjHJGdqsJb4PTcbPl8QcHtbzukXhSKNAt+ErfizAn80MuzReBPihsrMQhgfGqirdw==",
"requires": {
"arrify": "^2.0.0",
"base64-js": "^1.3.0",
@ -6402,9 +6426,9 @@
"dev": true
},
"gtoken": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.2.tgz",
"integrity": "sha512-/YvFlrrRjiSJf6a4Kmr4+E+HFbYdEntRPD/W+ZGLYDziHiWjX4S6ld8TNCyPMtUG3J3hPkZkpHbva40JA56spw==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.3.tgz",
"integrity": "sha512-ofW+FiXjswyKdkjMcDbe6E4K7cDDdE82dGDhZIc++kUECqaE7MSErf6arJPAjcnYn1qxE1/Ti06qQuqgVusovQ==",
"requires": {
"gaxios": "^2.1.0",
"google-p12-pem": "^2.0.0",
@ -6563,28 +6587,14 @@
}
},
"habitica-markdown": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-1.3.0.tgz",
"integrity": "sha1-DN8rTqjwNjPXBmBRyI0W6YOTeSE=",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-1.3.2.tgz",
"integrity": "sha512-IyiS583DfqE+KvW4NQAB4K2HjJZ1oF50L0EDz7KaixyK7C41s47wsbN81QtNMB8LnRqbMHFDesD2xEzdicjFXw==",
"requires": {
"habitica-markdown-emoji": "1.2.4",
"markdown-it": "8.0.0",
"markdown-it": "8.4.2",
"markdown-it-link-attributes": "1.0.0",
"markdown-it-linkify-images": "1.0.0"
},
"dependencies": {
"markdown-it": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.0.0.tgz",
"integrity": "sha1-5mJVSXoOQJ6Bb7xngHl19PJvb4I=",
"requires": {
"argparse": "^1.0.7",
"entities": "~1.1.1",
"linkify-it": "^2.0.0",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.3"
}
}
"markdown-it-linkify-images": "^1.1.1"
}
},
"habitica-markdown-emoji": {
@ -8522,9 +8532,12 @@
"integrity": "sha1-jaHKFynw+hbGVhWwsQdbecg3Gi4="
},
"markdown-it-linkify-images": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-1.0.0.tgz",
"integrity": "sha1-gTTsj0gM4pxD44Ck4narBnyuRoY="
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-1.1.1.tgz",
"integrity": "sha512-1IEmAaAjIgAwY+tZI0sxDXdy9QKHutj5cN0lH2JBiSZt+2NYKrWRJj0cloQW3OFIfP2MLFA1E+6OLJhXPiLgNw==",
"requires": {
"markdown-it": "^8.4.2"
}
},
"matchdep": {
"version": "2.0.0",
@ -8946,13 +8959,13 @@
}
},
"mongoose": {
"version": "5.7.12",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.7.12.tgz",
"integrity": "sha512-TqRiJPUeHN1u7Xs/zQmiMHcrzEb1/uKjK3uGzZhyqw25RKQiMV2vSBeBTMt5HXoYlUuDEWXE75FMjpK9X9kD0w==",
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.8.1.tgz",
"integrity": "sha512-8Cffl52cMK2iBlpLipoRKW/RdrhkxvVzXsy+xVsfbKHQBCWkFiS0T0jU4smYzomTMP4gW0sReJoRA7Gu/7VVgQ==",
"requires": {
"bson": "~1.1.1",
"kareem": "2.3.1",
"mongodb": "3.3.4",
"mongodb": "3.3.5",
"mongoose-legacy-pluralize": "1.0.2",
"mpath": "0.6.0",
"mquery": "3.2.2",
@ -8964,9 +8977,9 @@
},
"dependencies": {
"mongodb": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.4.tgz",
"integrity": "sha512-6fmHu3FJTpeZxacJcfjUGIP3BSteG0l2cxLkSrf1nnnS1OrlnVGiP9P/wAC4aB6dM6H4vQ2io8YDjkuPkje7AA==",
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.5.tgz",
"integrity": "sha512-6NAv5gTFdwRyVfCz+O+KDszvjpyxmZw+VlmqmqKR2GmpkeKrKFRv/ZslgTtZba2dc9JYixIf99T5Gih7TIWv7Q==",
"requires": {
"bson": "^1.1.1",
"require_optional": "^1.0.1",
@ -9463,9 +9476,9 @@
}
},
"node-releases": {
"version": "1.1.41",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.41.tgz",
"integrity": "sha512-+IctMa7wIs8Cfsa8iYzeaLTFwv5Y4r5jZud+4AnfymzeEXKBCavFX0KBgzVaPVqf0ywa6PrO8/b+bPqdwjGBSg==",
"version": "1.1.42",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.42.tgz",
"integrity": "sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==",
"requires": {
"semver": "^6.3.0"
},
@ -10131,9 +10144,9 @@
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
},
"passport": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz",
"integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
"integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
"requires": {
"passport-strategy": "1.x.x",
"pause": "0.0.1"
@ -10881,6 +10894,11 @@
}
}
},
"remove-markdown": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.3.0.tgz",
"integrity": "sha1-XktmdJOpNXlyjz1S7MHbnKUF3Jg="
},
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@ -10965,9 +10983,9 @@
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-in-the-middle": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.0.0.tgz",
"integrity": "sha512-92TmKFoE6qS0AbZvHPekkgon45OY5bbHl4+cCHm0qBx8+ktV9gEAQoYtuB1ArJnTbqb0020bn5Co9QzjsGkyJg==",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.0.2.tgz",
"integrity": "sha512-l2r6F9i6t5xp4OE9cw/daB/ooQKHZOOW1AYPADhEvk/Tj/THJDS8gePp76Zyuht6Cj57a0KL+eHK5Dyv7wZnKA==",
"requires": {
"debug": "^4.1.1",
"module-details-from-path": "^1.0.3",
@ -11988,9 +12006,9 @@
"integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164="
},
"stripe": {
"version": "7.13.1",
"resolved": "https://registry.npmjs.org/stripe/-/stripe-7.13.1.tgz",
"integrity": "sha512-aF9F/M10MulZumSrwoDULBOZKqNUvyYA22Bg6u98VJEH/2Dck60W1FcT793WhsHHa/+z6TPR/8Ev7GNq1+SYOQ==",
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/stripe/-/stripe-7.14.0.tgz",
"integrity": "sha512-2YSSuJ18jxue0xhW2gZIoz4asC8rITvsNxzh/KuYVKK3dELK6/4M8yoGfSjFcic0D1FtJBB0j6l8Ty26a2L08w==",
"requires": {
"qs": "^6.6.0"
},
@ -12008,30 +12026,30 @@
"integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls="
},
"superagent": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-5.1.1.tgz",
"integrity": "sha512-bpTO/3yQsHPH5w6f7qPCWGTuhEV2w93fwFGpYODnUc5tPa3rmbHUCmwC7iuEFBQQJsyhiW1WVc/ISpfAEv6ojQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-5.1.2.tgz",
"integrity": "sha512-VwPCbi9H02qDtTbdY+e3+cK5XR0YHsJy9hmeCOXLQ8ezjq8+S1Bs4MdNRmpmf2QjDBetD7drG7/nEta7E3E6Sg==",
"requires": {
"component-emitter": "^1.3.0",
"cookiejar": "^2.1.2",
"debug": "^4.1.1",
"fast-safe-stringify": "^2.0.6",
"form-data": "^2.3.3",
"fast-safe-stringify": "^2.0.7",
"form-data": "^3.0.0",
"formidable": "^1.2.1",
"methods": "^1.1.2",
"mime": "^2.4.4",
"qs": "^6.7.0",
"qs": "^6.9.1",
"readable-stream": "^3.4.0",
"semver": "^6.1.1"
"semver": "^6.3.0"
},
"dependencies": {
"form-data": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
@ -12179,9 +12197,9 @@
}
},
"teeny-request": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-5.3.1.tgz",
"integrity": "sha512-hnUeun3xryzv92FbrnprltcdeDfSVaGFBlFPRvKJ2fO/ioQx9N0aSUbbXSfTO+ArRXine1gSWdWFWcgfrggWXw==",
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-5.3.2.tgz",
"integrity": "sha512-5cse2cocD8Drq0uYN7NPcQ/6qbrxH6Kp0TlNLP+Y5f0twEkGtIg0ti8HoYF2LCC2TO5IkK/I9/Xs38/rDvs5UA==",
"requires": {
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^3.0.0",

View file

@ -4,26 +4,26 @@
"version": "4.126.1",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.7.4",
"@babel/preset-env": "^7.7.4",
"@babel/core": "^7.7.5",
"@babel/preset-env": "^7.7.6",
"@babel/register": "^7.7.4",
"@google-cloud/trace-agent": "^4.2.3",
"@google-cloud/trace-agent": "^4.2.4",
"@slack/client": "^3.8.1",
"accepts": "^1.3.5",
"amazon-payments": "^0.2.7",
"amplitude": "^3.5.0",
"apidoc": "^0.17.5",
"apn": "^2.2.0",
"aws-sdk": "^2.578.0",
"aws-sdk": "^2.590.0",
"bcrypt": "^3.0.7",
"body-parser": "^1.18.3",
"compression": "^1.7.4",
"cookie-session": "^1.3.3",
"coupon-code": "^0.4.5",
"csv-stringify": "^5.1.0",
"csv-stringify": "^5.3.4",
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"eslint": "^6.7.1",
"eslint": "^6.7.2",
"eslint-config-habitrpg": "^6.2.0",
"eslint-plugin-mocha": "^5.0.0",
"express": "^4.16.3",
@ -36,7 +36,7 @@
"gulp-imagemin": "^6.2.0",
"gulp-nodemon": "^2.4.1",
"gulp.spritesmith": "^6.9.0",
"habitica-markdown": "^1.3.0",
"habitica-markdown": "^1.3.2",
"helmet": "^3.21.2",
"image-size": "^0.8.3",
"in-app-purchase": "^1.11.3",
@ -46,12 +46,12 @@
"method-override": "^3.0.0",
"moment": "^2.24.0",
"moment-recur": "^1.0.7",
"mongoose": "^5.7.12",
"mongoose": "^5.8.1",
"morgan": "^1.7.0",
"nconf": "^0.10.0",
"node-gcm": "^1.0.2",
"pageres": "^5.1.0",
"passport": "^0.4.0",
"passport": "^0.4.1",
"passport-facebook": "^3.0.0",
"passport-google-oauth2": "^0.2.0",
"passport-google-oauth20": "1.0.0",
@ -59,10 +59,11 @@
"paypal-rest-sdk": "^1.8.1",
"ps-tree": "^1.0.0",
"regenerator-runtime": "^0.13.3",
"remove-markdown": "^0.3.0",
"rimraf": "^3.0.0",
"short-uuid": "^3.0.0",
"stripe": "^7.13.1",
"superagent": "^5.1.1",
"stripe": "^7.14.0",
"superagent": "^5.1.2",
"universal-analytics": "^0.4.17",
"useragent": "^2.1.9",
"uuid": "^3.3.3",

View file

@ -11,6 +11,7 @@ import { model as User } from '../../../../../../website/server/models/user';
import { model as Group } from '../../../../../../website/server/models/group';
import {
generateGroup,
sleep,
} from '../../../../../helpers/api-unit.helper';
describe('Purchasing a group plan for group', () => {
@ -307,6 +308,7 @@ describe('Purchasing a group plan for group', () => {
data.groupId = group._id;
await api.createSubscription(data);
await sleep(0.5);
expect(sender.sendTxn).to.have.callCount(4);
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
@ -340,6 +342,7 @@ describe('Purchasing a group plan for group', () => {
data.groupId = group._id;
await api.createSubscription(data);
await sleep(0.5);
expect(sender.sendTxn).to.have.callCount(4);
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);

View file

@ -5,6 +5,7 @@ import {
moveTask,
} from '../../../../website/server/libs/taskManager';
import i18n from '../../../../website/common/script/i18n';
import shared from '../../../../website/common/script';
import {
generateUser,
generateGroup,
@ -58,6 +59,51 @@ describe('taskManager', () => {
expect(newTask.createdAt).to.exist;
});
describe('onboarding', () => {
beforeEach(() => {
user.addAchievement = sinon.spy();
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
});
afterEach(() => {
shared.onboarding.checkOnboardingStatus.restore();
});
it('adds the onboarding achievement to the user and checks the onboarding status', async () => {
req.body = testHabit;
res.t = i18n.t;
user.flags.welcomed = true;
await createTasks(req, res, { user });
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('createdTask');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
});
it('does not add the onboarding achievement to the user if flags.welcomed is false', async () => {
req.body = testHabit;
res.t = i18n.t;
user.flags.welcomed = false;
await createTasks(req, res, { user });
expect(user.addAchievement).to.not.be.called;
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', async () => {
req.body = testHabit;
res.t = i18n.t;
user.achievements.createdTask = true;
await createTasks(req, res, { user });
expect(user.addAchievement).to.not.be.called;
});
});
it('gets user tasks', async () => {
req.body = testHabit;
res.t = i18n.t;

View file

@ -90,7 +90,37 @@ describe('Challenge Model', () => {
expect(syncedTask.tags[0]).to.eql(challenge._id);
});
it('syncs a challenge to a user', async () => {
it('adds a challenge to a user', async () => {
const newMember = new User({
guilds: [guild._id],
});
await newMember.save();
const addedSuccessfully = await challenge.addToUser(newMember);
const updatedNewMember = await User.findById(newMember._id);
expect(addedSuccessfully).to.eql(true);
expect(updatedNewMember.challenges).to.contain(challenge._id);
});
it('does not add a challenge to a user that already in the challenge', async () => {
const newMember = new User({
guilds: [guild._id],
challenges: [challenge._id],
});
await newMember.save();
const addedSuccessfully = await challenge.addToUser(newMember);
const updatedNewMember = await User.findById(newMember._id);
expect(addedSuccessfully).to.eql(false);
expect(updatedNewMember.challenges).to.contain(challenge._id);
expect(updatedNewMember.challenges.length).to.eql(1);
});
it('syncs challenge tasks to a user', async () => {
await challenge.addTasks([task]);
const newMember = new User({
@ -98,7 +128,7 @@ describe('Challenge Model', () => {
});
await newMember.save();
await challenge.syncToUser(newMember);
await challenge.syncTasksToUser(newMember);
const updatedNewMember = await User.findById(newMember._id);
const updatedNewMemberTasks = await Tasks.Task.find({ _id: { $in: updatedNewMember.tasksOrder[`${taskType}s`] } });
@ -110,14 +140,13 @@ describe('Challenge Model', () => {
),
);
expect(updatedNewMember.challenges).to.contain(challenge._id);
expect(updatedNewMember.tags[7].id).to.equal(challenge._id);
expect(updatedNewMember.tags[7].name).to.equal(challenge.shortName);
expect(syncedTask).to.exist;
expect(syncedTask.attribute).to.eql('str');
});
it('syncs a challenge to a user with the existing task', async () => {
it('syncs challenge tasks to a user with the existing task', async () => {
await challenge.addTasks([task]);
let updatedLeader = await User.findOne({ _id: leader._id });
@ -134,7 +163,7 @@ describe('Challenge Model', () => {
task.text = newTitle;
task.attribute = 'int';
await task.save();
await challenge.syncToUser(leader);
await challenge.syncTasksToUser(leader);
updatedLeader = await User.findOne({ _id: leader._id });
updatedLeadersTasks = await Tasks.Task.find({ _id: { $in: updatedLeader.tasksOrder[`${taskType}s`] } });

View file

@ -1317,7 +1317,7 @@ describe('Group Model', () => {
it('formats message', () => {
const chatMessage = party.sendChat({
message: 'a new message',
message: 'a _new_ message with *markdown*',
user: {
_id: 'user-id',
profile: { name: 'user name' },
@ -1336,7 +1336,8 @@ describe('Group Model', () => {
const chat = chatMessage;
expect(chat.text).to.eql('a new message');
expect(chat.text).to.eql('a _new_ message with *markdown*');
expect(chat.unformattedText).to.eql('a new message with markdown');
expect(validator.isUUID(chat.id)).to.eql(true);
expect(chat.timestamp).to.be.a('date');
expect(chat.likes).to.eql({});
@ -1878,6 +1879,8 @@ describe('Group Model', () => {
await questLeader.save();
await party.finishQuest(quest);
await sleep(0.5);
const [
updatedLeader,
updatedParticipatingMember,

View file

@ -84,6 +84,103 @@ describe('User Model', () => {
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
});
context('achievements', () => {
it('can add an achievement', () => {
const user = new User();
const originalUserToJSON = user.toJSON({ minimize: false });
expect(originalUserToJSON.achievements.createdTask).to.not.eql(true);
const notificationsN = originalUserToJSON.notifications.length;
user.addAchievement('createdTask');
const userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(notificationsN + 1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
expect(userToJSON.notifications[0].data).to.eql({
achievement: 'createdTask',
});
expect(userToJSON.notifications[0].seen).to.eql(false);
expect(userToJSON.achievements.createdTask).to.eql(true);
});
it('throws an error if the achievement is not valid', () => {
const user = new User();
expect(() => user.addAchievement('notAnAchievement')).to.throw;
});
context('static push method', () => {
it('throws an error if the achievement is not valid', async () => {
const user = new User();
await user.save();
await expect(User.addAchievementUpdate({ _id: user._id }, 'notAnAchievement'))
.to.eventually.be.rejected;
expect(() => user.addAchievement('notAnAchievement')).to.throw;
});
it('adds an achievement for a single member via static method', async () => {
let user = new User();
await user.save();
const originalUserToJSON = user.toJSON({ minimize: false });
expect(originalUserToJSON.achievements.createdTask).to.not.eql(true);
const notificationsN = originalUserToJSON.notifications.length;
await User.addAchievementUpdate({ _id: user._id }, 'createdTask');
user = await User.findOne({ _id: user._id }).exec();
const userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(notificationsN + 1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
expect(userToJSON.notifications[0].data).to.eql({
achievement: 'createdTask',
});
expect(userToJSON.notifications[0].seen).to.eql(false);
expect(userToJSON.achievements.createdTask).to.eql(true);
});
it('adds an achievement for all given users via static method', async () => {
let user = new User();
const otherUser = new User();
await Promise.all([user.save(), otherUser.save()]);
await User.addAchievementUpdate({ _id: { $in: [user._id, otherUser._id] } }, 'createdTask');
user = await User.findOne({ _id: user._id }).exec();
let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
expect(userToJSON.notifications[0].data).to.eql({
achievement: 'createdTask',
});
expect(userToJSON.notifications[0].seen).to.eql(false);
expect(userToJSON.achievements.createdTask).to.eql(true);
user = await User.findOne({ _id: otherUser._id }).exec();
userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
expect(userToJSON.notifications[0].data).to.eql({
achievement: 'createdTask',
});
expect(userToJSON.notifications[0].seen).to.eql(false);
expect(userToJSON.achievements.createdTask).to.eql(true);
});
});
});
context('notifications', () => {
it('can add notifications without data', () => {
const user = new User();

View file

@ -6,7 +6,8 @@ import {
describe('POST /members/send-private-message', () => {
let userToSendMessage;
const messageToSend = 'Test Private Message';
const messageToSend = 'Test *Private* Message';
const unformattedMessage = 'Test Private Message';
beforeEach(async () => {
userToSendMessage = await generateUser();
@ -110,7 +111,9 @@ describe('POST /members/send-private-message', () => {
const sendersMessageInReceiversInbox = _.find(
updatedReceiver.inbox.messages,
message => message.uuid === userToSendMessage._id && message.text === messageToSend,
message => message.uuid === userToSendMessage._id
&& message.text === messageToSend
&& message.unformattedText === unformattedMessage,
);
const sendersMessageInSendersInbox = _.find(

View file

@ -15,7 +15,7 @@ describe('GET /user/toggle-pinned-item', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('cannotUnpinArmoirPotion'),
message: t('cannotUnpinItem'),
});
});

View file

@ -189,6 +189,28 @@ describe('POST /user/auth/reset-password-set-new-one', () => {
});
});
it('renders the error page if the password is too short', async () => {
const user = await generateUser();
const code = encrypt(JSON.stringify({
userId: user._id,
expiresAt: moment().add({ days: 1 }),
}));
await user.update({
'auth.local.passwordResetCode': code,
});
await expect(api.post(`${endpoint}`, {
newPassword: 'short',
confirmPassword: 'short',
code,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('renders the success page and save the user', async () => {
const user = await generateUser();

View file

@ -326,6 +326,24 @@ describe('POST /user/auth/local/register', () => {
});
});
it('requires minimum length for the password', async () => {
const username = generateRandomUserName();
const email = `${username}@example.com`;
const password = '1234567';
const confirmPassword = '1234567';
await expect(api.post('/user/auth/local/register', {
username,
email,
password,
confirmPassword,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('requires a username', async () => {
const email = `${generateRandomUserName()}@example.com`;
const password = 'password';

View file

@ -82,6 +82,20 @@ describe('PUT /user/auth/update-password', async () => {
});
});
it('returns an error when newPassword is too short', async () => {
const body = {
password,
newPassword: '1234567',
confirmPassword: '1234567',
};
await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error when confirmPassword is missing', async () => {
const body = {
password,

View file

@ -239,6 +239,47 @@ describe('achievements', () => {
});
});
describe('unearned onboarding achievements', () => {
const user = generateUser();
const onboardingAchievs = shared
.achievements.getAchievementsForProfile(user).onboarding.achievements;
it('created task achievement exists with no count', () => {
const { createdTask } = onboardingAchievs;
expect(createdTask).to.exist;
expect(createdTask.optionalCount).to.be.undefined;
});
it('completed task achievement exists with no count', () => {
const { completedTask } = onboardingAchievs;
expect(completedTask).to.exist;
expect(completedTask.optionalCount).to.be.undefined;
});
it('hatched pet achievement exists with no count', () => {
const { hatchedPet } = onboardingAchievs;
expect(hatchedPet).to.exist;
expect(hatchedPet.optionalCount).to.be.undefined;
});
it('fed pet achievement exists with no count', () => {
const { fedPet } = onboardingAchievs;
expect(fedPet).to.exist;
expect(fedPet.optionalCount).to.be.undefined;
});
it('purchased equipment achievement exists with no count', () => {
const { purchasedEquipment } = onboardingAchievs;
expect(purchasedEquipment).to.exist;
expect(purchasedEquipment.optionalCount).to.be.undefined;
});
});
describe('earned seasonal achievements', () => {
const user = generateUser();
const quests = ['dilatory', 'stressbeast', 'burnout', 'bewilder'];

View file

@ -0,0 +1,49 @@
import {
generateUser,
} from '../../helpers/common.helper';
import getDebuffPotionItems from '../../../website/common/script/libs/getDebuffPotionItems';
import { TRANSFORMATION_DEBUFFS_LIST } from '../../../website/common/script/constants';
describe('getDebuffPotionItems', () => {
let user;
beforeEach(() => {
user = generateUser();
});
for (const key of Object.keys(TRANSFORMATION_DEBUFFS_LIST)) {
const debuff = TRANSFORMATION_DEBUFFS_LIST[key];
// Here we itterate whole object to dynamicaly create test suites as
// it described in dock of mocha
// https://mochajs.org/#dynamically-generating-tests
// That's why we have eslint-disable here
// eslint-disable-next-line no-loop-func
it(`Should return the ${debuff} on ${key} buff`, () => {
user.stats.buffs[key] = true;
const result = getDebuffPotionItems(user);
expect(result).to.be.an('array').that.deep
.includes({ path: `spells.special.${debuff}`, type: 'debuffPotion' });
});
}
it('Should return all debuff potions for all buffs', () => {
user.stats.buffs.seafoam = true;
user.stats.buffs.spookySparkles = true;
user.stats.buffs.snowball = true;
user.stats.buffs.shinySeed = true;
const result = getDebuffPotionItems(user);
expect(result).to.be.an('array').that.deep.include.members([
{ path: 'spells.special.sand', type: 'debuffPotion' },
{ path: 'spells.special.petalFreePotion', type: 'debuffPotion' },
{ path: 'spells.special.salt', type: 'debuffPotion' },
{ path: 'spells.special.opaquePotion', type: 'debuffPotion' },
]);
});
});

View file

@ -0,0 +1,100 @@
import moment from 'moment';
import {
hasActiveOnboarding,
hasCompletedOnboarding,
onOnboardingComplete,
checkOnboardingStatus,
} from '../../../website/common/script/libs/onboarding';
import { generateUser } from '../../helpers/common.helper';
describe('onboarding', () => {
let user;
beforeEach(() => {
user = generateUser();
user.addNotification = sinon.spy();
// Make sure the onboarding is active
user.auth.timestamps.created = moment('2019-12-20').toDate();
});
describe('hasActiveOnboarding', () => {
// The value of BEGIN DATE is available in common/script/libs/onboarding
it('returns true if the account is created after BEGIN_DATE', () => {
expect(hasActiveOnboarding(user)).to.eql(true);
});
it('returns false if the account is created before BEGIN_DATE', () => {
user.auth.timestamps.created = moment('2019-12-01').toDate();
expect(hasActiveOnboarding(user)).to.eql(false);
});
});
describe('hasCompletedOnboarding', () => {
it('returns false if no achievement has been awarded', () => {
const result = hasCompletedOnboarding(user);
expect(result).to.eql(false);
});
it('returns false if not all achievements have been awarded', () => {
user.achievements.completedTask = true;
const result = hasCompletedOnboarding(user);
expect(result).to.eql(false);
});
it('returns true if all achievements have been awarded', () => {
user.achievements.createdTask = true;
user.achievements.completedTask = true;
user.achievements.hatchedPet = true;
user.achievements.fedPet = true;
user.achievements.purchasedEquipment = true;
const result = hasCompletedOnboarding(user);
expect(result).to.eql(true);
});
});
describe('onOnboardingComplete', () => {
it('awards prizes', () => {
const { gp } = user.stats;
onOnboardingComplete(user);
expect(user.stats.gp).to.eql(gp + 100);
});
});
describe('checkOnboardingStatus', () => {
it('does nothing if onboarding is not active', () => {
const { gp } = user.stats;
user.auth.timestamps.created = moment('2019-12-01').toDate();
checkOnboardingStatus(user);
expect(user.addNotification).to.not.be.called;
expect(user.stats.gp).to.eql(gp);
});
it('does nothing if onboarding is not complete', () => {
const { gp } = user.stats;
checkOnboardingStatus(user);
expect(user.addNotification).to.not.be.called;
expect(user.stats.gp).to.eql(gp);
});
it('awards prize and add notification when onboarding is complete', () => {
user.achievements.createdTask = true;
user.achievements.completedTask = true;
user.achievements.hatchedPet = true;
user.achievements.fedPet = true;
user.achievements.purchasedEquipment = true;
const { gp } = user.stats;
checkOnboardingStatus(user);
expect(user.addNotification).to.be.calledOnce;
expect(user.addNotification).to.be.calledWith('ONBOARDING_COMPLETE');
expect(user.stats.gp).to.eql(gp + 100);
});
});
});

View file

@ -0,0 +1,53 @@
import {
generateUser,
} from '../../helpers/common.helper';
import setDebuffPotionItems from '../../../website/common/script/libs/setDebuffPotionItems';
describe('setDebuffPotionItems', () => {
let user;
beforeEach(() => {
user = generateUser();
});
it('Should push the debuff item to pinned items of user', () => {
user.stats.buffs.spookySparkles = true;
const previousPinnedItemsLength = user.pinnedItems.length;
const result = setDebuffPotionItems(user);
expect(result.pinnedItems.length).to.be.greaterThan(previousPinnedItemsLength);
});
it('Shouldn\'t create duplicate of already added debuff potion', () => {
user.stats.buffs.spookySparkles = true;
const firstSetResult = [...setDebuffPotionItems(user).pinnedItems];
const secondSetResult = [...setDebuffPotionItems(user).pinnedItems];
expect(firstSetResult).to.be.deep.equal(secondSetResult);
});
it('Should remove all debuff items from pinnedItems of the user if user have no buffs', () => {
user.stats.buffs.seafoam = true;
user.stats.buffs.spookySparkles = true;
user.stats.buffs.snowball = true;
user.stats.buffs.shinySeed = true;
const firstSetResult = [...setDebuffPotionItems(user).pinnedItems];
expect(firstSetResult).to.have.lengthOf(4);
user.stats.buffs.seafoam = false;
user.stats.buffs.spookySparkles = false;
user.stats.buffs.snowball = false;
user.stats.buffs.shinySeed = false;
const secondSetResult = [...setDebuffPotionItems(user).pinnedItems];
expect(secondSetResult).to.have.lengthOf(0);
});
});

View file

@ -41,7 +41,10 @@ describe('shared.ops.buyMarketGear', () => {
},
});
user.addAchievement = sinon.spy();
sinon.stub(shared, 'randomVal');
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
sinon.stub(shared.fns, 'predictableRandom');
sinon.stub(analytics, 'track');
});
@ -49,6 +52,7 @@ describe('shared.ops.buyMarketGear', () => {
afterEach(() => {
shared.randomVal.restore();
shared.fns.predictableRandom.restore();
shared.onboarding.checkOnboardingStatus.restore();
analytics.track.restore();
});
@ -86,6 +90,27 @@ describe('shared.ops.buyMarketGear', () => {
expect(analytics.track).to.be.calledOnce;
});
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
user.stats.gp = 31;
buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('purchasedEquipment');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
user.stats.gp = 31;
user.achievements.purchasedEquipment = true;
buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
expect(user.addAchievement).to.not.be.called;
});
it('deducts gold from user', () => {
user.stats.gp = 31;

View file

@ -10,12 +10,19 @@ import {
generateUser,
} from '../../helpers/common.helper';
import errorMessage from '../../../website/common/script/libs/errorMessage';
import shared from '../../../website/common/script';
describe('shared.ops.feed', () => {
let user;
beforeEach(() => {
user = generateUser();
user.addAchievement = sinon.spy();
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
});
afterEach(() => {
shared.onboarding.checkOnboardingStatus.restore();
});
context('failure conditions', () => {
@ -223,5 +230,28 @@ describe('shared.ops.feed', () => {
expect(user.items.mounts['Wolf-Base']).to.equal(true);
expect(user.items.currentPet).to.equal('');
});
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
user.items.pets['Wolf-Base'] = 5;
user.items.food.Meat = 2;
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' } });
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('fedPet');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
user.items.pets['Wolf-Base'] = 5;
user.items.food.Meat = 2;
user.achievements.fedPet = true;
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' } });
expect(user.addAchievement).to.not.be.called;
});
});
});

View file

@ -9,12 +9,19 @@ import {
generateUser,
} from '../../helpers/common.helper';
import errorMessage from '../../../website/common/script/libs/errorMessage';
import shared from '../../../website/common/script';
describe('shared.ops.hatch', () => {
let user;
beforeEach(() => {
user = generateUser();
user.addAchievement = sinon.spy();
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
});
afterEach(() => {
shared.onboarding.checkOnboardingStatus.restore();
});
context('Pet Hatching', () => {
@ -195,6 +202,30 @@ describe('shared.ops.hatch', () => {
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Spooky' } });
expect(user.achievements.dustDevil).to.eql(true);
});
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
user.items.eggs = { Wolf: 1 };
user.items.hatchingPotions = { Base: 1 };
user.items.pets = {};
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Base' } });
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('hatchedPet');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
user.items.eggs = { Wolf: 1 };
user.items.hatchingPotions = { Base: 1 };
user.items.pets = {};
user.achievements.hatchedPet = true;
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Base' } });
expect(user.addAchievement).to.not.be.called;
});
});
});
});

View file

@ -12,6 +12,7 @@ import {
NotAuthorized,
} from '../../../website/common/script/libs/errors';
import crit from '../../../website/common/script/fns/crit';
import shared from '../../../website/common/script';
const EPSILON = 0.0001; // negligible distance between datapoints
@ -340,5 +341,49 @@ describe('shared.ops.scoreTask', () => {
expectClosePoints(ref.beforeUser, ref.afterUser, freshTodo, todo);
});
});
context('onboarding', () => {
beforeEach(() => {
ref.afterUser.addAchievement = sinon.spy();
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
});
afterEach(() => {
shared.onboarding.checkOnboardingStatus.restore();
});
it('adds the achievement to the user and checks the onboarding status', () => {
scoreTask({ user: ref.afterUser, task: todo, direction: 'up' });
expect(ref.afterUser.addAchievement).to.be.calledOnce;
expect(ref.afterUser.addAchievement).to.be.calledWith('completedTask');
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(ref.afterUser);
});
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
ref.afterUser.achievements.completedTask = true;
scoreTask({ user: ref.afterUser, task: todo, direction: 'up' });
expect(ref.afterUser.addAchievement).to.not.be.called;
});
it('does not add the onboarding achievement to the user if it\'s scored down', () => {
scoreTask({ user: ref.afterUser, task: todo, direction: 'down' });
expect(ref.afterUser.addAchievement).to.not.be.called;
});
it('does not add the onboarding achievement to the user if cron is running', () => {
scoreTask({
user: ref.afterUser,
task: todo,
direction: 'up',
cron: true,
});
expect(ref.afterUser.addAchievement).to.not.be.called;
});
});
});
});

View file

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

View file

@ -1,4 +1,4 @@
# new_client
# Habitica Client
## Project setup
```
@ -27,3 +27,31 @@ npm run lint
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
## Storybook
Storybook is mainly used while working on UI-Components to see changes faster instead of using the website.
### Start Storybook
```
npm run storybook:serve
```
This will start the storybook process, every `*.stories.js`-File is searched and added to the storybook overview.
### Storybook Worklow
Usually when you working on `component-name.vue` you also create a `component-name.stories.js` file.
Example of the stories structure - [Storybook Docs][StorybookDocsExample] - [CountBadge][CountBadgeExample]
[StorybookDocsExample]: https://storybook.js.org/docs/guides/guide-vue/#step-4-write-your-stories
[CountBadgeExample]: src/components/ui/countBadge.stories.js
Each function or example of this component will be put after `storiesOf('Your Component', module)`,
in a separate `.add('function of component', ...`
### Storybook Build
After each client build, storybook build is also triggered and will be available in `dist/storybook`

View file

@ -0,0 +1,5 @@
/* eslint-disable import/no-extraneous-dependencies */
import '@storybook/addon-actions/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-links/register';
import '@storybook/addon-notes/register';

View file

@ -0,0 +1,11 @@
/* eslint-disable import/no-extraneous-dependencies */
import { configure } from '@storybook/vue';
import '../../src/assets/scss/index.scss';
const req = require.context('../../src', true, /.stories.js$/);
function loadStories () {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);

File diff suppressed because it is too large Load diff

View file

@ -5,31 +5,38 @@
"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 .",
"postinstall": "node ./scripts/npm-postinstall.js"
"postinstall": "node ./scripts/npm-postinstall.js",
"storybook:build": "vue-cli-service storybook:build -c config/storybook -o dist/storybook",
"storybook:serve": "vue-cli-service storybook:serve -p 6006 -c config/storybook",
"test:unit": "vue-cli-service test:unit --require ./tests/unit/helpers.js"
},
"dependencies": {
"@vue/cli-plugin-babel": "^4.0.5",
"@vue/cli-plugin-eslint": "^4.0.5",
"@vue/cli-plugin-router": "^4.0.5",
"@vue/cli-plugin-unit-mocha": "^4.0.5",
"@vue/cli-service": "^4.0.5",
"@vue/cli-plugin-babel": "^4.1.1",
"@vue/cli-plugin-eslint": "^4.1.1",
"@vue/cli-plugin-router": "^4.1.1",
"@vue/cli-plugin-unit-mocha": "^4.1.1",
"@vue/cli-service": "^4.1.1",
"@storybook/addon-actions": "^5.0.0",
"@storybook/addon-knobs": "^5.0.0",
"@storybook/addon-links": "^5.0.0",
"@storybook/addon-notes": "^5.0.0",
"@storybook/vue": "^5.2.5",
"@vue/test-utils": "1.0.0-beta.29",
"amplitude-js": "^5.7.0",
"amplitude-js": "^5.8.0",
"axios": "^0.19.0",
"axios-progress-bar": "^1.2.0",
"babel-eslint": "^10.0.1",
"bootstrap": "^4.3.1",
"bootstrap": "^4.4.1",
"bootstrap-vue": "^2.1.0",
"chai": "^4.1.2",
"core-js": "^3.4.2",
"eslint": "^6.7.1",
"core-js": "^3.5.0",
"eslint": "^6.7.2",
"eslint-config-habitrpg": "^6.2.0",
"eslint-plugin-mocha": "^5.3.0",
"eslint-plugin-vue": "^6.0.1",
"habitica-markdown": "^1.3.0",
"habitica-markdown": "^1.3.2",
"hellojs": "^1.18.1",
"inspectpack": "^4.2.2",
"intro.js": "^2.9.3",
@ -46,10 +53,11 @@
"svgo-loader": "^2.2.1",
"uuid": "^3.3.3",
"validator": "^11.1.0",
"vue": "^2.6.10",
"vue": "^2.6.11",
"vue-cli-plugin-storybook": "^0.6.1",
"vue-mugen-scroll": "^0.2.6",
"vue-router": "^3.0.6",
"vue-template-compiler": "^2.6.10",
"vue-template-compiler": "^2.6.11",
"vuedraggable": "^2.23.1",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^4.41.2"

View file

@ -6,4 +6,8 @@ if (process.env.NODE_ENV === 'production') {
execSync('npm run build', {
stdio: 'inherit',
});
execSync('npm run storybook:build', {
stdio: 'inherit',
});
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,69 +1,87 @@
.weapon_warrior_3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1361px;
width: 90px;
height: 90px;
}
.weapon_warrior_4 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1270px;
width: 90px;
height: 90px;
}
.weapon_warrior_5 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1452px;
width: 90px;
height: 90px;
}
.weapon_warrior_6 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1452px;
width: 90px;
height: 90px;
}
.weapon_wizard_0 {
.weapon_warrior_0 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1543px;
width: 90px;
height: 90px;
}
.weapon_wizard_1 {
.weapon_warrior_1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1543px;
background-position: -1893px -1361px;
width: 90px;
height: 90px;
}
.weapon_wizard_2 {
.weapon_warrior_2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -422px;
width: 90px;
height: 90px;
}
.weapon_warrior_3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -604px;
width: 90px;
height: 90px;
}
.weapon_warrior_4 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -997px;
width: 90px;
height: 90px;
}
.weapon_warrior_5 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -997px;
width: 90px;
height: 90px;
}
.weapon_wizard_3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1088px;
width: 90px;
height: 90px;
}
.weapon_wizard_4 {
.weapon_warrior_6 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1088px;
width: 90px;
height: 90px;
}
.weapon_wizard_5 {
.weapon_wizard_0 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1179px;
width: 90px;
height: 90px;
}
.weapon_wizard_6 {
.weapon_wizard_1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1179px;
width: 90px;
height: 90px;
}
.weapon_wizard_2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1270px;
width: 90px;
height: 90px;
}
.weapon_wizard_3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1270px;
width: 90px;
height: 90px;
}
.weapon_wizard_4 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1361px;
width: 90px;
height: 90px;
}
.weapon_wizard_5 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1452px;
width: 90px;
height: 90px;
}
.weapon_wizard_6 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1452px;
width: 90px;
height: 90px;
}
.Pet_Currency_Gem {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1921px -444px;
@ -84,43 +102,43 @@
}
.PixelPaw-Gold {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1688px -1436px;
background-position: -660px -508px;
width: 51px;
height: 51px;
}
.PixelPaw {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1740px -1436px;
background-position: -660px -560px;
width: 51px;
height: 51px;
}
.PixelPaw002 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -220px -203px;
background-position: -880px -655px;
width: 51px;
height: 51px;
}
.avatar_floral_healer {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1887px -576px;
background-position: -1802px -897px;
width: 99px;
height: 99px;
}
.avatar_floral_rogue {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -697px;
background-position: -1802px -797px;
width: 99px;
height: 99px;
}
.avatar_floral_warrior {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -897px;
background-position: -1802px -697px;
width: 99px;
height: 99px;
}
.avatar_floral_wizard {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -797px;
background-position: -1887px -576px;
width: 99px;
height: 99px;
}
@ -132,19 +150,19 @@
}
.ghost {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -997px;
background-position: -1802px -1088px;
width: 90px;
height: 90px;
}
.inventory_present {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -608px;
background-position: -1627px -812px;
width: 68px;
height: 68px;
}
.inventory_present_01 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1902px -797px;
background-position: -1902px -697px;
width: 68px;
height: 68px;
}
@ -156,199 +174,199 @@
}
.inventory_present_03 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -470px;
background-position: -1718px -422px;
width: 68px;
height: 68px;
}
.inventory_present_04 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -539px;
background-position: -1718px -513px;
width: 68px;
height: 68px;
}
.inventory_present_05 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -539px;
background-position: -1627px -743px;
width: 68px;
height: 68px;
}
.inventory_present_06 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -884px;
background-position: -1696px -1019px;
width: 68px;
height: 68px;
}
.inventory_present_07 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -884px;
background-position: -1627px -1088px;
width: 68px;
height: 68px;
}
.inventory_present_08 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -953px;
background-position: -1696px -1088px;
width: 68px;
height: 68px;
}
.inventory_present_09 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1367px;
background-position: -220px -341px;
width: 68px;
height: 68px;
}
.inventory_present_10 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1902px -697px;
background-position: -1902px -797px;
width: 68px;
height: 68px;
}
.inventory_present_11 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -470px;
background-position: -1718px -604px;
width: 68px;
height: 68px;
}
.inventory_present_12 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -608px;
background-position: -1696px -743px;
width: 68px;
height: 68px;
}
.inventory_special_birthday {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -746px;
background-position: -1696px -881px;
width: 68px;
height: 68px;
}
.inventory_special_congrats {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -746px;
background-position: -1627px -950px;
width: 68px;
height: 68px;
}
.inventory_special_fortify {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -815px;
background-position: -1696px -950px;
width: 68px;
height: 68px;
}
.inventory_special_getwell {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -953px;
background-position: -1627px -1157px;
width: 68px;
height: 68px;
}
.inventory_special_goodluck {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1367px;
background-position: -220px -272px;
width: 68px;
height: 68px;
}
.inventory_special_greeting {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1298px;
background-position: -220px -203px;
width: 68px;
height: 68px;
}
.inventory_special_nye {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1298px;
background-position: -1696px -1433px;
width: 68px;
height: 68px;
}
.inventory_special_opaquePotion {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1229px;
background-position: -1627px -1433px;
width: 68px;
height: 68px;
}
.inventory_special_seafoam {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1229px;
background-position: -1696px -1364px;
width: 68px;
height: 68px;
}
.inventory_special_shinySeed {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1160px;
background-position: -1627px -1364px;
width: 68px;
height: 68px;
}
.inventory_special_snowball {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1160px;
background-position: -1696px -1295px;
width: 68px;
height: 68px;
}
.inventory_special_spookySparkles {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1091px;
background-position: -1627px -1295px;
width: 68px;
height: 68px;
}
.inventory_special_thankyou {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1091px;
background-position: -1696px -1226px;
width: 68px;
height: 68px;
}
.inventory_special_trinket {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -1022px;
background-position: -1627px -1226px;
width: 68px;
height: 68px;
}
.inventory_special_valentine {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1022px;
background-position: -1696px -1157px;
width: 68px;
height: 68px;
}
.knockout {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -422px;
background-position: -1627px -695px;
width: 120px;
height: 47px;
}
.pet_key {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -815px;
background-position: -1627px -1019px;
width: 68px;
height: 68px;
}
.rebirth_orb {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1696px -677px;
background-position: -1627px -881px;
width: 68px;
height: 68px;
}
.seafoam_star {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1893px -1179px;
background-position: -1893px -1543px;
width: 90px;
height: 90px;
}
.shop_armoire {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -677px;
background-position: -1696px -812px;
width: 68px;
height: 68px;
}
.snowman {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1802px -1361px;
background-position: -1627px -513px;
width: 90px;
height: 90px;
}
.zzz {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -220px -255px;
background-position: -660px -612px;
width: 40px;
height: 40px;
}
.zzz_light {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1748px -422px;
background-position: -1748px -695px;
width: 40px;
height: 40px;
}
@ -402,19 +420,19 @@
}
.notif_inventory_present_09 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1953px -351px;
background-position: -1953px -258px;
width: 28px;
height: 28px;
}
.notif_inventory_present_10 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1953px -322px;
background-position: -1953px -351px;
width: 28px;
height: 28px;
}
.notif_inventory_present_11 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1953px -258px;
background-position: -1953px -322px;
width: 28px;
height: 28px;
}
@ -426,7 +444,7 @@
}
.notif_inventory_special_birthday {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1971px -797px;
background-position: -1971px -724px;
width: 20px;
height: 24px;
}
@ -462,19 +480,19 @@
}
.notif_inventory_special_thankyou {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1971px -724px;
background-position: -1971px -822px;
width: 20px;
height: 24px;
}
.notif_inventory_special_valentine {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1971px -822px;
background-position: -1971px -797px;
width: 20px;
height: 24px;
}
.npc_bailey {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1627px -1436px;
background-position: -660px -435px;
width: 60px;
height: 72px;
}
@ -486,7 +504,7 @@
}
.npc_matt {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -208px -1529px;
background-position: 0px -1529px;
width: 195px;
height: 138px;
}
@ -504,55 +522,55 @@
}
.phobia_dysheartener {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -307px -220px;
background-position: -527px -220px;
width: 201px;
height: 195px;
}
.quest_alligator {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -967px -660px;
background-position: -1187px -880px;
width: 201px;
height: 213px;
}
.quest_amber {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1187px -660px;
background-position: -527px 0px;
width: 219px;
height: 219px;
}
.quest_armadillo {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -527px 0px;
background-position: 0px -435px;
width: 219px;
height: 219px;
}
.quest_atom1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1335px -1315px;
background-position: -1124px -1315px;
width: 250px;
height: 150px;
}
.quest_atom2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: 0px -1529px;
background-position: -1375px -1315px;
width: 207px;
height: 138px;
}
.quest_atom3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -433px -1315px;
background-position: -222px -1315px;
width: 216px;
height: 180px;
}
.quest_axolotl {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -660px -655px;
background-position: 0px -655px;
width: 219px;
height: 219px;
}
.quest_badger {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: 0px -875px;
background-position: -967px 0px;
width: 219px;
height: 219px;
}
@ -564,31 +582,31 @@
}
.quest_beetle {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -747px -440px;
background-position: -967px -660px;
width: 204px;
height: 201px;
}
.quest_bronze {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -880px -875px;
background-position: -220px -875px;
width: 219px;
height: 219px;
}
.quest_bunny {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -222px -1315px;
background-position: -307px -220px;
width: 210px;
height: 186px;
}
.quest_butterfly {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1187px -440px;
background-position: -1187px 0px;
width: 219px;
height: 219px;
}
.quest_cheetah {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: 0px -1095px;
background-position: -1187px -660px;
width: 219px;
height: 219px;
}
@ -600,13 +618,13 @@
}
.quest_dilatory {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1407px -220px;
background-position: -880px -1095px;
width: 219px;
height: 219px;
}
.quest_dilatoryDistress1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1187px -880px;
background-position: -1407px -1097px;
width: 210px;
height: 210px;
}
@ -618,25 +636,25 @@
}
.quest_dilatoryDistress3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1407px -440px;
background-position: -1100px -1095px;
width: 219px;
height: 219px;
}
.quest_dilatory_derby {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -880px -1095px;
background-position: -220px -1095px;
width: 219px;
height: 219px;
}
.quest_dolphin {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1407px -660px;
background-position: -1407px -440px;
width: 219px;
height: 219px;
}
.quest_dustbunnies {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -660px -1095px;
background-position: -1407px -660px;
width: 219px;
height: 219px;
}
@ -654,19 +672,19 @@
}
.quest_evilsanta2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1407px 0px;
background-position: -1407px -220px;
width: 219px;
height: 219px;
}
.quest_falcon {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1100px -1095px;
background-position: -1407px 0px;
width: 219px;
height: 219px;
}
.quest_ferret {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -440px -875px;
background-position: -307px 0px;
width: 219px;
height: 219px;
}
@ -678,19 +696,19 @@
}
.quest_ghost_stag {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -440px -1095px;
background-position: -660px -1095px;
width: 219px;
height: 219px;
}
.quest_goldenknight1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -220px -1095px;
background-position: -440px -1095px;
width: 219px;
height: 219px;
}
.quest_goldenknight2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1084px -1315px;
background-position: -873px -1315px;
width: 250px;
height: 150px;
}
@ -702,67 +720,67 @@
}
.quest_gryphon {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -867px -1315px;
background-position: -439px -1315px;
width: 216px;
height: 177px;
}
.quest_guineapig {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1187px -220px;
background-position: -1187px -440px;
width: 219px;
height: 219px;
}
.quest_harpy {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1187px 0px;
background-position: -1187px -220px;
width: 219px;
height: 219px;
}
.quest_hedgehog {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -527px -220px;
background-position: -747px -440px;
width: 219px;
height: 186px;
}
.quest_hippo {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -660px -875px;
background-position: -880px -875px;
width: 219px;
height: 219px;
}
.quest_horse {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -307px 0px;
background-position: -660px -875px;
width: 219px;
height: 219px;
}
.quest_kangaroo {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -220px -875px;
background-position: -440px -875px;
width: 219px;
height: 219px;
}
.quest_kraken {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -650px -1315px;
background-position: -656px -1315px;
width: 216px;
height: 177px;
}
.quest_lostMasterclasser1 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -967px -440px;
background-position: 0px -875px;
width: 219px;
height: 219px;
}
.quest_lostMasterclasser2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -967px -220px;
background-position: -967px -440px;
width: 219px;
height: 219px;
}
.quest_lostMasterclasser3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -967px 0px;
background-position: -967px -220px;
width: 219px;
height: 219px;
}
@ -774,19 +792,19 @@
}
.quest_mayhemMistiflying2 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -440px -655px;
background-position: -660px -655px;
width: 219px;
height: 219px;
}
.quest_mayhemMistiflying3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -220px -655px;
background-position: -440px -655px;
width: 219px;
height: 219px;
}
.quest_monkey {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: 0px -655px;
background-position: -220px -655px;
width: 219px;
height: 219px;
}
@ -822,13 +840,7 @@
}
.quest_moonstone3 {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: 0px -435px;
background-position: 0px -1095px;
width: 219px;
height: 219px;
}
.quest_nudibranch {
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
background-position: -1407px -1097px;
width: 216px;
height: 216px;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 KiB

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 KiB

After

Width:  |  Height:  |  Size: 688 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 KiB

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View file

@ -20,7 +20,7 @@
@include btn-focus-hover-shadow();
}
&:hover:not(.btn-flat):not(.disabled) {
&:hover:not(.btn-flat):not(.disabled):not(:disabled) {
@include btn-focus-hover-shadow();
border-color: transparent;
}
@ -49,7 +49,7 @@
background: $purple-200;
}
&:hover:not(:disabled), &:active, &.active, &:focus {
&:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled), &:focus:not(:disabled):not(.disabled) {
background: #5d3b9c !important;
color: $white;
}
@ -59,7 +59,7 @@
color: $gray-50;
background: $white !important;
&:hover:not(:disabled):not(.disabled), &:active, &.active, &:focus {
&:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled), &:focus:not(:disabled):not(.disabled) {
color: $purple-200 !important;
}
@ -80,7 +80,7 @@
background: $green-100;
}
&:hover:not(:disabled), &:active, &.active {
&:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {
background: $green-50;
}
}
@ -96,8 +96,12 @@
background: $blue-50;
}
&:hover:not(:disabled), &:active, &.active {
background: $blue-100;
&:hover:not(:disabled):not(.disabled) {
background-color: $blue-100;
}
&:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {
background: $blue-50;
}
}
@ -108,7 +112,11 @@
background: $red-50;
}
&:hover:not(:disabled), &:active, &.active {
&:hover:not(:disabled):not(.disabled) {
background: $red-100;
}
&:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {
background: $red-100;
}
}

View file

@ -65,6 +65,7 @@ input, textarea, input.form-control, textarea.form-control {
padding-right: 40px;
background-image: url(~@/assets/svg/for-css/alert.svg);
background-size: 16px 16px;
border-color: $red-100 !important;
}
}
@ -276,3 +277,19 @@ $bg-disabled-control: #34303a;
.toggle-switch-container.no-margin {
margin-top: 0 !important;
}
// Disable default style Firefox for invalid elements.
// Selectors taken from view-source:resource://gre-resources/forms.css on Firefox
:not(output):-moz-ui-invalid {
box-shadow: none;
}
:not(output):-moz-ui-invalid:-moz-focusring {
box-shadow: none;
}
output:-moz-ui-invalid {
color: inherit;
}

View file

@ -50,3 +50,18 @@
width: 20px;
}
}
.close-icon {
position: absolute;
top: 24px;
right: 24px;
cursor: pointer;
& svg path {
stroke: $gray-200;
}
&:hover svg path {
stroke: $gray-100;
}
}

View file

@ -17,7 +17,7 @@
.tier3 {
color: #d70e14;
:hover, :active, :focus {
&:hover, &:active, &:focus {
color: #d70e14;
}
}
@ -25,7 +25,7 @@
.tier4 {
color: #c24d00;
&:hover, &:active, :focus {
&:hover, &:active, &:focus {
color: #c24d00;
}
}
@ -33,7 +33,7 @@
.tier5 {
color: #9e650f;
:hover, :active, :focus {
&:hover, &:active, &:focus {
color: #9e650f;
}
}
@ -41,7 +41,7 @@
.tier6 {
color: #2b8363;
:hover, :active, &:focus {
&:hover, &:active, &:focus {
color: #2b8363;
}
}

View file

@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="48" viewBox="0 0 150 48">
<defs>
<filter id="a" width="225%" height="225%" x="-62.5%" y="-62.5%" filterUnits="objectBoundingBox">
<feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="4"/>
<feColorMatrix in="shadowBlurOuter1" result="shadowMatrixOuter1" values="0 0 0 0 0.101960784 0 0 0 0 0.0941176471 0 0 0 0 0.11372549 0 0 0 0.32 0"/>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g fill="none" fill-rule="evenodd">
<g fill="#FFA624">
<path d="M125.45 35.414l3.346-2.967-4.382-.896-2.967-3.347-.896 4.382-3.347 2.967 4.382.896 2.967 3.347z" opacity=".75"/>
<path d="M136.43 30.407l3.325-.442-2.348-2.395-.442-3.325-2.395 2.348-3.325.442 2.348 2.395.442 3.325z" opacity=".5"/>
<path d="M146.901 35.41l2.067.854-.557-2.165.853-2.067-2.165.557-2.067-.853.557 2.165-.853 2.067z" opacity=".25"/>
<path d="M138.353 39.874l1.488-.093-.967-1.134-.093-1.488-1.134.967-1.488.093.967 1.134.093 1.488z" opacity=".35"/>
</g>
<g fill="#FFA624">
<path d="M24.55 35.414l-3.346-2.967 4.382-.896 2.967-3.347.896 4.382 3.347 2.967-4.382.896-2.967 3.347z" opacity=".75"/>
<path d="M13.57 30.407l-3.325-.442 2.348-2.395.442-3.325 2.395 2.348 3.325.442-2.348 2.395-.442 3.325z" opacity=".5"/>
<path d="M3.099 35.41l-2.067.854.557-2.165-.853-2.067 2.165.557 2.067-.853-.557 2.165.853 2.067z" opacity=".25"/>
<path d="M11.647 39.874l-1.488-.093.967-1.134.093-1.488 1.134.967 1.488.093-.967 1.134-.093 1.488z" opacity=".35"/>
</g>
<g opacity=".5" transform="translate(83 22)">
<circle cx="12" cy="12" r="12" fill="#FFA623"/>
<path fill="#FFF" d="M6.3 17.7c-3.1-3.1-3.1-8.2 0-11.3 3.1-3.1 8.2-3.1 11.3 0" opacity=".5"/>
<path fill="#FFF" d="M17.7 6.3c3.1 3.1 3.1 8.2 0 11.3-3.1 3.1-8.2 3.1-11.3 0" opacity=".25"/>
<path fill="#BF7D1A" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" opacity=".5"/>
<path fill="#BF7D1A" d="M13 9v2h-2V9H9v6h2v-2h2v2h2V9z" opacity=".75"/>
</g>
<g opacity=".5" transform="translate(43 22)">
<circle cx="12" cy="12" r="12" fill="#FFA623"/>
<path fill="#FFF" d="M6.3 17.7c-3.1-3.1-3.1-8.2 0-11.3 3.1-3.1 8.2-3.1 11.3 0" opacity=".5"/>
<path fill="#FFF" d="M17.7 6.3c3.1 3.1 3.1 8.2 0 11.3-3.1 3.1-8.2 3.1-11.3 0" opacity=".25"/>
<path fill="#BF7D1A" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" opacity=".5"/>
<path fill="#BF7D1A" d="M13 9v2h-2V9H9v6h2v-2h2v2h2V9z" opacity=".75"/>
</g>
<g transform="translate(59 6)">
<circle cx="16" cy="16" r="16" fill="#FFA623"/>
<path fill="#FFF" d="M8.4 23.6c-4.133-4.133-4.133-10.933 0-15.067 4.133-4.133 10.933-4.133 15.067 0" opacity=".5"/>
<path fill="#FFF" d="M23.6 8.4c4.133 4.133 4.133 10.933 0 15.067-4.133 4.133-10.933 4.133-15.067 0" opacity=".25"/>
<path fill="#BF7D1A" d="M16 2.667c-7.333 0-13.333 6-13.333 13.333s6 13.333 13.333 13.333 13.333-6 13.333-13.333S23.333 2.667 16 2.667zm0 24c-5.867 0-10.667-4.8-10.667-10.667S10.133 5.333 16 5.333 26.667 10.133 26.667 16 21.867 26.667 16 26.667z" opacity=".5"/>
<path fill="#BF7D1A" d="M17.333 12v2.667h-2.666V12H12v8h2.667v-2.667h2.666V20H20v-8z" opacity=".75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" viewBox="0 0 12 11">
<path fill="#FFF" fill-rule="evenodd" d="M6 8.76l-3.527 2.094.902-4.001L.294 4.146l4.084-.379L6 0l1.622 3.767 4.084.379-3.081 2.707.902 4.001z"/>
</svg>

After

Width:  |  Height:  |  Size: 241 B

View file

@ -1,104 +1,16 @@
<template>
<div
class="modal-footer"
style="margin-top:0"
ng-init="loadWidgets()"
>
<div class="container-fluid share-buttons">
<div class="row">
<div class="col-12 text-center">
<a
class="twitter-share-button share-button"
:href="twitterLink"
target="_blank"
>
<div
class="social-icon twitter svg-icon"
v-html="icons.twitter"
></div>
{{ $t('tweet') }}
</a>
<a
class="fb-share-button share-button"
:href="facebookLink"
target="_blank"
>
<div
class="social-icon facebook svg-icon"
v-html="icons.facebook"
></div>
{{ $t('share') }}
</a>
<!-- @TODO: Still want this?
.col-4a.tumblr-share-button(:data-href='socialLevelLink', data-notes='none')-->
</div>
</div>
</div>
<!-- TODO disabled for now, see https://github.com/HabitRPG/habitica/issues/11283 -->
</div>
</template>
<style scoped>
.share-buttons {
margin-top: 1em;
margin-bottom: 1em;
}
.share-button {
display: inline-block;
width: 77px;
padding: .5em;
border-radius: 2px;
text-align: center;
color: #fff;
}
.fb-share-button {
background-color: #2995cd;
}
.twitter-share-button {
margin-right: .5em;
background-color: #3bcad7;
}
.social-icon {
width: 16px;
display: inline-block;
vertical-align: bottom;
margin-right: .5em;
}
.social-icon.facebook svg {
width: 7.5px;
margin-bottom: .2em;
}
.social-icon.twitter {
margin-bottom: .2em;
}
<style lang="scss" scoped>
.modal-footer {
border-top: none;
}
</style>
<script>
// @TODO:
import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg';
const BASE_URL = 'https://habitica.com';
export default {
data () {
const tweet = this.$t('achievementShare');
return {
icons: Object.freeze({
twitter,
facebook,
}),
tweet,
achievementLink: `${BASE_URL}`,
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}&count=none`,
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}`,
};
},
};
</script>

View file

@ -3,14 +3,32 @@
id="generic-achievement"
:title="data.message"
size="md"
:hide-footer="true"
:hide-header="true"
>
<div class="modal-body">
<div class="col-12">
<achievement-avatar class="avatar" />
<span
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<div class="content">
<div
v-once
class="dialog-header title"
>
{{ $t('earnedAchievement') }}
</div>
<div class="col-6 offset-3 text-center">
<p v-html="data.modalText"></p>
<div class="inner-content">
<div class="achievement-background d-flex align-items-center">
<div
class="icon"
:class="achievementClass"
></div>
</div>
<h4
class="title"
v-html="$t(achievement.titleKey)"
>
</h4>
<button
class="btn btn-primary"
@click="close()"
@ -19,33 +37,86 @@
</button>
</div>
</div>
<achievement-footer />
<div
slot="modal-footer"
class="clearfix"
></div>
</b-modal>
</template>
<style scoped>
.avatar {
width: 140px;
<style lang="scss">
@import '~@/assets/scss/modal.scss';
#generic-achievement {
@include centeredModal();
.modal-dialog {
width: 330px;
}
.modal-footer {
padding-top: 0px;
}
}
</style>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.content {
text-align: center;
}
.inner-content {
margin: 24px auto auto;
display: flex;
flex-direction: column;
align-items: center;
}
.achievement-background {
width: 112px;
height: 112px;
border-radius: 4px;
background-color: $gray-700;
}
.dialog-header {
margin-top: 16px !important;
color: $purple-200 !important;
}
.title {
margin-bottom: 24px !important;
}
.icon {
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import { mapState } from '@/libs/store';
import achievements from '@/../../common/script/content/achievements';
import svgClose from '@/assets/svg/close.svg';
export default {
components: {
achievementFooter,
achievementAvatar,
},
props: ['data'],
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
};
},
computed: {
...mapState({ user: 'user.data' }),
achievement () {
return achievements[this.data.achievement];
},
achievementClass () {
return `${this.achievement.icon}2x`;
},
},
methods: {
close () {

View file

@ -27,36 +27,6 @@
label(style='display:inline-block') {{ $t('dontShowAgain') }}
-->
</div>
<div class="container-fluid share-buttons">
<div class="row">
<div class="col-12 text-center">
<a
class="twitter-share-button share-button"
:href="twitterLink"
target="_blank"
>
<div
class="social-icon twitter svg-icon"
v-html="icons.twitter"
></div>
{{ $t('tweet') }}
</a>
<a
class="fb-share-button share-button"
:href="facebookLink"
target="_blank"
>
<div
class="social-icon facebook svg-icon"
v-html="icons.facebook"
></div>
{{ $t('share') }}
</a>
</div>
<!-- @TODO: Still want this? .col-4a.tumblr
-share-button(:data-href='socialLevelLink', data-notes='none')-->
</div>
</div>
</b-modal>
</template>
@ -72,7 +42,6 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
.modal-body {
padding-top: 1em;
padding-bottom: 0;
}
.modal-footer {
@ -99,45 +68,6 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
margin-top: 1em;
min-height: 0px;
}
.share-buttons {
margin-top: 1em;
margin-bottom: 1em;
}
.share-button {
display: inline-block;
width: 77px;
padding: .5em;
border-radius: 2px;
text-align: center;
color: #fff;
}
.fb-share-button {
background-color: #2995cd;
}
.twitter-share-button {
margin-right: .5em;
background-color: #3bcad7;
}
.social-icon {
width: 16px;
display: inline-block;
vertical-align: bottom;
margin-right: .5em;
}
.social-icon.facebook svg {
width: 7.5px;
margin-bottom: .2em;
}
.social-icon.twitter {
margin-bottom: .2em;
}
}
</style>
@ -152,10 +82,6 @@ import Avatar from '../avatar';
import { mapState } from '@/libs/store';
import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
import styleHelper from '@/mixins/styleHelper';
import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg';
const BASE_URL = 'https://habitica.com';
export default {
components: {
@ -163,18 +89,9 @@ export default {
},
mixins: [styleHelper],
data () {
const tweet = this.$t('levelUpShare');
return {
icons: Object.freeze({
twitter,
facebook,
}),
statsAllocationBoxIsOpen: true,
maxHealth,
tweet,
socialLevelLink: `${BASE_URL}/social/level-up`,
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}/social/level-up&count=none`,
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}/social/level-up`,
};
},
computed: {
@ -183,16 +100,10 @@ export default {
return this.$store.getters['members:hasClass'](this.user) && !this.user.preferences.automaticAllocation;
},
},
mounted () {
this.loadWidgets();
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'level-up');
},
loadWidgets () {
// @TODO:
},
changeLevelupSuppress () {
// @TODO: dispatch set({"preferences.suppressModals.levelUp":
// user.preferences.suppressModals.levelUp?true: false})

View file

@ -0,0 +1,106 @@
<template>
<b-modal
id="onboarding-complete"
size="sm"
:hide-footer="true"
:hide-header="true"
>
<div class="content text-center">
<span
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<h2>{{ $t('congratulations') }}</h2>
<img
class="onboarding-complete-banner d-block"
src="~@/assets/images/onboarding-complete-banner@2x.png"
>
<p
class="onboarding-complete-text"
v-html="$t('onboardingCompleteDesc')"
></p>
<button
class="btn btn-primary"
@click="closeWithAction()"
>
{{ $t('viewAchievements') }}
</button>
</div>
</b-modal>
</template>
<style lang="scss">
#onboarding-complete {
.modal-content {
min-width: 330px;
}
.modal-body {
padding-top: 1em;
padding-bottom: 0;
}
.modal-footer {
margin-top: 0;
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
h2 {
color: $purple-200;
margin-bottom: 24px;
margin-top: 2px;
}
.content {
padding: 0 8px;
margin-top: 18px;
}
.onboarding-complete-banner {
width: 282px;
margin: 0 auto;
margin-bottom: 24px;
}
.onboarding-complete-text {
margin-bottom: 24px;
}
.onboarding-complete-text ::v-deep .gold-amount {
color: $yellow-5;
}
button {
margin-bottom: 24px;
}
</style>
<script>
import svgClose from '@/assets/svg/close.svg';
export default {
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'onboarding-complete');
},
closeWithAction () {
this.close();
setTimeout(() => {
this.$router.push({ name: 'achievements' });
}, 200);
},
},
};
</script>

View file

@ -30,10 +30,10 @@
</button>
<div class="checkbox">
<input
id="user-preferences-suppressModals-streak"
v-model="user.preferences.suppressModals.streak"
type="checkbox"
@change="suppressModals"
id="user-preferences-suppressModals-streak"
>
<label for="user-preferences-suppressModals-streak">{{ $t('dontShowAgain') }}</label>
</div>

View file

@ -46,6 +46,13 @@
:placeholder="$t('usernamePlaceholder')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
>
<div
v-for="issue in usernameIssues"
:key="issue"
class="input-error"
>
{{ issue }}
</div>
</div>
<div
v-if="!registering"
@ -97,7 +104,17 @@
class="form-control"
type="password"
:placeholder="$t(registering ? 'passwordPlaceholder' : 'password')"
:class="{
'input-valid': registering ? passwordValid : false,
'input-invalid': registering ? passwordInvalid: false,
}"
>
<div
v-if="passwordInvalid && registering"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div>
<div
v-if="registering"
@ -115,6 +132,12 @@
:placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<small
v-once
class="form-text"
@ -183,8 +206,11 @@
text-align: center;
}
.input-valid {
color: #fff;
.input-error {
margin-top: 0.25em;
font-weight: normal;
font-size: 90%;
width: 100%;
}
}
</style>
@ -194,7 +220,7 @@ import hello from 'hellojs';
import debounce from 'lodash/debounce';
import isEmail from 'validator/lib/isEmail';
import { setUpAxios } from '@/libs/auth';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import facebookSquareIcon from '@/assets/svg/facebook-square.svg';
import googleIcon from '@/assets/svg/google.svg';
@ -223,6 +249,7 @@ export default {
return isEmail(this.email);
},
emailInvalid () {
if (this.email.length <= 3) return false;
return !this.emailValid;
},
usernameValid () {
@ -230,13 +257,23 @@ export default {
return this.usernameIssues.length === 0;
},
usernameInvalid () {
if (this.username.length < 1) return false;
return !this.usernameValid;
},
passwordValid () {
if (this.password.length <= 0) return false;
return this.password.length >= MINIMUM_PASSWORD_LENGTH;
},
passwordInvalid () {
if (this.password.length <= 0) return false;
return this.password.length < MINIMUM_PASSWORD_LENGTH;
},
passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password;
},
passwordConfirmInvalid () {
if (this.passwordConfirm.length <= 3) return false;
return !this.passwordConfirmValid;
},
},

View file

@ -6,8 +6,7 @@
<form
v-if="!forgotPassword && !resetPasswordSetNewOne"
id="login-form"
@submit.prevent="handleSubmit"
@keyup.enter="handleSubmit"
@submit.prevent.stop="handleSubmit"
>
<div class="text-center">
<div>
@ -69,7 +68,7 @@
<input
id="usernameInput"
v-model="username"
class="form-control"
class="form-control input-with-error"
type="text"
:placeholder="$t('usernamePlaceholder')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
@ -132,7 +131,17 @@
class="form-control"
type="password"
:placeholder="$t(registering ? 'passwordPlaceholder' : 'password')"
:class="{
'input-invalid input-with-error': registering && passwordInvalid,
'input-valid': registering && passwordValid
}"
>
<div
v-if="passwordInvalid && registering"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div>
<div
v-if="registering"
@ -145,11 +154,17 @@
<input
id="confirmPasswordInput"
v-model="passwordConfirm"
class="form-control"
class="form-control input-with-error"
type="password"
:placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<small
v-once
class="form-text"
@ -157,22 +172,22 @@
></small>
</div>
<div class="text-center">
<div
<button
v-if="registering"
v-once
type="submit"
class="btn btn-info"
@click="register()"
:disabled="signupFormInvalid"
>
{{ $t('joinHabitica') }}
</div>
<div
</button>
<button
v-if="!registering"
v-once
type="submit"
class="btn btn-info"
@click="login()"
>
{{ $t('login') }}
</div>
</button>
<div class="toggle-links">
<router-link
v-if="registering"
@ -275,10 +290,17 @@
<input
id="passwordInput"
v-model="password"
class="form-control"
class="form-control input-with-error"
type="password"
:placeholder="$t('password')"
:class="{'input-invalid': passwordInvalid, 'input-valid': passwordValid}"
>
<div
v-if="passwordInvalid"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div>
<div class="form-group">
<label
@ -288,10 +310,17 @@
<input
id="confirmPasswordInput"
v-model="passwordConfirm"
class="form-control"
class="form-control input-with-error"
type="password"
:placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
</div>
<div class="text-center">
<div
@ -426,10 +455,14 @@
color: $white;
}
#usernameInput.input-invalid {
.input-with-error.input-invalid {
margin-bottom: 0.5em;
}
#confirmPasswordInput + .input-error {
margin-bottom: 2em;
}
.form-text {
font-size: 14px;
color: $white;
@ -480,7 +513,7 @@
background-image: url('~@/assets/images/auth/seamless_mountains_demo.png');
background-repeat: repeat-x;
width: 100%;
height: 500px;
height: 300px;
position: absolute;
z-index: 0;
bottom: 0;
@ -512,7 +545,6 @@
color: #fff;
font-size: 90%;
width: 100%;
text-align: center;
}
</style>
@ -522,6 +554,7 @@ import hello from 'hellojs';
import debounce from 'lodash/debounce';
import isEmail from 'validator/lib/isEmail';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import gryphon from '@/assets/svg/gryphon.svg';
import habiticaIcon from '@/assets/svg/habitica-logo.svg';
import facebookSquareIcon from '@/assets/svg/facebook-square.svg';
@ -580,6 +613,14 @@ export default {
if (this.username.length < 1) return false;
return !this.usernameValid;
},
passwordValid () {
if (this.password.length <= 0) return false;
return this.password.length >= MINIMUM_PASSWORD_LENGTH;
},
passwordInvalid () {
if (this.password.length <= 0) return false;
return this.password.length < MINIMUM_PASSWORD_LENGTH;
},
passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password;
@ -588,6 +629,12 @@ export default {
if (this.passwordConfirm.length <= 3) return false;
return !this.passwordConfirmValid;
},
signupFormInvalid () {
return this.usernameInvalid
|| this.emailInvalid
|| this.passwordInvalid
|| this.passwordConfirmInvalid;
},
},
watch: {
$route: {

View file

@ -1,7 +1,10 @@
<template>
<div class="row">
<challenge-modal @updatedChallenge="updatedChallenge" />
<leave-challenge-modal :challenge-id="challenge._id" @update-challenge="updateChallenge" />
<leave-challenge-modal
:challenge-id="challenge._id"
@update-challenge="updateChallenge"
/>
<close-challenge-modal
:members="members"
:challenge-id="challenge._id"

View file

@ -215,7 +215,6 @@
}
.meta-info {
height: 24px;
margin-bottom: 8px;
}

View file

@ -595,7 +595,7 @@ export default {
this.$root.$emit('bv::hide::modal', 'challenge-modal');
this.$router.push(`/challenges/${challenge._id}`);
},
updateChallenge () {
async updateChallenge () {
const categoryKeys = this.workingChallenge.categories;
const serverCategories = [];
categoryKeys.forEach(key => {
@ -610,10 +610,8 @@ export default {
const challengeDetails = clone(this.workingChallenge);
challengeDetails.categories = serverCategories;
this.$emit('updatedChallenge', {
challenge: challengeDetails,
});
this.$store.dispatch('challenges:updateChallenge', { challenge: challengeDetails });
const challenge = await this.$store.dispatch('challenges:updateChallenge', { challenge: challengeDetails });
this.$emit('updatedChallenge', { challenge });
this.resetWorkingChallenge();
this.$root.$emit('bv::hide::modal', 'challenge-modal');
},

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