Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 537313a21e | |||
| 453383af9f | |||
| a19db5798d | |||
| ea1569e23e | |||
| ec5de91123 | |||
| b1a348aee3 | |||
| be86812900 | |||
| c879560445 | |||
| 188023b197 | |||
| c4343379a1 | |||
| ce07d06c15 | |||
| a0041221be | |||
| ebfb6f96b3 | |||
| 22d696219a | |||
| 9014943a86 | |||
| 6a70487fa6 | |||
| 4fa381f153 | |||
| 97209e40ad | |||
| 2e97f9864e | |||
| 6d6adfd919 | |||
| 13123c0bae | |||
| 577e6f005e | |||
| 1361fea2d4 | |||
| cbcc7cd479 | |||
| 0e36c1aa0f | |||
| 5b4505ac62 | |||
| aa3d972cb4 | |||
| d4b729c95e | |||
| d4b867acc3 | |||
| 15c09691af | |||
| 27263e9b2f | |||
| 2d9715b657 | |||
| 8ca5ee99b0 | |||
| 9f9da5632d | |||
| 9364cdc2b4 | |||
| 2896cf77e0 | |||
| 936d3ffc98 | |||
| 9608b9fa9f | |||
| 657327edd7 | |||
| 9706a9c8be | |||
| 484bae40cd | |||
| c0b6353ded | |||
| f71062e86c | |||
| 8da36bf27c | |||
| 1800fabaaa | |||
| 193e7062c3 | |||
| cda5c6fbb0 | |||
| 495d01f386 | |||
| 24e1bfdfba | |||
| f757e645b7 | |||
| bf492933cc | |||
| 86d2fed76e | |||
| 2b7fe7c1d5 | |||
| 999b62df43 | |||
| cc7dac47c4 | |||
| c8189360d6 | |||
| 88183149c5 | |||
| a8f397c674 | |||
| c5aeab652d | |||
| cc04761c24 | |||
| 29dccdd148 | |||
| 186b929e59 | |||
| 551abf292c | |||
| 1e1c058d82 | |||
| 2ea9070a9b | |||
| 168a3a6e89 | |||
| 67bd2d9130 | |||
| cb0280ca8b | |||
| 259b15877b | |||
| dd31f559c7 | |||
| 6c29ea8c4c | |||
| dc316ad1c8 | |||
| a5b37fcc02 | |||
| 16f1c7286b | |||
| d5b3e4ec9f | |||
| 47eb9bf3ff | |||
| 2977346ccb | |||
| a545cc72d0 | |||
| 82548f69e5 | |||
| 5aacae3ead | |||
| 9f8162b82c | |||
| 98a749b239 | |||
| 0cef9eff87 | |||
| 3189a944d7 | |||
| 79066165e2 | |||
| c6f6df3f14 | |||
| 7bfd6ceca1 | |||
| 400f4dfb01 | |||
| 57a9fb5241 | |||
| 08c63f94fc | |||
| 9c6c90a2e9 | |||
| 39765895ee | |||
| 2a8fc7aea2 | |||
| 0a86d04a15 | |||
| 3afa7e6da5 | |||
| 0ac0723be5 | |||
| 0f2d2ddad6 | |||
| e19837f58e | |||
| 0ea1ce9758 | |||
| 9d16ab7dba | |||
| 776e9834f3 | |||
| 1d98929453 | |||
| 80664b6c5f | |||
| 01123367b1 | |||
| f6a80d18b6 | |||
| 71af306f02 | |||
| 5ccd2ae262 | |||
| a1a1fd939d | |||
| 9ac7840940 | |||
| 100275f460 | |||
| 489b9c4019 | |||
| ba61da4acb | |||
| 903851f1fd | |||
| 371a1542e7 | |||
| 0c640f07d1 | |||
| cbc6e7aa0c | |||
| b6eab67e6a | |||
| f920a441a5 | |||
| 12a9eec540 | |||
| f83d86b7f3 | |||
| 0410c97001 | |||
| 4482b734a5 | |||
| 0131cc07bf | |||
| 70a9f66dcd | |||
| 3057fdbd4a | |||
| cc48479c66 | |||
| f4e573f684 | |||
| a2e59d0920 | |||
| 39fe86c688 | |||
| 78ceb427a3 | |||
| 7c17a32bbb | |||
| 9a34c16fa2 | |||
| 38c24763fa | |||
| 776f3d288b | |||
| b7448e2cfe | |||
| 0bc836b490 | |||
| fdf7e3a665 | |||
| 00d12e83bd | |||
| e4661c3763 | |||
| 156d15c540 | |||
| 7d9b8a5ceb | |||
| 23b19853b4 | |||
| c6839c4478 | |||
| 167f4f07b8 | |||
| 95c9dbaa73 | |||
| d9f7772453 | |||
| 8efed37241 | |||
| 66422d4235 | |||
| eb4e382ecf |
@@ -37,6 +37,7 @@ yarn.lock
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
||||
/.vscode
|
||||
|
||||
# webstorm fake webpack for path intellisense
|
||||
webpack.webstorm.config
|
||||
|
||||
@@ -9,3 +9,4 @@ test/**
|
||||
*.swp
|
||||
*.swx
|
||||
website/raw_sprites/**
|
||||
content_cache/**
|
||||
|
||||
@@ -12,6 +12,7 @@ ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleu
|
||||
ENV LOGGLY_CLIENT_TOKEN ab5663bf-241f-4d14-8783-7d80db77089a
|
||||
ENV NODE_ENV production
|
||||
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||
ENV APPLE_AUTH_CLIENT_ID 9Q9SMRMCNN.com.habitrpg.ios.Habitica
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
@@ -75,5 +75,10 @@
|
||||
"WEB_CONCURRENCY": 1,
|
||||
"SKIP_SSL_CHECK_KEY": "key",
|
||||
"ENABLE_STACKDRIVER_TRACING": "false",
|
||||
"BLOCKED_IPS": ""
|
||||
"APPLE_AUTH_PRIVATE_KEY": "",
|
||||
"APPLE_TEAM_ID": "",
|
||||
"APPLE_AUTH_CLIENT_ID": "",
|
||||
"APPLE_AUTH_KEY_ID": "",
|
||||
"BLOCKED_IPS": "",
|
||||
"LOG_AMPLITUDE_EVENTS": "false"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ gulp.task('content:cache', done => {
|
||||
// Requiring at runtime because these files access `common`
|
||||
// code which in production works only if transpiled so after
|
||||
// gulp build:babel:common has run
|
||||
const { CONTENT_CACHE_PATH, getLocalizedContent } = require('../website/server/libs/content'); // eslint-disable-line global-require
|
||||
const { CONTENT_CACHE_PATH, getLocalizedContentResponse } = require('../website/server/libs/content'); // eslint-disable-line global-require
|
||||
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
||||
|
||||
try {
|
||||
@@ -23,7 +23,7 @@ gulp.task('content:cache', done => {
|
||||
langCodes.forEach(langCode => {
|
||||
fs.writeFileSync(
|
||||
`${CONTENT_CACHE_PATH}${langCode}.json`,
|
||||
getLocalizedContent(langCode),
|
||||
getLocalizedContentResponse(langCode),
|
||||
'utf8',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.139.0",
|
||||
"version": "4.140.12",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/register": "^7.9.0",
|
||||
"@google-cloud/trace-agent": "^4.2.5",
|
||||
"@slack/client": "^4.12.0",
|
||||
@@ -14,7 +14,7 @@
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"aws-sdk": "^2.648.0",
|
||||
"apple-auth": "^1.0.5",
|
||||
"bcrypt": "^3.0.8",
|
||||
"body-parser": "^1.18.3",
|
||||
"compression": "^1.7.4",
|
||||
@@ -41,18 +41,20 @@
|
||||
"image-size": "^0.8.3",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^4.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "^1.8.0",
|
||||
"lodash": "^4.17.15",
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.9.6",
|
||||
"mongoose": "^5.9.9",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.4.1",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-google-oauth2": "^0.2.0",
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
@@ -69,7 +71,7 @@
|
||||
"validator": "^11.0.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^3.2.1",
|
||||
"winston-loggly-bulk": "^3.0.1",
|
||||
"winston-loggly-bulk": "^3.1.0",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"private": true,
|
||||
@@ -109,7 +111,7 @@
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^7.1.2",
|
||||
"monk": "^7.2.0",
|
||||
"require-again": "^2.0.0",
|
||||
"sinon": "^7.2.4",
|
||||
"sinon-chai": "^3.5.0",
|
||||
|
||||
@@ -8,9 +8,9 @@ describe('contentLib', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLocalizedContent', () => {
|
||||
describe('getLocalizedContentResponse', () => {
|
||||
it('clones, not modify, the original content data', () => {
|
||||
contentLib.getLocalizedContent();
|
||||
contentLib.getLocalizedContentResponse();
|
||||
expect(typeof content.backgrounds.backgrounds062014.beach.text).to.equal('function');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,16 @@ function getUser () {
|
||||
value: 'email@facebook',
|
||||
}],
|
||||
},
|
||||
google: {
|
||||
emails: [{
|
||||
value: 'email@google',
|
||||
}],
|
||||
},
|
||||
apple: {
|
||||
emails: [{
|
||||
value: 'email@apple',
|
||||
}],
|
||||
},
|
||||
},
|
||||
profile: {
|
||||
name: 'profile name',
|
||||
@@ -58,6 +68,8 @@ describe('emails', () => {
|
||||
const user = getUser();
|
||||
delete user.profile.name;
|
||||
delete user.auth.local.email;
|
||||
delete user.auth.google.emails;
|
||||
delete user.auth.apple.emails;
|
||||
|
||||
const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
@@ -67,12 +79,48 @@ describe('emails', () => {
|
||||
expect(data).to.have.property('canSend', true);
|
||||
});
|
||||
|
||||
it('returns correct user data [google users]', () => {
|
||||
const attachEmail = requireAgain(pathToEmailLib);
|
||||
const { getUserInfo } = attachEmail;
|
||||
const user = getUser();
|
||||
delete user.profile.name;
|
||||
delete user.auth.local.email;
|
||||
delete user.auth.facebook.emails;
|
||||
delete user.auth.apple.emails;
|
||||
|
||||
const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
expect(data).to.have.property('name', user.auth.local.username);
|
||||
expect(data).to.have.property('email', user.auth.google.emails[0].value);
|
||||
expect(data).to.have.property('_id', user._id);
|
||||
expect(data).to.have.property('canSend', true);
|
||||
});
|
||||
|
||||
it('returns correct user data [apple users]', () => {
|
||||
const attachEmail = requireAgain(pathToEmailLib);
|
||||
const { getUserInfo } = attachEmail;
|
||||
const user = getUser();
|
||||
delete user.profile.name;
|
||||
delete user.auth.local.email;
|
||||
delete user.auth.google.emails;
|
||||
delete user.auth.facebook.emails;
|
||||
|
||||
const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
expect(data).to.have.property('name', user.auth.local.username);
|
||||
expect(data).to.have.property('email', user.auth.apple.emails[0].value);
|
||||
expect(data).to.have.property('_id', user._id);
|
||||
expect(data).to.have.property('canSend', true);
|
||||
});
|
||||
|
||||
it('has fallbacks for missing data', () => {
|
||||
const attachEmail = requireAgain(pathToEmailLib);
|
||||
const { getUserInfo } = attachEmail;
|
||||
const user = getUser();
|
||||
delete user.auth.local.email;
|
||||
delete user.auth.facebook;
|
||||
delete user.auth.google;
|
||||
delete user.auth.apple;
|
||||
|
||||
const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
highlightMentions,
|
||||
} from '../../../../website/server/libs/highlightMentions';
|
||||
|
||||
describe('highlightMentions', () => {
|
||||
beforeEach(() => {
|
||||
const mockFind = {
|
||||
select () {
|
||||
return this;
|
||||
},
|
||||
lean () {
|
||||
return this;
|
||||
},
|
||||
exec () {
|
||||
return Promise.resolve([{
|
||||
auth: { local: { username: 'user' } }, _id: '111',
|
||||
}, { auth: { local: { username: 'user2' } }, _id: '222' }, { auth: { local: { username: 'user3' } }, _id: '333' }, { auth: { local: { username: 'user-dash' } }, _id: '444' }, { auth: { local: { username: 'user_underscore' } }, _id: '555' },
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
sinon.stub(mongoose.Model, 'find').returns(mockFind);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('doesn\'t change text without mentions', async () => {
|
||||
const text = 'some chat text';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
it('highlights existing users', async () => {
|
||||
const text = '@user: message';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user](/profile/111): message');
|
||||
});
|
||||
it('highlights special characters', async () => {
|
||||
const text = '@user-dash: message @user_underscore';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
|
||||
});
|
||||
it('doesn\'t highlight nonexisting users', async () => {
|
||||
const text = '@nouser message';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('@nouser message');
|
||||
});
|
||||
it('highlights multiple existing users', async () => {
|
||||
const text = '@user message (@user2) @user3 @user';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user](/profile/111) message ([@user2](/profile/222)) [@user3](/profile/333) [@user](/profile/111)');
|
||||
});
|
||||
it('doesn\'t highlight more than 5 users', async () => {
|
||||
const text = '@user @user2 @user3 @user4 @user5 @user6';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
import mongoose from 'mongoose';
|
||||
import highlightMentions from '../../../../website/server/libs/highlightMentions';
|
||||
|
||||
describe('highlightMentions', () => {
|
||||
beforeEach(() => {
|
||||
const mockFind = {
|
||||
select () {
|
||||
return this;
|
||||
},
|
||||
lean () {
|
||||
return this;
|
||||
},
|
||||
exec () {
|
||||
return Promise.resolve([
|
||||
{ auth: { local: { username: 'user' } }, _id: '111' },
|
||||
{ auth: { local: { username: 'user2' } }, _id: '222' },
|
||||
{ auth: { local: { username: 'user3' } }, _id: '333' },
|
||||
{ auth: { local: { username: 'user-dash' } }, _id: '444' },
|
||||
{ auth: { local: { username: 'user_underscore' } }, _id: '555' },
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
sinon.stub(mongoose.Model, 'find').returns(mockFind);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('doesn\'t change text without mentions', async () => {
|
||||
const text = 'some chat text';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
it('highlights existing users', async () => {
|
||||
const text = '@user: message';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user](/profile/111): message');
|
||||
});
|
||||
|
||||
it('highlights special characters', async () => {
|
||||
const text = '@user-dash: message @user_underscore';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
|
||||
});
|
||||
|
||||
it('doesn\'t highlight nonexisting users', async () => {
|
||||
const text = '@nouser message';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('@nouser message');
|
||||
});
|
||||
|
||||
it('highlights multiple existing users', async () => {
|
||||
const text = '@user message (@user2) @user3 @user';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user](/profile/111) message ([@user2](/profile/222)) [@user3](/profile/333) [@user](/profile/111)');
|
||||
});
|
||||
|
||||
it('doesn\'t highlight more than 5 users', async () => {
|
||||
const text = '@user @user2 @user3 @user4 @user5 @user6';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
describe('exceptions in code blocks', () => {
|
||||
it('doesn\'t highlight user in inline code block', async () => {
|
||||
const text = '`@user`';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
it('doesn\'t highlight user in fenced code block', async () => {
|
||||
const text = 'Text\n\n```\n// code referencing @user\n```\n\nText';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
it('doesn\'t highlight user in indented code block', async () => {
|
||||
const text = ' @user';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
it('does highlight user that\'s after in-line code block', async () => {
|
||||
const text = '`<code />` for @user';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal('`<code />` for [@user](/profile/111)');
|
||||
});
|
||||
|
||||
it('does highlight same content properly', async () => {
|
||||
const text = '@user `@user`';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal('[@user](/profile/111) `@user`');
|
||||
});
|
||||
});
|
||||
|
||||
it('github issue 12118, method crashes when square brackets are used', async () => {
|
||||
const text = '[test]';
|
||||
|
||||
let err;
|
||||
|
||||
try {
|
||||
await highlightMentions(text);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
expect(err).to.be.undefined;
|
||||
});
|
||||
|
||||
it('github issue 12138, method crashes when regex chars are used in code block', async () => {
|
||||
const text = '`[test]`';
|
||||
|
||||
let err;
|
||||
|
||||
try {
|
||||
await highlightMentions(text);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
expect(err).to.be.undefined;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import faq from '../../../../website/common/script/content/faq';
|
||||
import common from '../../../../website/common';
|
||||
import { localizeContentData } from '../../../../website/server/libs/content';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('localizeContentData', () => {
|
||||
it('Should take a an object with localization identifiers and '
|
||||
+ 'return an object with actual translations in English', () => {
|
||||
const faqInEnglish = localizeContentData(faq, 'en');
|
||||
|
||||
expect(faqInEnglish).to.have.property('stillNeedHelp');
|
||||
expect(faqInEnglish.stillNeedHelp.ios).to.equal(i18n.t('iosFaqStillNeedHelp', 'en'));
|
||||
});
|
||||
it('Should take an object with localization identifiers and '
|
||||
+ 'return an object with actual translations in German', () => {
|
||||
const faqInEnglish = localizeContentData(faq, 'de');
|
||||
|
||||
expect(faqInEnglish).to.have.property('stillNeedHelp');
|
||||
expect(faqInEnglish.stillNeedHelp.ios).to.equal(i18n.t('iosFaqStillNeedHelp', 'de'));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import moment from 'moment';
|
||||
import { calculateSubscriptionTerminationDate } from '../../../../../../website/server/libs/payments/util';
|
||||
import api from '../../../../../../website/server/libs/payments/payments';
|
||||
|
||||
describe('#calculateSubscriptionTerminationDate', () => {
|
||||
let plan;
|
||||
let nextBill;
|
||||
|
||||
beforeEach(() => {
|
||||
plan = {
|
||||
customerId: 'customer-id',
|
||||
extraMonths: 0,
|
||||
};
|
||||
nextBill = moment();
|
||||
});
|
||||
it('should extend date to the exact amount of days left before the next bill will occur', () => {
|
||||
nextBill = moment()
|
||||
.add(5, 'days');
|
||||
const expectedTerminationDate = moment()
|
||||
.add(5, 'days');
|
||||
|
||||
const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants);
|
||||
expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0);
|
||||
});
|
||||
it('if nextBill is null, add 30 days to termination date', () => {
|
||||
nextBill = null;
|
||||
const expectedTerminationDate = moment()
|
||||
.add(30, 'days');
|
||||
const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants);
|
||||
|
||||
expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0);
|
||||
});
|
||||
it('if nextBill is null and it\'s a group plan, add 2 days instead of 30', () => {
|
||||
nextBill = null;
|
||||
plan.customerId = api.constants.GROUP_PLAN_CUSTOMER_ID;
|
||||
const expectedTerminationDate = moment()
|
||||
.add(2, 'days');
|
||||
|
||||
const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants);
|
||||
expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0);
|
||||
});
|
||||
it('should add 30.5 days for each extraMonth', () => {
|
||||
plan.extraMonths = 4;
|
||||
const expectedTerminationDate = moment()
|
||||
.add(30.5 * 4, 'days');
|
||||
|
||||
const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants);
|
||||
expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0);
|
||||
});
|
||||
it('should round up if total days gained by extraMonth is a decimal number', () => {
|
||||
plan.extraMonths = 5;
|
||||
const expectedTerminationDate = moment()
|
||||
.add(Math.ceil(30.5 * 5), 'days');
|
||||
|
||||
const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants);
|
||||
expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0);
|
||||
});
|
||||
it('behaves like extraMonths is 0 if it\'s set to a negative number', () => {
|
||||
plan.extraMonths = -5;
|
||||
const expectedTerminationDate = moment();
|
||||
const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants);
|
||||
expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,15 @@
|
||||
import requireAgain from 'require-again';
|
||||
import apn from 'apn/mock';
|
||||
import _ from 'lodash';
|
||||
import nconf from 'nconf';
|
||||
import gcmLib from 'node-gcm'; // works with FCM notifications too
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import {
|
||||
sendNotification as sendPushNotification,
|
||||
MAX_MESSAGE_LENGTH,
|
||||
} from '../../../../website/server/libs/pushNotifications';
|
||||
|
||||
describe('pushNotifications', () => {
|
||||
let user;
|
||||
let sendPushNotification;
|
||||
const pathToPushNotifications = '../../../../website/server/libs/pushNotifications';
|
||||
let fcmSendSpy;
|
||||
let apnSendSpy;
|
||||
|
||||
@@ -28,8 +30,6 @@ describe('pushNotifications', () => {
|
||||
on: () => null,
|
||||
send: apnSendSpy,
|
||||
});
|
||||
|
||||
sendPushNotification = requireAgain(pathToPushNotifications).sendNotification;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -86,6 +86,67 @@ describe('pushNotifications', () => {
|
||||
expect(apnSendSpy).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('cuts the message to 300 chars', () => {
|
||||
const longMessage = `12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
|
||||
|
||||
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
|
||||
|
||||
const details = {
|
||||
identifier,
|
||||
title,
|
||||
message: longMessage,
|
||||
payload: {
|
||||
message: longMessage,
|
||||
},
|
||||
};
|
||||
|
||||
sendPushNotification(user, details);
|
||||
|
||||
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
||||
expect(details.payload.message)
|
||||
.to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
||||
|
||||
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||
expect(details.payload.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||
});
|
||||
|
||||
it('cuts the message to 300 chars (no payload)', () => {
|
||||
const longMessage = `12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
|
||||
|
||||
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
|
||||
|
||||
const details = {
|
||||
identifier,
|
||||
title,
|
||||
message: longMessage,
|
||||
};
|
||||
|
||||
sendPushNotification(user, details);
|
||||
|
||||
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
||||
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||
});
|
||||
|
||||
// TODO disabled because APN relies on a Promise
|
||||
xit('uses APN for iOS devices', () => {
|
||||
user.pushDevices.push({
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
defer,
|
||||
sleep,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
|
||||
import logger from '../../../../website/server/libs/logger';
|
||||
|
||||
describe('webhooks', () => {
|
||||
let webhooks; let
|
||||
@@ -356,6 +356,7 @@ describe('webhooks', () => {
|
||||
});
|
||||
|
||||
it('records failures', async () => {
|
||||
sinon.stub(logger, 'error');
|
||||
const body = {};
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
@@ -369,6 +370,9 @@ describe('webhooks', () => {
|
||||
|
||||
expect(user.webhooks[0].failures).to.equal(1);
|
||||
expect((Date.now() - user.webhooks[0].lastFailureAt.getTime()) < 10000).to.be.true;
|
||||
|
||||
expect(logger.error).to.be.calledOnce;
|
||||
logger.error.restore();
|
||||
});
|
||||
|
||||
it('disables a webhook after 10 failures', async () => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
generateRes,
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
import {
|
||||
disableCache,
|
||||
} from '../../../../website/server/middlewares/cache';
|
||||
|
||||
describe('cache middlewares', () => {
|
||||
let res; let req; let
|
||||
next;
|
||||
|
||||
beforeEach(() => {
|
||||
req = generateReq();
|
||||
res = generateRes();
|
||||
next = generateNext();
|
||||
});
|
||||
|
||||
describe('disableCache', () => {
|
||||
it('sets the correct headers', () => {
|
||||
disableCache(req, res, next);
|
||||
expect(res.set).to.have.been.calledWith('Cache-Control', 'no-store');
|
||||
expect(next).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
xit('removes the etag header', () => {
|
||||
// @TODO how to stub onHeaders
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -170,6 +170,7 @@ describe('errorHandler', () => {
|
||||
originalUrl: req.originalUrl,
|
||||
headers: req.headers,
|
||||
body: req.body,
|
||||
query: req.query,
|
||||
httpCode: 400,
|
||||
isHandledError: true,
|
||||
});
|
||||
|
||||
@@ -538,52 +538,74 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sends a chat message if no progress is made on quest with multiple items', async () => {
|
||||
progress.collectedItems = 0;
|
||||
party.quest.key = 'dilatoryDistress1';
|
||||
party.quest.active = false;
|
||||
describe('collection quests with multiple items', () => {
|
||||
it('sends a chat message if no progress is made on quest with multiple items', async () => {
|
||||
progress.collectedItems = 0;
|
||||
party.quest.key = 'dilatoryDistress1';
|
||||
party.quest.active = false;
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
Group.prototype.sendChat.resetHistory();
|
||||
await party.save();
|
||||
await party.startQuest(questLeader);
|
||||
Group.prototype.sendChat.resetHistory();
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
|
||||
party = await Group.findOne({ _id: party._id });
|
||||
party = await Group.findOne({ _id: party._id });
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`Participating Member found 0 Fire Coral, 0 Blue Fins.`',
|
||||
info: {
|
||||
items: { blueFins: 0, fireCoral: 0 },
|
||||
quest: 'dilatoryDistress1',
|
||||
type: 'user_found_items',
|
||||
user: 'Participating Member',
|
||||
},
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`Participating Member found 0 Fire Coral, 0 Blue Fins.`',
|
||||
info: {
|
||||
items: { blueFins: 0, fireCoral: 0 },
|
||||
quest: 'dilatoryDistress1',
|
||||
type: 'user_found_items',
|
||||
user: 'Participating Member',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles collection quests with multiple items', async () => {
|
||||
progress.collectedItems = 10;
|
||||
party.quest.key = 'evilsanta2';
|
||||
party.quest.active = false;
|
||||
it('handles correctly', async () => {
|
||||
progress.collectedItems = 10;
|
||||
party.quest.key = 'evilsanta2';
|
||||
party.quest.active = false;
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
Group.prototype.sendChat.resetHistory();
|
||||
await party.save();
|
||||
await party.startQuest(questLeader);
|
||||
Group.prototype.sendChat.resetHistory();
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
|
||||
party = await Group.findOne({ _id: party._id });
|
||||
party = await Group.findOne({ _id: party._id });
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch({
|
||||
message: sinon.match(/`Participating Member found/).and(sinon.match(/\d* (Tracks|Broken Twigs)/)),
|
||||
info: {
|
||||
quest: 'evilsanta2',
|
||||
type: 'user_found_items',
|
||||
user: 'Participating Member',
|
||||
},
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch({
|
||||
message: sinon.match(/`Participating Member found/).and(sinon.match(/\d* (Tracks|Broken Twigs)/)),
|
||||
info: {
|
||||
quest: 'evilsanta2',
|
||||
type: 'user_found_items',
|
||||
user: 'Participating Member',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot collect excess items', async () => {
|
||||
// Make sure the quest progress isn't erased
|
||||
sandbox.stub(Group.prototype, 'finishQuest').returns(Promise.resolve());
|
||||
|
||||
progress.collectedItems = 500;
|
||||
party.quest.key = 'evilsanta2';
|
||||
party.quest.active = false;
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
party = await Group.findOne({ _id: party._id });
|
||||
|
||||
expect(party.quest.progress.collect.tracks)
|
||||
.to.eql(questScrolls.evilsanta2.collect.tracks.count);
|
||||
expect(party.quest.progress.collect.branches)
|
||||
.to.eql(questScrolls.evilsanta2.collect.branches.count);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2380,29 +2402,29 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('isSubscribed', () => {
|
||||
context('hasActiveGroupPlan', () => {
|
||||
it('returns false if group does not have customer id', () => {
|
||||
expect(party.isSubscribed()).to.be.undefined;
|
||||
expect(party.hasActiveGroupPlan()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns true if group does not have plan.dateTerminated', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
|
||||
expect(party.isSubscribed()).to.be.true;
|
||||
expect(party.hasActiveGroupPlan()).to.be.true;
|
||||
});
|
||||
|
||||
it('returns true if group if plan.dateTerminated is after today', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
party.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
|
||||
|
||||
expect(party.isSubscribed()).to.be.true;
|
||||
expect(party.hasActiveGroupPlan()).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false if group if plan.dateTerminated is before today', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
party.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
||||
|
||||
expect(party.isSubscribed()).to.be.false;
|
||||
expect(party.hasActiveGroupPlan()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
each,
|
||||
} from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
generateChallenge,
|
||||
checkExistence,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import payments from '../../../../../website/server/libs/payments/payments';
|
||||
import { calculateSubscriptionTerminationDate } from '../../../../../website/server/libs/payments/util';
|
||||
|
||||
describe('POST /groups/:groupId/leave', () => {
|
||||
const typesOfGroups = {
|
||||
@@ -338,4 +340,48 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => {
|
||||
const extraMonths = 12;
|
||||
let groupWithPlan;
|
||||
let member;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
[member] = members;
|
||||
groupWithPlan = group;
|
||||
await member.update({
|
||||
'purchased.plan.extraMonths': extraMonths,
|
||||
});
|
||||
});
|
||||
it('calculates dateTerminated and sets extraMonths to zero after user leaves the group', async () => {
|
||||
const userBeforeLeave = await User.findById(member._id).exec();
|
||||
|
||||
await member.post(`/groups/${groupWithPlan._id}/leave`);
|
||||
const userAfterLeave = await User.findById(member._id).exec();
|
||||
|
||||
const dateTerminatedBefore = userBeforeLeave.purchased.plan.dateTerminated;
|
||||
const extraMonthsBefore = userBeforeLeave.purchased.plan.extraMonths;
|
||||
const dateTerminatedAfter = userAfterLeave.purchased.plan.dateTerminated;
|
||||
const extraMonthsAfter = userAfterLeave.purchased.plan.extraMonths;
|
||||
|
||||
const expectedTerminationDate = calculateSubscriptionTerminationDate(null, {
|
||||
customerId: payments.constants.GROUP_PLAN_CUSTOMER_ID,
|
||||
extraMonths,
|
||||
}, payments.constants);
|
||||
|
||||
expect(extraMonthsBefore).to.gte(12);
|
||||
expect(extraMonthsAfter).to.equal(0);
|
||||
expect(dateTerminatedBefore).to.be.null;
|
||||
expect(dateTerminatedAfter).to.exist;
|
||||
|
||||
expect(moment(dateTerminatedAfter).diff(expectedTerminationDate, 'days')).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -112,7 +112,7 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
||||
|
||||
await Promise.all([partyMembers[0].sync(), questingGroup.sync()]);
|
||||
expect(leader.party.quest.RSVPNeeded).to.equal(false);
|
||||
expect(questingGroup.quest.members[partyMembers[0]._id]);
|
||||
expect(questingGroup.quest.members[partyMembers[0]._id]).to.equal(true);
|
||||
});
|
||||
|
||||
it('does not begin the quest if pending invitations remain', async () => {
|
||||
|
||||
@@ -346,4 +346,23 @@ describe('DELETE /user', () => {
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Apple auth', async () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
auth: {
|
||||
apple: {
|
||||
id: 'apple-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a Apple user', async () => {
|
||||
await user.del('/user', {
|
||||
password: DELETE_CONFIRMATION,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,4 +95,42 @@ describe('DELETE social registration', () => {
|
||||
expect(user.auth.goodl).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
context('Apple', () => {
|
||||
it('fails if user does not have an alternative registration method', async () => {
|
||||
await user.update({
|
||||
'auth.apple.id': 'some-apple-id',
|
||||
'auth.local': { ok: true },
|
||||
});
|
||||
await expect(user.del('/user/auth/social/apple')).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cantDetachSocial'),
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds if user has a local registration', async () => {
|
||||
await user.update({
|
||||
'auth.apple.id': 'some-apple-id',
|
||||
});
|
||||
|
||||
const response = await user.del('/user/auth/social/apple');
|
||||
expect(response).to.eql({});
|
||||
await user.sync();
|
||||
expect(user.auth.apple).to.be.undefined;
|
||||
});
|
||||
|
||||
it('succeeds if user has a facebook registration', async () => {
|
||||
await user.update({
|
||||
'auth.apple.id': 'some-apple-id',
|
||||
'auth.facebook.id': 'some-facebook-id',
|
||||
'auth.local': { ok: true },
|
||||
});
|
||||
|
||||
const response = await user.del('/user/auth/social/apple');
|
||||
expect(response).to.eql({});
|
||||
await user.sync();
|
||||
expect(user.auth.goodl).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
generateUser,
|
||||
requester,
|
||||
getProperty,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import * as appleAuth from '../../../../../../website/server/libs/auth/apple';
|
||||
|
||||
describe('GET /user/auth/apple', () => {
|
||||
let api;
|
||||
let user;
|
||||
const appleEndpoint = '/user/auth/apple';
|
||||
|
||||
before(async () => {
|
||||
const expectedResult = { id: 'appleId', name: 'an apple user' };
|
||||
sandbox.stub(appleAuth, 'appleProfile').returns(Promise.resolve(expectedResult));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester();
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('registers a new user', async () => {
|
||||
const response = await api.get(appleEndpoint);
|
||||
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.true;
|
||||
await expect(getProperty('users', response.id, 'auth.apple.id')).to.eventually.equal('appleId');
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('an apple user');
|
||||
});
|
||||
|
||||
it('logs an existing user in', async () => {
|
||||
const registerResponse = await api.get(appleEndpoint);
|
||||
|
||||
const response = await api.get(appleEndpoint);
|
||||
|
||||
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
||||
expect(response.id).to.eql(registerResponse.id);
|
||||
expect(response.newUser).to.be.false;
|
||||
});
|
||||
|
||||
it('add social auth to an existing user', async () => {
|
||||
const response = await user.get(appleEndpoint);
|
||||
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.false;
|
||||
});
|
||||
});
|
||||
@@ -492,6 +492,74 @@ describe('POST /user/auth/local/register', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('attach to google user', () => {
|
||||
let user;
|
||||
const email = 'some@email-google.net';
|
||||
const username = 'some-username-google';
|
||||
const password = 'some-password';
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
it('checks onlySocialAttachLocal', async () => {
|
||||
await expect(user.post('/user/auth/local/register', {
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlySocialAttachLocal'),
|
||||
});
|
||||
});
|
||||
it('succeeds', async () => {
|
||||
await user.update({ 'auth.google.id': 'some-google-id', 'auth.local': { ok: true } });
|
||||
await user.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.auth.local.email).to.eql(email);
|
||||
});
|
||||
});
|
||||
|
||||
context('attach to apple user', () => {
|
||||
let user;
|
||||
const email = 'some@email-apple.net';
|
||||
const username = 'some-username-apple';
|
||||
const password = 'some-password';
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
it('checks onlySocialAttachLocal', async () => {
|
||||
await expect(user.post('/user/auth/local/register', {
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlySocialAttachLocal'),
|
||||
});
|
||||
});
|
||||
it('succeeds', async () => {
|
||||
await user.update({ 'auth.apple.id': 'some-apple-id', 'auth.local': { ok: true } });
|
||||
await user.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.auth.local.email).to.eql(email);
|
||||
});
|
||||
});
|
||||
|
||||
context('login is already taken', () => {
|
||||
let username; let email; let
|
||||
api;
|
||||
|
||||
@@ -51,6 +51,7 @@ describe('POST /user/auth/social', () => {
|
||||
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user');
|
||||
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.exist;
|
||||
await expect(getProperty('users', response.id, 'auth.facebook.id')).to.eventually.equal(facebookId);
|
||||
});
|
||||
|
||||
it('logs an existing user in', async () => {
|
||||
@@ -106,6 +107,7 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.true;
|
||||
await expect(getProperty('users', response.id, 'auth.google.id')).to.eventually.equal(googleId);
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
requester,
|
||||
translate,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
describe('GET /faq', () => {
|
||||
describe('language parameter', () => {
|
||||
it('returns faq (and does not require authentication)', async () => {
|
||||
const res = await requester().get('/faq');
|
||||
|
||||
expect(res).to.have.property('stillNeedHelp');
|
||||
expect(res.stillNeedHelp.ios).to.equal(translate('iosFaqStillNeedHelp'));
|
||||
expect(res).to.have.property('questions');
|
||||
expect(res.questions[0].question).to.equal(translate('faqQuestion0'));
|
||||
});
|
||||
|
||||
it('returns faq not in English', async () => {
|
||||
const res = await requester().get('/faq?language=de');
|
||||
expect(res).to.have.nested.property('stillNeedHelp.ios');
|
||||
expect(res.stillNeedHelp.ios).to.equal(i18n.t('iosFaqStillNeedHelp', 'de'));
|
||||
});
|
||||
|
||||
it('falls back to English if the desired language is not found', async () => {
|
||||
const res = await requester().get('/faq?language=wrong');
|
||||
expect(res).to.have.nested.property('stillNeedHelp.ios');
|
||||
expect(res.stillNeedHelp.ios).to.equal(translate('iosFaqStillNeedHelp'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('platform parameter', () => {
|
||||
it('returns faq with answers for ios platform only', async () => {
|
||||
const res = await requester().get('/faq?platform=ios');
|
||||
|
||||
expect(res).to.have.property('stillNeedHelp');
|
||||
expect(res.stillNeedHelp).to.eql({ ios: translate('iosFaqStillNeedHelp') });
|
||||
|
||||
expect(res).to.have.property('questions');
|
||||
expect(res.questions[0]).to.eql({
|
||||
question: translate('faqQuestion0'),
|
||||
ios: translate('iosFaqAnswer0'),
|
||||
});
|
||||
});
|
||||
it('returns an error when invalid platform parameter is specified', async () => {
|
||||
const request = requester().get('/faq?platform=wrong');
|
||||
await expect(request)
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: i18n.t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
it('falls back to "web" description if there is no description for specified platform', async () => {
|
||||
const res = await requester().get('/faq?platform=android');
|
||||
expect(res).to.have.property('stillNeedHelp');
|
||||
expect(res.stillNeedHelp).to.eql({ web: translate('webFaqStillNeedHelp') });
|
||||
|
||||
expect(res).to.have.property('questions');
|
||||
expect(res.questions[0]).to.eql({
|
||||
question: translate('faqQuestion0'),
|
||||
android: translate('androidFaqAnswer0'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -457,6 +457,74 @@ describe('POST /user/auth/local/register', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('attach to google user', () => {
|
||||
let user;
|
||||
const email = 'some-google@email.net';
|
||||
const username = 'some-username-google';
|
||||
const password = 'some-password';
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
it('checks onlySocialAttachLocal', async () => {
|
||||
await expect(user.post('/user/auth/local/register', {
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlySocialAttachLocal'),
|
||||
});
|
||||
});
|
||||
it('succeeds', async () => {
|
||||
await user.update({ 'auth.google.id': 'some-google-id', 'auth.local': { ok: true } });
|
||||
await user.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.auth.local.email).to.eql(email);
|
||||
});
|
||||
});
|
||||
|
||||
context('attach to apple user', () => {
|
||||
let user;
|
||||
const email = 'some-apple@email.net';
|
||||
const username = 'some-username-apple';
|
||||
const password = 'some-password';
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
it('checks onlySocialAttachLocal', async () => {
|
||||
await expect(user.post('/user/auth/local/register', {
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlySocialAttachLocal'),
|
||||
});
|
||||
});
|
||||
it('succeeds', async () => {
|
||||
await user.update({ 'auth.apple.id': 'some-apple-id', 'auth.local': { ok: true } });
|
||||
await user.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.auth.local.email).to.eql(email);
|
||||
});
|
||||
});
|
||||
|
||||
context('login is already taken', () => {
|
||||
let username; let email; let
|
||||
api;
|
||||
|
||||
@@ -89,6 +89,18 @@ describe('shared.ops.feed', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow feeding of wacky pets', done => {
|
||||
user.items.pets['Wolf-Veggie'] = 5;
|
||||
user.items.food.Meat = 1;
|
||||
try {
|
||||
feed(user, { params: { pet: 'Wolf-Veggie', food: 'Meat' } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('messageCannotFeedPet'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow feeding of mounts', done => {
|
||||
user.items.pets['Wolf-Base'] = -1;
|
||||
user.items.mounts['Wolf-Base'] = true;
|
||||
|
||||
@@ -5,6 +5,8 @@ import { v4 as generateUUID } from 'uuid';
|
||||
import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes';
|
||||
import { requester } from '../requester';
|
||||
import * as Tasks from '../../../../website/server/models/task';
|
||||
import payments from '../../../../website/server/libs/payments/payments';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
|
||||
// Creates a new user and returns it
|
||||
// If you need the user to have specific requirements,
|
||||
@@ -83,14 +85,35 @@ export async function generateGroup (leader, details = {}, update = {}) {
|
||||
return apiGroup;
|
||||
}
|
||||
|
||||
async function _upgradeToGroupPlan (groupLeader, group) {
|
||||
const groupLeaderModel = await User.findById(groupLeader._id).exec();
|
||||
|
||||
// Create subscription
|
||||
const paymentData = {
|
||||
user: groupLeaderModel,
|
||||
groupId: group._id,
|
||||
sub: {
|
||||
key: 'basic_3mo',
|
||||
},
|
||||
customerId: 'customer-id',
|
||||
paymentMethod: 'Payment Method',
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
};
|
||||
await payments.createSubscription(paymentData);
|
||||
}
|
||||
|
||||
// This is generate group + the ability to create
|
||||
// real users to populate it. The settings object
|
||||
// takes in:
|
||||
// members: Number - the number of group members to create.
|
||||
// Defaults to 0. Does not include group leader.
|
||||
// inivtes: Number - the number of users to create and invite to the group. Defaults to 0.
|
||||
// invites: Number - the number of users to create and invite to the group. Defaults to 0.
|
||||
// groupDetails: Object - how to initialize the group
|
||||
// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user
|
||||
// addGroupPlan: boolean - will add group plan with basic subscription. Defaults to false
|
||||
// can create the group
|
||||
//
|
||||
// Returns an object with
|
||||
@@ -101,6 +124,7 @@ export async function generateGroup (leader, details = {}, update = {}) {
|
||||
export async function createAndPopulateGroup (settings = {}) {
|
||||
const numberOfMembers = settings.members || 0;
|
||||
const numberOfInvites = settings.invites || 0;
|
||||
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
||||
const { groupDetails } = settings;
|
||||
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||
|
||||
@@ -130,6 +154,10 @@ export async function createAndPopulateGroup (settings = {}) {
|
||||
|
||||
await Promise.all(invitees.map(invitee => invitee.sync()));
|
||||
|
||||
if (upgradeToGroupPlan) {
|
||||
await _upgradeToGroupPlan(groupLeader, group);
|
||||
}
|
||||
|
||||
return {
|
||||
groupLeader,
|
||||
group,
|
||||
|
||||
@@ -13,25 +13,25 @@
|
||||
"test:unit": "vue-cli-service test:unit --require ./tests/unit/helpers.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.2.3",
|
||||
"@vue/cli-plugin-eslint": "^4.2.3",
|
||||
"@vue/cli-plugin-router": "^4.2.3",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.2.3",
|
||||
"@vue/cli-service": "^4.2.3",
|
||||
"@storybook/addon-actions": "^5.3.17",
|
||||
"@storybook/addon-knobs": "^5.3.17",
|
||||
"@storybook/addon-links": "^5.3.17",
|
||||
"@storybook/addon-notes": "^5.3.17",
|
||||
"@storybook/vue": "^5.3.17",
|
||||
"@vue/cli-plugin-babel": "^4.3.1",
|
||||
"@vue/cli-plugin-eslint": "^4.3.1",
|
||||
"@vue/cli-plugin-router": "^4.3.1",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.3.1",
|
||||
"@vue/cli-service": "^4.3.1",
|
||||
"@storybook/addon-actions": "^5.3.18",
|
||||
"@storybook/addon-knobs": "^5.3.18",
|
||||
"@storybook/addon-links": "^5.3.18",
|
||||
"@storybook/addon-notes": "^5.3.18",
|
||||
"@storybook/vue": "^5.3.18",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^5.10.0",
|
||||
"amplitude-js": "^5.11.0",
|
||||
"axios": "^0.19.2",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^4.4.1",
|
||||
"bootstrap-vue": "^2.9.0",
|
||||
"bootstrap-vue": "^2.11.0",
|
||||
"chai": "^4.1.2",
|
||||
"core-js": "^3.6.4",
|
||||
"core-js": "^3.6.5",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
@@ -40,7 +40,7 @@
|
||||
"hellojs": "^1.18.4",
|
||||
"inspectpack": "^4.4.0",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": "^3.4.1",
|
||||
"jquery": "^3.5.0",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0",
|
||||
"nconf": "^0.10.0",
|
||||
@@ -58,7 +58,6 @@
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.1.6",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vue2-perfect-scrollbar": "^1.4.0",
|
||||
"vuedraggable": "^2.23.1",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^4.42.1"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
The files in the following subfolders:
|
||||
|
||||
- audio
|
||||
- emails
|
||||
- icons
|
||||
- merch
|
||||
- presskit
|
||||
|
||||
are not processed by Webpack so their filenames don't get hashed, but given that they almost never change, they're still cached for 1 week.
|
||||
|
||||
In case one of the files needs to be updated the filename should be changed if possible.
|
||||
|
||||
For more information see `website/server/middlewares/static.js`.
|
||||
@@ -20,10 +20,6 @@
|
||||
</svg>
|
||||
<!-- eslint-enable max-len -->
|
||||
</div>
|
||||
<div class="col-12 text-center">
|
||||
<h2>{{ $t('tipTitle', {tipNumber: currentTipNumber}) }}</h2>
|
||||
<p>{{ currentTip }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -297,7 +293,6 @@ export default {
|
||||
audioSuffix: null,
|
||||
|
||||
loading: true,
|
||||
currentTipNumber: 0,
|
||||
bannerHidden: false,
|
||||
};
|
||||
},
|
||||
@@ -310,15 +305,6 @@ export default {
|
||||
castingSpell () {
|
||||
return this.$store.state.spellOptions.castingSpell;
|
||||
},
|
||||
currentTip () {
|
||||
const numberOfTips = 35 + 1;
|
||||
const min = 1;
|
||||
const randomNumber = Math.random() * (numberOfTips - min) + min;
|
||||
const tipNumber = Math.floor(randomNumber);
|
||||
this.currentTipNumber = tipNumber; // eslint-disable-line vue/no-side-effects-in-computed-properties, max-len
|
||||
|
||||
return this.$t(`tip${tipNumber}`);
|
||||
},
|
||||
showRestingBanner () {
|
||||
return !this.bannerHidden && this.user && this.user.preferences.sleep;
|
||||
},
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Dessert {
|
||||
background: url("~@/assets/images/animated/Pet_HatchingPotion_Dessert.gif") no-repeat;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Veggie {
|
||||
background: url("~@/assets/images/animated/Pet_HatchingPotion_Veggie.gif") no-repeat;
|
||||
width: 68px;
|
||||
|
||||
@@ -1,27 +1,51 @@
|
||||
.promo_armoire_backgrounds_202004 {
|
||||
.promo_april_fools_2020 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -445px -184px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_armoire_backgrounds_202004 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -433px -337px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_egg_quest {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -500px;
|
||||
background-position: 0px -648px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_202004 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -355px -500px;
|
||||
background-position: 0px -796px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_pastel_skin_hair {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -355px -648px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.customize-option.promo_pastel_skin_hair {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -380px -663px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_seasonal_shop_spring {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -638px -500px;
|
||||
background-position: -875px -524px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_shiny_seeds {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -500px;
|
||||
width: 360px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_spring_2019 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -337px;
|
||||
@@ -36,16 +60,22 @@
|
||||
}
|
||||
.promo_spring_potions_2020 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -433px -337px;
|
||||
background-position: 0px -500px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -151px -648px;
|
||||
background-position: -875px -663px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.scene_citrusella {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -875px -196px;
|
||||
width: 152px;
|
||||
height: 176px;
|
||||
}
|
||||
.scene_hat_guild {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
@@ -54,7 +84,13 @@
|
||||
}
|
||||
.scene_meditation {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -648px;
|
||||
background-position: -875px -373px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_tasks {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -875px 0px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
.quest_dustbunnies {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -660px;
|
||||
background-position: -440px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -30,19 +30,19 @@
|
||||
}
|
||||
.quest_evilsanta2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -232px;
|
||||
background-position: 0px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_falcon {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -232px;
|
||||
background-position: -220px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_ferret {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px 0px;
|
||||
background-position: -440px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -54,13 +54,13 @@
|
||||
}
|
||||
.quest_ghost_stag {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -452px;
|
||||
background-position: -660px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -452px;
|
||||
background-position: -660px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -84,13 +84,13 @@
|
||||
}
|
||||
.quest_guineapig {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -220px;
|
||||
background-position: 0px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_harpy {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -440px;
|
||||
background-position: -220px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -102,43 +102,43 @@
|
||||
}
|
||||
.quest_hippo {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -672px;
|
||||
background-position: -440px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_horse {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -672px;
|
||||
background-position: -660px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kangaroo {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -672px;
|
||||
background-position: -880px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kraken {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1311px -1332px;
|
||||
background-position: -660px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_lostMasterclasser1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px 0px;
|
||||
background-position: -880px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -220px;
|
||||
background-position: -880px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -440px;
|
||||
background-position: 0px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -150,61 +150,61 @@
|
||||
}
|
||||
.quest_mayhemMistiflying2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -892px;
|
||||
background-position: -220px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -892px;
|
||||
background-position: -440px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_monkey {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -892px;
|
||||
background-position: -660px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -1082px;
|
||||
background-position: -1540px -214px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_moon2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -892px;
|
||||
background-position: -880px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -892px;
|
||||
background-position: -1100px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px 0px;
|
||||
background-position: -1100px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -220px;
|
||||
background-position: -1100px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -440px;
|
||||
background-position: -1100px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_nudibranch {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -865px;
|
||||
background-position: -1540px -431px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
@@ -216,7 +216,7 @@
|
||||
}
|
||||
.quest_owl {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -1112px;
|
||||
background-position: 0px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -234,25 +234,25 @@
|
||||
}
|
||||
.quest_pterodactyl {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -1112px;
|
||||
background-position: -220px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rat {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -1112px;
|
||||
background-position: -440px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_robot {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -1112px;
|
||||
background-position: -660px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rock {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -214px;
|
||||
background-position: -1540px -865px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
@@ -264,43 +264,43 @@
|
||||
}
|
||||
.quest_ruby {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -1112px;
|
||||
background-position: -880px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sabretooth {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -1112px;
|
||||
background-position: -1100px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_seaserpent {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -880px;
|
||||
background-position: -1320px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sheep {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -892px;
|
||||
background-position: -1320px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_silver {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -660px;
|
||||
background-position: -1320px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_slime {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -672px;
|
||||
background-position: -1320px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sloth {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -672px;
|
||||
background-position: -1320px -880px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -312,7 +312,7 @@
|
||||
}
|
||||
.quest_snake {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -1332px;
|
||||
background-position: -877px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
@@ -324,43 +324,43 @@
|
||||
}
|
||||
.quest_squirrel {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px 0px;
|
||||
background-position: 0px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -872px;
|
||||
background-position: -1762px -721px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_stoikalmCalamity2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -452px;
|
||||
background-position: -220px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -220px;
|
||||
background-position: -440px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_taskwoodsTerror1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -721px;
|
||||
background-position: -1762px -872px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_taskwoodsTerror2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -431px;
|
||||
background-position: -1540px -1082px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_taskwoodsTerror3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -232px;
|
||||
background-position: -660px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -378,19 +378,19 @@
|
||||
}
|
||||
.quest_trex_undead {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -877px -1332px;
|
||||
background-position: -1311px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_triceratops {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px 0px;
|
||||
background-position: -880px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_turtle {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -452px;
|
||||
background-position: -1100px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
@@ -12,25 +12,25 @@
|
||||
}
|
||||
.hair_bangs_3_peppermint {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -91px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_bangs_3_peppermint {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -116px -15px;
|
||||
background-position: -25px -15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.hair_bangs_3_pgreen {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -728px -1092px;
|
||||
background-position: -91px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_bangs_3_pgreen {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -753px -1107px;
|
||||
background-position: -116px -15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
@@ -1512,13 +1512,13 @@
|
||||
}
|
||||
.hair_base_11_ppurple2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -728px -1092px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_11_ppurple2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -25px -15px;
|
||||
background-position: -753px -1107px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
.hair_base_15_white {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -91px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_15_white {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -116px -0px;
|
||||
background-position: -25px -0px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.hair_base_15_winternight {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -728px -1092px;
|
||||
background-position: -91px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_15_winternight {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -753px -1092px;
|
||||
background-position: -116px -0px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
@@ -2448,13 +2448,13 @@
|
||||
}
|
||||
.hair_base_2_blue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -728px -1092px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_2_blue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -25px -15px;
|
||||
background-position: -753px -1107px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
@@ -12,25 +12,25 @@
|
||||
}
|
||||
.hair_base_5_holly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -91px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_5_holly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -116px -15px;
|
||||
background-position: -25px -15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.hair_base_5_hollygreen {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -728px -1092px;
|
||||
background-position: -91px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_5_hollygreen {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -753px -1107px;
|
||||
background-position: -116px -15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
@@ -1980,13 +1980,13 @@
|
||||
}
|
||||
.hair_base_9_porange {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -728px -1092px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_9_porange {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -25px -15px;
|
||||
background-position: -753px -1107px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 643 KiB After Width: | Height: | Size: 643 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 424 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
@@ -53,8 +53,8 @@
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
cursor: pointer;
|
||||
|
||||
& svg path {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="170px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 170 170" version="1.1" height="170px">
|
||||
<path d="m150.37 130.25c-2.45 5.66-5.35 10.87-8.71 15.66-4.58 6.53-8.33 11.05-11.22 13.56-4.48 4.12-9.28 6.23-14.42 6.35-3.69 0-8.14-1.05-13.32-3.18-5.197-2.12-9.973-3.17-14.34-3.17-4.58 0-9.492 1.05-14.746 3.17-5.262 2.13-9.501 3.24-12.742 3.35-4.929 0.21-9.842-1.96-14.746-6.52-3.13-2.73-7.045-7.41-11.735-14.04-5.032-7.08-9.169-15.29-12.41-24.65-3.471-10.11-5.211-19.9-5.211-29.378 0-10.857 2.346-20.221 7.045-28.068 3.693-6.303 8.606-11.275 14.755-14.925s12.793-5.51 19.948-5.629c3.915 0 9.049 1.211 15.429 3.591 6.362 2.388 10.447 3.599 12.238 3.599 1.339 0 5.877-1.416 13.57-4.239 7.275-2.618 13.415-3.702 18.445-3.275 13.63 1.1 23.87 6.473 30.68 16.153-12.19 7.386-18.22 17.731-18.1 31.002 0.11 10.337 3.86 18.939 11.23 25.769 3.34 3.17 7.07 5.62 11.22 7.36-0.9 2.61-1.85 5.11-2.86 7.51zm-31.26-123.01c0 8.1021-2.96 15.667-8.86 22.669-7.12 8.324-15.732 13.134-25.071 12.375-0.119-0.972-0.188-1.995-0.188-3.07 0-7.778 3.386-16.102 9.399-22.908 3.002-3.446 6.82-6.3113 11.45-8.597 4.62-2.2516 8.99-3.4968 13.1-3.71 0.12 1.0831 0.17 2.1663 0.17 3.2409z" fill="#FFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="1000"
|
||||
viewBox="0 0 1000 1187.198"
|
||||
version="1.1"
|
||||
height="1187.198"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="Apple_1998.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="705"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.1767767"
|
||||
inkscape:cx="-1066.5045"
|
||||
inkscape:cy="964.94669"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="m 979.04184,925.18785 c -17.95397,41.47737 -39.20563,79.65705 -63.82824,114.75895 -33.56298,47.8528 -61.04356,80.9761 -82.22194,99.3698 -32.83013,30.192 -68.00529,45.6544 -105.67203,46.5338 -27.04089,0 -59.6512,-7.6946 -97.61105,-23.3035 -38.08442,-15.5358 -73.08371,-23.2303 -105.08578,-23.2303 -33.56296,0 -69.55888,7.6945 -108.06101,23.2303 -38.5608,15.6089 -69.62484,23.7432 -93.37541,24.5493 -36.12049,1.5389 -72.1237,-14.3632 -108.06101,-47.7796 -22.93711,-20.0059 -51.62684,-54.3017 -85.99592,-102.8874 C 92.254176,984.54592 61.937588,924.38175 38.187028,855.7902 12.750995,781.70252 0,709.95986 0,640.50361 0,560.94181 17.191859,492.32094 51.626869,434.81688 78.689754,388.62753 114.69299,352.19192 159.75381,325.44413 c 45.06086,-26.74775 93.74914,-40.37812 146.18212,-41.25019 28.68971,0 66.3125,8.8744 113.06613,26.31542 46.62174,17.49964 76.55727,26.37404 89.68198,26.37404 9.8124,0 43.06758,-10.37669 99.4431,-31.06405 53.31237,-19.18512 98.30724,-27.12887 135.16787,-23.99975 99.8828,8.06098 174.92313,47.43518 224.82789,118.37174 -89.33023,54.12578 -133.51903,129.93556 -132.63966,227.18753 0.8061,75.75115 28.28668,138.78795 82.2952,188.8393 24.47603,23.23022 51.81008,41.18421 82.22186,53.93522 -6.59525,19.12648 -13.557,37.44688 -20.95846,55.03446 z M 749.96366,23.751237 c 0,59.37343 -21.69138,114.810233 -64.92748,166.121963 -52.17652,60.99961 -115.28658,96.24803 -183.72426,90.68597 -0.87204,-7.12298 -1.37769,-14.61967 -1.37769,-22.49743 0,-56.99843 24.81315,-117.99801 68.87738,-167.873453 21.99909,-25.25281 49.978,-46.25018 83.90738,-63.00018 C 686.57507,10.688027 718.59913,1.5631274 748.71783,5.2734376e-4 749.59727,7.9378274 749.96366,15.875627 749.96366,23.750467 Z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -7,20 +7,31 @@
|
||||
>
|
||||
<div class="content text-center">
|
||||
<span
|
||||
v-once
|
||||
class="close-icon svg-icon inline icon-10"
|
||||
@click="close()"
|
||||
v-html="icons.close"
|
||||
></span>
|
||||
<h2>{{ $t('congratulations') }}</h2>
|
||||
<h2 v-once>
|
||||
{{ $t('onboardingComplete') }}
|
||||
</h2>
|
||||
<img
|
||||
class="onboarding-complete-banner d-block"
|
||||
src="~@/assets/images/onboarding-complete-banner@2x.png"
|
||||
>
|
||||
<p
|
||||
v-once
|
||||
class="onboarding-complete-text"
|
||||
v-html="$t('onboardingCompleteDesc')"
|
||||
></p>
|
||||
<p
|
||||
v-once
|
||||
class="onboarding-complete-text-small"
|
||||
>
|
||||
{{ $t('onboardingCompleteDescSmall') }}
|
||||
</p>
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary"
|
||||
@click="closeWithAction()"
|
||||
>
|
||||
@@ -37,7 +48,7 @@
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-top: 1em;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -58,7 +69,7 @@ h2 {
|
||||
|
||||
.content {
|
||||
padding: 0 8px;
|
||||
margin-top: 18px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.onboarding-complete-banner {
|
||||
@@ -68,13 +79,21 @@ h2 {
|
||||
}
|
||||
|
||||
.onboarding-complete-text {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.onboarding-complete-text ::v-deep .gold-amount {
|
||||
color: $yellow-5;
|
||||
}
|
||||
|
||||
.onboarding-complete-text-small {
|
||||
margin-bottom: 1.5rem;
|
||||
color: $gray-100;
|
||||
font-style: normal;
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@@ -49,11 +49,13 @@
|
||||
target="_blank"
|
||||
>{{ $t('oldNews') }}</a>
|
||||
</li>
|
||||
<!-- Commenting out merch page see https://github.com/HabitRPG/habitica/issues/12039
|
||||
<li>
|
||||
<router-link to="/static/merch">
|
||||
{{ $t('merch') }}
|
||||
</router-link>
|
||||
</li>
|
||||
-->
|
||||
<li>
|
||||
<router-link to="/static/press-kit">
|
||||
{{ $t('presskit') }}
|
||||
@@ -214,7 +216,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-5">
|
||||
© 2019 Habitica. All rights reserved.
|
||||
© 2020 Habitica. All rights reserved.
|
||||
<div
|
||||
v-if="!IS_PRODUCTION && isUserLoaded"
|
||||
class="debug float-left"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="form">
|
||||
<div class="form-group row text-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12">
|
||||
<div
|
||||
class="btn btn-secondary social-button"
|
||||
@click="socialAuth('facebook')"
|
||||
@@ -15,7 +15,9 @@
|
||||
: $t('loginWithSocial', {social: 'Facebook'}) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
</div>
|
||||
<div class="form-group row text-center">
|
||||
<div class="col-12">
|
||||
<div
|
||||
class="btn btn-secondary social-button"
|
||||
@click="socialAuth('google')"
|
||||
@@ -30,6 +32,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row text-center">
|
||||
<div class="col-12">
|
||||
<div
|
||||
class="btn btn-secondary social-button"
|
||||
@click="socialAuth('apple')"
|
||||
>
|
||||
<div
|
||||
class="svg-icon social-icon apple-icon"
|
||||
v-html="icons.appleIcon"
|
||||
></div>
|
||||
<span>{{ registering
|
||||
? $t('signUpWithSocial', {social: 'Apple'})
|
||||
: $t('loginWithSocial', {social: 'Apple'}) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="registering"
|
||||
class="form-group"
|
||||
@@ -199,7 +217,11 @@
|
||||
height: 18px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-top: .2em;
|
||||
margin-top: .1em;
|
||||
}
|
||||
|
||||
.apple-icon {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
small.form-text {
|
||||
@@ -219,10 +241,11 @@
|
||||
import hello from 'hellojs';
|
||||
import debounce from 'lodash/debounce';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
import { setUpAxios } from '@/libs/auth';
|
||||
import { setUpAxios, buildAppleAuthUrl } from '@/libs/auth';
|
||||
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
|
||||
import facebookSquareIcon from '@/assets/svg/facebook-square.svg';
|
||||
import googleIcon from '@/assets/svg/google.svg';
|
||||
import appleIcon from '@/assets/svg/apple_black.svg';
|
||||
|
||||
export default {
|
||||
name: 'AuthForm',
|
||||
@@ -239,6 +262,7 @@ export default {
|
||||
data.icons = Object.freeze({
|
||||
facebookIcon: facebookSquareIcon,
|
||||
googleIcon,
|
||||
appleIcon,
|
||||
});
|
||||
|
||||
return data;
|
||||
@@ -307,27 +331,31 @@ export default {
|
||||
}, 500),
|
||||
// @TODO: Abstract hello in to action or lib
|
||||
async socialAuth (network) {
|
||||
try {
|
||||
await hello(network).logout();
|
||||
} catch (e) {} // eslint-disable-line
|
||||
if (network === 'apple') {
|
||||
window.location.href = buildAppleAuthUrl();
|
||||
} else {
|
||||
try {
|
||||
await hello(network).logout();
|
||||
} catch (e) {} // eslint-disable-line
|
||||
|
||||
try {
|
||||
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
|
||||
const auth = await hello(network).login({
|
||||
scope: 'email',
|
||||
redirect_uri: redirectUrl, // eslint-disable-line camelcase
|
||||
});
|
||||
try {
|
||||
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
|
||||
const auth = await hello(network).login({
|
||||
scope: 'email',
|
||||
redirect_uri: redirectUrl, // eslint-disable-line camelcase
|
||||
});
|
||||
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
auth,
|
||||
});
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
auth,
|
||||
});
|
||||
|
||||
await this.finishAuth();
|
||||
} catch (err) {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
// logout the user
|
||||
await hello(network).logout();
|
||||
this.socialAuth(network); // login again
|
||||
await this.finishAuth();
|
||||
} catch (err) {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
// logout the user
|
||||
await hello(network).logout();
|
||||
this.socialAuth(network); // login again
|
||||
}
|
||||
}
|
||||
},
|
||||
async register () {
|
||||
|
||||