Compare commits

..

84 Commits

Author SHA1 Message Date
Sabe Jones
87b9e72b56 4.80.2 2019-01-14 12:39:03 -06:00
Sabe Jones
962662fe7c chore(news): Bailey 2019-01-14 12:38:35 -06:00
Sabe Jones
349a1032b6 4.80.1 2019-01-11 20:00:31 +00:00
Sabe Jones
476131835d chore(i18n): update locales 2019-01-11 19:59:54 +00:00
Sabe Jones
8237b7f2de chore(news): Bailey 2019-01-11 13:37:58 -06:00
Sabe Jones
6ee2b3690a chore(i18n): update locales 2019-01-10 23:28:33 +00:00
Sabe Jones
a08cca807a 4.80.0 2019-01-09 01:38:46 +00:00
Sabe Jones
8c51f36784 chore(i18n): update locales 2019-01-09 01:35:24 +00:00
Sabe Jones
d35f81cdae chore(sprites): compile 2019-01-08 19:30:49 -06:00
Sabe Jones
1d1b25391f feat(content): customizations and dinosaurs 2019-01-08 19:30:41 -06:00
Sabe Jones
ec81c02d72 4.79.0 2019-01-04 20:04:19 +00:00
Sabe Jones
166da3c2f8 chore(i18n): update locales 2019-01-04 20:04:12 +00:00
Sabe Jones
23b72a673d chore(sprites): compile 2019-01-04 13:59:27 -06:00
Sabe Jones
d22b4bb2f7 feat(content): Armoire and Backgrounds Jan 2019 2019-01-04 13:58:51 -06:00
Sabe Jones
aaebd4da77 4.78.2 2019-01-03 22:31:27 +00:00
Sabe Jones
b128e7874e chore(i18n): update locales 2019-01-03 22:31:16 +00:00
Juliusdotsh
1e10e20a24 apidocs should say how to provide comment when flagging #10916 (#10925) 2019-01-03 11:31:03 +01:00
Sabe Jones
b1aeb8ed87 Merge branch 'release' into develop 2019-01-02 17:08:50 -06:00
Sabe Jones
2cb80e2275 4.78.1 2019-01-02 22:14:24 +00:00
Sabe Jones
ee32e24ff2 chore(i18n): update locales 2019-01-02 22:14:05 +00:00
Sabe Jones
af40c437be chore(news): Bailey
also fix erroneously active hatching potion and remove concluded bundle 
from pinned items
2019-01-02 16:10:57 -06:00
Sabe Jones
a99150c485 Merge branch 'release' into develop 2019-01-01 07:14:04 -06:00
Sabe Jones
696b67204d fix(lint): ’ 2019-01-01 07:13:41 -06:00
Sabe Jones
4f3536e887 Merge branch 'release' into develop 2018-12-31 23:48:59 +00:00
Sabe Jones
07e5bf1437 4.78.0 2018-12-31 23:48:21 +00:00
Sabe Jones
d2ca738256 chore(i18n): update locales 2018-12-31 23:48:11 +00:00
Sabe Jones
f7983f39eb feat(event): New Year's 2018-19 2018-12-31 17:44:13 -06:00
Rene Cordier
7c954f7073 Prevent progress being cleared when quest ends (#10870)
* Prevent progress being cleared when quest ends

changing group tests to make sure it keeps user's progress

fix and remove .only() from tests

* fix tests and check null case for clearing up user's quest without resetting progress
2018-12-28 19:16:21 +01:00
Sabe Jones
82d0e737a6 4.77.6 2018-12-27 15:47:36 +00:00
Sabe Jones
f8213aaf1b chore(i18n): update locales 2018-12-27 15:47:25 +00:00
Matteo Pagliazzi
2335ad4167 fix(gems modal): remove duplicate id property, fix #10919 2018-12-27 15:04:14 +01:00
negue
d84631255b extract site urls from translations (#10909) 2018-12-23 19:56:21 +01:00
Raymond Grumney
0b352b9103 Unit Tests; 1 fix to appFooter debug menu (#10894)
* Built

* Update .editorconfig

* Fixed filename ('translaotr.js' => 'translator.js')

* Fixed Filename

* User Unit Tests

* Writing tests for avatar.vue

* writing tests for getters/user.js

* Writing Tests for avatar.vue

* Writing Tests for avatar.vue

* writing tests for store/getters/user.js

* renamed file

* Integrated test into user.test.js

* Reverting

* Fetching most recent version

* +1 Level now adds a level

* updating to current version

* Minor edits to pass lint test

* Removing non-functional Tests

* Cleaning up
2018-12-23 19:45:59 +01:00
negue
19b75c6257 drag tags to reorder (#10911)
* drag tags to reorder

* change color, remove unneeded sortTag-call
2018-12-23 19:45:05 +01:00
negue
b66904a3a7 notification click always shows the modal, setting only controls the notification timeout (#10913) 2018-12-23 19:35:30 +01:00
Phillip Thelen
cfbfec34aa Allow gems and subs to be gifted through in-app-purchases (#10892)
* Allow gems to be gifted through IAPs

* implement non recurring IAP subscriptions

* fix localization issue in error

* fix non renewing subscription handling

* Fix lint error

* fix tests

* move findbyId mock to helper file

* undo package-lock changes

* Fix lint error
2018-12-23 19:20:14 +01:00
Sabe Jones
88f28188a1 4.77.5 2018-12-21 20:45:20 +00:00
Sabe Jones
fce5be2e8f chore(i18n): update locales 2018-12-21 20:44:16 +00:00
Sabe Jones
1d68cbfaa2 chore(news): more Bailey 2018-12-21 14:42:11 -06:00
Sabe Jones
a44222a350 4.77.4 2018-12-20 16:37:04 -06:00
Sabe Jones
4d2510e322 fix(sprites): add missing dragon icon 2018-12-20 16:36:57 -06:00
Sabe Jones
3b5ed33e03 4.77.3 2018-12-20 21:29:58 +00:00
Sabe Jones
0a6bf92b6b chore(i18n): update locales 2018-12-20 21:29:24 +00:00
Sabe Jones
05ae40bc2e fix(content): correct timeframe tag for string 2018-12-20 15:26:17 -06:00
Sabe Jones
57e96ea092 4.77.2 2018-12-20 12:14:38 -06:00
Sabe Jones
ea49b5b8e0 fix(content): add set string for Dec Mystery 2018-12-20 12:14:28 -06:00
Sabe Jones
11a5de714a 4.77.1 2018-12-20 17:25:33 +00:00
Sabe Jones
f74b4d3e73 Merge branch 'develop' into release 2018-12-20 17:24:59 +00:00
Sabe Jones
ab6fdb99af 4.77.0 2018-12-20 17:23:29 +00:00
Sabe Jones
e3efa557dd chore(i18n): update locales 2018-12-20 17:22:57 +00:00
Sabe Jones
a6cea47789 feat(event): Winter Wonderland 2018 2018-12-20 11:11:50 -06:00
Sabe Jones
5a200a88bd chore(sprites): compile 2018-12-20 09:42:04 -06:00
Sabe Jones
545499ea0b WIP(event): partial content build 2018-12-20 09:40:54 -06:00
Sabe Jones
3e7f4229ec Merge branch 'release' into develop 2018-12-19 13:03:20 +00:00
Sabe Jones
56d5f77b6e 4.76.1 2018-12-19 13:02:58 +00:00
Sabe Jones
efdf5a2e16 chore(i18n): update locales 2018-12-19 13:01:40 +00:00
Sabe Jones
add3a2887f fix(css): restore close svg defaults 2018-12-19 06:58:56 -06:00
Sabe Jones
0ed7c7596a Merge branch 'release' into develop 2018-12-18 21:32:39 +00:00
Sabe Jones
55a452694f 4.76.0 2018-12-18 21:32:12 +00:00
Sabe Jones
cd4d5f83ff chore(i18n): update locales 2018-12-18 21:31:56 +00:00
Sabe Jones
8220199e49 Gift 1 Get 1 Promo 2018-19! (#10915)
* feat(subscription): promo banner in modal

* feat(subscription): promo banner on main page

* fix(banners): remove extraneous margin adjustment

* fix(banners): various

* feat(promotion): gift 1, get 1

* fix(promo): various

* chore(promo): add Bailey

* fix(promo): use different email template for promo beneficiary

* fix(promo): turns out Winter is meaningful
2018-12-18 15:28:53 -06:00
Sabe Jones
bd1f6918ba fix(sprites): wolf and penguin tweaks 2018-12-18 12:22:46 -06:00
Matteo Pagliazzi
5b21e62647 fix(i18n): format 2018-12-18 15:02:03 +01:00
Matteo Pagliazzi
5e76d6df21 fix(i18n): position of $ sign 2018-12-17 13:12:35 +01:00
Matteo Pagliazzi
7348145b7d fix(success-modal): misc fixes 2018-12-16 15:39:37 +01:00
Sabe Jones
ba2832d21f 4.75.5 2018-12-14 21:22:05 +00:00
Sabe Jones
688f5084a1 chore(i18n): update locales 2018-12-14 21:19:56 +00:00
Sabe Jones
d5955b8889 Merge branch 'develop' into release 2018-12-14 15:15:06 -06:00
Sabe Jones
01fd17ee3f fix(script): revert email query 2018-12-14 15:14:50 -06:00
Sabe Jones
979497dd35 fix(deletion): user delete bugs
Correct lookup in GDPR script, and address a TypeError when deleting a user with no tasks
2018-12-14 00:54:59 +00:00
Sabe Jones
a2c8b8b05c Usernames Misc: Bulk Email and New Party Modal (#10898)
* feat(migrations): genericize bulk email

* chore(migrations): archive one-offs

* feat(usernames): change create party modal to copy username

* fix(modal): styling

* fix(modal): add Chrome clipboard implementation
2018-12-11 14:47:50 -06:00
Sabe Jones
8750701c08 Merge branch 'release' into develop 2018-12-11 20:45:44 +00:00
Matteo Pagliazzi
8441b0a3d6 fix http auth env var 2018-12-09 20:12:50 +01:00
Matteo Pagliazzi
0667695390 Payments: Success Messages (#10903)
* fix v-once

* gems purchase success dialog

* amazon and stripe support for gems message, translations

* subscriptions success message

* subscriptions success message

* group plans success message and many fixes

* finish success messages for payments

* add success modal when gifting from balance
2018-12-09 20:08:01 +01:00
Matteo Pagliazzi
4d322c1bf6 Merge branch 'sukh0128-develop' into develop 2018-12-07 15:20:27 +01:00
Matteo Pagliazzi
a855ddacc7 Merge branch 'develop' of https://github.com/sukh0128/habitica into sukh0128-develop 2018-12-07 15:20:12 +01:00
Matteo Pagliazzi
9d48ef7322 Merge pull request #10896 from AyoubElk/patch-1
Fixed typo in folder path
2018-12-07 15:13:08 +01:00
Ayoub El Khattabi
95f3315796 Fixed typo in folder path
The tast test:api:unit:watch should watch the path 'test/api/unit/**/*' instead of the non existent 'test/api/v3/unit/**/*'
2018-12-06 11:26:22 +00:00
Erdenesukh Tsendjav
5e232d8c9f Merge branch 'develop' of github.com:sukh0128/habitica into develop 2018-12-04 17:29:16 -06:00
Erdenesukh Tsendjav
13793f8b3c revert 2018-12-04 17:28:27 -06:00
Erdenesukh Tsendjav
55f875f95a Delete package-lock.json 2018-12-04 17:04:55 -06:00
Erdenesukh Tsendjav
14576be374 merged 2018-12-04 16:03:11 -06:00
Erdenesukh Tsendjav
4cb2c26475 The istanbul command string in the gulp-tests.js file for each of the tests had to be changed to just the keyword istanbul instead of pointing to the istanbul file location on WINDOWS machines 2018-12-04 15:55:09 -06:00
Erdenesukh Tsendjav
3d757c7814 trying something 2018-10-24 16:31:46 -05:00
698 changed files with 30232 additions and 25628 deletions

View File

@@ -8,6 +8,7 @@ ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV LOGGLY_CLIENT_TOKEN ab5663bf-241f-4d14-8783-7d80db77089a
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA

View File

@@ -167,7 +167,7 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => {
gulp.task('test:api:unit', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
(err) => {
if (err) {
process.exit(1);
@@ -180,12 +180,12 @@ gulp.task('test:api:unit', (done) => {
});
gulp.task('test:api:unit:watch', () => {
return gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
return gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
});
gulp.task('test:api-v3:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err) => {
if (err) {
@@ -217,7 +217,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
gulp.task('test:api-v4:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err) => {
if (err) {
@@ -254,4 +254,4 @@ gulp.task('test:api-v3', gulp.series(
'test:api:unit',
'test:api-v3:integration',
done => done()
));
));

View File

@@ -0,0 +1,110 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20181231_nye';
import { model as User } from '../../../website/server/models/user';
import mongoose from 'mongoose';
import { v4 as uuid } from 'uuid';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {'flags.newStuff': true};
let push;
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
set['items.gear.owned.head_special_nye2018'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2018',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
set['items.gear.owned.head_special_nye2017'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2017',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
set['items.gear.owned.head_special_nye2016'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2016',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
set['items.gear.owned.head_special_nye2015'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2015',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
set['items.gear.owned.head_special_nye2014'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2014',
_id: uuid(),
},
];
} else {
set['items.gear.owned.head_special_nye'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye',
_id: uuid(),
},
];
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.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
}
};

View File

@@ -17,7 +17,7 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
const processUsers = require('./users/20181122_turkey_day.js');
const processUsers = require('./archive/2018/20181231_nye.js');
processUsers()
.then(function success () {
process.exit(0);

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-console */
const MIGRATION_NAME = 'mystery_items_201811';
const MYSTERY_ITEMS = ['head_mystery_201811', 'weapon_mystery_201811'];
const MIGRATION_NAME = 'mystery_items_201812';
const MYSTERY_ITEMS = ['headAccessory_mystery_201812', 'back_mystery_201812'];
import { model as User } from '../../website/server/models/user';
import { model as UserNotification } from '../../website/server/models/userNotification';

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "4.75.4",
"version": "4.80.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.75.4",
"version": "4.80.2",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",

View File

@@ -27,12 +27,13 @@ async function _deleteAmplitudeData (userId, email) {
if (response) console.log(`${response.status} ${response.statusText}`);
}
async function _deleteHabiticaData (user) {
async function _deleteHabiticaData (user, email) {
await User.update(
{_id: user._id},
{$set: {
'auth.local.passwordHashMethod': 'bcrypt',
'auth.local.email': email,
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
'auth.local.passwordHashMethod': 'bcrypt',
}}
);
const response = await axios.delete(
@@ -75,7 +76,7 @@ async function _processEmailAddress (email) {
} else {
for (const user of users) {
await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
await _deleteHabiticaData(user); // eslint-disable-line no-await-in-loop
await _deleteHabiticaData(user, email); // eslint-disable-line no-await-in-loop
}
}
}

View File

@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
import {model as User} from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import moment from 'moment';
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
const i18n = common.i18n;
@@ -49,7 +50,7 @@ describe('Apple Payments', () => {
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(false);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -61,7 +62,7 @@ describe('Apple Payments', () => {
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData').returns([]);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -71,7 +72,7 @@ describe('Apple Payments', () => {
it('errors if the user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -89,7 +90,7 @@ describe('Apple Payments', () => {
transactionId: token,
}]);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -131,7 +132,7 @@ describe('Apple Payments', () => {
}]);
sinon.stub(user, 'canGetGems').resolves(true);
await applePayments.verifyGemPurchase(user, receipt, headers);
await applePayments.verifyGemPurchase({user, receipt, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
@@ -151,6 +152,38 @@ describe('Apple Payments', () => {
user.canGetGems.restore();
});
});
it('gifts gems', async () => {
const receivingUser = new User();
await receivingUser.save();
mockFindById(receivingUser);
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
.returns([{productId: gemsCanPurchase[0].productId,
transactionId: token,
}]);
const gift = {uuid: receivingUser._id};
await applePayments.verifyGemPurchase({user, gift, receipt, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user: receivingUser,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
amount: gemsCanPurchase[0].amount,
headers,
});
restoreFindById();
});
});
describe('subscribe', () => {

View File

@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
import {model as User} from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import moment from 'moment';
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
const i18n = common.i18n;
@@ -44,7 +45,7 @@ describe('Google Payments', () => {
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(false);
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -55,7 +56,7 @@ describe('Google Payments', () => {
it('should throw an error if productId is invalid', async () => {
receipt = `{"token": "${token}", "productId": "invalid"}`;
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -66,7 +67,7 @@ describe('Google Payments', () => {
it('should throw an error if user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false);
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -78,7 +79,7 @@ describe('Google Payments', () => {
it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').resolves(true);
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
await googlePayments.verifyGemPurchase({user, receipt, signature, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
@@ -99,6 +100,34 @@ describe('Google Payments', () => {
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
});
it('gifts gems', async () => {
const receivingUser = new User();
await receivingUser.save();
mockFindById(receivingUser);
const gift = {uuid: receivingUser._id};
await googlePayments.verifyGemPurchase({user, gift, receipt, signature, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
data: receipt,
signature,
});
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user: receivingUser,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
amount: 5.25,
headers,
});
restoreFindById();
});
});
describe('subscribe', () => {

View File

@@ -209,7 +209,7 @@ describe('payments/index', () => {
await api.createSubscription(data);
let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`';
expect(user.sendMessage).to.be.calledOnce;
expect(user.sendMessage).to.be.calledTwice;
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
});
@@ -247,6 +247,77 @@ describe('payments/index', () => {
},
});
});
context('Winter 2018-19 Gift-1-Get-1 Promotion', async () => {
it('creates a gift subscription for purchaser and recipient if none exist', async () => {
await api.createSubscription(data);
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(user.purchased.plan.customerId).to.eql('Gift');
expect(user.purchased.plan.dateTerminated).to.exist;
expect(user.purchased.plan.dateUpdated).to.exist;
expect(user.purchased.plan.dateCreated).to.exist;
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(recipient.purchased.plan.customerId).to.eql('Gift');
expect(recipient.purchased.plan.dateTerminated).to.exist;
expect(recipient.purchased.plan.dateUpdated).to.exist;
expect(recipient.purchased.plan.dateCreated).to.exist;
});
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
user.purchased.plan = plan;
expect(user.purchased.plan.extraMonths).to.eql(0);
await api.createSubscription(data);
expect(user.purchased.plan.extraMonths).to.eql(3);
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(recipient.purchased.plan.customerId).to.eql('Gift');
expect(recipient.purchased.plan.dateTerminated).to.exist;
expect(recipient.purchased.plan.dateUpdated).to.exist;
expect(recipient.purchased.plan.dateCreated).to.exist;
});
it('adds extraMonths to existing subscription for recipient and creates a gift subscription for purchaser without sub', async () => {
recipient.purchased.plan = plan;
expect(recipient.purchased.plan.extraMonths).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.extraMonths).to.eql(3);
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(user.purchased.plan.customerId).to.eql('Gift');
expect(user.purchased.plan.dateTerminated).to.exist;
expect(user.purchased.plan.dateUpdated).to.exist;
expect(user.purchased.plan.dateCreated).to.exist;
});
it('adds extraMonths to existing subscriptions for purchaser and recipient', async () => {
user.purchased.plan = plan;
recipient.purchased.plan = plan;
expect(user.purchased.plan.extraMonths).to.eql(0);
expect(recipient.purchased.plan.extraMonths).to.eql(0);
await api.createSubscription(data);
expect(user.purchased.plan.extraMonths).to.eql(3);
expect(recipient.purchased.plan.extraMonths).to.eql(3);
});
it('sends a private message about the promotion', async () => {
await api.createSubscription(data);
let msg = '\`Hello sender, you received 3 months of subscription as part of our holiday gift-giving promotion!\`';
expect(user.sendMessage).to.be.calledTwice;
expect(user.sendMessage).to.be.calledWith(user, { senderMsg: msg });
});
});
});
context('Purchasing a subscription for self', () => {

View File

@@ -32,8 +32,19 @@ describe('Group Model', () => {
privacy: 'private',
});
let _progress = {
up: 10,
down: 8,
collectedItems: 5,
};
questLeader = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Quest Leader' },
items: {
quests: {
@@ -45,20 +56,40 @@ describe('Group Model', () => {
party.leader = questLeader._id;
participatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Participating Member' },
});
sleepingParticipatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Sleeping Participating Member' },
preferences: { sleep: true },
});
nonParticipatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Non-Participating Member' },
});
undecidedMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Undecided Member' },
});
@@ -1163,16 +1194,17 @@ describe('Group Model', () => {
expect(party.quest.members).to.eql(expectedQuestMembers);
});
it('applies updates to user object directly if user is participating', async () => {
it('applies updates to user object directly if user is participating (without resetting progress, except progress.down)', async () => {
await party.startQuest(participatingMember);
expect(participatingMember.party.quest.key).to.eql('whale');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.completed).to.eql(null);
});
it('applies updates to other participating members', async () => {
it('applies updates to other participating members (without resetting progress, except progress.down)', async () => {
await party.startQuest(nonParticipatingMember);
questLeader = await User.findById(questLeader._id);
@@ -1180,18 +1212,21 @@ describe('Group Model', () => {
sleepingParticipatingMember = await User.findById(sleepingParticipatingMember._id);
expect(participatingMember.party.quest.key).to.eql('whale');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.completed).to.eql(null);
expect(sleepingParticipatingMember.party.quest.key).to.eql('whale');
expect(sleepingParticipatingMember.party.quest.progress.up).to.eql(10);
expect(sleepingParticipatingMember.party.quest.progress.down).to.eql(0);
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(sleepingParticipatingMember.party.quest.completed).to.eql(null);
expect(questLeader.party.quest.key).to.eql('whale');
expect(questLeader.party.quest.progress.up).to.eql(10);
expect(questLeader.party.quest.progress.down).to.eql(0);
expect(questLeader.party.quest.progress.collectedItems).to.eql(0);
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
expect(questLeader.party.quest.completed).to.eql(null);
});
@@ -1202,6 +1237,9 @@ describe('Group Model', () => {
undecidedMember = await User.findById(undecidedMember._id);
expect(nonParticipatingMember.party.quest.key).to.not.eql('whale');
expect(nonParticipatingMember.party.quest.progress.up).to.eql(10);
expect(nonParticipatingMember.party.quest.progress.down).to.eql(8);
expect(nonParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(undecidedMember.party.quest.key).to.not.eql('whale');
});
@@ -1369,8 +1407,9 @@ describe('Group Model', () => {
let userQuest = participatingMember.party.quest;
expect(userQuest.key).to.eql('whale');
expect(userQuest.progress.up).to.eql(10);
expect(userQuest.progress.down).to.eql(0);
expect(userQuest.progress.collectedItems).to.eql(0);
expect(userQuest.progress.collectedItems).to.eql(5);
expect(userQuest.completed).to.eql(null);
});
@@ -1670,16 +1709,23 @@ describe('Group Model', () => {
});
});
it('sets user quest object to a clean state', async () => {
it('updates participating members quest object to a clean state (except for progress)', async () => {
await party.finishQuest(quest);
let updatedLeader = await User.findById(questLeader._id);
questLeader = await User.findById(questLeader._id);
participatingMember = await User.findById(participatingMember._id);
expect(updatedLeader.party.quest.completed).to.eql('whale');
expect(updatedLeader.party.quest.progress.up).to.eql(0);
expect(updatedLeader.party.quest.progress.down).to.eql(0);
expect(updatedLeader.party.quest.progress.collectedItems).to.eql(0);
expect(updatedLeader.party.quest.RSVPNeeded).to.eql(false);
expect(questLeader.party.quest.completed).to.eql('whale');
expect(questLeader.party.quest.progress.up).to.eql(10);
expect(questLeader.party.quest.progress.down).to.eql(8);
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
expect(questLeader.party.quest.RSVPNeeded).to.eql(false);
expect(participatingMember.party.quest.completed).to.eql('whale');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(8);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.RSVPNeeded).to.eql(false);
});
});

View File

@@ -0,0 +1,67 @@
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/payments/apple';
describe('payments : apple #norenewsubscribe', () => {
let endpoint = '/iap/ios/norenew-subscribe';
let sku = 'com.habitrpg.ios.habitica.subscription.3month';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint, {
sku,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(applePayments, 'noRenewSubscribe').resolves({});
});
afterEach(() => {
applePayments.noRenewSubscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {receipt: 'receipt'},
gift: {
uuid: '1',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

@@ -1,4 +1,4 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/payments/apple';
describe('payments : apple #verify', () => {
@@ -9,6 +9,14 @@ describe('payments : apple #verify', () => {
user = await generateUser();
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let verifyStub;
@@ -31,10 +39,31 @@ describe('payments : apple #verify', () => {
}});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0]._id).to.eql(user._id);
expect(verifyStub.args[0][1]).to.eql('receipt');
expect(verifyStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][2]['x-api-user']).to.eql(user._id);
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
balance: 2,
});
await user.post(endpoint, {
transaction: {
receipt: 'receipt',
},
gift: {
uuid: '1',
}});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

@@ -0,0 +1,97 @@
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/payments/google';
describe('payments : google #norenewsubscribe', () => {
let endpoint = '/iap/android/norenew-subscribe';
let sku = 'com.habitrpg.android.habitica.subscription.3month';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint, {
sku,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(googlePayments, 'noRenewSubscribe').resolves({});
});
afterEach(() => {
googlePayments.noRenewSubscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {
receipt: 'receipt',
signature: 'signature',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].signature).to.eql('signature');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {
receipt: 'receipt',
signature: 'signature',
},
gift: {
uuid: '1',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].signature).to.eql('signature');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

@@ -1,4 +1,4 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/payments/google';
describe('payments : google #verify', () => {
@@ -9,6 +9,14 @@ describe('payments : google #verify', () => {
user = await generateUser();
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let verifyStub;
@@ -30,11 +38,30 @@ describe('payments : google #verify', () => {
});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0]._id).to.eql(user._id);
expect(verifyStub.args[0][1]).to.eql('receipt');
expect(verifyStub.args[0][2]).to.eql('signature');
expect(verifyStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][3]['x-api-user']).to.eql(user._id);
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].signature).to.eql('signature');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
balance: 2,
});
await user.post(endpoint, {
transaction: {receipt: 'receipt', signature: 'signature'},
gift: {uuid: '1'},
});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].signature).to.eql('signature');
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

@@ -0,0 +1,300 @@
import Avatar from 'client/components/avatar';
import Vue from 'vue';
import generateStore from 'client/store';
context('avatar.vue', () => {
let Constructr;
let vm;
beforeEach(() => {
Constructr = Vue.extend(Avatar);
vm = new Constructr({
propsData: {
member: {
stats: {
buffs: {},
},
preferences: {
hair: {},
},
items: {
gear: {
equipped: {},
},
},
},
},
}).$mount();
vm.$store = generateStore();
});
afterEach(() => {
vm.$destroy();
});
describe('hasClass', () => {
beforeEach(() => {
vm.member = {
stats: { lvl: 17 },
preferences: { disableClasses: true },
flags: { classSelected: false },
};
});
it('accurately reports class status', () => {
expect(vm.hasClass).to.equal(false);
vm.member.preferences.disableClasses = false;
vm.member.flags.classSelected = true;
expect(vm.hasClass).to.equal(true);
});
});
describe('isBuffed', () => {
beforeEach(() => {
vm.member = {
stats: {
buffs: {},
},
};
});
it('accurately reports if buffed', () => {
expect(vm.isBuffed).to.equal(undefined);
vm.member.stats.buffs = { str: 1 };
expect(vm.isBuffed).to.equal(1);
});
});
describe('paddingTop', () => {
beforeEach(() => {
vm.member = {
items: {},
};
});
it('defaults to 28px', () => {
vm.avatarOnly = true;
expect(vm.paddingTop).to.equal('28px');
});
it('is 24.5px if user has a pet', () => {
vm.member.items = {
currentPet: { name: 'Foo' },
};
expect(vm.paddingTop).to.equal('24.5px');
});
it('is 0px if user has a mount', () => {
vm.member.items = {
currentMount: { name: 'Bar' },
};
expect(vm.paddingTop).to.equal('0px');
});
it('can be overriden', () => {
vm.overrideTopPadding = '27px';
expect(vm.paddingTop).to.equal('27px');
});
});
describe('costumeClass', () => {
beforeEach(() => {
vm.member = {
preferences: {},
};
});
it('returns if showing equiped gear', () => {
expect(vm.costumeClass).to.equal('equipped');
});
it('returns if wearing a costume', () => {
vm.member.preferences = { costume: true };
expect(vm.costumeClass).to.equal('costume');
});
});
describe('visualBuffs', () => {
it('returns an array of buffs', () => {
vm.member = {
stats: {
class: 'Warrior',
},
};
expect(vm.visualBuffs).to.include({snowball: 'snowman'});
expect(vm.visualBuffs).to.include({spookySparkles: 'ghost'});
expect(vm.visualBuffs).to.include({shinySeed: 'avatar_floral_Warrior'});
expect(vm.visualBuffs).to.include({seafoam: 'seafoam_star'});
});
});
describe('backgroundClass', () => {
beforeEach(() => {
vm.member.preferences = { background: 'pony' };
});
it('shows the background', () => {
expect(vm.backgroundClass).to.equal('background_pony');
});
it('can be overridden', () => {
vm.overrideAvatarGear = { background: 'character' };
expect(vm.backgroundClass).to.equal('background_character');
});
it('returns to a blank string if not showing background', () => {
vm.withBackground = false;
vm.avatarOnly = true;
expect(vm.backgroundClass).to.equal('');
});
});
describe('specialMountClass', () => {
it('checks if riding a Kangaroo', () => {
vm.member = {
stats: {
class: 'None',
},
items: {},
};
expect(vm.specialMountClass).to.equal(undefined);
vm.member.items = {
currentMount: ['Kangaroo'],
};
expect(vm.specialMountClass).to.equal('offset-kangaroo');
});
});
describe('skinClass', () => {
it('returns current skin color', () => {
vm.member = {
stats: {},
preferences: {
skin: 'blue',
},
};
expect(vm.skinClass).to.equal('skin_blue');
});
it('returns if sleep or not', () => {
vm.member = {
stats: {},
preferences: {
skin: 'blue',
sleep: false,
},
};
expect(vm.skinClass).to.equal('skin_blue');
vm.member.preferences.sleep = true;
expect(vm.skinClass).to.equal('skin_blue_sleep');
});
});
context('methods', () => {
describe('getGearClass', () => {
beforeEach(() => {
vm.member = {
items: {
gear: {
equipped: { Hat: 'Fancy Tophat' },
},
},
preferences: { costume: false },
};
});
it('returns undefined if no match', () => {
expect(vm.getGearClass('foo')).to.equal(undefined);
});
it('returns the matching gear', () => {
expect(vm.getGearClass('Hat')).to.equal('Fancy Tophat');
});
it('can be overridden', () => {
vm.overrideAvatarGear = { Hat: 'Dapper Bowler' };
expect(vm.getGearClass('Hat')).to.equal('Dapper Bowler');
});
});
describe('hideGear', () => {
it('returns no weapon equipped', () => {
vm.member.items.gear.equipped = {};
expect(vm.hideGear('weapon')).to.equal(false);
});
beforeEach(() => {
vm.member = {
items: {
gear: {
equipped: {
weapon: {
baseWeapon: 'Spoon',
twoHanded: false,
},
},
},
},
preferences: { costume: false },
};
});
});
describe('show avatar', () => {
beforeEach(() => {
vm.member = {
stats: {
buffs: {
snowball: false,
seafoam: false,
spookySparkles: false,
shinySeed: false,
},
},
};
});
it('does if not showing visual buffs', () => {
expect(vm.showAvatar()).to.equal(true);
let buffs = vm.member.stats.buffs;
buffs.snowball = true;
expect(vm.showAvatar()).to.equal(false);
buffs.snowball = false;
buffs.spookySparkles = true;
expect(vm.showAvatar()).to.equal(false);
buffs.spookySparkles = false;
buffs.shinySeed = true;
expect(vm.showAvatar()).to.equal(false);
buffs.shinySeed = false;
buffs.seafoam = true;
expect(vm.showAvatar()).to.equal(false);
buffs.seafoam = false;
vm.showVisualBuffs = false;
expect(vm.showAvatar()).to.equal(true);
});
});
});
});

View File

@@ -0,0 +1,76 @@
import { data, gems, buffs, preferences, tasksOrder } from 'client/store/getters/user';
context('user getters', () => {
describe('data', () => {
it('returns the user\'s data', () => {
expect(data({
state: {
user: {
data: {
lvl: 1,
},
},
},
}).lvl).to.equal(1);
});
});
describe('gems', () => {
it('returns the user\'s gems', () => {
expect(gems({
state: {
user: {
data: { balance: 4.5 },
},
},
})).to.equal(18);
});
});
describe('buffs', () => {
it('returns the user\'s buffs', () => {
expect(buffs({
state: {
user: {
data: {
stats: {
buffs: [1],
},
},
},
},
})(0)).to.equal(1);
});
});
describe('preferences', () => {
it('returns the user\'s preferences', () => {
expect(preferences({
state: {
user: {
data: {
preferences: 1,
},
},
},
})).to.equal(1);
});
});
describe('tasksOrder', () => {
it('returns the user\'s tasksOrder', () => {
expect(tasksOrder({
state: {
user: {
tasksOrder: {
masters: 1,
},
},
},
})('master')).to.equal(1);
expect(tasksOrder()).to.not.equal('null');
expect(tasksOrder()).to.not.equal('undefined');
});
});
});

View File

@@ -1,13 +0,0 @@
import { gems as userGems } from 'client/store/getters/user';
describe('userGems getter', () => {
it('returns the user\'s gems', () => {
expect(userGems({
state: {
user: {
data: {balance: 4.5},
},
},
})).to.equal(18);
});
});

View File

@@ -0,0 +1,20 @@
import mongoose from 'mongoose';
export async function mockFindById (response) {
const mockFind = {
select () {
return this;
},
lean () {
return this;
},
exec () {
return Promise.resolve(response);
},
};
sinon.stub(mongoose.Model, 'findById').returns(mockFind);
}
export function restoreFindById () {
return mongoose.Model.findById.restore();
}

View File

@@ -16,18 +16,24 @@ div
router-view(v-if="!isUserLoggedIn || isStaticPage")
template(v-else)
template(v-if="isUserLoaded")
div.resting-banner(v-show="showRestingBanner", ref="restingBanner")
.resting-banner(v-show="showRestingBanner", ref="restingBanner")
span.content
span.label.d-inline.d-sm-none {{ $t('innCheckOutBannerShort') }}
span.label.d-none.d-sm-inline {{ $t('innCheckOutBanner') }}
span.separator |
span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }}
div.closepadding(@click="hideBanner()")
.closepadding(@click="hideBanner()")
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
.g1g1-banner.d-flex.justify-content-center.align-items-center(v-if="!giftingHidden")
.svg-icon.svg-gifts.left-gift(v-html="icons.gifts")
router-link(:to="{name: 'subscription'}") {{ $t('g1g1Announcement') }}
.svg-icon.svg-gifts.right-gift(v-html="icons.gifts")
.closepadding(@click="hideGiftingBanner()")
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
notifications-display
app-menu(:class='{"restingInn": showRestingBanner}' :style="{ marginTop: bannerHeight + 'px' }")
app-menu
.container-fluid
app-header(:class='{"restingInn": showRestingBanner}')
app-header
buyModal(
:item="selectedItemToBuy || {}",
:withPin="true",
@@ -50,6 +56,13 @@ div
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
#app {
height: calc(100% - 56px); /* 56px is the menu */
display: flex;
flex-direction: column;
min-height: 100vh;
}
#loading-screen-inapp {
#melior {
margin: 0 auto;
@@ -79,6 +92,46 @@ div
cursor: crosshair;
}
.container-fluid {
overflow-x: hidden;
flex: 1 0 auto;
}
.g1g1-banner {
width: 100%;
min-height: 2.5rem;
background-color: #34b5c1;
a {
color: $white;
text-decoration: none;
font-weight: bold;
}
.closepadding {
margin: 11px 24px;
display: inline-block;
position: relative;
right: 0;
top: 0;
cursor: pointer;
}
.left-gift {
margin: auto 1rem auto auto;
}
.right-gift {
margin: auto auto auto 1rem;
filter: FlipH;
transform: scaleX(-1);
}
.svg-gifts {
width: 4.6rem;
}
}
.notification {
border-radius: 1000px;
background-color: $green-10;
@@ -89,42 +142,10 @@ div
margin-bottom: .5em;
}
.container-fluid {
overflow-x: hidden;
flex: 1 0 auto;
}
#app {
height: calc(100% - 56px); /* 56px is the menu */
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>
<style lang='scss'>
@import '~client/assets/scss/colors.scss';
/* @TODO: The modal-open class is not being removed. Let's try this for now */
.modal {
overflow-y: scroll !important;
}
.modal-backdrop.show {
opacity: .9 !important;
background-color: $purple-100 !important;
}
/* Push progress bar above modals */
#nprogress .bar {
z-index: 1600 !important; /* Must stay above nav bar */
}
.resting-banner {
width: 100%;
min-height: 40px;
background-color: $blue-10;
position: fixed;
top: 0;
z-index: 1300;
display: flex;
@@ -140,14 +161,10 @@ div
.closepadding {
margin: 11px 24px;
display: inline-block;
position: absolute;
position: relative;
right: 0;
top: 0;
cursor: pointer;
span svg path {
stroke: $blue-500;
}
}
@media only screen and (max-width: 768px) {
@@ -170,6 +187,30 @@ div
}
</style>
<style lang='scss'>
@import '~client/assets/scss/colors.scss';
.closepadding span svg path {
stroke: #FFF;
opacity: 0.48;
}
/* @TODO: The modal-open class is not being removed. Let's try this for now */
.modal {
overflow-y: scroll !important;
}
.modal-backdrop.show {
opacity: .9 !important;
background-color: $purple-100 !important;
}
/* Push progress bar above modals */
#nprogress .bar {
z-index: 1600 !important; /* Must stay above nav bar */
}
</style>
<script>
import axios from 'axios';
import { loadProgressBar } from 'axios-progress-bar';
@@ -189,8 +230,9 @@ import amazonPaymentsModal from 'client/components/payments/amazonModal';
import paymentsSuccessModal from 'client/components/payments/successModal';
import spellsMixin from 'client/mixins/spells';
import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager';
import { CONSTANTS, getLocalSetting, removeLocalSetting, setLocalSetting } from 'client/libs/userlocalManager';
import gifts from 'assets/svg/gifts.svg';
import svgClose from 'assets/svg/close.svg';
import bannedAccountModal from 'client/components/bannedAccountModal';
@@ -215,6 +257,7 @@ export default {
return {
icons: Object.freeze({
close: svgClose,
gifts,
}),
selectedItemToBuy: null,
selectedSpellToBuy: null,
@@ -226,6 +269,7 @@ export default {
currentTipNumber: 0,
bannerHidden: false,
bannerHeight: 0,
giftingHidden: getLocalSetting(CONSTANTS.keyConstants.GIFTING_BANNER_DISPLAY) === 'dismissed',
};
},
computed: {
@@ -423,14 +467,6 @@ export default {
this.hideLoadingScreen();
window.addEventListener('resize', this.setBannerOffset);
// Adjust the positioning of the header banners
this.$watch('showRestingBanner', () => {
this.$nextTick(() => {
this.setBannerOffset();
});
}, {immediate: true});
// Adjust the timezone offset
if (this.user.preferences.timezoneOffset !== this.browserTimezoneOffset) {
this.$store.dispatch('user:set', {
@@ -443,7 +479,7 @@ export default {
appState = JSON.parse(appState);
if (appState.paymentCompleted) {
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
this.$root.$emit('bv::show::modal', 'payments-success-modal');
this.$root.$emit('habitica:payment-success', appState);
}
}
this.$nextTick(() => {
@@ -465,7 +501,6 @@ export default {
this.$root.$off('bv::show::modal');
this.$root.$off('buyModal::showItem');
this.$root.$off('selectMembersModal::showItem');
window.removeEventListener('resize', this.setBannerOffset);
},
mounted () {
// Remove the index.html loading screen and now show the inapp loading
@@ -624,22 +659,14 @@ export default {
},
hideBanner () {
this.bannerHidden = true;
this.setBannerOffset();
},
hideGiftingBanner () {
setLocalSetting(CONSTANTS.keyConstants.GIFTING_BANNER_DISPLAY, 'dismissed');
this.giftingHidden = true;
},
resumeDamage () {
this.$store.dispatch('user:sleep');
},
setBannerOffset () {
let contentPlacement = 0;
if (this.showRestingBanner && this.$refs.restingBanner !== undefined) {
contentPlacement = this.$refs.restingBanner.clientHeight;
}
this.bannerHeight = contentPlacement;
let smartBanner = document.getElementsByClassName('smartbanner')[0];
if (smartBanner !== undefined) {
smartBanner.style.top = `${contentPlacement}px`;
}
},
},
};
</script>

View File

@@ -1,96 +1,72 @@
.achievement-costumeContest6x {
.promo_armoire_backgrounds_201901 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px -438px;
width: 144px;
height: 156px;
}
.promo_alligator {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 480px;
height: 360px;
}
.promo_animal_tails {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -994px 0px;
width: 141px;
height: 441px;
}
.promo_armoire_backgrounds_201812 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -723px;
background-position: 0px -148px;
width: 423px;
height: 147px;
}
.promo_bird_buddies_bundle {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -723px;
background-position: -424px 0px;
width: 420px;
height: 147px;
}
.promo_frost_potions {
.promo_g1g1 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -421px -871px;
width: 417px;
height: 147px;
background-position: -241px -444px;
width: 237px;
height: 150px;
}
.promo_ios {
.promo_npc_alex {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -361px;
width: 375px;
height: 361px;
background-position: -403px -296px;
width: 162px;
height: 138px;
}
.promo_mystery_201811 {
.promo_seasonal_shop {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px -142px;
width: 282px;
height: 147px;
background-position: -566px -296px;
width: 162px;
height: 138px;
}
.promo_piyo {
.promo_snow_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px -290px;
width: 279px;
background-position: 0px 0px;
width: 423px;
height: 147px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1281px -438px;
background-position: -729px -296px;
width: 96px;
height: 69px;
}
.promo_turkey_day_2018 {
.promo_winter_wonderland_2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -871px;
background-position: 0px -296px;
width: 402px;
height: 147px;
}
.promo_wintery_skins {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -148px;
width: 420px;
height: 147px;
}
.promo_veteran_pets {
.customize-option.promo_wintery_skins {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px 0px;
width: 363px;
height: 141px;
background-position: -449px -163px;
width: 60px;
height: 60px;
}
.scene_hiking {
.scene_starting_over {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -481px -420px;
width: 258px;
height: 258px;
background-position: -479px -444px;
width: 150px;
height: 150px;
}
.scene_nametag {
.scene_todo_list {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -481px 0px;
width: 512px;
height: 208px;
}
.scene_sleep {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -481px -209px;
width: 390px;
height: 210px;
}
.scene_veteran_pets {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px -595px;
width: 242px;
height: 62px;
background-position: 0px -444px;
width: 240px;
height: 195px;
}

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,402 +1,402 @@
.quest_TEMPLATE_FOR_MISSING_IMAGE {
.npc_matt {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -502px -1546px;
width: 221px;
height: 39px;
background-position: -597px -1535px;
width: 195px;
height: 138px;
}
.background_dysheartener {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px 0px;
width: 306px;
height: 202px;
}
.banner_flair_dysheartener {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1963px -836px;
width: 69px;
height: 18px;
}
.phobia_dysheartener {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1187px -880px;
width: 201px;
height: 195px;
}
.quest_alligator {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1627px -1079px;
width: 201px;
height: 213px;
}
.quest_armadillo {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -435px;
width: 219px;
height: 219px;
}
.quest_atom1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1105px -1315px;
width: 250px;
height: 150px;
}
.quest_atom2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -389px -1535px;
width: 207px;
height: 138px;
}
.quest_atom3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -747px -440px;
width: 216px;
height: 180px;
}
.quest_axolotl {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -747px -220px;
width: 219px;
height: 219px;
}
.quest_badger {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -655px;
width: 219px;
height: 219px;
}
.quest_basilist {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1844px -392px;
width: 189px;
height: 141px;
}
.quest_beetle {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1627px -1293px;
width: 204px;
height: 201px;
}
.quest_bunny {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1322px -1112px;
background-position: -967px -660px;
width: 210px;
height: 186px;
}
.quest_butterfly {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1320px -440px;
background-position: -967px 0px;
width: 219px;
height: 219px;
}
.quest_cheetah {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -220px -892px;
background-position: -967px -220px;
width: 219px;
height: 219px;
}
.quest_cow {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1757px 0px;
background-position: 0px -1535px;
width: 174px;
height: 213px;
}
.quest_dilatory {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -220px -232px;
background-position: -220px -875px;
width: 219px;
height: 219px;
}
.quest_dilatoryDistress1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1540px -1085px;
background-position: -1627px -868px;
width: 210px;
height: 210px;
}
.quest_dilatoryDistress2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1757px -573px;
background-position: -1844px -534px;
width: 150px;
height: 150px;
}
.quest_dilatoryDistress3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -660px -220px;
background-position: -880px -875px;
width: 219px;
height: 219px;
}
.quest_dilatory_derby {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -232px;
background-position: 0px -875px;
width: 219px;
height: 219px;
}
.quest_dustbunnies {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -452px;
background-position: -1187px 0px;
width: 219px;
height: 219px;
}
.quest_egg {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1757px -214px;
background-position: -1844px -184px;
width: 165px;
height: 207px;
}
.quest_evilsanta {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1757px -1026px;
background-position: -1844px -836px;
width: 118px;
height: 131px;
}
.quest_evilsanta2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -660px -452px;
background-position: -1187px -660px;
width: 219px;
height: 219px;
}
.quest_falcon {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -880px 0px;
background-position: 0px -1095px;
width: 219px;
height: 219px;
}
.quest_ferret {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -880px -220px;
background-position: -307px 0px;
width: 219px;
height: 219px;
}
.quest_frog {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1100px -1112px;
background-position: -660px -1315px;
width: 221px;
height: 213px;
}
.quest_ghost_stag {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -672px;
background-position: -660px -1095px;
width: 219px;
height: 219px;
}
.quest_goldenknight1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -220px -672px;
background-position: -880px -1095px;
width: 219px;
height: 219px;
}
.quest_goldenknight2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -251px -1546px;
background-position: -1356px -1315px;
width: 250px;
height: 150px;
}
.quest_goldenknight3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px 0px;
background-position: 0px -203px;
width: 219px;
height: 231px;
}
.quest_gryphon {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1097px -1332px;
background-position: -307px -220px;
width: 216px;
height: 177px;
}
.quest_guineapig {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1100px 0px;
background-position: -1407px -440px;
width: 219px;
height: 219px;
}
.quest_harpy {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1100px -220px;
background-position: -1407px -660px;
width: 219px;
height: 219px;
}
.quest_hedgehog {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -220px -1332px;
background-position: -1407px -1100px;
width: 219px;
height: 186px;
}
.quest_hippo {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1100px -660px;
background-position: 0px -1315px;
width: 219px;
height: 219px;
}
.quest_horse {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -892px;
background-position: -220px -1315px;
width: 219px;
height: 219px;
}
.quest_kangaroo {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -220px 0px;
background-position: -1407px -880px;
width: 219px;
height: 219px;
}
.quest_kraken {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -880px -1332px;
background-position: -527px -220px;
width: 216px;
height: 177px;
}
.quest_lostMasterclasser1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -660px -892px;
background-position: -1100px -1095px;
width: 219px;
height: 219px;
}
.quest_lostMasterclasser2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -880px -892px;
background-position: -440px -1095px;
width: 219px;
height: 219px;
}
.quest_lostMasterclasser3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1100px -892px;
background-position: -1187px -220px;
width: 219px;
height: 219px;
}
.quest_mayhemMistiflying1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1757px -724px;
background-position: -1844px -685px;
width: 150px;
height: 150px;
}
.quest_mayhemMistiflying2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1320px -220px;
background-position: -440px -875px;
width: 219px;
height: 219px;
}
.quest_mayhemMistiflying3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -660px -672px;
background-position: -660px -655px;
width: 219px;
height: 219px;
}
.quest_monkey {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1320px -660px;
background-position: -440px -655px;
width: 219px;
height: 219px;
}
.quest_moon1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1540px -868px;
background-position: -1627px -651px;
width: 216px;
height: 216px;
}
.quest_moon2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -1112px;
background-position: -220px -655px;
width: 219px;
height: 219px;
}
.quest_moon3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -220px -1112px;
background-position: -747px 0px;
width: 219px;
height: 219px;
}
.quest_moonstone1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -440px -1112px;
background-position: -527px 0px;
width: 219px;
height: 219px;
}
.quest_moonstone2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -660px -1112px;
background-position: -1407px -220px;
width: 219px;
height: 219px;
}
.quest_moonstone3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1320px -880px;
background-position: -440px -1315px;
width: 219px;
height: 219px;
}
.quest_nudibranch {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1540px -651px;
background-position: -1627px -217px;
width: 216px;
height: 216px;
}
.quest_octopus {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -440px -1332px;
background-position: -882px -1315px;
width: 222px;
height: 177px;
}
.quest_owl {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1100px -440px;
background-position: -1187px -440px;
width: 219px;
height: 219px;
}
.quest_peacock {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1540px -434px;
background-position: -1627px 0px;
width: 216px;
height: 216px;
}
.quest_penguin {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -1697px;
background-position: -1844px 0px;
width: 190px;
height: 183px;
}
.quest_pterodactyl {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -880px -440px;
background-position: -660px -875px;
width: 219px;
height: 219px;
}
.quest_rat {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -660px 0px;
background-position: -967px -440px;
width: 219px;
height: 219px;
}
.quest_rock {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1540px 0px;
background-position: -1627px -434px;
width: 216px;
height: 216px;
}
.quest_rooster {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1531px -1332px;
background-position: -175px -1535px;
width: 213px;
height: 174px;
}
.quest_sabretooth {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -880px -1112px;
background-position: -440px -435px;
width: 219px;
height: 219px;
}
.quest_seaserpent {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -440px -232px;
background-position: -220px -435px;
width: 219px;
height: 219px;
}
.quest_sheep {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -880px -672px;
background-position: -220px -1095px;
width: 219px;
height: 219px;
}
.quest_slime {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1320px 0px;
background-position: -1407px 0px;
width: 219px;
height: 219px;
}
.quest_sloth {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -440px -892px;
width: 219px;
height: 219px;
}
.quest_snail {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -1332px;
width: 219px;
height: 213px;
}
.quest_snake {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1314px -1332px;
width: 216px;
height: 177px;
}
.quest_spider {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: 0px -1546px;
width: 250px;
height: 150px;
}
.quest_squirrel {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -440px -672px;
width: 219px;
height: 219px;
}
.quest_stoikalmCalamity1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1757px -875px;
width: 150px;
height: 150px;
}
.quest_stoikalmCalamity2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -440px -452px;
width: 219px;
height: 219px;
}
.quest_stoikalmCalamity3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -220px -452px;
width: 219px;
height: 219px;
}
.quest_taskwoodsTerror1 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1757px -422px;
width: 150px;
height: 150px;
}
.quest_taskwoodsTerror2 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -1540px -217px;
width: 216px;
height: 216px;
}
.quest_taskwoodsTerror3 {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -440px 0px;
width: 219px;
height: 219px;
}
.quest_treeling {
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
background-position: -663px -1332px;
width: 216px;
height: 177px;
}

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.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 KiB

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 181 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: 74 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -2,8 +2,8 @@
// possible values are: normal, fall, habitoween, thanksgiving, winter, nye, birthday, valentines, spring, summer
// more to be added on future seasons
$npc_market_flavor: 'normal';
$npc_quests_flavor: 'normal';
$npc_seasonal_flavor: 'normal';
$npc_timetravelers_flavor: 'normal';
$npc_tavern_flavor: 'normal';
$npc_market_flavor: 'winter';
$npc_quests_flavor: 'winter';
$npc_seasonal_flavor: 'winter';
$npc_timetravelers_flavor: 'winter';
$npc_tavern_flavor: 'winter';

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" width="73" height="27" viewBox="0 0 73 27">
<g fill="none" fill-rule="evenodd">
<path fill="#8EEDF6" d="M4.333 9.757l2.166-.554-1.742-1.401-.554-2.166-1.401 1.742-2.166.554 1.742 1.4.554 2.167zM70.333 5.757l2.166-.554-1.742-1.401-.554-2.166-1.401 1.742-2.166.554 1.742 1.4.554 2.167zM37.147 5.518l2.573.428-1.202-2.315.428-2.574-2.315 1.202-2.574-.427 1.202 2.315-.427 2.573zM68.735 22.132l2.367 1.812.03-2.981 1.812-2.368-2.981-.03-2.368-1.812-.03 2.982-1.812 2.367zM33.676 23.656l1.839.305-.859-1.653.305-1.839-1.653.859-1.839-.305.859 1.653-.305 1.839zM10.695 25.362l.805 1.68.862-1.651 1.68-.804-1.651-.862-.804-1.681-.862 1.652-1.681.804z"/>
<path fill="#F8F9F9" d="M21.862 9.542l5.914-1.585 2.219 8.28-5.914 1.584z"/>
<path fill="#DDF3F3" d="M15.949 11.126l5.913-1.584 2.219 8.28-5.914 1.584z"/>
<path fill="#FFA624" d="M20.68 9.859l1.182-.317 2.219 8.28-1.183.316z"/>
<path fill="#FFBE5D" d="M21.862 9.542l1.183-.317 2.219 8.28-1.183.316z"/>
<path fill="#EE9109" d="M22.581 16.955l1.183-.317.317 1.183-1.183.317zM20.68 9.859l1.182-.317.317 1.183-1.182.317z"/>
<path fill="#C1E9E9" d="M15.949 11.126l4.73-1.267.318 1.183-4.732 1.267zM17.85 18.223l4.731-1.268.317 1.183-4.731 1.268z"/>
<path fill="#DDF3F3" d="M23.045 9.225l4.731-1.268.317 1.183-4.73 1.268zM24.947 16.322l4.73-1.268.318 1.183-4.731 1.267z"/>
<path fill="#FFA624" d="M23.764 16.638l1.183-.316.317 1.182-1.183.317zM21.862 9.542l1.183-.317.317 1.183-1.183.317z"/>
<g>
<path stroke="#FFA624" stroke-width="1.5" d="M21.737 3.348c-.295-1.038-1.009-2.02-2.008-2.204-1-.184-1.69.542-1.495 1.297.195.755.88.984 3.075 1.916.623.264.723.03.428-1.009z"/>
<path stroke="#FFBE5D" stroke-width="1.5" d="M24.307 3.71c.57-.918 1.527-1.664 2.538-1.565 1.012.098 1.475.986 1.08 1.658-.396.671-1.117.703-3.484.994-.672.083-.703-.17-.134-1.088z"/>
<path fill="#EE9109" d="M23.129 2.769c-1.968-.277-1.106 2.141-.317 2.252.79.111 2.285-1.976.317-2.252z"/>
<path fill="#F8F9F9" d="M22.862 4.665l7.276 1.022-.511 3.638-7.276-1.022z"/>
<path fill="#DDF3F3" d="M15.587 3.642l7.275 1.023-.51 3.638-7.276-1.023z"/>
<path fill="#FFBE5D" d="M20.437 4.324l4.85.682-.51 3.638-4.85-.682z"/>
<path fill="#FFA624" d="M20.437 4.324l2.425.341-.51 3.638-2.426-.341z"/>
<path fill="#FFA624" d="M22.862 4.665l2.426.34-.17 1.213-2.426-.34z"/>
<path fill="#EE9109" d="M20.437 4.324l2.425.341-.17 1.213-2.425-.341z"/>
<path fill="#C1E9E9" d="M15.246 6.068l4.85.681-.17 1.213-4.85-.682z"/>
<path fill="#DDF3F3" d="M24.947 7.431l4.85.682-.17 1.212-4.85-.681z"/>
</g>
<g>
<path stroke="#FFA624" stroke-width="1.5" d="M49.696 7.04c-.549-1.24-1.609-2.337-2.885-2.391-1.276-.054-2.007.969-1.632 1.874.374.905 1.266 1.071 4.16 1.847.822.22.905-.09.357-1.33z"/>
<path stroke="#FFBE5D" stroke-width="1.5" d="M52.956 7.04c.548-1.24 1.609-2.337 2.885-2.391 1.276-.054 2.007.969 1.632 1.874-.374.905-1.266 1.071-4.16 1.847-.822.22-.905-.09-.357-1.33z"/>
<path fill="#EE9109" d="M51.326 6.075c-2.497 0-1.002 2.858 0 2.858s2.497-2.858 0-2.858z"/>
<path fill="#F8F9F9" d="M51.326 8.481h9.23v4.616h-9.23z"/>
<path fill="#DDF3F3" d="M42.095 8.481h9.23v4.616h-9.23z"/>
<path fill="#FFBE5D" d="M48.249 8.481h6.154v4.616h-6.154z"/>
<path fill="#FFA624" d="M48.249 8.481h3.077v4.616h-3.077zM51.326 8.481h3.077v1.539h-3.077z"/>
<path fill="#EE9109" d="M48.249 8.481h3.077v1.539h-3.077z"/>
<path fill="#F8F9F9" d="M51.326 13.097h7.692v10.769h-7.692z"/>
<path fill="#DDF3F3" d="M43.634 13.097h7.692v10.769h-7.692z"/>
<path fill="#FFA624" d="M49.787 13.097h1.538v10.769h-1.538z"/>
<path fill="#FFBE5D" d="M51.326 13.097h1.538v10.769h-1.538z"/>
<path fill="#EE9109" d="M49.787 22.327h1.539v1.539h-1.539zM49.787 13.097h1.539v1.538h-1.539z"/>
<path fill="#C1E9E9" d="M43.634 13.097h6.153v1.538h-6.153zM42.095 11.558h6.154v1.539h-6.154zM43.634 22.327h6.153v1.539h-6.153z"/>
<path fill="#DDF3F3" d="M52.864 13.097h6.154v1.538h-6.154zM54.403 11.558h6.154v1.539h-6.154zM52.864 22.327h6.154v1.539h-6.154z"/>
<path fill="#FFA624" d="M51.326 22.327h1.538v1.539h-1.538zM51.326 13.097h1.538v1.538h-1.538z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -164,30 +164,30 @@ export default {
classGear (heroClass) {
if (heroClass === 'rogue') {
return {
armor: 'armor_rogue_5',
head: 'head_rogue_5',
shield: 'shield_rogue_6',
weapon: 'weapon_rogue_6',
armor: 'armor_special_winter2019Rogue',
head: 'head_special_winter2019Rogue',
shield: 'shield_special_winter2019Rogue',
weapon: 'weapon_special_winter2019Rogue',
};
} else if (heroClass === 'wizard') {
return {
armor: 'armor_wizard_5',
head: 'head_wizard_5',
weapon: 'weapon_wizard_6',
armor: 'armor_special_winter2019Mage',
head: 'head_special_winter2019Mage',
weapon: 'weapon_special_winter2019Mage',
};
} else if (heroClass === 'healer') {
return {
armor: 'armor_healer_5',
head: 'head_healer_5',
shield: 'shield_healer_5',
weapon: 'weapon_healer_6',
armor: 'armor_special_winter2019Healer',
head: 'head_special_winter2019Healer',
shield: 'shield_special_winter2019Healer',
weapon: 'weapon_special_winter2019Healer',
};
} else {
return {
armor: 'armor_warrior_5',
head: 'head_warrior_5',
shield: 'shield_warrior_5',
weapon: 'weapon_warrior_6',
armor: 'armor_special_winter2019Warrior',
head: 'head_special_winter2019Warrior',
shield: 'shield_special_winter2019Warrior',
weapon: 'weapon_special_winter2019Warrior',
};
}
},

View File

@@ -6,7 +6,7 @@
.quest(:class='`quest_${user.party.quest.completed}`')
p(v-if='questData.completion && typeof questData.completion === "function"', v-html='questData.completion()')
.quest-rewards.text-center
h3 {{ $t('youReceived') }}
h3(v-once) {{ $t('paymentYouReceived') }}
questDialogDrops(:item="questData")
.modal-footer
button.btn.btn-primary(@click='setQuestCompleted()') {{ $t('ok') }}

View File

@@ -81,7 +81,7 @@
hr
.row
.col-12.col-md-5
| © 2018 Habitica. All rights reserved.
| © 2019 Habitica. All rights reserved.
.debug.float-left(v-if="!IS_PRODUCTION && isUserLoaded")
button.btn.btn-primary(@click="debugMenuShown = !debugMenuShown") Toggle Debug Menu
.debug-group(v-if="debugMenuShown")
@@ -96,6 +96,7 @@
a.btn.btn-secondary(@click="plusTenHealth()") + 10HP
a.btn.btn-secondary(@click="addMana()") +MP
a.btn.btn-secondary(@click="addLevelsAndGold()") +Exp +GP +MP
a.btn.btn-secondary(@click="addExp()") +Exp
a.btn.btn-secondary(@click="addOneLevel()") +1 Level
a.btn.btn-secondary(@click="addQuestProgress()", tooltip="+1000 to boss quests. 300 items to collection quests") Quest Progress Up
a.btn.btn-secondary(@click="makeAdmin()") Make Admin
@@ -326,7 +327,7 @@ export default {
'stats.mp': this.user.stats.mp + 10000,
});
},
addOneLevel () {
addExp () {
// @TODO: Name these variables better
let exp = 0;
let five = 10 * this.user.stats.lvl;
@@ -340,6 +341,11 @@ export default {
'stats.exp': exp,
});
},
addOneLevel () {
this.$store.dispatch('user:set', {
'stats.lvl': this.user.stats.lvl + 1,
});
},
async addQuestProgress () {
await axios.post('/api/v4/debug/quest-progress');

View File

@@ -32,7 +32,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
.menu-item
.svg-icon(v-html='icons.accessoriesIcon')
strong(v-once) {{$t('extra')}}
.menu-container.col-2(@click='changeTopPage("backgrounds", "2018")', v-if='editing', :class='{active: activeTopPage === "backgrounds"}')
.menu-container.col-2(@click='changeTopPage("backgrounds", "2019")', v-if='editing', :class='{active: activeTopPage === "backgrounds"}')
.menu-item
.svg-icon(v-html='icons.backgroundsIcon')
strong(v-once) {{$t('backgrounds')}}
@@ -274,9 +274,11 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
.incentive-background(:class='[`background_${bg.key}`]')
.small-rectangle
.row.sub-menu.col-10.offset-1(v-if='!filterBackgrounds')
.col-3.text-center.sub-menu-item(@click='changeSubPage("2018")', :class='{active: activeSubPage === "2018"}')
.col-2.text-center.sub-menu-item(@click='changeSubPage("2019")', :class='{active: activeSubPage === "2019"}')
strong(v-once) 2019
.col-2.text-center.sub-menu-item(@click='changeSubPage("2018")', :class='{active: activeSubPage === "2018"}')
strong(v-once) 2018
.col-3.text-center.sub-menu-item(@click='changeSubPage("2017")', :class='{active: activeSubPage === "2017"}')
.col-2.text-center.sub-menu-item(@click='changeSubPage("2017")', :class='{active: activeSubPage === "2017"}')
strong(v-once) 2017
.col-2.text-center.sub-menu-item(@click='changeSubPage("2016")', :class='{active: activeSubPage === "2016"}')
strong(v-once) 2016
@@ -1297,6 +1299,7 @@ export default {
2016: [],
2017: [],
2018: [],
2019: [],
};
// Hack to force update for now until we restructure the data

View File

@@ -141,7 +141,7 @@ export default {
this.changePage(this.PAGES.PAY);
},
pay (paymentMethod) {
const subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
const subscriptionKey = 'group_monthly';
let paymentData = {
subscription: subscriptionKey,
coupon: null,
@@ -149,6 +149,7 @@ export default {
if (this.upgradingGroup && this.upgradingGroup._id) {
paymentData.groupId = this.upgradingGroup._id;
paymentData.group = this.upgradingGroup;
} else {
paymentData.groupToCreate = this.newGroup;
}

View File

@@ -395,14 +395,15 @@ export default {
this.changePage(this.PAGES.PAY);
},
pay (paymentMethod) {
let subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
let paymentData = {
const subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
const paymentData = {
subscription: subscriptionKey,
coupon: null,
};
if (this.upgradingGroup && this.upgradingGroup._id) {
paymentData.groupId = this.upgradingGroup._id;
paymentData.group = this.upgradingGroup;
} else {
paymentData.groupToCreate = this.newGroup;
}

View File

@@ -11,7 +11,7 @@ menu-dropdown.item-user(:right="true")
a.nav-link.dropdown-item.dropdown-separated.d-flex.justify-content-between.align-items-center(@click.prevent='showInbox()')
div {{ $t('messages') }}
message-count(v-if='user.inbox.newMessages > 0', :count="user.inbox.newMessages")
a.dropdown-item(@click='showAvatar("backgrounds", "2018")') {{ $t('backgrounds') }}
a.dropdown-item(@click='showAvatar("backgrounds", "2019")') {{ $t('backgrounds') }}
a.dropdown-item(@click='showProfile("stats")') {{ $t('stats') }}
a.dropdown-item(@click='showProfile("achievements")') {{ $t('achievements') }}
a.dropdown-item.dropdown-separated(@click='showProfile("profile")') {{ $t('profile') }}

View File

@@ -6,7 +6,7 @@ b-modal#hatchedPet-modal(:hide-header="true")
div.pet-background
div(:class="pet.class")
h4.title {{ pet.name }}
div.text(v-if="!hideText", v-markdown="$t('hatchedPetHowToUse')")
div.text(v-if="!hideText", v-markdown="$t('hatchedPetHowToUse', { stableUrl: '/inventory/stable' })")
button.btn.btn-primary(@click="close()") {{ $t('onward') }}
div.clearfix(slot="modal-footer")
</template>

View File

@@ -569,10 +569,8 @@ export default {
break;
case 'STREAK_ACHIEVEMENT':
this.text(`${this.$t('streaks')}: ${this.user.achievements.streak}`, () => {
if (!this.user.preferences.suppressModals.streak) {
this.$root.$emit('bv::show::modal', 'streak');
}
});
this.$root.$emit('bv::show::modal', 'streak');
}, this.user.preferences.suppressModals.streak);
this.playSound('Achievement_Unlocked');
break;
case 'ULTIMATE_GEAR_ACHIEVEMENT':

View File

@@ -34,6 +34,7 @@ import * as Analytics from 'client/libs/analytics';
import axios from 'axios';
import { mapState } from 'client/libs/store';
import { CONSTANTS, setLocalSetting } from 'client/libs/userlocalManager';
import pick from 'lodash/pick';
const AMAZON_PAYMENTS = process.env.AMAZON_PAYMENTS; // eslint-disable-line
const habiticaUrl = `${location.protocol}//${location.host}`;
@@ -56,6 +57,8 @@ export default {
OffAmazonPayments: {},
isAmazonSetup: false,
amazonButtonEnabled: false,
groupToCreate: null, // creating new group
group: null, // upgrading existing group
};
},
computed: {
@@ -189,10 +192,37 @@ export default {
new this.OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet');
},
storePaymentStatusAndReload (url) {
let paymentType;
if (this.amazonPayments.type === 'single' && !this.amazonPayments.gift) paymentType = 'gems';
if (this.amazonPayments.type === 'subscription') paymentType = 'subscription';
if (this.amazonPayments.groupId || this.amazonPayments.groupToCreate) paymentType = 'groupPlan';
if (this.amazonPayments.type === 'single' && this.amazonPayments.gift && this.amazonPayments.giftReceiver) {
paymentType = this.amazonPayments.gift.type === 'gems' ? 'gift-gems' : 'gift-subscription';
}
const appState = {
paymentMethod: 'amazon',
paymentCompleted: true,
paymentType,
};
if (paymentType === 'subscription') {
appState.subscriptionKey = this.amazonPayments.subscription;
} else if (paymentType === 'groupPlan') {
appState.subscriptionKey = this.amazonPayments.subscription;
if (this.amazonPayments.groupToCreate) {
appState.newGroup = true;
appState.group = pick(this.amazonPayments.groupToCreate, ['_id', 'memberCount', 'name']);
} else {
appState.newGroup = false;
appState.group = pick(this.amazonPayments.group, ['_id', 'memberCount', 'name']);
}
} else if (paymentType.indexOf('gift-') === 0) {
appState.gift = this.amazonPayments.gift;
appState.giftReceiver = this.amazonPayments.giftReceiver;
}
setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState));
if (url) {
window.location.assign(url);
@@ -215,11 +245,11 @@ export default {
});
this.$set(this, 'amazonButtonEnabled', true);
this.reset();
this.storePaymentStatusAndReload();
} catch (e) {
console.error(e); // eslint-disable-line no-console
this.$set(this, 'amazonButtonEnabled', true);
this.amazonPaymentsreset();
this.reset();
}
} else if (this.amazonPayments.type === 'subscription') {
let url = '/amazon/subscribe';
@@ -265,7 +295,6 @@ export default {
return;
}
this.reset();
this.storePaymentStatusAndReload();
} catch (e) {
this.$set(this, 'amazonButtonEnabled', true);
@@ -286,13 +315,19 @@ export default {
this.amazonPayments.modal = null;
this.amazonPayments.type = null;
this.amazonPayments.loggedIn = false;
// Gift
this.amazonPayments.gift = null;
this.amazonPayments.giftReceiver = null;
this.amazonPayments.billingAgreementId = null;
this.amazonPayments.orderReferenceId = null;
this.amazonPayments.paymentSelected = false;
this.amazonPayments.recurringConsent = false;
this.amazonPayments.subscription = null;
this.amazonPayments.coupon = null;
this.amazonPayments.groupToCreate = null;
this.amazonPayments.group = null;
},
},
};

View File

@@ -2,180 +2,172 @@
mixin featureBullet (text)
.row
.col-md-2.offset-1
.bubble.mx-auto
.svg-icon.check(v-html='icons.check')
.d-flex.bubble.justify-content-center.align-items-center
.svg-icon.check.mx-auto(v-html='icons.check')
.col-md-8.align-self-center
p=text
div(v-if='user')
b-modal(:hide-footer='true', :hide-header='true', :id='"buy-gems"', size='lg')
.container-fluid.purple-gradient
.gemfall
b-modal#buy-gems(:hide-footer='true', size='lg')
.header-wrap(slot='modal-header')
.image-gemfall
.row
h2.text-invert.mx-auto {{ $t('support') }}
h2.header-invert.mx-auto {{ $t('support') }}
.row
.logo.svg-icon.mx-auto(v-html="icons.logo")
.container-fluid
.row
.col-6.offset-3.nav
.nav-item(@click='selectedPage = "subscribe"', :class="{active: selectedPage === 'subscribe'}") {{ $t('subscribe') }}
.nav-item(@click='selectedPage = "gems"', :class="{active: selectedPage === 'gems'}") {{ $t('buyGems') }}
div(v-show='selectedPage === "gems"')
div(v-if='hasSubscription')
.row.text-center
h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }}
.row.text-center
.col-6.offset-3
p {{ $t("gemsPurchaseNote") }}
.d-flex.nav.justify-content-center
.nav-item.text-center(@click='selectedPage = "subscribe"', :class="{active: selectedPage === 'subscribe'}") {{ $t('subscribe') }}
.nav-item.text-center(@click='selectedPage = "gems"', :class="{active: selectedPage === 'gems'}") {{ $t('buyGems') }}
div(v-show='selectedPage === "gems"')
div(v-if='hasSubscription')
.row.text-center
h2.mx-auto.text-leadin {{ $t('gemBenefitLeadin') }}
h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }}
.row.text-center
.col-6.offset-3
p {{ $t("gemsPurchaseNote") }}
.row.text-center
h2.mx-auto.text-leadin {{ $t('gemBenefitLeadin') }}
.row
.col
+featureBullet("{{ $t('gemBenefit1') }}")
+featureBullet("{{ $t('gemBenefit2') }}")
.col
+featureBullet("{{ $t('gemBenefit3') }}")
+featureBullet("{{ $t('gemBenefit4') }}")
.card-deck.gem-deck
.card.text-center.col-3(:class="{active: gemAmount === 20 }")
.card-img-top
.mx-auto(v-html='icons.twentyOneGems', style='"height: 55px; width: 47.5px; margin-top: 1.85em;"')
.card-body
.gem-count 20
.gem-text {{ $t('gems') }}
.divider
button.btn.btn-primary(@click='gemAmount === 20 ? gemAmount = 0 : gemAmount = 20') {{gemAmount === 20 ? $t('selected') : '$5.00'}}
.row.text-center
h2.mx-auto.text-payment {{ $t('choosePaymentMethod') }}
.card-deck
.card.text-center.payment-method(@click='showStripe({})')
.card-body
.mx-auto(v-html='icons.creditCard', style='"height: 56px; width: 159px; margin-top: 1em;"')
.card.text-center.payment-method
a.card-body.paypal(@click="openPaypal(paypalCheckoutLink, 'gems')")
img(src='~assets/images/paypal.png')
.card.text-center.payment-method(@click="amazonPaymentsInit({type: 'single'})")
.card-body.amazon
img(src='~assets/images/amazon-payments.png')
.row.text-center
.svg-icon.mx-auto(v-html='icons.heart', style='"height: 24px; width: 24px;"')
.row.text-center.text-outtro
.col-6.offset-3 {{ $t('buyGemsSupportsDevs') }}
div(v-show='selectedPage === "subscribe"')
.g1g1-promo.d-flex.justify-content-center.align-items-center
.svg-icon.svg-gifts.left-gift(v-html="icons.gifts")
.text-center
strong.gift-text {{ $t('g1g1Announcement') }}
.gift-text {{ $t('g1g1Details') }}
.svg-icon.svg-gifts.right-gift(v-html="icons.gifts")
div(v-if='hasSubscription')
.row.text-center
h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }}
.row.text-center
.col-10.offset-1
p(v-html='$t("subscriptionAlreadySubscribed1")')
div(v-if='!hasSubscription')
.row.text-center
h2.mx-auto.text-leadin {{ $t('subscriptionBenefitLeadin') }}
.row
.col
+featureBullet("{{ $t('gemBenefit1') }}")
+featureBullet("{{ $t('gemBenefit2') }}")
+featureBullet("{{ $t('subscriptionBenefit1') }}")
+featureBullet("{{ $t('subscriptionBenefit2') }}")
+featureBullet("{{ $t('subscriptionBenefit3') }}")
.col
+featureBullet("{{ $t('gemBenefit3') }}")
+featureBullet("{{ $t('gemBenefit4') }}")
.card-deck.gem-deck
//.card.text-center(:class="{active: gemAmount === 4}")
.card-img-top
.mx-auto(v-html='icons.fourGems', style='"height: 53px; width: 49.5px; margin-top: 2em;"')
.card-body
.gem-count 4
.gem-text {{ $t('gems') }}
.divider
button.btn.btn-primary(@click='gemAmount = 4') {{gemAmount === 4 ? $t('selected') : '$1.00'}}
.card.text-center.col-3(:class="{active: gemAmount === 20 }")
.card-img-top
.mx-auto(v-html='icons.twentyOneGems', style='"height: 55px; width: 47.5px; margin-top: 1.85em;"')
.card-body
.gem-count 20
.gem-text {{ $t('gems') }}
.divider
button.btn.btn-primary(@click='gemAmount === 20 ? gemAmount = 0 : gemAmount = 20') {{gemAmount === 20 ? $t('selected') : '$5.00'}}
//.card.text-center(:class="{active: gemAmount === 42}")
.card-img-top
.mx-auto(v-html='icons.fortyTwoGems', style='"height: 49.5px; width: 51px; margin-top: 1.9em;"')
.card-body
.gem-count 42
.gem-text {{ $t('gems') }}
.divider
button.btn.btn-primary(@click='gemAmount = 42') {{gemAmount === 42 ? $t('selected') : '$10.00'}}
//.card.text-center(:class="{active: gemAmount === 84}")
.card-img-top
.mx-auto(v-html='icons.eightyFourGems', style='"height: 65px; width: 67px; margin-top: 1em;"')
.card-body
.gem-count 84
.gem-text {{ $t('gems') }}
.divider
button.btn.btn-primary(@click='gemAmount = 84') {{gemAmount === 84 ? $t('selected') : '$20.00'}}
.row.text-center
h2.mx-auto.text-payment {{ $t('choosePaymentMethod') }}
+featureBullet("{{ $t('subscriptionBenefit4') }}")
+featureBullet("{{ $t('subscriptionBenefit5') }}")
+featureBullet("{{ $t('subscriptionBenefit6') }}")
.card-deck
.card.text-center.payment-method(@click='showStripe({})')
.card.text-center(:class='{active: subscriptionPlan === "basic_earned"}')
.card-body
.subscription-price
span.superscript $
span 5
span.superscript.muted .00
.small(v-once) {{ $t('everyMonth') }}
.divider
p.benefits(v-markdown='$t("earnGemsMonthly", {cap:25})')
.spacer
button.btn.btn-primary(@click='subscriptionPlan = "basic_earned"') {{ subscriptionPlan === "basic_earned" ? $t('selected') : $t('select') }}
.card.text-center(:class='{active: subscriptionPlan === "basic_3mo"}')
.card-body
.subscription-price
span.superscript $
span 15
span.superscript.muted .00
.small(v-once) {{ $t('everyXMonths', {interval: 3}) }}
.divider
p.benefits(v-markdown='$t("earnGemsMonthly", {cap:30})')
p.benefits(v-markdown='$t("receiveMysticHourglass")')
button.btn.btn-primary(@click='subscriptionPlan = "basic_3mo"') {{ subscriptionPlan === "basic_3mo" ? $t('selected') : $t('select') }}
.card.text-center(:class='{active: subscriptionPlan === "basic_6mo"}')
.card-body
.subscription-price
span.superscript $
span 30
span.superscript.muted .00
.small(v-once) {{ $t('everyXMonths', {interval: 6}) }}
.divider
p.benefits(v-markdown='$t("earnGemsMonthly", {cap:35})')
p.benefits(v-markdown='$t("receiveMysticHourglasses", {amount:2})')
button.btn.btn-primary(@click='subscriptionPlan = "basic_6mo"') {{ subscriptionPlan === "basic_6mo" ? $t('selected') : $t('select') }}
.card.text-center(:class='{active: subscriptionPlan === "basic_12mo"}')
.card-body
.subscription-price
span.superscript $
span 48
span.superscript.muted .00
.small(v-once) {{ $t('everyYear') }}
.divider
p.benefits(v-markdown='$t("earnGemsMonthly", {cap:45})')
p.benefits(v-markdown='$t("receiveMysticHourglasses", {amount:4})')
button.btn.btn-primary(@click='subscriptionPlan = "basic_12mo"') {{ subscriptionPlan === "basic_12mo" ? $t('selected') : $t('select') }}
.row.text-center(v-if='subscriptionPlan')
h2.mx-auto.text-payment(v-once) {{ $t('choosePaymentMethod') }}
.row.text-center
a.mx-auto(v-once) {{ $t('haveCouponCode') }}
.card-deck(v-if='subscriptionPlan')
.card.text-center.payment-method
.card-body(@click='showStripe({subscription: subscriptionPlan})')
.mx-auto(v-html='icons.creditCard', style='"height: 56px; width: 159px; margin-top: 1em;"')
.card.text-center.payment-method
a.card-body.paypal(@click="openPaypal(paypalCheckoutLink, 'gems')")
a.card-body.paypal(@click="openPaypal(paypalSubscriptionLink, 'subscription')")
img(src='~assets/images/paypal.png')
.card.text-center.payment-method(@click="amazonPaymentsInit({type: 'single'})")
.card-body.amazon
.card.text-center.payment-method
.card-body.amazon(@click="amazonPaymentsInit({type: 'subscription', subscription: subscriptionPlan})")
img(src='~assets/images/amazon-payments.png')
.row.text-center
.svg-icon.mx-auto(v-html='icons.heart', style='"height: 24px; width: 24px;"')
.row.text-center.text-outtro
.col-6.offset-3 {{ $t('buyGemsSupportsDevs') }}
div(v-show='selectedPage === "subscribe"')
div(v-if='hasSubscription')
.row.text-center
h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }}
.row.text-center
.col
p(v-html='$t("subscriptionAlreadySubscribed1")')
div(v-if='!hasSubscription')
.row.text-center
h2.mx-auto.text-leadin {{ $t('subscriptionBenefitLeadin') }}
.row
.col
+featureBullet("{{ $t('subscriptionBenefit1') }}")
+featureBullet("{{ $t('subscriptionBenefit2') }}")
+featureBullet("{{ $t('subscriptionBenefit3') }}")
.col
+featureBullet("{{ $t('subscriptionBenefit4') }}")
+featureBullet("{{ $t('subscriptionBenefit5') }}")
+featureBullet("{{ $t('subscriptionBenefit6') }}")
.card-deck
.card.text-center(:class='{active: subscriptionPlan === "basic_earned"}')
.card-body
.subscription-price
span.superscript $
span 5
span.superscript.muted .00
.small {{ $t('everyMonth') }}
.divider
p.benefits(v-markdown='$t("earnGemsMonthly", {cap:25})')
.spacer
button.btn.btn-primary(@click='subscriptionPlan = "basic_earned"') {{ subscriptionPlan === "basic_earned" ? $t('selected') : $t('select') }}
.card.text-center(:class='{active: subscriptionPlan === "basic_3mo"}')
.card-body
.subscription-price
span.superscript $
span 15
span.superscript.muted .00
.small {{ $t('everyXMonths', {interval: 3}) }}
.divider
p.benefits(v-markdown='$t("earnGemsMonthly", {cap:30})')
p.benefits(v-markdown='$t("receiveMysticHourglass")')
button.btn.btn-primary(@click='subscriptionPlan = "basic_3mo"') {{ subscriptionPlan === "basic_3mo" ? $t('selected') : $t('select') }}
.card.text-center(:class='{active: subscriptionPlan === "basic_6mo"}')
.card-body
.subscription-price
span.superscript $
span 30
span.superscript.muted .00
.small {{ $t('everyXMonths', {interval: 6}) }}
.divider
p.benefits(v-markdown='$t("earnGemsMonthly", {cap:35})')
p.benefits(v-markdown='$t("receiveMysticHourglasses", {amount:2})')
button.btn.btn-primary(@click='subscriptionPlan = "basic_6mo"') {{ subscriptionPlan === "basic_6mo" ? $t('selected') : $t('select') }}
.card.text-center(:class='{active: subscriptionPlan === "basic_12mo"}')
.card-body
.subscription-price
span.superscript $
span 48
span.superscript.muted .00
.small {{ $t('everyYear') }}
.divider
p.benefits(v-markdown='$t("earnGemsMonthly", {cap:45})')
p.benefits(v-markdown='$t("receiveMysticHourglasses", {amount:4})')
button.btn.btn-primary(@click='subscriptionPlan = "basic_12mo"') {{ subscriptionPlan === "basic_12mo" ? $t('selected') : $t('select') }}
.row.text-center(v-if='subscriptionPlan')
h2.mx-auto.text-payment {{ $t('choosePaymentMethod') }}
.row.text-center
a.mx-auto {{ $t('haveCouponCode') }}
.card-deck(v-if='subscriptionPlan')
.card.text-center.payment-method
.card-body(@click='showStripe({subscription: subscriptionPlan})')
.mx-auto(v-html='icons.creditCard', style='"height: 56px; width: 159px; margin-top: 1em;"')
.card.text-center.payment-method
a.card-body.paypal(@click="openPaypal(paypalSubscriptionLink, 'subscription')")
img(src='~assets/images/paypal.png')
.card.text-center.payment-method
.card-body.amazon(@click="amazonPaymentsInit({type: 'subscription', subscription: subscriptionPlan})")
img(src='~assets/images/amazon-payments.png')
.row.text-center
.svg-icon.mx-auto(v-html='icons.heart', style='"height: 24px; width: 24px;"')
.row.text-center.text-outtro
.col-6.offset-3 {{ $t('subscribeSupportsDevs') }}
.col-6.offset-3 {{ $t('subscribeSupportsDevs') }}
</template>
<style lang="scss">
#buy-gems__BV_body_ {
#buy-gems .modal-body {
padding: 0;
}
#buy-gems .modal-content {
border-radius: 8px;
width: 824px;
}
#buy-gems .modal-header {
padding: 0;
border-bottom: 0px;
}
</style>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
a.mx-auto {
color: #2995cd;
}
@@ -228,6 +220,11 @@
margin: 1em auto;
}
.g1g1-promo {
background-color: #34b5c1;
height: 3.75rem;
}
.gem-count {
font-family: Roboto;
font-size: 40px;
@@ -242,11 +239,40 @@
margin-bottom: 1em;
}
.gemfall {
.gift-text {
color: $white;
font-size: 14px;
}
.image-gemfall {
background: url(~assets/images/gemfall.png) center repeat-y;
height: 14em;
}
.svg-gifts {
width: 4.6rem;
}
.left-gift {
margin: auto 1rem auto 4.8rem;
}
.right-gift {
margin: auto 4.8rem auto 1rem;
filter: FlipH;
transform: scaleX(-1);
}
.header-wrap {
background-image: linear-gradient(74deg, #4f2a93, #6133b4);
height: 14em;
width: 100%;
color: #4e4a57;
padding: 0;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
}
.logo {
width: 256px;
height: 56px;
@@ -258,19 +284,21 @@
.nav {
font-weight: bold;
height: 40px;
background-color: $gray-600;
}
.nav-item {
box-sizing: border-box;
display: inline-block;
font-size: 16px;
margin: 0 auto;
padding: 1.5em;
margin: 0rem;
padding: 1rem;
width: 7.5rem;
}
.nav-item:hover, .nav-item.active {
color: #4f2a93;
border-bottom: 2px solid #4f2a93;
border-bottom: 4px solid $purple-300;
cursor: pointer;
}
@@ -286,11 +314,6 @@
padding-top: 1.3em;
}
.purple-gradient {
background-image: linear-gradient(74deg, #4f2a93, #6133b4);
height: 14em;
}
.spacer {
height: 4em;
}
@@ -309,10 +332,11 @@
.svg-icon.check {
color: #bda8ff;
width: 1rem;
}
.text-invert {
margin: 1.6em;
.header-invert {
margin: 3rem auto 1.5rem;
color: #FFFFFF;
font-family: Roboto;
font-weight: normal;
@@ -320,9 +344,9 @@
}
.text-leadin {
margin: 1.6em;
font-weight: normal;
color: #4f2a93;
margin: 1rem;
font-weight: bold;
color: $purple-200;
}
.text-outtro {
@@ -346,6 +370,7 @@
import checkIcon from 'assets/svg/check.svg';
import creditCard from 'assets/svg/credit-card.svg';
import gifts from 'assets/svg/gifts.svg';
import heart from 'assets/svg/health.svg';
import logo from 'assets/svg/habitica-logo.svg';
@@ -381,6 +406,7 @@
check: checkIcon,
creditCard,
fourGems,
gifts,
heart,
twentyOneGems,
fortyTwoGems,

View File

@@ -31,11 +31,17 @@ b-modal#send-gems(:title="title", :hide-footer="true", size='lg', @hide='onHide(
)
h3.panel-heading {{ $t('subscription') }}
.panel-body
.form-group
.radio(v-for='block in subscriptionBlocks', v-if="block.target !== 'group' && block.canSubscribe === true")
label
input(type="radio", name="subRadio", :value="block.key", v-model='gift.subscription.key')
| {{ $t('sendGiftSubscription', {price: block.price, months: block.months}) }}
.row
.col-md-4
.form-group
.radio(v-for='block in subscriptionBlocks', v-if="block.target !== 'group' && block.canSubscribe === true")
label
input(type="radio", name="subRadio", :value="block.key", v-model='gift.subscription.key')
| {{ $t('sendGiftSubscription', {price: block.price, months: block.months}) }}
.col-md-8
h4 {{ $t('winterPromoGiftHeader') }}
p {{ $t('winterPromoGiftDetails1') }}
p {{ $t('winterPromoGiftDetails2') }}
textarea.form-control(rows='3', v-model='gift.message', :placeholder="$t('sendGiftMessagePlaceholder')")
//include ../formatting-help
@@ -47,9 +53,9 @@ b-modal#send-gems(:title="title", :hide-footer="true", size='lg', @hide='onHide(
:disabled="sendingInProgress"
) {{ $t("send") }}
template(v-else)
button.btn.btn-primary(@click='showStripe({gift, uuid: userReceivingGems._id})') {{ $t('card') }}
button.btn.btn-warning(@click='openPaypalGift({gift: gift, giftedTo: userReceivingGems._id})') PayPal
button.btn.btn-success(@click="amazonPaymentsInit({type: 'single', gift, giftedTo: userReceivingGems._id})") Amazon Payments
button.btn.btn-primary(@click='showStripe({gift, uuid: userReceivingGems._id, receiverName})') {{ $t('card') }}
button.btn.btn-warning(@click='openPaypalGift({gift: gift, giftedTo: userReceivingGems._id, receiverName})') PayPal
button.btn.btn-success(@click="amazonPaymentsInit({type: 'single', gift, giftedTo: userReceivingGems._id, receiverName})") Amazon Payments
button.btn.btn-secondary(@click='close()') {{$t('cancel')}}
</template>
@@ -131,6 +137,13 @@ export default {
if (!this.userReceivingGems) return '';
return this.$t('sendGiftHeading', {name: this.userReceivingGems.profile.name});
},
receiverName () {
if (this.userReceivingGems.auth && this.userReceivingGems.auth.local && this.userReceivingGems.auth.local.username) {
return this.userReceivingGems.auth.local.username;
} else {
return this.userReceivingGems.profile.name;
}
},
},
methods: {
// @TODO move to payments mixin or action (problem is that we need notifications)
@@ -141,11 +154,23 @@ export default {
toUserId: this.userReceivingGems._id,
gemAmount: this.gift.gems.amount,
});
this.text(this.$t('sentGems'));
this.close();
setTimeout(() => { // wait for the send gem modal to be closed
this.$root.$emit('habitica:payment-success', {
paymentMethod: 'balance',
paymentCompleted: true,
paymentType: 'gift-gems-balance',
gift: {
gems: {
amount: this.gift.gems.amount,
},
},
giftReceiver: this.receiverName,
});
}, 500);
},
onHide () {
// TODO this breaks amazon purchases because when the amazon modal
// @TODO this breaks amazon purchases because when the amazon modal
// is opened this one is closed and the amount reset
// this.gift.gems.amount = 0;
this.gift.message = '';

View File

@@ -1,17 +1,39 @@
<template lang="pug">
b-modal#payments-success-modal(
:title="$t('accountSuspendedTitle')",
size='sm',
size='sm',
:hideFooter="isFromBalance",
:modalClass="isFromBalance ? ['modal-hidden-footer'] : []"
)
div(slot="modal-header")
.check-container.d-flex.align-items-center.justify-content-center
.svg-icon.check(v-html="icons.check")
h2(v-ocne) {{ $t('paymentSuccessful') }}
.svg-icon.check(v-html="icons.check", v-once)
h2 {{ $t(isFromBalance ? 'success' : 'paymentSuccessful') }}
div(slot="modal-footer")
.small-text(v-once) {{ $t('giftSubscriptionText4') }}
.row
.col-12.text-center
button.btn.btn-primary(@click='close()') {{$t('onwards')}}
.col-12.modal-body-col
template(v-if="paymentData.paymentType === 'gems'")
strong(v-once) {{ $t('paymentYouReceived') }}
.details-block.gems
.svg-icon(v-html="icons.gem", v-once)
span 20
template(v-if="paymentData.paymentType === 'gift-gems' || paymentData.paymentType === 'gift-gems-balance'")
span(v-html="$t('paymentYouSentGems', {name: paymentData.giftReceiver})")
.details-block.gems
.svg-icon(v-html="icons.gem", v-once)
span {{ paymentData.gift.gems.amount }}
template(v-if="paymentData.paymentType === 'gift-subscription'")
span(v-html="$t('paymentYouSentSubscription', {name: paymentData.giftReceiver, months: paymentData.subscription.months})")
template(v-if="paymentData.paymentType === 'subscription'")
strong(v-once) {{ $t('nowSubscribed') }}
.details-block
span(v-html="$t('paymentSubBilling', {amount: paymentData.subscription.price, months: paymentData.subscription.months})")
template(v-if="paymentData.paymentType === 'groupPlan'")
span(v-html="$t(paymentData.newGroup ? 'groupPlanCreated' : 'groupPlanUpgraded', {groupName: paymentData.group.name})")
.details-block
span(v-html="$t('paymentSubBilling', {amount: groupPlanCost, months: paymentData.subscription.months})")
button.btn.btn-primary(@click='close()', v-once) {{$t('onwards')}}
</template>
<style lang="scss">
@@ -21,6 +43,11 @@
background: transparent;
}
#payments-success-modal.modal-hidden-footer .modal-body {
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}
#payments-success-modal .modal-header {
justify-content: center;
padding-top: 24px;
@@ -28,6 +55,8 @@
background: $green-10;
border-top-right-radius: 8px;
border-top-left-radius: 8px;
border-bottom: none;
h2 {
color: white;
}
@@ -52,6 +81,41 @@
padding-top: 16px;
padding-bottom: 24px;
background: white;
.modal-body-col {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
.btn.btn-primary {
margin-top: 24px;
}
}
.details-block {
background: $gray-700;
border-radius: 4px;
padding: 8px 24px;
margin-top: 16px;
display: flex;
flex-direction: row;
text-align: center;
&.gems {
padding: 12px 16px 12px 20px;
color: $green-10;
font-size: 24px;
font-weight: bold;
line-height: 1.33;
.svg-icon {
margin-right: 8px;
width: 32px;
height: 32px;
}
}
}
}
#payments-success-modal .modal-footer {
@@ -59,6 +123,7 @@
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
justify-content: center;
border-top: none;
.small-text {
font-style: normal;
@@ -68,17 +133,45 @@
<script>
import checkIcon from 'assets/svg/check.svg';
import gemIcon from 'assets/svg/gem.svg';
import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks';
export default {
data () {
return {
icons: Object.freeze({
check: checkIcon,
gem: gemIcon,
}),
paymentData: {},
};
},
mounted () {
this.$root.$on('habitica:payment-success', (data) => {
if (['subscription', 'groupPlan', 'gift-subscription'].indexOf(data.paymentType) !== -1) {
data.subscription = subscriptionBlocks[data.subscriptionKey || data.gift.subscription.key];
}
this.paymentData = data;
this.$root.$emit('bv::show::modal', 'payments-success-modal');
});
},
destroyed () {
this.paymentData = {};
this.$root.$off('habitica:payments-success');
},
computed: {
groupPlanCost () {
const sub = this.paymentData.subscription;
const memberCount = this.paymentData.group.memberCount || 1;
return sub.price + 3 * (memberCount - 1);
},
isFromBalance () {
return this.paymentData.paymentType === 'gift-gems-balance';
},
},
methods: {
close () {
this.paymentData = {};
this.$root.$emit('bv::hide::modal', 'payments-success-modal');
},
},

View File

@@ -84,12 +84,16 @@
img(src='https://payments.amazon.com/gp/cba/button', :alt="$t('amazonPayments')")
.row
.col-6
h2 {{ $t('giftSubscription') }}
h2(v-once) {{ $t('giftSubscription') }}
ol
li {{ $t('giftSubscriptionText1') }}
li {{ $t('giftSubscriptionText2') }}
li {{ $t('giftSubscriptionText3') }}
h4 {{ $t('giftSubscriptionText4') }}
li(v-once) {{ $t('giftSubscriptionText1') }}
li(v-once) {{ $t('giftSubscriptionText2') }}
li(v-once) {{ $t('giftSubscriptionText3') }}
h4(v-once) {{ $t('giftSubscriptionText4') }}
.col-6
h2 {{ $t('winterPromoGiftHeader') }}
p {{ $t('winterPromoGiftDetails1') }}
p {{ $t('winterPromoGiftDetails2') }}
</template>
<style scoped>

View File

@@ -76,7 +76,7 @@
div(
v-for="(groupSets, categoryGroup) in getGroupedCategories(categories)",
)
h3.classgroup(v-if='categoryGroup !== "spells"')
h3.classgroup(v-if='categoryGroup !== "spells" && categoryGroup !== "quests"')
span.svg-icon.inline(v-html="icons[categoryGroup]")
span.name(:class="categoryGroup") {{ getClassName(categoryGroup) }}

View File

@@ -12,6 +12,10 @@
z-index: 1400; // 1400 is above modal backgrounds
&-top-pos {
&-double {
top: 145px;
}
&-normal {
top: 65px;
}
@@ -26,6 +30,7 @@
<script>
import { mapState } from 'client/libs/store';
import notification from './notification';
import { CONSTANTS, getLocalSetting } from 'client/libs/userlocalManager';
export default {
components: {
@@ -39,7 +44,9 @@ export default {
notificationsTopPos () {
const base = 'notifications-top-pos-';
let modifier = '';
if (this.userSleeping) {
if (this.userSleeping && this.giftingShown) {
modifier = 'double';
} else if (this.userSleeping || this.giftingShown) {
modifier = 'sleeping';
} else {
modifier = 'normal';
@@ -47,5 +54,10 @@ export default {
return `${base}${modifier}`;
},
},
data () {
return {
giftingShown: getLocalSetting(CONSTANTS.keyConstants.GIFTING_BANNER_DISPLAY) !== 'dismissed',
};
},
};
</script>

View File

@@ -6,9 +6,9 @@
p {{ $t('needTips') }}
div(v-for='step in stepsNum')
h3 {{ $t('step'+step) }}
p(v-markdown="$t('webStep'+step+'Text')")
p(v-markdown="$t('webStep'+step+'Text', stepVars[step])")
hr
p(v-markdown="$t('overviewQuestions')")
p(v-markdown="$t('overviewQuestions', {faqUrl: '/static/faq/', helpGuildUrl: '/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a'})")
</template>
<style lang='scss'>
@@ -31,6 +31,15 @@ export default {
data () {
return {
stepsNum: ['1', '2', '3'],
stepVars: {
1: {},
2: {},
3: {
partyUrl: '/party',
equipUrl: '/inventory/equipment',
shopUrl: '/shops/market',
},
},
};
},
};

View File

@@ -30,15 +30,29 @@
a.d-block(v-if="tagsType.key !== 'groups' && !editingTags", @click="editTags(tagsType.key)") {{ $t('editTags2') }}
.tags-list.container
.row(:class="{'no-gutters': !editingTags}")
template(v-if="editingTags && tagsType.key !== 'groups'")
.col-6(v-for="(tag, tagIndex) in tagsSnap[tagsType.key]")
.inline-edit-input-group.tag-edit-item.input-group
input.tag-edit-input.inline-edit-input.form-control(type="text", v-model="tag.name")
.input-group-append(@click="removeTag(tagIndex, tagsType.key)")
.svg-icon.destroy-icon(v-html="icons.destroy")
.col-6(v-if="tagsType.key === 'tags'")
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event, tagsType.key)", v-model="newTag")
template(v-else)
template(v-if="editingTags && tagsType.key === 'tags'")
draggable(
v-if="tagsType.key === 'tags'",
v-model="tagsSnap[tagsType.key]",
class="row"
)
.col-6(v-for="(tag, tagIndex) in tagsSnap[tagsType.key]")
.inline-edit-input-group.tag-edit-item.input-group
.svg-icon.inline.drag(v-html="icons.drag")
input.tag-edit-input.inline-edit-input.form-control(type="text", v-model="tag.name")
.input-group-append(@click="removeTag(tagIndex, tagsType.key)")
.svg-icon.destroy-icon(v-html="icons.destroy")
.col-6.dragSpace
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event, tagsType.key)", v-model="newTag")
template(v-if="editingTags && tagsType.key === 'challenges'")
.col-6(v-for="(tag, tagIndex) in tagsSnap[tagsType.key]")
.inline-edit-input-group.tag-edit-item.input-group
input.tag-edit-input.inline-edit-input.form-control(type="text", v-model="tag.name")
.input-group-append(@click="removeTag(tagIndex, tagsType.key)")
.svg-icon.destroy-icon(v-html="icons.destroy")
template(v-if="!editingTags || tagsType.key === 'groups'")
.col-6(v-for="(tag, tagIndex) in tagsType.tags")
.custom-control.custom-checkbox
input.custom-control-input(
@@ -289,6 +303,23 @@
}
}
.drag {
cursor: grab;
margin: auto 0;
height: 20px;
width: 20px;
color: #C3C0C7;
&:hover {
color: #878190;
}
}
.dragSpace {
padding-left: 32px;
}
@media only screen and (max-width: 768px) {
.filter-panel {
max-width: none;
@@ -310,6 +341,7 @@ import habitIcon from 'assets/svg/habit.svg';
import dailyIcon from 'assets/svg/daily.svg';
import todoIcon from 'assets/svg/todo.svg';
import rewardIcon from 'assets/svg/reward.svg';
import dragIcon from 'assets/svg/drag_indicator.svg';
import uuid from 'uuid';
import Vue from 'vue';
@@ -320,6 +352,7 @@ import taskDefaults from 'common/script/libs/taskDefaults';
import brokenTaskModal from './brokenTaskModal';
import Item from 'client/components/inventory/item.vue';
import draggable from 'vuedraggable';
export default {
components: {
@@ -328,6 +361,7 @@ export default {
Item,
spells,
brokenTaskModal,
draggable,
},
directives: {
markdown,
@@ -347,6 +381,7 @@ export default {
daily: dailyIcon,
todo: todoIcon,
reward: rewardIcon,
drag: dragIcon,
}),
selectedTags: [],
temporarilySelectedTags: [],

View File

@@ -5,6 +5,7 @@ const CONSTANTS = {
EQUIPMENT_DRAWER_STATE: 'equipment-drawer-state',
CURRENT_EQUIPMENT_DRAWER_TAB: 'current-equipment-drawer-tab',
STABLE_SORT_STATE: 'stable-sort-state',
GIFTING_BANNER_DISPLAY: 'gifting-banner-display',
},
drawerStateValues: {
DRAWER_CLOSED: 'drawer-closed',

View File

@@ -1,12 +1,13 @@
import axios from 'axios';
const STRIPE_PUB_KEY = process.env.STRIPE_PUB_KEY; // eslint-disable-line
const STRIPE_PUB_KEY = process.env.STRIPE_PUB_KEY; // eslint-disable-line no-process-env
import subscriptionBlocks from '../../common/script/content/subscriptionBlocks';
import { mapState } from 'client/libs/store';
import encodeParams from 'client/libs/encodeParams';
import notificationsMixin from 'client/mixins/notifications';
import * as Analytics from 'client/libs/analytics';
import { CONSTANTS, setLocalSetting } from 'client/libs/userlocalManager';
import pick from 'lodash/pick';
const habiticaUrl = `${location.protocol}//${location.host}`;
@@ -43,13 +44,24 @@ export default {
let gift = this.encodeGift(data.giftedTo, data.gift);
const url = `/paypal/checkout?gift=${gift}`;
this.openPaypal(url, 'gift');
this.openPaypal(url, `gift-${data.gift.type === 'gems' ? 'gems' : 'subscription'}`, data);
},
openPaypal (url/* , type*/) {
openPaypal (url, type, giftData) {
const appState = {
paymentMethod: 'paypal',
paymentCompleted: false,
paymentType: type,
};
if (type === 'subscription') {
appState.subscriptionKey = this.subscriptionPlan || this.subscription.key;
}
if (type.indexOf('gift-') === 0) {
appState.gift = giftData.gift;
appState.giftReceiver = giftData.receiverName;
}
setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState));
window.open(url, '_blank');
@@ -77,11 +89,18 @@ export default {
sub = sub && subscriptionBlocks[sub];
let amount = 500;// 500 = $5
let amount = 500; // 500 = $5
if (sub) amount = sub.price * 100;
if (data.gift && data.gift.type === 'gems') amount = data.gift.gems.amount / 4 * 100;
if (data.group) amount = (sub.price + 3 * (data.group.memberCount - 1)) * 100;
let paymentType;
if (sub === false && !data.gift) paymentType = 'gems';
if (sub !== false && !data.gift) paymentType = 'subscription';
if (data.group || data.groupToCreate) paymentType = 'groupPlan';
if (data.gift && data.gift.type === 'gems') paymentType = 'gift-gems';
if (data.gift && data.gift.type === 'subscription') paymentType = 'gift-subscription';
window.StripeCheckout.open({
key: STRIPE_PUB_KEY,
address: false,
@@ -116,7 +135,26 @@ export default {
const appState = {
paymentMethod: 'stripe',
paymentCompleted: true,
paymentType,
};
if (paymentType === 'subscription') {
appState.subscriptionKey = sub.key;
} else if (paymentType === 'groupPlan') {
appState.subscriptionKey = sub.key;
if (data.groupToCreate) {
appState.newGroup = true;
appState.group = pick(data.groupToCreate, ['_id', 'memberCount', 'name']);
} else {
appState.newGroup = false;
appState.group = pick(data.group, ['_id', 'memberCount', 'name']);
}
} else if (paymentType.indexOf('gift-') === 0) {
appState.gift = data.gift;
appState.giftReceiver = data.receiverName;
}
setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState));
let newGroup = response.data.data;
@@ -191,6 +229,7 @@ export default {
if (data.gift) {
if (data.gift.gems && data.gift.gems.amount && data.gift.gems.amount <= 0) return;
data.gift.uuid = data.giftedTo;
this.amazonPayments.giftReceiver = data.receiverName;
}
if (data.subscription) {
@@ -202,7 +241,11 @@ export default {
this.amazonPayments.groupId = data.groupId;
}
if (data.groupToCreate) {
if (data.group) { // upgrading a group
this.amazonPayments.group = data.group;
}
if (data.groupToCreate) { // creating a group
this.amazonPayments.groupToCreate = data.groupToCreate;
}

View File

@@ -1,7 +1,6 @@
import axios from 'axios';
const LOCALSTORAGE_AUTH_KEY = 'habit-mobile-settings';
const LOCALSTORAGE_SOCIAL_AUTH_KEY = 'hello'; // Used by hello.js for social auth
export async function register (store, params) {
let url = '/api/v4/user/auth/local/register';
@@ -84,7 +83,6 @@ export async function socialAuth (store, params) {
}
export function logout () {
localStorage.removeItem(LOCALSTORAGE_AUTH_KEY);
localStorage.removeItem(LOCALSTORAGE_SOCIAL_AUTH_KEY);
localStorage.clear();
window.location.href = '/logout-server';
}

View File

@@ -31,7 +31,7 @@ export async function updateTag (store, payload) {
export async function sortTag (store, payload) {
let url = 'api/v4/reorder-tags';
let response = await axios.post(url, {
tagDetails: payload.tagDetails,
tagId: payload.tagId,
to: payload.to,
});
return response.data.data;

View File

@@ -401,5 +401,12 @@
"backgroundFrostyForestText": "Ледена гора",
"backgroundFrostyForestNotes": "Пригответе се за поход през ледена гора.",
"backgroundSnowyDayFireplaceText": "Огнище в снежен ден",
"backgroundSnowyDayFireplaceNotes": "Сгушете се пред огнището в снежен ден."
"backgroundSnowyDayFireplaceNotes": "Сгушете се пред огнището в снежен ден.",
"backgrounds012019": "КОМПЛЕКТ 56: януари 2019 г.",
"backgroundAvalancheText": "Лавина",
"backgroundAvalancheNotes": "Избягайте от страховитата мощ на лавина.",
"backgroundArchaeologicalDigText": "Археологически разкопки",
"backgroundArchaeologicalDigNotes": "Разкрийте тайните на древността в археологически разкопки.",
"backgroundScribesWorkshopText": "Работилница на книжник",
"backgroundScribesWorkshopNotes": "Напишете следващия си списък в работилница на книжник."
}

View File

@@ -179,6 +179,9 @@
"questEggAlligatorText": "Алигатор",
"questEggAlligatorMountText": "Алигатор",
"questEggAlligatorAdjective": "хитър",
"questEggVelociraptorText": "Велоцираптор",
"questEggVelociraptorMountText": "Велоцираптор",
"questEggVelociraptorAdjective": "умен",
"eggNotes": "Намерете излюпваща отвара, която да излеете върху това яйце и от него ще се излюпи <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Нормален цвят",
"hatchingPotionWhite": "Бял цвят",
@@ -207,6 +210,7 @@
"hatchingPotionGlass": "Стъкло",
"hatchingPotionGlow": "Светещо в тъмното",
"hatchingPotionFrost": "Скреж",
"hatchingPotionIcySnow": "Леден сняг",
"hatchingPotionNotes": "Излейте това върху яйце и от него ще се излюпи любимец с(ъс) <%= potText(locale) %>.",
"premiumPotionAddlNotes": "Не може да се използва върху яйца за любимци от мисии.",
"foodMeat": "Месо",

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