Compare commits
1 Commits
v4.276.2
...
phillip/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a018588021 |
@@ -86,6 +86,5 @@
|
||||
"RATE_LIMITER_ENABLED": "false",
|
||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||
"REDIS_PORT": "1234",
|
||||
"REDIS_PASSWORD": "12345678",
|
||||
"TRUSTED_DOMAINS": "https://localhost,https://habitica.com"
|
||||
"REDIS_PASSWORD": "12345678"
|
||||
}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20221213_pet_group_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['BearCub-Base']
|
||||
&& pets['BearCub-CottonCandyBlue']
|
||||
&& pets['BearCub-CottonCandyPink']
|
||||
&& pets['BearCub-Desert']
|
||||
&& pets['BearCub-Golden']
|
||||
&& pets['BearCub-Red']
|
||||
&& pets['BearCub-Shade']
|
||||
&& pets['BearCub-Skeleton']
|
||||
&& pets['BearCub-White']
|
||||
&& pets['BearCub-Zombie']
|
||||
&& pets['Fox-Base']
|
||||
&& pets['Fox-CottonCandyBlue']
|
||||
&& pets['Fox-CottonCandyPink']
|
||||
&& pets['Fox-Desert']
|
||||
&& pets['Fox-Golden']
|
||||
&& pets['Fox-Red']
|
||||
&& pets['Fox-Shade']
|
||||
&& pets['Fox-Skeleton']
|
||||
&& pets['Fox-White']
|
||||
&& pets['Fox-Zombie']
|
||||
&& pets['Penguin-Base']
|
||||
&& pets['Penguin-CottonCandyBlue']
|
||||
&& pets['Penguin-CottonCandyPink']
|
||||
&& pets['Penguin-Desert']
|
||||
&& pets['Penguin-Golden']
|
||||
&& pets['Penguin-Red']
|
||||
&& pets['Penguin-Shade']
|
||||
&& pets['Penguin-Skeleton']
|
||||
&& pets['Penguin-White']
|
||||
&& pets['Penguin-Zombie']
|
||||
&& pets['Whale-Base']
|
||||
&& pets['Whale-CottonCandyBlue']
|
||||
&& pets['Whale-CottonCandyPink']
|
||||
&& pets['Whale-Desert']
|
||||
&& pets['Whale-Golden']
|
||||
&& pets['Whale-Red']
|
||||
&& pets['Whale-Shade']
|
||||
&& pets['Whale-Skeleton']
|
||||
&& pets['Whale-White']
|
||||
&& pets['Whale-Zombie']
|
||||
&& pets['Wolf-Base']
|
||||
&& pets['Wolf-CottonCandyBlue']
|
||||
&& pets['Wolf-CottonCandyPink']
|
||||
&& pets['Wolf-Desert']
|
||||
&& pets['Wolf-Golden']
|
||||
&& pets['Wolf-Red']
|
||||
&& pets['Wolf-Shade']
|
||||
&& pets['Wolf-Skeleton']
|
||||
&& pets['Wolf-White']
|
||||
&& pets['Wolf-Zombie'] {
|
||||
set['achievements.polarPro'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
// migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2022-11-01') },
|
||||
};
|
||||
|
||||
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]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,144 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20221227_nye';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = { migration: MIGRATION_NAME };
|
||||
let push;
|
||||
|
||||
if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2022'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2022',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2021'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2021',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2020'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2020',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2019'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2019',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else 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();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-01')},
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230123_habit_birthday';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const inc = { 'balance': 5 };
|
||||
const set = {};
|
||||
const push = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.armor_special_birthday2022 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2023'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2021 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2022'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2020 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2021'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2019 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2020'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2019'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2018'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2017'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2016'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2015'] = true;
|
||||
} else {
|
||||
set['items.gear.owned.armor_special_birthday'] = true;
|
||||
}
|
||||
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 1!',
|
||||
text: 'Enjoy your new Birthday Robe and 20 Gems on us!',
|
||||
destination: 'equipment',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230127_habit_birthday_day5';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const set = {};
|
||||
const push = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
set['items.gear.owned.back_special_anniversary'] = true;
|
||||
set['items.gear.owned.body_special_anniversary'] = true;
|
||||
set['items.gear.owned.eyewear_special_anniversary'] = true;
|
||||
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 5!',
|
||||
text: 'Come celebrate by wearing your new Habitica Hero Cape, Collar, and Mask!',
|
||||
destination: 'equipment',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230201_habit_birthday_day10';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
'purchased.background.birthday_bash': true,
|
||||
};
|
||||
const push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 10!',
|
||||
text: 'Join in for the end of our birthday celebrations with 10th Birthday background, Cake, and achievement!',
|
||||
destination: 'backgrounds',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
const inc = {
|
||||
'items.food.Cake_Skeleton': 1,
|
||||
'items.food.Cake_Base': 1,
|
||||
'items.food.Cake_CottonCandyBlue': 1,
|
||||
'items.food.Cake_CottonCandyPink': 1,
|
||||
'items.food.Cake_Shade': 1,
|
||||
'items.food.Cake_White': 1,
|
||||
'items.food.Cake_Golden': 1,
|
||||
'items.food.Cake_Zombie': 1,
|
||||
'items.food.Cake_Desert': 1,
|
||||
'items.food.Cake_Red': 1,
|
||||
'achievements.habitBirthdays': 1,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push, $inc: inc }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -1,158 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230522_pet_group_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['Parrot-Base']
|
||||
&& pets['Parrot-CottonCandyBlue']
|
||||
&& pets['Parrot-CottonCandyPink']
|
||||
&& pets['Parrot-Desert']
|
||||
&& pets['Parrot-Golden']
|
||||
&& pets['Parrot-Red']
|
||||
&& pets['Parrot-Shade']
|
||||
&& pets['Parrot-Skeleton']
|
||||
&& pets['Parrot-White']
|
||||
&& pets['Parrot-Zombie']
|
||||
&& pets['Rooster-Base']
|
||||
&& pets['Rooster-CottonCandyBlue']
|
||||
&& pets['Rooster-CottonCandyPink']
|
||||
&& pets['Rooster-Desert']
|
||||
&& pets['Rooster-Golden']
|
||||
&& pets['Rooster-Red']
|
||||
&& pets['Rooster-Shade']
|
||||
&& pets['Rooster-Skeleton']
|
||||
&& pets['Rooster-White']
|
||||
&& pets['Rooster-Zombie']
|
||||
&& pets['Triceratops-Base']
|
||||
&& pets['Triceratops-CottonCandyBlue']
|
||||
&& pets['Triceratops-CottonCandyPink']
|
||||
&& pets['Triceratops-Desert']
|
||||
&& pets['Triceratops-Golden']
|
||||
&& pets['Triceratops-Red']
|
||||
&& pets['Triceratops-Shade']
|
||||
&& pets['Triceratops-Skeleton']
|
||||
&& pets['Triceratops-White']
|
||||
&& pets['Triceratops-Zombie']
|
||||
&& pets['TRex-Base']
|
||||
&& pets['TRex-CottonCandyBlue']
|
||||
&& pets['TRex-CottonCandyPink']
|
||||
&& pets['TRex-Desert']
|
||||
&& pets['TRex-Golden']
|
||||
&& pets['TRex-Red']
|
||||
&& pets['TRex-Shade']
|
||||
&& pets['TRex-Skeleton']
|
||||
&& pets['TRex-White']
|
||||
&& pets['TRex-Zombie']
|
||||
&& pets['Pterodactyl-Base']
|
||||
&& pets['Pterodactyl-CottonCandyBlue']
|
||||
&& pets['Pterodactyl-CottonCandyPink']
|
||||
&& pets['Pterodactyl-Desert']
|
||||
&& pets['Pterodactyl-Golden']
|
||||
&& pets['Pterodactyl-Red']
|
||||
&& pets['Pterodactyl-Shade']
|
||||
&& pets['Pterodactyl-Skeleton']
|
||||
&& pets['Pterodactyl-White']
|
||||
&& pets['Pterodactyl-Zombie']
|
||||
&& pets['Owl-Base']
|
||||
&& pets['Owl-CottonCandyBlue']
|
||||
&& pets['Owl-CottonCandyPink']
|
||||
&& pets['Owl-Desert']
|
||||
&& pets['Owl-Golden']
|
||||
&& pets['Owl-Red']
|
||||
&& pets['Owl-Shade']
|
||||
&& pets['Owl-Skeleton']
|
||||
&& pets['Owl-White']
|
||||
&& pets['Owl-Zombie']
|
||||
&& pets['Velociraptor-Base']
|
||||
&& pets['Velociraptor-CottonCandyBlue']
|
||||
&& pets['Velociraptor-CottonCandyPink']
|
||||
&& pets['Velociraptor-Desert']
|
||||
&& pets['Velociraptor-Golden']
|
||||
&& pets['Velociraptor-Red']
|
||||
&& pets['Velociraptor-Shade']
|
||||
&& pets['Velociraptor-Skeleton']
|
||||
&& pets['Velociraptor-White']
|
||||
&& pets['Velociraptor-Zombie']
|
||||
&& pets['Penguin-Base']
|
||||
&& pets['Penguin-CottonCandyBlue']
|
||||
&& pets['Penguin-CottonCandyPink']
|
||||
&& pets['Penguin-Desert']
|
||||
&& pets['Penguin-Golden']
|
||||
&& pets['Penguin-Red']
|
||||
&& pets['Penguin-Shade']
|
||||
&& pets['Penguin-Skeleton']
|
||||
&& pets['Penguin-White']
|
||||
&& pets['Penguin-Zombie']
|
||||
&& pets['Falcon-Base']
|
||||
&& pets['Falcon-CottonCandyBlue']
|
||||
&& pets['Falcon-CottonCandyPink']
|
||||
&& pets['Falcon-Desert']
|
||||
&& pets['Falcon-Golden']
|
||||
&& pets['Falcon-Red']
|
||||
&& pets['Falcon-Shade']
|
||||
&& pets['Falcon-Skeleton']
|
||||
&& pets['Falcon-White']
|
||||
&& pets['Falcon-Zombie']
|
||||
&& pets['Peacock-Base']
|
||||
&& pets['Peacock-CottonCandyBlue']
|
||||
&& pets['Peacock-CottonCandyPink']
|
||||
&& pets['Peacock-Desert']
|
||||
&& pets['Peacock-Golden']
|
||||
&& pets['Peacock-Red']
|
||||
&& pets['Peacock-Shade']
|
||||
&& pets['Peacock-Skeleton']
|
||||
&& pets['Peacock-White']
|
||||
&& pets['Peacock-Zombie']) {
|
||||
set['achievements.dinosaurDynasty'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
// migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-04-15') },
|
||||
};
|
||||
|
||||
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]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230718_summer_splash_orcas';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = { migration: MIGRATION_NAME };
|
||||
const push = {};
|
||||
|
||||
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||
return;
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||
set['items.pets.Orca-Base'] = 5;
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_orca_pet',
|
||||
title: 'Orcas for Summer Splash!',
|
||||
text: 'To celebrate Summer Splash, we\'ve given you an Orca Pet!',
|
||||
destination: 'stable',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
} else {
|
||||
set['items.mounts.Orca-Base'] = true;
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_orca_mount',
|
||||
title: 'Orcas for Summer Splash!',
|
||||
text: 'To celebrate Summer Splash, we\'ve given you an Orca Mount!',
|
||||
destination: 'stable',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await user.updateOne({ $set: set, $push: push }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2023-06-18')},
|
||||
};
|
||||
|
||||
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)
|
||||
.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
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230314_pi_day';
|
||||
const MIGRATION_NAME = '20220314_pi_day';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
@@ -54,7 +54,7 @@ async function updateUser (user) {
|
||||
export default async function processUsers () {
|
||||
const query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-02-15') },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2022-02-15') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
|
||||
3425
package-lock.json
generated
38
package.json
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.276.2",
|
||||
"version": "4.251.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/preset-env": "^7.22.5",
|
||||
"@babel/register": "^7.22.5",
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/register": "^7.18.9",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.1.3",
|
||||
"@slack/webhook": "^6.1.0",
|
||||
"accepts": "^1.3.8",
|
||||
"amazon-payments": "^0.2.9",
|
||||
"amplitude": "^6.0.0",
|
||||
"apidoc": "^0.54.0",
|
||||
"apple-auth": "^1.0.9",
|
||||
"apidoc": "^0.53.1",
|
||||
"apple-auth": "^1.0.7",
|
||||
"bcrypt": "^5.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"body-parser": "^1.20.1",
|
||||
"bootstrap": "^4.6.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^2.0.0",
|
||||
@@ -30,7 +30,7 @@
|
||||
"express": "^4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-validator": "^5.2.0",
|
||||
"glob": "^8.1.0",
|
||||
"glob": "^8.0.3",
|
||||
"got": "^11.8.3",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
@@ -42,7 +42,7 @@
|
||||
"image-size": "^1.0.2",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^5.0.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "^2.1.5",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -54,7 +54,7 @@
|
||||
"nconf": "^0.12.0",
|
||||
"node-gcm": "^1.0.5",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.5.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-google-oauth2": "^0.2.0",
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
@@ -67,16 +67,16 @@
|
||||
"remove-markdown": "^0.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.2.2",
|
||||
"stripe": "^12.9.0",
|
||||
"superagent": "^8.0.9",
|
||||
"stripe": "^10.13.0",
|
||||
"superagent": "^8.0.5",
|
||||
"universal-analytics": "^0.5.3",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.9.0",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.7.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^3.9.0",
|
||||
"winston": "^3.8.2",
|
||||
"winston-loggly-bulk": "^3.2.1",
|
||||
"xml2js": "^0.6.0"
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
@@ -110,11 +110,11 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.3.6",
|
||||
"axios": "^0.27.2",
|
||||
"chai": "^4.3.7",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
"chalk": "^5.2.0",
|
||||
"chalk": "^5.1.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
@@ -122,7 +122,7 @@
|
||||
"monk": "^7.3.4",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.7",
|
||||
"sinon": "^15.1.2",
|
||||
"sinon": "^14.0.2",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -231,16 +231,13 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user1 has a 1-month recurring subscription starting today
|
||||
beforeEach(async () => {
|
||||
user1.purchased.plan.customerId = 'subscribedId';
|
||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||
user1.purchased.plan.planId = 'basic';
|
||||
user1.purchased.plan.consecutive.count = 0;
|
||||
user1.purchased.plan.perkMonthCount = 0;
|
||||
user1.purchased.plan.consecutive.offset = 0;
|
||||
user1.purchased.plan.consecutive.trinkets = 0;
|
||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||
});
|
||||
user1.purchased.plan.customerId = 'subscribedId';
|
||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||
user1.purchased.plan.planId = 'basic';
|
||||
user1.purchased.plan.consecutive.count = 0;
|
||||
user1.purchased.plan.consecutive.offset = 0;
|
||||
user1.purchased.plan.consecutive.trinkets = 0;
|
||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||
|
||||
it('does not increment consecutive benefits after the first month', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -274,24 +271,6 @@ describe('cron', async () => {
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => {
|
||||
user1.purchased.plan.perkMonthCount = 1;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
||||
// e.g., from time zone oddness.
|
||||
await cron({
|
||||
user: user1, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user1.purchased.plan.perkMonthCount).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(2);
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the third month', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
||||
.add(2, 'days')
|
||||
@@ -336,30 +315,6 @@ describe('cron', async () => {
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||
});
|
||||
|
||||
it('initializes plan.perkMonthCount if necessary', async () => {
|
||||
user.purchased.plan.perkMonthCount = undefined;
|
||||
clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated)
|
||||
.utcOffset(0)
|
||||
.startOf('month')
|
||||
.add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user.purchased.plan.perkMonthCount).to.equal(1);
|
||||
user.purchased.plan.perkMonthCount = undefined;
|
||||
user.purchased.plan.consecutive.count = 8;
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user.purchased.plan.perkMonthCount).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 3-month recurring subscription', async () => {
|
||||
@@ -375,16 +330,13 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user3 has a 3-month recurring subscription starting today
|
||||
beforeEach(async () => {
|
||||
user3.purchased.plan.customerId = 'subscribedId';
|
||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3.purchased.plan.planId = 'basic_3mo';
|
||||
user3.purchased.plan.perkMonthCount = 0;
|
||||
user3.purchased.plan.consecutive.count = 0;
|
||||
user3.purchased.plan.consecutive.offset = 3;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
});
|
||||
user3.purchased.plan.customerId = 'subscribedId';
|
||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3.purchased.plan.planId = 'basic_3mo';
|
||||
user3.purchased.plan.consecutive.count = 0;
|
||||
user3.purchased.plan.consecutive.offset = 3;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -438,21 +390,6 @@ describe('cron', async () => {
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
});
|
||||
|
||||
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
|
||||
user3.purchased.plan.perkMonthCount = 2;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user3, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user3.purchased.plan.perkMonthCount).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
|
||||
.add(2, 'days')
|
||||
@@ -519,16 +456,13 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user6 has a 6-month recurring subscription starting today
|
||||
beforeEach(async () => {
|
||||
user6.purchased.plan.customerId = 'subscribedId';
|
||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||
user6.purchased.plan.planId = 'google_6mo';
|
||||
user6.purchased.plan.perkMonthCount = 0;
|
||||
user6.purchased.plan.consecutive.count = 0;
|
||||
user6.purchased.plan.consecutive.offset = 6;
|
||||
user6.purchased.plan.consecutive.trinkets = 2;
|
||||
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||
});
|
||||
user6.purchased.plan.customerId = 'subscribedId';
|
||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||
user6.purchased.plan.planId = 'google_6mo';
|
||||
user6.purchased.plan.consecutive.count = 0;
|
||||
user6.purchased.plan.consecutive.offset = 6;
|
||||
user6.purchased.plan.consecutive.trinkets = 2;
|
||||
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -569,19 +503,6 @@ describe('cron', async () => {
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
});
|
||||
|
||||
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
|
||||
user6.purchased.plan.perkMonthCount = 2;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user6, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user6.purchased.plan.perkMonthCount).to.equal(2);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
|
||||
.add(2, 'days')
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('Amazon Payments - Checkout', () => {
|
||||
let closeOrderReferenceSpy;
|
||||
|
||||
let paymentBuyGemsStub;
|
||||
let paymentCreateSubscriptionStub;
|
||||
let paymentCreateSubscritionStub;
|
||||
let amount = gemsBlock.price / 100;
|
||||
|
||||
function expectOrderReferenceSpy () {
|
||||
@@ -85,8 +85,8 @@ describe('Amazon Payments - Checkout', () => {
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
|
||||
paymentBuyGemsStub.resolves({});
|
||||
|
||||
paymentCreateSubscriptionStub = sinon.stub(payments, 'createSubscription');
|
||||
paymentCreateSubscriptionStub.resolves({});
|
||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription');
|
||||
paymentCreateSubscritionStub.resolves({});
|
||||
|
||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||
sandbox.stub(gems, 'validateGiftMessage');
|
||||
@@ -109,7 +109,6 @@ describe('Amazon Payments - Checkout', () => {
|
||||
user,
|
||||
paymentMethod,
|
||||
headers,
|
||||
sku: undefined,
|
||||
};
|
||||
if (gift) {
|
||||
expectedArgs.gift = gift;
|
||||
@@ -216,14 +215,13 @@ describe('Amazon Payments - Checkout', () => {
|
||||
});
|
||||
|
||||
gift.member = receivingUser;
|
||||
expect(paymentCreateSubscriptionStub).to.be.calledOnce;
|
||||
expect(paymentCreateSubscriptionStub).to.be.calledWith({
|
||||
expect(paymentCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT,
|
||||
headers,
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
sku: undefined,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
});
|
||||
|
||||
@@ -12,10 +12,10 @@ const { i18n } = common;
|
||||
describe('Apple Payments', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
|
||||
describe('verifyPurchase', () => {
|
||||
describe('verifyGemPurchase', () => {
|
||||
let sku; let user; let token; let receipt; let
|
||||
headers;
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuySkuStub; let
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let
|
||||
iapGetPurchaseDataStub; let validateGiftMessageStub;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -29,15 +29,14 @@ describe('Apple Payments', () => {
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iap, 'validate')
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
sinon.stub(iap, 'isCanceled').returns(false);
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||
transactionId: token,
|
||||
}]);
|
||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
@@ -45,10 +44,8 @@ describe('Apple Payments', () => {
|
||||
iap.setup.restore();
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
iap.isExpired.restore();
|
||||
iap.isCanceled.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
payments.buySkuItem.restore();
|
||||
payments.buyGems.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
});
|
||||
|
||||
@@ -57,7 +54,7 @@ describe('Apple Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -69,7 +66,7 @@ describe('Apple Payments', () => {
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData').returns([]);
|
||||
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -79,7 +76,7 @@ describe('Apple Payments', () => {
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -97,16 +94,14 @@ describe('Apple Payments', () => {
|
||||
productId: 'badProduct',
|
||||
transactionId: token,
|
||||
}]);
|
||||
paymentBuySkuStub.restore();
|
||||
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_INVALID_ITEM,
|
||||
});
|
||||
|
||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
@@ -143,7 +138,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await applePayments.verifyPurchase({ user, receipt, headers });
|
||||
await applePayments.verifyGemPurchase({ user, receipt, headers });
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -153,13 +148,13 @@ describe('Apple Payments', () => {
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.not.be.called;
|
||||
|
||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
||||
expect(paymentBuySkuStub).to.be.calledWith({
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
gift: undefined,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sku: gemTest.productId,
|
||||
gemsBlock: common.content.gems[gemTest.gemsBlock],
|
||||
headers,
|
||||
gift: undefined,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
@@ -178,7 +173,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
const gift = { uuid: receivingUser._id };
|
||||
await applePayments.verifyPurchase({
|
||||
await applePayments.verifyGemPurchase({
|
||||
user, gift, receipt, headers,
|
||||
});
|
||||
|
||||
@@ -192,16 +187,18 @@ describe('Apple Payments', () => {
|
||||
expect(validateGiftMessageStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
|
||||
|
||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
||||
expect(paymentBuySkuStub).to.be.calledWith({
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
gift: {
|
||||
uuid: receivingUser._id,
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
},
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sku: 'com.habitrpg.ios.Habitica.4gems',
|
||||
headers,
|
||||
gift: {
|
||||
type: 'gems',
|
||||
gems: { amount: 4 },
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
gemsBlock: common.content.gems['4gems'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -221,7 +218,6 @@ describe('Apple Payments', () => {
|
||||
headers = {};
|
||||
receipt = `{"token": "${token}"}`;
|
||||
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
||||
user = new User();
|
||||
|
||||
iapSetupStub = sinon.stub(iap, 'setup')
|
||||
.resolves();
|
||||
@@ -232,17 +228,14 @@ describe('Apple Payments', () => {
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: 'wrongsku',
|
||||
transactionId: token,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
}]);
|
||||
@@ -257,12 +250,21 @@ describe('Apple Payments', () => {
|
||||
if (payments.createSubscription.restore) payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if sku is empty', async () => {
|
||||
await expect(applePayments.subscribe('', user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if receipt is invalid', async () => {
|
||||
iap.isValidated.restore();
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -293,15 +295,13 @@ describe('Apple Payments', () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: new Date(),
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
productId: option.sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[option.subKey];
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
await applePayments.subscribe(option.sku, user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -321,253 +321,21 @@ describe('Apple Payments', () => {
|
||||
nextPaymentProcessing,
|
||||
});
|
||||
});
|
||||
if (option !== subOptions[3]) {
|
||||
const newOption = subOptions[3];
|
||||
it(`upgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => {
|
||||
const oldSub = common.content.subscriptionBlocks[option.subKey];
|
||||
oldSub.logic = 'refundAndRepay';
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
user.purchased.plan.customerId = token;
|
||||
user.purchased.plan.planId = option.subKey;
|
||||
user.purchased.plan.additionalData = receipt;
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: newOption.sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||
|
||||
await applePayments.subscribe(user,
|
||||
receipt,
|
||||
headers,
|
||||
nextPaymentProcessing);
|
||||
|
||||
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(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: token,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
updatedFrom: oldSub,
|
||||
});
|
||||
});
|
||||
}
|
||||
if (option !== subOptions[0]) {
|
||||
const newOption = subOptions[0];
|
||||
it(`downgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => {
|
||||
const oldSub = common.content.subscriptionBlocks[option.subKey];
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
user.purchased.plan.customerId = token;
|
||||
user.purchased.plan.planId = option.subKey;
|
||||
user.purchased.plan.additionalData = receipt;
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: newOption.sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||
|
||||
await applePayments.subscribe(user,
|
||||
receipt,
|
||||
headers,
|
||||
nextPaymentProcessing);
|
||||
|
||||
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(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: token,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
updatedFrom: oldSub,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('uses the most recent subscription data', async () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 4 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 5 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.3month',
|
||||
transactionId: `${token}oldest`,
|
||||
originalTransactionId: `${token}evenOlder`,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 1 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.12month',
|
||||
transactionId: `${token}newest`,
|
||||
originalTransactionId: `${token}newest`,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 2 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.6month',
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks.basic_12mo;
|
||||
it('errors when a user is already subscribed', async () => {
|
||||
payments.createSubscription.restore();
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: `${token}newest`,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
});
|
||||
});
|
||||
|
||||
describe('does not apply multiple times', async () => {
|
||||
it('errors when a user is using the same subscription', async () => {
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a user is using a rebill of the same subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: `${token}renew`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a different user is using the subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
const secondUser = new User();
|
||||
await secondUser.save();
|
||||
await expect(applePayments.subscribe(
|
||||
secondUser, receipt, headers, nextPaymentProcessing,
|
||||
))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a multiple users exist using the subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
const secondUser = new User();
|
||||
secondUser.purchased.plan = user.purchased.plan;
|
||||
secondUser.purchased.plan.dateTerminate = new Date();
|
||||
secondUser.save();
|
||||
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
const thirdUser = new User();
|
||||
await thirdUser.save();
|
||||
await expect(applePayments.subscribe(
|
||||
thirdUser, receipt, headers, nextPaymentProcessing,
|
||||
))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -592,9 +360,9 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: expirationDate.toDate() }]);
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
||||
sinon.stub(iap, 'isCanceled').returns(false);
|
||||
sinon.stub(iap, 'isExpired').returns(true);
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
@@ -609,8 +377,6 @@ describe('Apple Payments', () => {
|
||||
iap.setup.restore();
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
iap.isExpired.restore();
|
||||
iap.isCanceled.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
});
|
||||
@@ -630,8 +396,6 @@ describe('Apple Payments', () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]);
|
||||
iap.isExpired.restore();
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
|
||||
await expect(applePayments.cancelSubscribe(user, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -654,38 +418,7 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should cancel a cancelled subscription with termination date in the future', async () => {
|
||||
const futureDate = expirationDate.add({ day: 1 });
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: futureDate }]);
|
||||
iap.isExpired.restore();
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
|
||||
iap.isCanceled.restore();
|
||||
sinon.stub(iap, 'isCanceled').returns(true);
|
||||
|
||||
await applePayments.cancelSubscribe(user, 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({
|
||||
expirationDate: futureDate,
|
||||
});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
|
||||
expect(paymentCancelSubscriptionSpy).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
nextBill: futureDate.toDate(),
|
||||
headers,
|
||||
});
|
||||
});
|
||||
|
||||
it('should cancel an expired subscription', async () => {
|
||||
it('should cancel a user subscription', async () => {
|
||||
await applePayments.cancelSubscribe(user, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
|
||||
@@ -12,11 +12,11 @@ const { i18n } = common;
|
||||
describe('Google Payments', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
|
||||
describe('verifyPurchase', () => {
|
||||
describe('verifyGemPurchase', () => {
|
||||
let sku; let user; let token; let receipt; let signature; let
|
||||
headers;
|
||||
headers; const gemsBlock = common.content.gems['21gems'];
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
|
||||
paymentBuySkuStub; let validateGiftMessageStub;
|
||||
paymentBuyGemsStub; let validateGiftMessageStub;
|
||||
|
||||
beforeEach(() => {
|
||||
sku = 'com.habitrpg.android.habitica.iap.21gems';
|
||||
@@ -27,10 +27,11 @@ describe('Google Payments', () => {
|
||||
|
||||
iapSetupStub = sinon.stub(iap, 'setup')
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iap, 'validate').resolves({ productId: sku });
|
||||
iapValidateStub = sinon.stub(iap, 'validate')
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
@@ -38,7 +39,7 @@ describe('Google Payments', () => {
|
||||
iap.setup.restore();
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
payments.buySkuItem.restore();
|
||||
payments.buyGems.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
});
|
||||
|
||||
@@ -47,7 +48,7 @@ describe('Google Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -59,25 +60,21 @@ describe('Google Payments', () => {
|
||||
|
||||
it('should throw an error if productId is invalid', async () => {
|
||||
receipt = `{"token": "${token}", "productId": "invalid"}`;
|
||||
iapValidateStub.restore();
|
||||
iapValidateStub = sinon.stub(iap, 'validate').resolves({});
|
||||
|
||||
paymentBuySkuStub.restore();
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: googlePayments.constants.RESPONSE_INVALID_ITEM,
|
||||
});
|
||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||
});
|
||||
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -91,7 +88,7 @@ describe('Google Payments', () => {
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await googlePayments.verifyPurchase({
|
||||
await googlePayments.verifyGemPurchase({
|
||||
user, receipt, signature, headers,
|
||||
});
|
||||
|
||||
@@ -104,17 +101,15 @@ describe('Google Payments', () => {
|
||||
signature,
|
||||
});
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith(
|
||||
{ productId: sku },
|
||||
);
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
|
||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
||||
expect(paymentBuySkuStub).to.be.calledWith({
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
gift: undefined,
|
||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||
sku,
|
||||
gemsBlock,
|
||||
headers,
|
||||
gift: undefined,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
@@ -125,7 +120,7 @@ describe('Google Payments', () => {
|
||||
await receivingUser.save();
|
||||
|
||||
const gift = { uuid: receivingUser._id };
|
||||
await googlePayments.verifyPurchase({
|
||||
await googlePayments.verifyGemPurchase({
|
||||
user, gift, receipt, signature, headers,
|
||||
});
|
||||
|
||||
@@ -139,20 +134,20 @@ describe('Google Payments', () => {
|
||||
signature,
|
||||
});
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith(
|
||||
{ productId: sku },
|
||||
);
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
|
||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
||||
expect(paymentBuySkuStub).to.be.calledWith({
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
gift: {
|
||||
uuid: receivingUser._id,
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
},
|
||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||
sku,
|
||||
gemsBlock,
|
||||
headers,
|
||||
gift: {
|
||||
type: 'gems',
|
||||
gems: { amount: 21 },
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -203,28 +203,6 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => {
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.exist;
|
||||
});
|
||||
|
||||
it('keeps plan.dateCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = recipient.purchased.plan.dateCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(recipient.purchased.plan.dateCreated).to.eql(initialDate);
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = recipient.purchased.plan.dateCurrentTypeCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
|
||||
});
|
||||
|
||||
it('does not change plan.customerId if it already exists', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
data.customerId = 'purchaserCustomerId';
|
||||
@@ -235,116 +213,6 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 1;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if field is not initialized', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = -1;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('adds to plan.perkMonthCount if user is already subscribed', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 1;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => {
|
||||
recipient.purchased.plan.perkMonthCount = 0;
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => {
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount goes over 3', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
data.sub.key = 'basic_earned';
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('sets plan.customerId to "Gift" if it does not already exist', async () => {
|
||||
expect(recipient.purchased.plan.customerId).to.not.exist;
|
||||
|
||||
@@ -511,7 +379,6 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.gemsBought).to.eql(0);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(user.purchased.plan.paymentMethod).to.eql('Payment Method');
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
expect(user.purchased.plan.dateTerminated).to.eql(null);
|
||||
@@ -519,63 +386,6 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCreated if it did not previously exist', async () => {
|
||||
expect(user.purchased.plan.dateCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => {
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.exist;
|
||||
});
|
||||
|
||||
it('keeps plan.dateCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = user.purchased.plan.dateCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.dateCreated).to.eql(initialDate);
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = user.purchased.plan.dateCurrentTypeCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
|
||||
});
|
||||
|
||||
it('keeps plan.perkMonthCount when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(2);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => {
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => {
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('updates plan.consecutive.offset when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(3);
|
||||
data.sub.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(6);
|
||||
});
|
||||
|
||||
it('awards the Royal Purple Jackalope pet', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -655,89 +465,6 @@ describe('payments/index', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('Upgrades subscription', () => {
|
||||
it('from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom = { key: 'basic_earned' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom = { key: 'basic_3mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
});
|
||||
|
||||
context('Downgrades subscription', () => {
|
||||
it('from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Block subscription perks', () => {
|
||||
@@ -748,19 +475,9 @@ describe('payments/index', () => {
|
||||
});
|
||||
|
||||
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
||||
});
|
||||
|
||||
it('resets plans.consecutive.offset if 1 month subscription', async () => {
|
||||
user.purchased.plan.consecutive.offset = 1;
|
||||
await user.save();
|
||||
data.sub.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
});
|
||||
|
||||
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
|
||||
@@ -771,6 +488,7 @@ describe('payments/index', () => {
|
||||
|
||||
it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
@@ -778,6 +496,7 @@ describe('payments/index', () => {
|
||||
|
||||
it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => {
|
||||
data.sub.key = 'basic_12mo';
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
@@ -813,532 +532,6 @@ describe('payments/index', () => {
|
||||
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
context('Upgrades subscription', () => {
|
||||
context('Using payDifference logic', () => {
|
||||
beforeEach(async () => {
|
||||
data.updatedFrom = { logic: 'payDifference' };
|
||||
});
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
|
||||
context('Using payFull logic', () => {
|
||||
beforeEach(async () => {
|
||||
data.updatedFrom = { logic: 'payFull' };
|
||||
});
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
});
|
||||
|
||||
context('Using refundAndRepay logic', () => {
|
||||
let clock;
|
||||
beforeEach(async () => {
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-01'));
|
||||
data.updatedFrom = { logic: 'refundAndRepay' };
|
||||
});
|
||||
context('Upgrades within first half of subscription', () => {
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-10'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-02-05'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-08'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-31'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-08'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-08-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-07-31'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
context('Upgrades within second half of subscription', () => {
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-20'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-02-24'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-03-03'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2023-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2023-09-03'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
});
|
||||
afterEach(async () => {
|
||||
if (clock !== null) clock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Downgrades subscription', () => {
|
||||
it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Mystery Items', () => {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import {
|
||||
canBuySkuItem,
|
||||
} from '../../../../../website/server/libs/payments/skuItem';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
|
||||
describe('payments/skuItems', () => {
|
||||
let user;
|
||||
let clock;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
clock = null;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (clock !== null) clock.restore();
|
||||
});
|
||||
|
||||
describe('#canBuySkuItem', () => {
|
||||
it('returns true for random sku', () => {
|
||||
expect(canBuySkuItem('something', user)).to.be.true;
|
||||
});
|
||||
|
||||
describe('#gryphatrice', () => {
|
||||
const sku = 'Pet-Gryphatrice-Jubilant';
|
||||
it('returns true during birthday week', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-01-31'));
|
||||
expect(canBuySkuItem(sku, user)).to.be.true;
|
||||
});
|
||||
it('returns false outside of birthday week', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-01-20'));
|
||||
expect(canBuySkuItem(sku, user)).to.be.false;
|
||||
});
|
||||
it('returns false if user already owns it', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-02-01'));
|
||||
user.items.pets['Gryphatrice-Jubilant'] = 5;
|
||||
expect(canBuySkuItem(sku, user)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -242,7 +242,7 @@ describe('cron middleware', () => {
|
||||
|
||||
sandbox.spy(cronLib, 'recoverCron');
|
||||
|
||||
sandbox.stub(User, 'updateOne')
|
||||
sandbox.stub(User, 'update')
|
||||
.withArgs({
|
||||
_id: user._id,
|
||||
$or: [
|
||||
|
||||
@@ -1359,7 +1359,6 @@ describe('Group Model', () => {
|
||||
describe('#sendChat', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(User, 'update');
|
||||
sandbox.spy(User, 'updateMany');
|
||||
});
|
||||
|
||||
it('formats message', () => {
|
||||
@@ -1414,8 +1413,8 @@ describe('Group Model', () => {
|
||||
it('updates users about new messages in party', () => {
|
||||
party.sendChat({ message: 'message' });
|
||||
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: '' },
|
||||
});
|
||||
@@ -1428,8 +1427,8 @@ describe('Group Model', () => {
|
||||
|
||||
group.sendChat({ message: 'message' });
|
||||
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
guilds: group._id,
|
||||
_id: { $ne: '' },
|
||||
});
|
||||
@@ -1438,8 +1437,8 @@ describe('Group Model', () => {
|
||||
it('does not send update to user that sent the message', () => {
|
||||
party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
|
||||
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: 'user-id' },
|
||||
});
|
||||
@@ -1732,7 +1731,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates participting members (not including user)', async () => {
|
||||
sandbox.spy(User, 'updateMany');
|
||||
sandbox.spy(User, 'update');
|
||||
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
@@ -1740,7 +1739,7 @@ describe('Group Model', () => {
|
||||
questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
|
||||
];
|
||||
|
||||
expect(User.updateMany).to.be.calledWith(
|
||||
expect(User.update).to.be.calledWith(
|
||||
{ _id: { $in: members } },
|
||||
{
|
||||
$set: {
|
||||
@@ -1753,11 +1752,11 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates non-user quest leader and decrements quest scroll', async () => {
|
||||
sandbox.spy(User, 'updateOne');
|
||||
sandbox.spy(User, 'update');
|
||||
|
||||
await party.startQuest(participatingMember);
|
||||
|
||||
expect(User.updateOne).to.be.calledWith(
|
||||
expect(User.update).to.be.calledWith(
|
||||
{ _id: questLeader._id },
|
||||
{
|
||||
$inc: {
|
||||
@@ -1819,29 +1818,29 @@ describe('Group Model', () => {
|
||||
};
|
||||
|
||||
it('doesn\'t retry successful operations', async () => {
|
||||
sandbox.stub(User, 'updateOne').returns(successfulMock);
|
||||
sandbox.stub(User, 'update').returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.updateOne).to.be.calledThrice;
|
||||
expect(User.update).to.be.calledThrice;
|
||||
});
|
||||
|
||||
it('stops retrying when a successful update has occurred', async () => {
|
||||
const updateStub = sandbox.stub(User, 'updateOne');
|
||||
const updateStub = sandbox.stub(User, 'update');
|
||||
updateStub.onCall(0).returns(failedMock);
|
||||
updateStub.returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.updateOne.callCount).to.equal(4);
|
||||
expect(User.update.callCount).to.equal(4);
|
||||
});
|
||||
|
||||
it('retries failed updates at most five times per user', async () => {
|
||||
sandbox.stub(User, 'updateOne').returns(failedMock);
|
||||
sandbox.stub(User, 'update').returns(failedMock);
|
||||
|
||||
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||
|
||||
expect(User.updateOne.callCount).to.eql(15); // for 3 users
|
||||
expect(User.update.callCount).to.eql(15); // for 3 users
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2088,17 +2087,17 @@ describe('Group Model', () => {
|
||||
|
||||
context('Party quests', () => {
|
||||
it('updates participating members with rewards', async () => {
|
||||
sandbox.spy(User, 'updateOne');
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.updateOne).to.be.calledThrice;
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledThrice;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: questLeader._id,
|
||||
});
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: participatingMember._id,
|
||||
});
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: sleepingParticipatingMember._id,
|
||||
});
|
||||
});
|
||||
@@ -2173,11 +2172,11 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates all users with rewards', async () => {
|
||||
sandbox.spy(User, 'updateMany');
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(tavernQuest);
|
||||
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({});
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({});
|
||||
});
|
||||
|
||||
it('sets quest completed to the world quest key', async () => {
|
||||
|
||||
@@ -541,35 +541,6 @@ describe('POST /chat', () => {
|
||||
.to.eql(userWithStyle.preferences.background);
|
||||
});
|
||||
|
||||
it('creates equipped to user styles', async () => {
|
||||
const userWithStyle = await generateUser({
|
||||
'preferences.costume': false,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.gear.equipped)
|
||||
.to.eql(userWithStyle.items.gear.equipped);
|
||||
expect(message.message.userStyles.items.gear.costume).to.not.exist;
|
||||
});
|
||||
|
||||
it('creates costume to user styles', async () => {
|
||||
const userWithStyle = await generateUser({
|
||||
'preferences.costume': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.gear.costume).to.eql(userWithStyle.items.gear.costume);
|
||||
expect(message.message.userStyles.items.gear.equipped).to.not.exist;
|
||||
});
|
||||
|
||||
it('adds backer info to chat', async () => {
|
||||
const backerInfo = {
|
||||
npc: 'Town Crier',
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 2,
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
@@ -76,17 +76,12 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/flag`);
|
||||
|
||||
// first test that the flag was actually successful
|
||||
// author always sees own message; flag count is hidden from non-admins
|
||||
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
messages = await members[1].get(`/groups/${group._id}/chat`);
|
||||
expect(messages.length).to.eql(0);
|
||||
expect(messages[0].flagCount).to.eql(5);
|
||||
|
||||
// admin cannot directly request private group chat, but after unflag,
|
||||
// message should be revealed again and still have flagCount of 0
|
||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/clearflags`);
|
||||
messages = await members[1].get(`/groups/${group._id}/chat`);
|
||||
expect(messages.length).to.eql(1);
|
||||
|
||||
messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
checkExistence,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -257,6 +258,47 @@ describe('POST /group/:groupId/join', () => {
|
||||
await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 2);
|
||||
});
|
||||
|
||||
it('deletes previous party where the user was the only member', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const oldParty = await userToInvite.post('/groups', { // add user to a party
|
||||
name: 'Another Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
|
||||
await user.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(user.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('does not allow user to leave a party if a quest was active and they were the only member', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const oldParty = await userToInvite.post('/groups', { // add user to a party
|
||||
name: 'Another Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await userToInvite.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
await userToInvite.post(`/groups/${oldParty._id}/quests/invite/${PET_QUEST}`);
|
||||
|
||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
|
||||
await user.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
await expect(userToInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageCannotLeaveWhileQuesting'),
|
||||
});
|
||||
});
|
||||
|
||||
it('invites joining member to active quest', async () => {
|
||||
await user.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import nconf from 'nconf';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
@@ -49,19 +48,6 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when recipient has blocked the senders', async () => {
|
||||
const inviterNoBlocks = await inviter.update({ 'inbox.blocks': [] });
|
||||
const userWithBlockedInviter = await generateUser({ 'inbox.blocks': [inviter._id] });
|
||||
await expect(inviterNoBlocks.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userWithBlockedInviter.auth.local.lowerCaseUsername],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notAuthorizedToSendMessageToThisUser'),
|
||||
});
|
||||
});
|
||||
|
||||
it('invites a user to a group by username', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
@@ -582,7 +568,20 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('allows inviting a user to 2 different parties', async () => {
|
||||
it('allow inviting a user to a party if they are partying solo', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await userToInvite.post('/groups', { // add user to a party
|
||||
name: 'Another Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user to 2 different parties', async () => {
|
||||
// Create another inviter
|
||||
const inviter2 = await generateUser();
|
||||
|
||||
@@ -623,48 +622,29 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('party size limits', () => {
|
||||
let party;
|
||||
let partyLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
group = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
// Generate party with 20 members
|
||||
members: PARTY_LIMIT_MEMBERS - 10,
|
||||
});
|
||||
party = group.group;
|
||||
partyLeader = group.groupLeader;
|
||||
});
|
||||
|
||||
it('allows 30 members in a party', async () => {
|
||||
const invitesToGenerate = [];
|
||||
// Generate 10 new invites
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
// Generate 29 users to invite (29 + leader = 30 members)
|
||||
for (let i = 0; i < PARTY_LIMIT_MEMBERS - 1; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
expect(await partyLeader.post(`/groups/${party._id}/invite`, {
|
||||
expect(await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
})).to.be.an('array');
|
||||
}).timeout(10000);
|
||||
|
||||
it('does not allow >30 members in a party', async () => {
|
||||
it('does not allow 30+ members in a party', async () => {
|
||||
const invitesToGenerate = [];
|
||||
// Generate 11 invites
|
||||
for (let i = 1; i < 11; i += 1) {
|
||||
// Generate 30 users to invite (30 + leader = 31 members)
|
||||
for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
await expect(partyLeader.post(`/groups/${party._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
|
||||
@@ -45,10 +45,11 @@ describe('payments : apple #subscribe', () => {
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0]._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][1]).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][2]['x-api-user']).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0]).to.eql(sku);
|
||||
expect(subscribeStub.args[0][1]._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][2]).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][3]['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,11 +21,11 @@ describe('payments : apple #verify', () => {
|
||||
let verifyStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
verifyStub = sinon.stub(applePayments, 'verifyPurchase').resolves({});
|
||||
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
applePayments.verifyPurchase.restore();
|
||||
applePayments.verifyGemPurchase.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
|
||||
@@ -21,11 +21,11 @@ describe('payments : google #verify', () => {
|
||||
let verifyStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
verifyStub = sinon.stub(googlePayments, 'verifyPurchase').resolves({});
|
||||
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
googlePayments.verifyPurchase.restore();
|
||||
googlePayments.verifyGemPurchase.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
|
||||
@@ -96,20 +96,6 @@ describe('PUT /user/auth/update-password', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when newPassword is too long', async () => {
|
||||
const body = {
|
||||
password,
|
||||
newPassword: '12345678910111213141516171819202122232425262728293031323334353637383940',
|
||||
confirmPassword: '12345678910111213141516171819202122232425262728293031323334353637383940',
|
||||
};
|
||||
|
||||
await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when confirmPassword is missing', async () => {
|
||||
const body = {
|
||||
password,
|
||||
|
||||
@@ -35,6 +35,13 @@ describe('GET /world-state', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a string representing the current season for NPC sprites', async () => {
|
||||
const res = await requester().get('/world-state');
|
||||
|
||||
expect(res).to.have.nested.property('npcImageSuffix');
|
||||
expect(res.npcImageSuffix).to.be.a('string');
|
||||
});
|
||||
|
||||
context('no current event', () => {
|
||||
beforeEach(async () => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns(null);
|
||||
|
||||
@@ -37,8 +37,6 @@ describe('GET /faq', () => {
|
||||
|
||||
expect(res).to.have.property('questions');
|
||||
expect(res.questions[0]).to.eql({
|
||||
exclusions: [],
|
||||
heading: 'overview',
|
||||
question: translate('faqQuestion0'),
|
||||
ios: translate('iosFaqAnswer0'),
|
||||
});
|
||||
@@ -59,8 +57,6 @@ describe('GET /faq', () => {
|
||||
|
||||
expect(res).to.have.property('questions');
|
||||
expect(res.questions[0]).to.eql({
|
||||
exclusions: [],
|
||||
heading: 'overview',
|
||||
question: translate('faqQuestion0'),
|
||||
android: translate('androidFaqAnswer0'),
|
||||
});
|
||||
|
||||
@@ -202,86 +202,18 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
await group.groupLeader.post('/user/class/cast/mpheal');
|
||||
|
||||
promises = [];
|
||||
promises.push(group.groupLeader.sync());
|
||||
promises.push(group.members[0].sync());
|
||||
promises.push(group.members[1].sync());
|
||||
promises.push(group.members[2].sync());
|
||||
promises.push(group.members[3].sync());
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(group.groupLeader.stats.mp).to.be.equal(170); // spell caster
|
||||
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
||||
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
||||
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
||||
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
||||
});
|
||||
|
||||
const spellList = [
|
||||
{
|
||||
className: 'warrior',
|
||||
spells: [['smash', 'task'], ['defensiveStance'], ['valorousPresence'], ['intimidate']],
|
||||
},
|
||||
{
|
||||
className: 'wizard',
|
||||
spells: [['fireball', 'task'], ['mpheal'], ['earth'], ['frost']],
|
||||
},
|
||||
{
|
||||
className: 'healer',
|
||||
spells: [['heal'], ['brightness'], ['protectAura'], ['healAll']],
|
||||
},
|
||||
{
|
||||
className: 'rogue',
|
||||
spells: [['pickPocket', 'task'], ['backStab', 'task'], ['toolsOfTrade'], ['stealth']],
|
||||
},
|
||||
];
|
||||
|
||||
spellList.forEach(async habitClass => {
|
||||
describe(`For a ${habitClass.className}`, async () => {
|
||||
habitClass.spells.forEach(async spell => {
|
||||
describe(`Using ${spell[0]}`, async () => {
|
||||
it('Deducts MP from spell caster', async () => {
|
||||
const { groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 3,
|
||||
});
|
||||
await groupLeader.update({
|
||||
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
|
||||
});
|
||||
// need this for task spells and for stealth
|
||||
const task = await groupLeader.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
});
|
||||
if (spell.length === 2 && spell[1] === 'task') {
|
||||
await groupLeader.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
|
||||
} else {
|
||||
await groupLeader.post(`/user/class/cast/${spell[0]}`);
|
||||
}
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.stats.mp).to.be.lessThan(200);
|
||||
});
|
||||
it('works without a party', async () => {
|
||||
await user.update({
|
||||
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
|
||||
});
|
||||
// need this for task spells and for stealth
|
||||
const task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
});
|
||||
if (spell.length === 2 && spell[1] === 'task') {
|
||||
await user.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
|
||||
} else {
|
||||
await user.post(`/user/class/cast/${spell[0]}`);
|
||||
}
|
||||
await user.sync();
|
||||
expect(user.stats.mp).to.be.lessThan(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('cast bulk', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({ // eslint-disable-line prefer-const
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
|
||||
@@ -215,7 +215,6 @@ describe('cron utility functions', () => {
|
||||
|
||||
it('monthly plan, next date in 3 months', () => {
|
||||
const user = baseUserData(60, 0, 'group_plan_auto');
|
||||
user.purchased.plan.perkMonthCount = 0;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
@@ -225,7 +224,6 @@ describe('cron utility functions', () => {
|
||||
|
||||
it('monthly plan, next date in 1 month', () => {
|
||||
const user = baseUserData(62, 0, 'group_plan_auto');
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
@@ -250,15 +248,5 @@ describe('cron utility functions', () => {
|
||||
expect(planContext.nextHourglassDate)
|
||||
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
|
||||
});
|
||||
|
||||
it('multi-month plan with perk count', () => {
|
||||
const user = baseUserData(60, 1, 'basic_3mo');
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
expect(planContext.nextHourglassDate)
|
||||
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,9 +12,8 @@ const webhookData = {};
|
||||
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true,
|
||||
limit: '10mb',
|
||||
}));
|
||||
app.use(bodyParser.json({ limit: '10mb' }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.post('/webhooks/:id', (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
@@ -53,8 +53,7 @@ function _requestMaker (user, method, additionalSets = {}) {
|
||||
if (user && user._id && user.apiToken) {
|
||||
request
|
||||
.set('x-api-user', user._id)
|
||||
.set('x-api-key', user.apiToken)
|
||||
.set('x-client', 'habitica-web');
|
||||
.set('x-api-key', user.apiToken);
|
||||
}
|
||||
|
||||
if (!isEmpty(additionalSets)) {
|
||||
|
||||
12523
website/client/package-lock.json
generated
@@ -18,44 +18,44 @@
|
||||
"@storybook/addon-links": "6.5.8",
|
||||
"@storybook/addon-notes": "5.3.21",
|
||||
"@storybook/addons": "6.5.9",
|
||||
"@storybook/vue": "6.5.14",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@storybook/vue": "6.3.13",
|
||||
"@vue/cli-plugin-babel": "^4.5.15",
|
||||
"@vue/cli-plugin-eslint": "^4.5.19",
|
||||
"@vue/cli-plugin-router": "^5.0.8",
|
||||
"@vue/cli-plugin-unit-mocha": "^5.0.8",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.5.15",
|
||||
"@vue/cli-service": "^4.5.15",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^8.21.3",
|
||||
"amplitude-js": "^8.21.1",
|
||||
"axios": "^0.27.2",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"chai": "^4.3.7",
|
||||
"core-js": "^3.31.0",
|
||||
"dompurify": "^3.0.3",
|
||||
"bootstrap-vue": "^2.22.0",
|
||||
"chai": "^4.3.6",
|
||||
"core-js": "^3.26.0",
|
||||
"dompurify": "^2.4.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"hellojs": "^1.19.5",
|
||||
"inspectpack": "^4.7.1",
|
||||
"intro.js": "^7.0.1",
|
||||
"jquery": "^3.7.0",
|
||||
"intro.js": "^6.0.0",
|
||||
"jquery": "^3.6.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nconf": "^0.12.0",
|
||||
"sass": "^1.63.4",
|
||||
"sass": "^1.34.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.19.2",
|
||||
"stopword": "^2.0.8",
|
||||
"smartbanner.js": "^1.19.1",
|
||||
"stopword": "^2.0.5",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"svg-url-loader": "^7.1.1",
|
||||
"svgo": "^1.3.2",
|
||||
"svgo-loader": "^2.2.1",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.9.0",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.7.0",
|
||||
"vue": "^2.7.10",
|
||||
"vue-cli-plugin-storybook": "2.1.0",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
@@ -66,6 +66,6 @@
|
||||
"webpack": "^4.46.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0"
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.18.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@
|
||||
<sub-canceled-modal v-if="isUserLoaded" />
|
||||
<bug-report-modal v-if="isUserLoaded" />
|
||||
<bug-report-success-modal v-if="isUserLoaded" />
|
||||
<external-link-modal />
|
||||
<birthday-modal />
|
||||
<snackbars />
|
||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||
<template v-else>
|
||||
@@ -44,7 +42,6 @@
|
||||
<damage-paused-banner />
|
||||
<gems-promo-banner />
|
||||
<gift-promo-banner />
|
||||
<birthday-banner />
|
||||
<notifications-display />
|
||||
<app-menu />
|
||||
<div
|
||||
@@ -156,13 +153,11 @@
|
||||
import axios from 'axios';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
|
||||
import birthdayModal from '@/components/news/birthdayModal';
|
||||
import AppMenu from './components/header/menu';
|
||||
import AppHeader from './components/header/index';
|
||||
import DamagePausedBanner from './components/header/banners/damagePaused';
|
||||
import GemsPromoBanner from './components/header/banners/gemsPromo';
|
||||
import GiftPromoBanner from './components/header/banners/giftPromo';
|
||||
import BirthdayBanner from './components/header/banners/birthdayBanner';
|
||||
import AppFooter from './components/appFooter';
|
||||
import notificationsDisplay from './components/notifications';
|
||||
import snackbars from './components/snackbars/notifications';
|
||||
@@ -176,7 +171,6 @@ import amazonPaymentsModal from '@/components/payments/amazonModal';
|
||||
import paymentsSuccessModal from '@/components/payments/successModal';
|
||||
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
|
||||
import subCanceledModal from '@/components/payments/canceledModal';
|
||||
import externalLinkModal from '@/components/externalLinkModal.vue';
|
||||
|
||||
import spellsMixin from '@/mixins/spells';
|
||||
import {
|
||||
@@ -197,11 +191,9 @@ export default {
|
||||
AppMenu,
|
||||
AppHeader,
|
||||
AppFooter,
|
||||
birthdayModal,
|
||||
DamagePausedBanner,
|
||||
GemsPromoBanner,
|
||||
GiftPromoBanner,
|
||||
BirthdayBanner,
|
||||
notificationsDisplay,
|
||||
snackbars,
|
||||
BuyModal,
|
||||
@@ -212,7 +204,6 @@ export default {
|
||||
subCanceledModal,
|
||||
bugReportModal,
|
||||
bugReportSuccessModal,
|
||||
externalLinkModal,
|
||||
},
|
||||
mixins: [notifications, spellsMixin],
|
||||
data () {
|
||||
|
||||
@@ -156,12 +156,6 @@
|
||||
height: 99px;
|
||||
}
|
||||
|
||||
.Pet-Gryphatrice-Jubilant {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Gryphatrice-Jubilant.gif") no-repeat;
|
||||
width: 81px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.Mount_Head_Gryphon-Gryphatrice, .Mount_Body_Gryphon-Gryphatrice {
|
||||
width: 135px;
|
||||
height: 135px;
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 332 B |
|
Before Width: | Height: | Size: 358 B |
|
Before Width: | Height: | Size: 850 B |
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -19,12 +19,8 @@
|
||||
top: -16px !important;
|
||||
}
|
||||
|
||||
$foolPets: Veggie, Dessert, VirtualPet, TeaShop;
|
||||
|
||||
@each $foolPet in $foolPets {
|
||||
.Pet.Pet-FlyingPig-#{$foolPet} {
|
||||
top: -28px !important;
|
||||
}
|
||||
.Pet.Pet-FlyingPig-Veggie, .Pet.Pet-FlyingPig-Dessert, .Pet.Pet-FlyingPig-VirtualPet {
|
||||
top: -28px !important;
|
||||
}
|
||||
|
||||
.Pet[class*="Virtual"] {
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.icon-10 {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
.icon-16 {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.icon-12 {
|
||||
@@ -34,26 +34,21 @@
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.icon-16 {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.icon-24 {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.icon-32 {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.icon-48 {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.icon-10 {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,10 @@ h3.markdown {
|
||||
}
|
||||
|
||||
a {
|
||||
color: $blue-10;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
color: $blue-10;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,17 +26,19 @@ a:not([href]), a:not([href]):hover {
|
||||
|
||||
a, a:not([href]):not([tabindex]) {
|
||||
cursor: pointer;
|
||||
color: $purple-300;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
text-decoration: underline;
|
||||
color: $purple-300;
|
||||
}
|
||||
&.standard-link {
|
||||
color: $blue-10;
|
||||
|
||||
&[disabled="disabled"] {
|
||||
color: $gray-300;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
&:hover, &:active, &:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&[disabled="disabled"] {
|
||||
color: $gray-300;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
&.small-link {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<svg width="199" height="24" viewBox="0 0 199 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#c19w6aye5a)" fill="#fff">
|
||||
<path d="M56.47 18.83V6.003L56 3.662l.47-1.405h8.942c1.773 0 3.193.344 4.26 1.03 1.066.687 1.6 1.733 1.6 3.137 0 .765-.142 1.397-.424 1.896a4.175 4.175 0 0 1-1.035 1.24 4.14 4.14 0 0 1 1.505.703c.471.327.855.772 1.154 1.334.297.546.447 1.225.447 2.036 0 1.639-.487 2.918-1.46 3.839-.956.905-2.502 1.358-4.635 1.358H56.471zm5.177-10.136h2.777c.533 0 .918-.078 1.153-.234.235-.171.353-.429.353-.772 0-.359-.173-.609-.518-.75-.345-.155-.753-.233-1.223-.233h-2.542v1.99zm0 5.688h4c.628 0 1.067-.093 1.318-.28a.974.974 0 0 0 .377-.796c0-.75-.486-1.124-1.46-1.124h-4.235v2.2zM75.515 18.83V6.003l-.236-2.341.236-1.405h5.2V18.83h-5.2zM84 18.83V6.026l-.471-2.364.47-1.405h8.472c1.27 0 2.36.203 3.27.61.91.405 1.608 1.06 2.095 1.965.486.89.73 2.068.73 3.535 0 1.357-.228 2.473-.683 3.347a4.552 4.552 0 0 1-2 1.99l.4.327 1.623 2.2 1.341 1.194v1.405h-6.235l-2.588-4.284h-1.248v.515l.236 2.34-.236 1.429H84zm5.176-8.66h1.365c.55 0 1.02-.024 1.412-.071.408-.047.722-.187.941-.421.22-.25.33-.648.33-1.194 0-.578-.118-.991-.353-1.24-.236-.25-.565-.399-.989-.446a9.614 9.614 0 0 0-1.435-.093h-1.506l.235 3.464zM104.666 18.83V6.728l-4.706.234V2.257h14.707v4.705l-4.824-.234v8.357l.259 2.34-.259 1.405h-5.177zM116.785 18.83V6.026l-.235-2.34.235-1.429h5.177v6.344h4.918V2.257h5.177v8.802l.235 1.615v6.156h-5.412v-5.946h-4.918v1.639l.235 4.307h-5.412zM135.588 18.83V6.026l-.471-2.34.471-1.429h7.977c1.114 0 2.188.11 3.223.328 1.051.203 1.985.593 2.801 1.17.831.578 1.49 1.405 1.976 2.482.486 1.076.73 2.473.73 4.19 0 1.716-.251 3.128-.753 4.236-.487 1.092-1.146 1.943-1.977 2.552a7.477 7.477 0 0 1-2.8 1.264 14.463 14.463 0 0 1-3.2.35h-7.977zm5.224-4.448h1.788c.926 0 1.702-.101 2.33-.304a2.498 2.498 0 0 0 1.458-1.147c.33-.577.495-1.412.495-2.504 0-1.108-.173-1.92-.518-2.435-.33-.53-.816-.874-1.459-1.03-.628-.171-1.396-.257-2.306-.257h-1.788v7.677zM153.013 18.83l1.13-3.956V11.9l1.741-.702 3.294-8.918h7.083l4.024 10.486 1.812 3.324v2.739h-5.106l-1.177-3.488h-6.377l-1.012 3.488h-5.412zm7.977-7.584h3.53l-1.553-4.658h-.494l-1.483 4.658zM176.04 18.83v-6.788l-5.835-8.38V2.257h5.906l2.353 4.822h.47l2.33-4.822h5.883v1.405l-6.001 8.52.141 2.364v4.284h-5.247zM191.923 12.72l-2.07-8.847L192.676 2l2.8 1.896-2.141 8.824h-1.412zm.518 7.28-3.059-3.043 3.059-3.043 3.059 3.043L192.441 20z"/>
|
||||
</g>
|
||||
<g filter="url(#s1alkvv8kb)">
|
||||
<path d="M5.87 18.825V7.601H3V3.17l8.228-.937.239 1.406-.24 2.344v12.841H5.87z" fill="url(#xidihnl5xc)"/>
|
||||
<path d="M21.258 19.06a9.043 9.043 0 0 1-2.87-.446 6.484 6.484 0 0 1-2.369-1.453c-.67-.671-1.195-1.546-1.578-2.624-.383-1.094-.574-2.43-.574-4.007 0-1.562.191-2.883.574-3.96.382-1.094.909-1.977 1.578-2.648a6.092 6.092 0 0 1 2.368-1.453A8.63 8.63 0 0 1 21.257 2c1.356 0 2.584.281 3.684.844 1.116.562 2.001 1.468 2.655 2.718.67 1.234 1.004 2.89 1.004 4.968s-.335 3.741-1.004 4.991c-.654 1.25-1.539 2.156-2.655 2.718-1.1.547-2.328.82-3.683.82zm0-5.039c.701 0 1.187-.25 1.459-.75.27-.5.406-1.413.406-2.741 0-1.313-.136-2.219-.407-2.719-.27-.515-.757-.773-1.459-.773-.685 0-1.18.258-1.483.773-.287.516-.43 1.422-.43 2.719 0 1.312.143 2.226.43 2.742.303.5.798.75 1.483.75z" fill="url(#9hqzmmkygd)"/>
|
||||
<path d="M32.721 12.014V4.745l-2.87.14V2.06h8.97v2.826l-2.943-.141v5.02l.158 1.405-.158.844h-3.157z" fill="url(#bzq8gpt5ve)"/>
|
||||
<path d="M40.543 12.014v-7.69l-.144-1.407.144-.857H43.7v3.81h3V2.06h3.156v5.286l.144.97v3.698h-3.3V8.443h-3v.984l.143 2.587h-3.3z" fill="url(#4t6arxwa4f)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="xidihnl5xc" x1="3" y1="2" x2="29.822" y2="35.308" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6133B4"/>
|
||||
<stop offset="1" stop-color="#4F2A93"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="9hqzmmkygd" x1="3" y1="2" x2="29.822" y2="35.308" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6133B4"/>
|
||||
<stop offset="1" stop-color="#4F2A93"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="bzq8gpt5ve" x1="3" y1="2" x2="29.822" y2="35.308" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6133B4"/>
|
||||
<stop offset="1" stop-color="#4F2A93"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="4t6arxwa4f" x1="3" y1="2" x2="29.822" y2="35.308" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6133B4"/>
|
||||
<stop offset="1" stop-color="#4F2A93"/>
|
||||
</linearGradient>
|
||||
<filter id="c19w6aye5a" x="53" y="0" width="145.5" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix values="0 0 0 0 0.101961 0 0 0 0 0.0941176 0 0 0 0 0.113725 0 0 0 0.12 0"/>
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_45_799"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix values="0 0 0 0 0.101961 0 0 0 0 0.0941176 0 0 0 0 0.113725 0 0 0 0.24 0"/>
|
||||
<feBlend in2="effect1_dropShadow_45_799" result="effect2_dropShadow_45_799"/>
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow_45_799" result="shape"/>
|
||||
</filter>
|
||||
<filter id="s1alkvv8kb" x="0" y="0" width="53" height="23.059" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix values="0 0 0 0 0.101961 0 0 0 0 0.0941176 0 0 0 0 0.113725 0 0 0 0.12 0"/>
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_45_799"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix values="0 0 0 0 0.101961 0 0 0 0 0.0941176 0 0 0 0 0.113725 0 0 0 0.24 0"/>
|
||||
<feBlend in2="effect1_dropShadow_45_799" result="effect2_dropShadow_45_799"/>
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow_45_799" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.8 KiB |
@@ -1,22 +0,0 @@
|
||||
<svg width="58" height="48" viewBox="0 0 58 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m16.853 4.36 7.959-1.453-2.71 7.556-2.708 7.557-5.25-6.103-5.25-6.103 7.959-1.453z" fill="#5DDEAB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.771 1.454 40.731 0l-2.71 7.556-2.709 7.556-5.25-6.102-5.25-6.103 7.96-1.453z" fill="#5DDEAB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m43.272 13.659-7.96 1.453 2.71-7.556L40.73 0l5.25 6.103 5.25 6.102-7.96 1.454z" fill="#38C38D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m27.353 16.566-7.96 1.453 2.71-7.556 2.709-7.556 5.25 6.103 5.25 6.102-7.96 1.454zM11.434 19.473l-7.959 1.453 2.71-7.556 2.708-7.556 5.25 6.103 5.25 6.102-7.959 1.454z" fill="#B0F1D7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m3.475 20.926 28.05 18.662L19.394 18.02 3.475 20.926z" fill="#38C38D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.249 12.202 31.525 39.588l3.805-24.48 15.919-2.906z" fill="#B0F1D7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m19.394 18.02 12.131 21.568 3.787-24.476-15.918 2.907z" fill="#5DDEAB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m51.904 26.44-3.832-.897 1.132 3.736 1.132 3.737 2.7-2.84 2.7-2.84-3.832-.896zM44.24 24.647l-3.832-.897 1.132 3.736 1.132 3.736 2.7-2.84 2.7-2.839-3.832-.896z" fill="#87E3E1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m38.84 30.326 3.832.896-1.132-3.736-1.132-3.736-2.7 2.84-2.7 2.839 3.832.897zM46.504 32.12l3.832.896-1.132-3.736-1.132-3.737-2.7 2.84-2.7 2.84 3.832.896z" fill="#C0FBFA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.168 33.912 58 34.81l-1.132-3.736-1.133-3.736-2.7 2.84-2.7 2.839 3.833.896z" fill="#5EC5C2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m58 34.81-14.084 8.395 6.42-10.19L58 34.81z" fill="#C0FBFA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m35 29.427 8.916 13.779-1.252-11.986L35 29.427z" fill="#5EC5C2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m50.336 33.016-6.42 10.19-1.244-11.984 7.664 1.794z" fill="#87E3E1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m16.877 22.666-5.078 1.971 4.262 3.372 4.262 3.37.816-5.341.816-5.343-5.078 1.971zM6.721 26.609l-5.078 1.97 4.262 3.372 4.262 3.371.816-5.342.816-5.343-5.078 1.972z" fill="#7BE3CF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m5.09 37.294 5.077-1.972-4.262-3.371-4.261-3.371-.817 5.342-.816 5.343 5.078-1.971z" fill="#C5F3EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m15.245 33.351 5.078-1.971-4.262-3.371-4.262-3.371-.816 5.342-.816 5.342 5.078-1.97z" fill="#C5F3EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m25.4 29.41 5.078-1.972-4.262-3.371-4.261-3.372-.816 5.343-.816 5.342 5.078-1.97z" fill="#41C7AF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.478 27.438 21.117 48l-.794-16.62 10.155-3.942z" fill="#C5F3EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m0 39.269 21.117 8.73-10.961-12.672L0 39.269z" fill="#41C7AF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.323 31.38 21.117 48l-10.95-12.678 10.156-3.942z" fill="#7BE3CF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,22 +0,0 @@
|
||||
<svg width="518" height="152" viewBox="0 0 518 152" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.48 65.487v5.042h-1.772v-5.042h1.772zm1.621 6.671h5.013v1.782h-5.013v-1.782zm-10.027 0h5.013v1.782h-5.013v-1.782zm8.406 3.412v5.041h-1.772V75.57h1.772z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".5"/>
|
||||
<path opacity=".92" fill-rule="evenodd" clip-rule="evenodd" d="m9.504 29.894 2.707-4.715 1.658.962-2.707 4.715-1.658-.962zm2.066-7.12-4.689-2.722.958-1.667 4.688 2.723-.957 1.667zm9.378 5.445-4.689-2.722.957-1.667 4.69 2.722-.958 1.667zm-6.03-7.755 2.707-4.715 1.658.962-2.707 4.715-1.658-.962z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m60.85 11.508.708-3.662 1.288.251-.708 3.662-1.288-.252zm-.24-5.076-3.642-.712.25-1.295 3.642.712-.25 1.295zm7.283 1.423-3.642-.711.25-1.295 3.642.712-.25 1.294zm-5.627-3.671.708-3.662 1.287.251-.708 3.662-1.287-.251z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".81"/>
|
||||
<path opacity=".76" fill-rule="evenodd" clip-rule="evenodd" d="m107.034 22.162.493 5.675-1.995.175-.494-5.674 1.996-.176zm2.477 7.349 5.643-.497.175 2.007-5.644.496-.174-2.006zm-11.287.993 5.644-.497.174 2.007-5.643.496-.175-2.006zm9.797 3.008.494 5.675-1.996.175-.493-5.675 1.995-.175z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m16.191 92.492-5.006 1.636-.575-1.78 5.006-1.636.575 1.78zm-6.098 3.792 1.626 5.034-1.77.578-1.626-5.034 1.77-.578zM6.839 86.215l1.627 5.035-1.77.578-1.627-5.034 1.77-.579zm-.66 9.549-5.006 1.635-.576-1.78 5.007-1.635.575 1.78z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".91"/>
|
||||
<path opacity=".92" fill-rule="evenodd" clip-rule="evenodd" d="m35.176 59.176 5.102-1.97.692 1.814-5.101 1.97-.693-1.814zm6.118-4.264-1.958-5.13 1.803-.696 1.958 5.13-1.803.696zm3.916 10.26-1.958-5.13 1.804-.696 1.958 5.13-1.804.696zm.17-9.935 5.1-1.969.693 1.814-5.101 1.969-.693-1.814z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m95.733 86.583-4.383 2.649-.931-1.559 4.383-2.648.93 1.558zm-4.949 4.93 2.634 4.407-1.55.936-2.633-4.407 1.55-.937zm-5.267-8.816 2.633 4.408-1.55.936-2.633-4.408 1.55-.936zm1.45 9.183-4.384 2.648-.93-1.558 4.382-2.648.931 1.558z" fill="#36205D" style="mix-blend-mode:multiply"/>
|
||||
<path opacity=".98" fill-rule="evenodd" clip-rule="evenodd" d="m24.804 132.406-2.1-3.015 1.06-.746 2.1 3.014-1.06.747zm-3.747-3.307-2.998 2.111-.742-1.066 2.998-2.111.742 1.066zm5.996-4.222-2.998 2.111-.742-1.066 2.998-2.11.742 1.065zm-6.447 1.5-2.1-3.015 1.06-.746 2.1 3.014-1.06.747z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m60.65 142.295-1.594 3.144-1.105-.567 1.593-3.144 1.105.567zm-1.098 4.678 3.126 1.602-.563 1.112-3.127-1.602.564-1.112zm-6.254-3.204 3.127 1.602-.563 1.112-3.127-1.603.563-1.111zm4.165 4.814-1.593 3.144-1.106-.566 1.593-3.144 1.106.566z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".82"/>
|
||||
<path opacity=".71" fill-rule="evenodd" clip-rule="evenodd" d="m110.507 140.233 2.321-4.582 1.611.826-2.321 4.581-1.611-.825zm1.599-6.817-4.556-2.335.821-1.62 4.556 2.335-.821 1.62zm9.112 4.669-4.556-2.335.821-1.62 4.556 2.335-.821 1.62zm-6.068-7.015 2.321-4.582 1.611.825-2.321 4.582-1.611-.825z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M373.52 65.487v5.042h1.772v-5.042h-1.772zm-1.621 6.671h-5.013v1.782h5.013v-1.782zm10.027 0h-5.013v1.782h5.013v-1.782zm-8.406 3.412v5.041h1.772V75.57h-1.772z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".5"/>
|
||||
<path opacity=".92" fill-rule="evenodd" clip-rule="evenodd" d="m508.496 29.894-2.707-4.715-1.658.962 2.707 4.715 1.658-.962zm-2.066-7.12 4.689-2.722-.958-1.667-4.689 2.723.958 1.667zm-9.378 5.445 4.689-2.722-.957-1.667-4.689 2.722.957 1.667zm6.03-7.755-2.707-4.715-1.658.962 2.707 4.715 1.658-.962z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m457.15 11.508-.708-3.662-1.287.251.707 3.662 1.288-.252zm.24-5.076 3.642-.712-.25-1.295-3.642.712.25 1.295zm-7.283 1.423 3.642-.711-.251-1.295-3.641.712.25 1.294zm5.627-3.671-.708-3.662-1.287.251.708 3.662 1.287-.251z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".81"/>
|
||||
<path opacity=".76" fill-rule="evenodd" clip-rule="evenodd" d="m410.966 22.162-.493 5.675 1.995.175.494-5.674-1.996-.176zm-2.477 7.349-5.643-.497-.175 2.007 5.644.496.174-2.006zm11.287.993-5.644-.497-.174 2.007 5.643.496.175-2.006zm-9.797 3.008-.494 5.675 1.996.175.493-5.675-1.995-.175z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m501.809 92.492 5.006 1.636.575-1.78-5.006-1.636-.575 1.78zm6.098 3.792-1.626 5.034 1.77.578 1.626-5.034-1.77-.578zm3.254-10.069-1.627 5.035 1.77.578 1.627-5.034-1.77-.579zm.66 9.549 5.006 1.635.576-1.78-5.007-1.635-.575 1.78z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".91"/>
|
||||
<path opacity=".92" fill-rule="evenodd" clip-rule="evenodd" d="m482.824 59.176-5.102-1.97-.692 1.814 5.101 1.97.693-1.814zm-6.118-4.264 1.958-5.13-1.803-.696-1.958 5.13 1.803.696zm-3.916 10.26 1.958-5.13-1.804-.696-1.958 5.13 1.804.696zm-.169-9.935-5.102-1.969-.692 1.814 5.101 1.969.693-1.814z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m422.267 86.583 4.383 2.649.932-1.559-4.384-2.648-.931 1.558zm4.949 4.93-2.634 4.407 1.55.936 2.634-4.407-1.55-.937zm5.267-8.816-2.633 4.408 1.549.936 2.634-4.408-1.55-.936zm-1.449 9.183 4.383 2.648.931-1.558-4.383-2.648-.931 1.558z" fill="#36205D" style="mix-blend-mode:multiply"/>
|
||||
<path opacity=".98" fill-rule="evenodd" clip-rule="evenodd" d="m493.196 132.406 2.099-3.015-1.06-.746-2.099 3.014 1.06.747zm3.747-3.307 2.998 2.111.742-1.066-2.998-2.111-.742 1.066zm-5.996-4.222 2.998 2.111.742-1.066-2.998-2.11-.742 1.065zm6.447 1.5 2.1-3.015-1.06-.746-2.099 3.014 1.059.747z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m457.351 142.295 1.593 3.144 1.105-.567-1.593-3.144-1.105.567zm1.097 4.678-3.126 1.602.563 1.112 3.127-1.602-.564-1.112zm6.254-3.204-3.127 1.602.563 1.112 3.127-1.603-.563-1.111zm-4.165 4.814 1.593 3.144 1.106-.566-1.593-3.144-1.106.566z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".82"/>
|
||||
<path opacity=".71" fill-rule="evenodd" clip-rule="evenodd" d="m407.493 140.233-2.321-4.582-1.611.826 2.321 4.581 1.611-.825zm-1.599-6.817 4.556-2.335-.821-1.62-4.556 2.335.821 1.62zm-9.112 4.669 4.556-2.335-.821-1.62-4.556 2.335.821 1.62zm6.068-7.015-2.321-4.582-1.611.825 2.321 4.582 1.611-.825z" fill="#fff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.26512 0L4.84341 3.57829L3.57829 4.84341L0 1.26512L1.26512 0ZM7.15659 3.57829L10.7349 5.33207e-08L12 1.26512L8.42171 4.84341L7.15659 3.57829ZM5.33207e-08 10.7349L3.57829 7.15659L4.84341 8.42171L1.26512 12L5.33207e-08 10.7349ZM8.42171 7.15659L12 10.7349L10.7349 12L7.15659 8.42171L8.42171 7.15659Z" fill="#FFB445"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 469 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="138" height="12" viewBox="0 0 138 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m127.265 0 3.578 3.578-1.265 1.265L126 1.265 127.265 0zm5.892 3.578L136.735 0 138 1.265l-3.578 3.578-1.265-1.265zM126 10.735l3.578-3.578 1.265 1.265L127.265 12 126 10.735zm8.422-3.578L138 10.735 136.735 12l-3.578-3.578 1.265-1.265z" fill="#FFB445"/>
|
||||
<path d="M114.445 4.555 112.5 1l-1.945 3.555L107.914 6h-3.828l-1.349-.737L101.5 3l-1.237 2.263L98.914 6H0v1h98.914l1.349.737L101.5 10l1.237-2.263L104.086 7h3.828l2.641 1.445L112.5 12l1.945-3.555L118 6.5l-3.555-1.945z" fill="#36205D"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 647 B |
@@ -1,37 +0,0 @@
|
||||
<svg width="85" height="32" viewBox="0 0 85 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m4.93 12.255 2.466-.63-1.983-1.597-.63-2.468-1.595 1.986-2.465.63 1.983 1.597.63 2.468 1.595-1.986zM80.034 7.698l2.465-.63-1.983-1.597-.63-2.468-1.594 1.985-2.466.631 1.983 1.596.63 2.469 1.595-1.986zM42.27 7.427l2.929.487-1.368-2.638.487-2.932-2.635 1.37-2.928-.488 1.367 2.638-.486 2.932 2.634-1.37zM78.215 26.355l2.694 2.064.033-3.396 2.063-2.697-3.393-.034-2.694-2.065-.033 3.397-2.062 2.697 3.392.034zM38.321 28.092l2.092.348-.977-1.885.347-2.094-1.881.978-2.092-.348.977 1.884-.348 2.095 1.882-.978zM12.17 30.035l.916 1.915.981-1.882 1.913-.916-1.88-.982-.915-1.916-.981 1.882-1.913.917 1.88.982z" fill="#fff" fill-opacity=".5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m24.878 12.01 6.73-1.805 2.524 9.433-6.73 1.806-2.524-9.433z" fill="#F9F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m18.148 13.816 6.73-1.805 2.524 9.433-6.73 1.805-2.524-9.433z" fill="#E1E0E3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m23.532 12.372 1.346-.361 2.524 9.433-1.346.36-2.524-9.432z" fill="#6133B4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m24.878 12.01 1.346-.36 2.524 9.433-1.346.36-2.524-9.432z" fill="#9A62FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m25.696 20.457 1.345-.36.361 1.347-1.346.36-.36-1.347zM23.532 12.372l1.346-.361.36 1.347-1.346.361-.36-1.347z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m18.148 13.816 5.384-1.444.36 1.348-5.383 1.444-.36-1.348zM20.312 21.902l5.384-1.445.36 1.348-5.383 1.444-.36-1.347zM26.224 11.65l5.383-1.445.36 1.348-5.383 1.444-.36-1.347zM28.387 19.735l5.384-1.444.36 1.347-5.383 1.445-.36-1.348z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m27.041 20.096 1.346-.36.361 1.347-1.346.36-.36-1.347zM24.878 12.01l1.346-.36.36 1.347-1.346.361-.36-1.347z" fill="#6133B4"/>
|
||||
<path clip-rule="evenodd" d="M24.735 4.954c-.335-1.183-1.148-2.301-2.285-2.51-1.138-.21-1.923.616-1.7 1.476.221.86 1 1.122 3.498 2.183.71.302.823.034.487-1.149z" stroke="#6133B4" stroke-width="1.5"/>
|
||||
<path clip-rule="evenodd" d="M27.66 5.365c.648-1.044 1.737-1.895 2.888-1.782 1.151.112 1.678 1.123 1.228 1.889-.45.765-1.27.802-3.964 1.133-.765.094-.8-.195-.152-1.24z" stroke="#9A62FF" stroke-width="1.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.319 4.294c-2.24-.315-1.259 2.44-.36 2.566.898.126 2.6-2.25.36-2.566z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m26.016 6.454 8.279 1.165-.582 4.145-8.279-1.165.582-4.145z" fill="#F9F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m17.737 5.29 8.279 1.164-.582 4.145-8.279-1.165.582-4.145z" fill="#E1E0E3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m23.256 6.066 5.52.777-.582 4.144-5.52-.776.582-4.145z" fill="#9A62FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m23.256 6.066 2.76.388-.582 4.145-2.76-.388.582-4.145z" fill="#6133B4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m26.016 6.454 2.76.389-.195 1.381-2.76-.388.195-1.382z" fill="#6133B4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m23.256 6.066 2.76.388-.194 1.382-2.76-.388.194-1.382z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m17.349 8.053 5.52.776-.195 1.382-5.519-.777.194-1.381zM28.388 9.606l5.519.776-.194 1.382-5.52-.777.195-1.381z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path clip-rule="evenodd" d="M56.55 9.16c-.624-1.413-1.83-2.662-3.282-2.724-1.452-.062-2.285 1.104-1.858 2.135.426 1.031 1.441 1.22 4.734 2.104.935.25 1.03-.102.406-1.515z" stroke="#6133B4" stroke-width="1.5"/>
|
||||
<path clip-rule="evenodd" d="M60.26 9.16c.624-1.413 1.83-2.662 3.283-2.724 1.451-.062 2.284 1.104 1.857 2.135-.426 1.031-1.44 1.22-4.734 2.104-.935.25-1.03-.102-.406-1.515z" stroke="#9A62FF" stroke-width="1.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.405 8.061c-2.842 0-1.14 3.256 0 3.256s2.842-3.256 0-3.256z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.405 10.802H68.91v5.259H58.405v-5.259z" fill="#F9F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.901 10.802h10.504v5.259H47.901v-5.259z" fill="#E1E0E3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.904 10.802h7.002v5.259h-7.002v-5.259z" fill="#9A62FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.904 10.802h3.501v5.259h-3.501v-5.259zM58.405 10.802h3.501v1.753h-3.5v-1.753z" fill="#6133B4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.904 10.802h3.501v1.753h-3.501v-1.753z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.405 16.06h8.753v12.27h-8.753V16.06z" fill="#F9F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.652 16.06h8.753v12.27h-8.753V16.06z" fill="#E1E0E3"/>
|
||||
<path fill="#6133B4" d="M56.654 16.061h1.751v12.27h-1.751z"/>
|
||||
<path fill="#9A62FF" d="M58.405 16.061h1.751v12.27h-1.751z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M56.654 26.578h1.751v1.753h-1.75v-1.753zM56.654 16.06h1.751v1.754h-1.75V16.06z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.652 16.06h7.002v1.754h-7.002V16.06z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.901 14.308h7.003v1.753H47.9v-1.753zM49.652 26.578h7.002v1.753h-7.002v-1.753zM60.156 16.06h7.002v1.754h-7.002V16.06z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M61.906 14.308h7.003v1.753h-7.003v-1.753zM60.156 26.578h7.002v1.753h-7.002v-1.753z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.405 26.578h1.75v1.753h-1.75v-1.753zM58.405 16.06h1.75v1.754h-1.75V16.06z" fill="#6133B4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.7 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path fill="url(#sxizdfpdya)" d="M0 0h68v68H0z"/>
|
||||
<defs>
|
||||
<pattern id="sxizdfpdya" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#pomapjzcdb" transform="scale(.0147)"/>
|
||||
</pattern>
|
||||
<image id="pomapjzcdb" width="68" height="68" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAABECAYAAAA4E5OyAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5gwJFx8lKwafmAAABjRJREFUeNrtm39MVWUYx78IV5BfF3Qg8kNppNIoyhKWZmUBlpRNpC3Uac7EUUGrscxM+AOHia1foKmDHOHItkbhLGnpzGVBimtlozmVTeByCRDhwgXll/THc17de3zvuedernDd3uefc895f5/zuc/3ed57DyBNmjRpzpuH+sLvR8fGAODR0nS+YmAgAMCQNQMAMJKwkys//+tUAMDQDTrPzNgIACj58iAAoLH5LADgx/Kzwols/zyba3+jny9n7WNmJ3LXffzoONUHTrVPyvDg7sEUyQRvXrYKrvX2AgCmK2RUxL9OT35oDzWs38bVf3XlNe78sfindE3gam/TpCw87fwPwuuSEL2EXMg6DgBY/HW6+E4OdQAA4lZ4CYnotVgBAEvShuiIRwAAXxV/y9Xbve+jSSHj2SfIl1RLQpwkRO1LmDEimJV+Qyqy+709wvZx/3wBAFhk3QQAONlYqKoxpGuijLDfvnduodmnTwIAUrM2ataThDhKSEp9FH2o38/5iopCuuO1/drqceAy+YiSAPI5m+rE9UvLcjTnweKfJWk7nVtppb5qkhB7hLAI01Y8wdSjyzLM9aCOJ9qrI+jDZTqkNlwBAByLixZOpMtk4uIeZrVrqkgVhtYL4x9m830iXXJDJCH2CGGqwXKB4gKxevw9eBgA0HDwIgAg94UdAICZL1JSkd5H5w2VVB4ZHg8A6IkkEoKMREKPhVTM/AHFPdNLxHFP6TnKdTIX8vOp7a8AABxWcpnVwZ9pLvjSlRYAwNzoKEmIS1TGlnqc2H1aWO5n8qSIFGsAAKdazVx53pR5pDog1SkMuF8z7rnD1yi+a4bRICy39vUBAPwDAhSVe5uy3Scp283+iUjE8ymSEJcQolaPVxZkkldPJRKiYmZqtl+3iy9vaWwk1agcJTUKJ18S9TI5gSu59ASjP07R9F0PW1YLV1BlzSMfhh1SZSaEkHe25ZMK1LQBANpa2wEAyTELxjUw64dZFOYJs21bxkjxcdAL1qzdCgBYXrlLEuJSlWFPdGlmuOIL6Lz/L4pITeZmIicnnH/SVV5cOfM9rJ9TpUyF5rl0YZXtebpISTr6viTEpXHIgy9NAwD4+voCAA4da+YIamn05OqbzKNc+bqMhwAAAwMDCiHudUMkIc4Swr7zakvf7698omNoaKiKqA6uXG+/kpB7jRCmBsEhscoVyikWrpoFAPAYJZ8wcqvjQQBAeKgRADDmST7n3HdXlRoGpV+KXFcVxWiO391F+zCY7dxC9baXhOglxPIL/UhqNlOEunKfNwAgInKYU5kLJ9Sbqjds9Ej15iYZOJVhVlfM9k3oERqf8ZE+xK0JYZElix96/iQ1iFRtXcYm+3E+5OaUafwdv3md8yEWi0WoMrcjVjKjiyNXSYirCWE5hzqrPVPA5ybZR4xc+d6VlzlfkKbakj20voMrj00f4fZNWI4k4xB3J4R9p5dmQjM3OX7AU3Of4+cy7XI/Ve7Dxk3eEiMJuSciVZbd3jZSjfkgdVi2KUyzvb1ydf+Tnf1KQvQSYisLTcyYodlh8ub7uPjDXnt1xGpr3M7OrnEtVG97SYg9QrYVPQcAWDywXZXdQslF9HXMIlZbpDBrrTOofAhlv7W+bwjr118sI082SL/wfZpfJ33IhBJiDAxWvtx0iFg0zMcR9bwqtGFM1cOAQ0NGLBLXvzUPdRbe2y1Vxi1UpiFsr/LpE+76rIQR7jzI3yD0Hepsl1mPdVilMrbGhSY51zt7JSFuEameKZjOZbfrKnxdOoGqLCuX/U62SUIcJaS8ZQN3fmItX74hqpwj6M1q/h9BJataOQLKW14TD9Sib8JMZT7MrZOEuAUhDhO0AuMiwJb919k0LjIu/UH7MMuXSUIcsjveuYt7IH7MHSdq7FkOAAgJpm3/t/Lpf6v23rlj/7Pt7DYJ28t37hwlJDHh8TEA6LcOCJ8QM3an7e5DKE/mDrUIquHO/fzF8Y2X6WnhdTb+lqJsISHs/R1b47P2R/7NkYQ4pDJhIXPoCXp3c/sOIR7OvW1gi6QRPyJimnegMLtlqhI+JwgAYG7qEZL37uatuubB+rltVqkyExKH3G1TkzHefhixhkBJiDRp0qTddfsfGCIUXNZsU4gAAAAASUVORK5CYII="/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect width="16" height="16" fill="none"/><g id="b"><g id="c"><g id="d"><polygon id="e" points="12.2 2 14 3.8 9.8 8 14 12.2 12.2 14 8 9.8 3.8 14 2 12.2 6.2 8 2 3.8 3.8 2 8 6.2 12.2 2"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 308 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="48" height="20" viewBox="0 0 48 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48 10.334c0-3.418-1.653-6.115-4.813-6.115-3.174 0-5.094 2.697-5.094 6.088 0 4.019 2.267 6.048 5.52 6.048 1.587 0 2.787-.36 3.694-.868v-2.67c-.907.454-1.947.735-3.267.735-1.293 0-2.44-.454-2.587-2.03h6.52c0-.173.027-.868.027-1.188zm-6.587-1.268c0-1.51.92-2.137 1.76-2.137.813 0 1.68.628 1.68 2.136h-3.44zM32.947 4.22c-1.307 0-2.147.613-2.614 1.04l-.173-.827h-2.933V20l3.333-.707.013-3.779c.48.347 1.187.841 2.36.841 2.387 0 4.56-1.922 4.56-6.155-.013-3.871-2.213-5.98-4.546-5.98zm-.8 9.198c-.787 0-1.254-.28-1.574-.627l-.013-4.954c.347-.387.827-.654 1.587-.654 1.213 0 2.053 1.362 2.053 3.11 0 1.79-.827 3.125-2.053 3.125zM22.64 3.431l3.346-.72V0L22.64.708V3.43z" fill="#635BFF"/>
|
||||
<path fill="#635BFF" d="M22.64 4.446h3.347v11.682H22.64z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m19.053 5.434-.213-.988h-2.88v11.682h3.333V8.211c.787-1.028 2.12-.841 2.534-.694V4.446c-.427-.16-1.987-.454-2.774.988zM12.387 1.549l-3.254.694-.013 10.694c0 1.976 1.48 3.431 3.453 3.431 1.094 0 1.894-.2 2.334-.44v-2.71c-.427.173-2.534.787-2.534-1.189V7.29h2.534V4.447h-2.534l.014-2.897zM3.373 7.837c0-.52.427-.72 1.134-.72 1.013 0 2.293.306 3.306.854V4.833a8.783 8.783 0 0 0-3.306-.614C1.8 4.22 0 5.634 0 7.997c0 3.685 5.067 3.098 5.067 4.687 0 .614-.534.814-1.28.814-1.107 0-2.52-.454-3.64-1.068v3.178a9.233 9.233 0 0 0 3.64.76c2.773 0 4.68-1.375 4.68-3.764-.014-3.98-5.094-3.271-5.094-4.767z" fill="#635BFF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path id="b" d="m10,6h6V0h-2v2.72C12.49.99,10.3,0,8,0,3.59,0,0,3.59,0,8s3.59,8,8,8c2.69,0,5.2-1.35,6.68-3.6l-1.67-1.1c-1.11,1.69-2.99,2.71-5.01,2.7-3.31,0-6-2.69-6-6s2.69-6,6-6c1.72,0,3.33.74,4.46,2h-2.46v2Z" fill-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 341 B |
@@ -22,10 +22,6 @@
|
||||
Account created:
|
||||
<strong>{{ hero.auth.timestamps.created | formatDate }}</strong>
|
||||
</div>
|
||||
<div v-if="hero.flags.thirdPartyTools">
|
||||
User has employed <strong>third party tools</strong>. Last known usage:
|
||||
<strong>{{ hero.flags.thirdPartyTools | formatDate }}</strong>
|
||||
</div>
|
||||
<div v-if="cronError">
|
||||
"lastCron" value:
|
||||
<strong>{{ hero.lastCron | formatDate }}</strong>
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
v-model="hero.auth.blocked"
|
||||
type="checkbox"
|
||||
> Ban / Block
|
||||
<p>
|
||||
<small>
|
||||
Banning a user also auto-hides all their guild posts.
|
||||
</small>
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
|
||||
@@ -17,18 +17,10 @@
|
||||
Payment schedule ("basic-earned" is monthly):
|
||||
<strong>{{ hero.purchased.plan.planId }}</strong>
|
||||
</div>
|
||||
<div v-if="hero.purchased.plan.planId == 'group_plan_auto'">
|
||||
Group plan ID:
|
||||
<strong>{{ hero.purchased.plan.owner }}</strong>
|
||||
</div>
|
||||
<div v-if="hero.purchased.plan.dateCreated">
|
||||
Creation date:
|
||||
<strong>{{ dateFormat(hero.purchased.plan.dateCreated) }}</strong>
|
||||
</div>
|
||||
<div v-if="hero.purchased.plan.dateCurrentTypeCreated">
|
||||
Start date for current subscription type:
|
||||
<strong>{{ dateFormat(hero.purchased.plan.dateCurrentTypeCreated) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Termination date:
|
||||
<strong
|
||||
@@ -51,66 +43,42 @@
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
Perk offset months:
|
||||
Months until renewal:
|
||||
<strong>{{ hero.purchased.plan.consecutive.offset }}</strong>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
Perk month count:
|
||||
<input
|
||||
v-model="hero.purchased.plan.perkMonthCount"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="2"
|
||||
step="1"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
Next Mystic Hourglass:
|
||||
<strong>{{ nextHourglassDate }}</strong>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<label>
|
||||
Mystic Hourglasses:
|
||||
<input
|
||||
v-model="hero.purchased.plan.consecutive.trinkets"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<label>
|
||||
Gem cap increase:
|
||||
<input
|
||||
v-model="hero.purchased.plan.consecutive.gemCapExtra"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="25"
|
||||
step="5"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
Total Gem cap:
|
||||
<strong>{{ Number(hero.purchased.plan.consecutive.gemCapExtra) + 25 }}</strong>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<label>
|
||||
Gems bought this month:
|
||||
<input
|
||||
v-model="hero.purchased.plan.gemsBought"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="hero.purchased.plan.consecutive.gemCapExtra + 25"
|
||||
step="1"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
Next Mystic Hourglass:
|
||||
<strong>{{ nextHourglassDate }}</strong>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<label>
|
||||
Mystic Hourglasses:
|
||||
<input
|
||||
v-model="hero.purchased.plan.consecutive.trinkets"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
Gem cap:
|
||||
<strong>{{ hero.purchased.plan.consecutive.gemCapExtra + 25 }}</strong>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<label>
|
||||
Gems bought this month:
|
||||
<input
|
||||
v-model="hero.purchased.plan.gemsBought"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="hero.purchased.plan.consecutive.gemCapExtra + 25"
|
||||
step="1"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="hero.purchased.plan.extraMonths > 0"
|
||||
>
|
||||
@@ -168,7 +136,14 @@ export default {
|
||||
nextHourglassDate () {
|
||||
const currentPlanContext = getPlanContext(this.hero, new Date());
|
||||
|
||||
return currentPlanContext.nextHourglassDate.format('MMMM YYYY');
|
||||
return currentPlanContext.nextHourglassDate.format('MMMM');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'hero.purchased.plan.consecutive.count' () { // eslint-disable-line object-shorthand
|
||||
this.hero.purchased.plan.consecutive.gemCapExtra = Math.min(
|
||||
Math.floor(this.hero.purchased.plan.consecutive.count / 3) * 5, 25,
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -86,13 +86,6 @@
|
||||
>{{ $t('companyContribute') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://translate.habitica.com/"
|
||||
target="_blank"
|
||||
>{{ $t('translateHabitica') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Support -->
|
||||
@@ -108,7 +101,6 @@
|
||||
v-if="user"
|
||||
>
|
||||
<a
|
||||
href=""
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal()"
|
||||
>
|
||||
@@ -213,7 +205,7 @@
|
||||
</a>
|
||||
<a
|
||||
class="social-circle"
|
||||
href="https://twitter.com/habitica/"
|
||||
href="https://twitter.com/habitica"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
@@ -223,7 +215,7 @@
|
||||
</a>
|
||||
<a
|
||||
class="social-circle"
|
||||
href="https://www.facebook.com/Habitica/"
|
||||
href="https://www.facebook.com/Habitica"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
@@ -232,7 +224,7 @@
|
||||
></div>
|
||||
</a><a
|
||||
class="social-circle"
|
||||
href="http://blog.habitrpg.com/"
|
||||
href="https://www.tumblr.com/Habitica"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
@@ -480,6 +472,10 @@ footer {
|
||||
color: $purple-300;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:not([href]):not([class]):hover { // needed to make "report a bug"'s hover state correct
|
||||
color: $purple-300;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
column-gap: 1.5rem;
|
||||
display: grid;
|
||||
@@ -582,7 +578,6 @@ h3 {
|
||||
.text{
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
text-overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -679,6 +674,11 @@ h3 {
|
||||
|
||||
footer {
|
||||
padding: 24px 16px;
|
||||
a:not([href]):not([class]):hover { // needed to make "report a bug"'s hover state correct
|
||||
color: $purple-300;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
column-gap: 1.5rem;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
@@ -718,6 +718,10 @@ h3 {
|
||||
@media (max-width: 1024px) and (min-width: 768px) {
|
||||
footer {
|
||||
padding: 24px 24px;
|
||||
a:not([href]):not([class]):hover { // needed to make "report a bug"'s hover state correct
|
||||
color: $purple-300;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.desktop {
|
||||
@@ -810,7 +814,7 @@ export default {
|
||||
...mapState({ user: 'user.data' }),
|
||||
...mapState(['isUserLoaded']),
|
||||
getDataDisplayToolUrl () {
|
||||
const base = 'https://tools.habitica.com/';
|
||||
const base = 'https://oldgods.net/habitrpg/habitrpg_user_data_display.html';
|
||||
if (!this.user) return null;
|
||||
return `${base}?uuid=${this.user._id}`;
|
||||
},
|
||||
|
||||
@@ -244,7 +244,7 @@ export default {
|
||||
petClass () {
|
||||
if (some(
|
||||
this.currentEventList,
|
||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'teaShop',
|
||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'virtual',
|
||||
)) {
|
||||
return this.foolPet(this.member.items.currentPet);
|
||||
}
|
||||
|
||||
@@ -159,6 +159,7 @@ label {
|
||||
}
|
||||
|
||||
.cancel-link {
|
||||
color: $blue-10;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ label {
|
||||
}
|
||||
|
||||
.cancel-link {
|
||||
color: $blue-10;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
|
||||
@@ -322,7 +322,6 @@ import omit from 'lodash/omit';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
||||
import closeChallengeModal from './closeChallengeModal';
|
||||
import Column from '../tasks/column';
|
||||
@@ -359,7 +358,7 @@ export default {
|
||||
userLink,
|
||||
groupLink,
|
||||
},
|
||||
mixins: [challengeMemberSearchMixin, externalLinks, userStateMixin],
|
||||
mixins: [challengeMemberSearchMixin, userStateMixin],
|
||||
props: ['challengeId'],
|
||||
data () {
|
||||
return {
|
||||
@@ -415,10 +414,6 @@ export default {
|
||||
mounted () {
|
||||
if (!this.searchId) this.searchId = this.challengeId;
|
||||
if (!this.challenge._id) this.loadChallenge();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
async beforeRouteUpdate (to, from, next) {
|
||||
this.searchId = to.params.challengeId;
|
||||
|
||||
@@ -120,7 +120,6 @@ import { mapState } from '@/libs/store';
|
||||
import Sidebar from './sidebar';
|
||||
import ChallengeItem from './challengeItem';
|
||||
import challengeModal from './challengeModal';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
@@ -132,7 +131,7 @@ export default {
|
||||
challengeModal,
|
||||
MugenScroll,
|
||||
},
|
||||
mixins: [challengeUtilities, externalLinks],
|
||||
mixins: [challengeUtilities],
|
||||
data () {
|
||||
return {
|
||||
loading: true,
|
||||
@@ -178,10 +177,6 @@ export default {
|
||||
section: this.$t('challenges'),
|
||||
});
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
updateSearch (eventData) {
|
||||
|
||||
@@ -81,8 +81,6 @@ import challengeModal from './challengeModal';
|
||||
import { mapState } from '@/libs/store';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import challengeItem from './challengeItem';
|
||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||
|
||||
@@ -94,7 +92,6 @@ export default {
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
mixins: [externalLinks],
|
||||
props: ['group'],
|
||||
data () {
|
||||
return {
|
||||
@@ -121,10 +118,6 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
async loadChallenges () {
|
||||
|
||||
@@ -50,21 +50,7 @@ export default {
|
||||
challengeId: this.challengeId,
|
||||
keep,
|
||||
});
|
||||
const userTasksByType = (await this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true })).data;
|
||||
let tagInUse = false;
|
||||
Object.keys(userTasksByType).forEach(taskType => {
|
||||
userTasksByType[taskType].forEach(task => {
|
||||
if (task.tags.indexOf(this.challengeId) > -1) {
|
||||
tagInUse = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!tagInUse) {
|
||||
await this.$store.dispatch(
|
||||
'tags:deleteTag',
|
||||
{ tagId: this.challengeId },
|
||||
);
|
||||
}
|
||||
await this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true });
|
||||
this.close();
|
||||
},
|
||||
close () {
|
||||
|
||||
@@ -145,7 +145,6 @@ import Sidebar from './sidebar';
|
||||
import ChallengeItem from './challengeItem';
|
||||
import challengeModal from './challengeModal';
|
||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
|
||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
@@ -157,7 +156,7 @@ export default {
|
||||
challengeModal,
|
||||
MugenScroll,
|
||||
},
|
||||
mixins: [challengeUtilities, externalLinks],
|
||||
mixins: [challengeUtilities],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
@@ -204,10 +203,6 @@ export default {
|
||||
section: this.$t('challenges'),
|
||||
});
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
updateSearch (eventData) {
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
}
|
||||
|
||||
a.cancel-link {
|
||||
color: $blue-10;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
v-if="editing"
|
||||
class="menu-container col-2"
|
||||
:class="{active: activeTopPage === 'backgrounds'}"
|
||||
@click="changeTopPage('backgrounds', '2023')"
|
||||
@click="changeTopPage('backgrounds', '2022')"
|
||||
>
|
||||
<div class="menu-item">
|
||||
<div
|
||||
@@ -183,8 +183,10 @@
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[0].items"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-2"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
@@ -193,99 +195,55 @@
|
||||
>
|
||||
<div class="small-rectangle"></div>
|
||||
</div>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!filterBackgrounds && user.purchased.background.birthday_bash"
|
||||
v-if="!filterBackgrounds"
|
||||
class="row text-center title-row"
|
||||
>
|
||||
<strong>{{ backgroundShopSets[1].text }}</strong>
|
||||
</div>
|
||||
<div
|
||||
v-if="!filterBackgrounds"
|
||||
class="row title-row"
|
||||
>
|
||||
<div
|
||||
class="row text-center title-row"
|
||||
>
|
||||
<strong>{{ backgroundShopSets[2].text }}</strong>
|
||||
</div>
|
||||
<div
|
||||
class="row title-row"
|
||||
v-for="bg in backgroundShopSets[1].items"
|
||||
:key="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="!user.purchased.background[bg.key]
|
||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[2].items"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
@click="unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
class="background"
|
||||
:class="`background_${bg.key}`"
|
||||
></div>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!filterBackgrounds">
|
||||
<div
|
||||
class="row text-center title-row"
|
||||
>
|
||||
<strong>{{ backgroundShopSets[1].text }}</strong>
|
||||
</div>
|
||||
<div
|
||||
class="row title-row"
|
||||
>
|
||||
class="background"
|
||||
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
||||
></div>
|
||||
<i
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="glyphicon glyphicon-lock"
|
||||
></i>
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[1].items"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
@click="!user.purchased.background[bg.key]
|
||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="purchase-background single d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<div
|
||||
class="background"
|
||||
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
||||
class="svg-icon hourglass"
|
||||
v-html="icons.hourglass"
|
||||
></div>
|
||||
<i
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="glyphicon glyphicon-lock"
|
||||
></i>
|
||||
<div
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="purchase-background single d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<div
|
||||
class="svg-icon hourglass"
|
||||
v-html="icons.hourglass"
|
||||
></div>
|
||||
<span class="price">1</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="badge-top"
|
||||
@click.stop.prevent="togglePinned(bg)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isBackgroundPinned(bg)"
|
||||
/>
|
||||
</span>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
<span class="price">1</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="badge-top"
|
||||
@click.stop.prevent="togglePinned(bg)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isBackgroundPinned(bg)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<sub-menu
|
||||
@@ -317,8 +275,10 @@
|
||||
<div
|
||||
v-for="bg in set.items"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="!user.purchased.background[bg.key]
|
||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||
>
|
||||
@@ -349,13 +309,6 @@
|
||||
:pinned="isBackgroundPinned(bg)"
|
||||
/>
|
||||
</span>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!ownsSet('background', set.items) && set.identifier !== 'incentiveBackgrounds'"
|
||||
@@ -378,21 +331,16 @@
|
||||
<div
|
||||
v-for="(bg) in ownedBackgrounds"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
class="background"
|
||||
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
||||
></div>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1237,7 +1185,7 @@ export default {
|
||||
},
|
||||
],
|
||||
|
||||
bgSubMenuItems: ['2023', '2022', '2021', '2020', '2019', '2018', '2017', '2016', '2015', '2014'].map(y => ({
|
||||
bgSubMenuItems: ['2022', '2021', '2020', '2019', '2018', '2017', '2016', '2015', '2014'].map(y => ({
|
||||
id: y,
|
||||
label: y,
|
||||
})),
|
||||
@@ -1266,7 +1214,6 @@ export default {
|
||||
2020: [],
|
||||
2021: [],
|
||||
2022: [],
|
||||
2023: [],
|
||||
};
|
||||
|
||||
// Hack to force update for now until we restructure the data
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="external-link-modal"
|
||||
size="md"
|
||||
>
|
||||
<!-- HEADER -->
|
||||
<div slot="modal-header">
|
||||
<div
|
||||
class="modal-close"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
class="icon-close"
|
||||
v-html="icons.close"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="exclamation-container d-flex align-items-center justify-content-center">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-exclamation"
|
||||
v-html="icons.exclamation"
|
||||
></div>
|
||||
</div>
|
||||
<h2>
|
||||
{{ $t('leaveHabitica') }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- BODY -->
|
||||
<div
|
||||
class="row leave-warning-text"
|
||||
v-html="$t('leaveHabiticaText')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="skip-modal"
|
||||
>
|
||||
{{ $t('skipExternalLinkModal') }}
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div slot="modal-footer">
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary"
|
||||
@click="proceed()"
|
||||
>
|
||||
{{ $t('continue') }}
|
||||
</button>
|
||||
<div
|
||||
v-once
|
||||
class="close-link justify-content-center"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
#external-link-modal {
|
||||
&.modal {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.modal-md {
|
||||
max-width: 448px;
|
||||
min-width: 330px;
|
||||
margin: auto;
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
.icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
|
||||
& svg {
|
||||
fill: $yellow-1;
|
||||
opacity: 0.75;
|
||||
}
|
||||
& :hover {
|
||||
fill: $yellow-1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
justify-content: center;
|
||||
padding-top: 32px;
|
||||
padding-bottom: 0px;
|
||||
background: $yellow-100;
|
||||
border-top-right-radius: 8px;
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom: none;
|
||||
|
||||
.exclamation-container {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: $yellow-1;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.svg-exclamation {
|
||||
width: 8px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $yellow-1;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px 44px 20px 44px;
|
||||
background: $white;
|
||||
|
||||
.leave-warning-text {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
margin-top:24px;
|
||||
}
|
||||
|
||||
.skip-modal {
|
||||
color: $gray-100;
|
||||
font-size: 0.75rem;
|
||||
text-align: center;
|
||||
line-height: 1.33;
|
||||
margin-top: 16px;
|
||||
// padding-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background: $white;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
justify-content: center;
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
.close-link {
|
||||
color: $purple-300;
|
||||
line-height: 1.71;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
margin-top:16px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import exclamationIcon from '@/assets/svg/exclamation.svg';
|
||||
import closeIcon from '@/assets/svg/new-close.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: closeIcon,
|
||||
exclamation: exclamationIcon,
|
||||
}),
|
||||
url: '',
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:external-link', url => {
|
||||
this.url = url;
|
||||
this.$root.$emit('bv::show::modal', 'external-link-modal');
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica:external-link');
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'external-link-modal');
|
||||
},
|
||||
proceed () {
|
||||
window.open(this.url, '_blank').focus();
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -87,8 +87,6 @@
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import autocomplete from '../chat/autoComplete';
|
||||
import communityGuidelines from './communityGuidelines';
|
||||
import chatMessage from '../chat/chatMessages';
|
||||
@@ -105,7 +103,6 @@ export default {
|
||||
communityGuidelines,
|
||||
chatMessage,
|
||||
},
|
||||
mixins: [externalLinks],
|
||||
props: ['label', 'group', 'placeholder'],
|
||||
data () {
|
||||
return {
|
||||
@@ -135,10 +132,6 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.textbox = this.$refs['user-entry'];
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
||||
|
||||
@@ -11,12 +11,9 @@
|
||||
<div class="quest_screen"></div>
|
||||
<div class="row heading">
|
||||
<div class="col-12 text-center pr-5 pl-5">
|
||||
<h1
|
||||
v-once
|
||||
class="mb-2"
|
||||
>
|
||||
<h2 v-once>
|
||||
{{ $t('playInPartyTitle') }}
|
||||
</h1>
|
||||
</h2>
|
||||
<p
|
||||
v-once
|
||||
class="mb-4"
|
||||
@@ -25,91 +22,67 @@
|
||||
</p>
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary px-4 mb-2"
|
||||
class="btn btn-primary"
|
||||
@click="createParty()"
|
||||
>
|
||||
{{ $t('createParty') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<close-x
|
||||
@close="close()"
|
||||
/>
|
||||
</div>
|
||||
<div class="row grey-row">
|
||||
<div class="col-12 text-center px-0">
|
||||
<div class="col-12 text-center">
|
||||
<div class="join-party"></div>
|
||||
<h1
|
||||
v-once
|
||||
class="mb-2"
|
||||
>
|
||||
<h2 v-once>
|
||||
{{ $t('wantToJoinPartyTitle') }}
|
||||
</h1>
|
||||
<p
|
||||
v-once
|
||||
class="mb-4"
|
||||
v-html="$t('partyFinderDescription')"
|
||||
>
|
||||
</p>
|
||||
</h2>
|
||||
<p v-html="$t('wantToJoinPartyDescription')"></p>
|
||||
<div
|
||||
v-if="seeking"
|
||||
class="form-group"
|
||||
@click="copyUsername"
|
||||
>
|
||||
<div
|
||||
class="green-bar mb-3"
|
||||
>
|
||||
{{ $t('currentlyLookingForParty') }}
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div
|
||||
class="red-link"
|
||||
@click="seekParty()"
|
||||
>
|
||||
{{ $t('leave') }}
|
||||
<div class="d-flex align-items-center">
|
||||
<label
|
||||
v-once
|
||||
class="mr-3"
|
||||
>{{ $t('username') }}</label>
|
||||
<div class="flex-grow-1">
|
||||
<div class="input-group-prepend input-group-text">
|
||||
@
|
||||
<div class="text">
|
||||
{{ user.auth.local.username }}
|
||||
</div>
|
||||
<div
|
||||
class="svg-icon copy-icon"
|
||||
v-html="icons.copy"
|
||||
></div>
|
||||
<div
|
||||
v-once
|
||||
class="small"
|
||||
>
|
||||
{{ $t('copy') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-primary px-4 mt-2 mb-1"
|
||||
@click="seekParty()"
|
||||
>
|
||||
{{ $t('lookForParty') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#create-party-modal {
|
||||
display: flex !important;
|
||||
overflow-y: hidden;
|
||||
<style>
|
||||
#create-party-modal .modal-body {
|
||||
padding: 0rem 0.75rem;
|
||||
}
|
||||
|
||||
@media (max-height: 770px) {
|
||||
overflow-y: auto;
|
||||
}
|
||||
#create-party-modal .modal-dialog {
|
||||
width: 35.75rem;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0rem 0.75rem;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 566px;
|
||||
margin: auto;
|
||||
|
||||
@media (max-height: 826px) {
|
||||
margin-top: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 0;
|
||||
border-bottom: 0px;
|
||||
}
|
||||
#create-party-modal .modal-header {
|
||||
padding: 0;
|
||||
border-bottom: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -134,27 +107,15 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.green-bar {
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
color: $green-1;
|
||||
background-color: $green-100;
|
||||
border-radius: 2px;
|
||||
padding: 4px 0px 4px 0px;
|
||||
}
|
||||
|
||||
.grey-row {
|
||||
background-color: $gray-700;
|
||||
color: #4e4a57;
|
||||
padding: 2em;
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
border-radius: 0px 0px 2px 2px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: $purple-300;
|
||||
h2 {
|
||||
color: $gray-100;
|
||||
}
|
||||
|
||||
.header-wrap {
|
||||
@@ -171,6 +132,10 @@
|
||||
border-radius: 2px 2px 0 0;
|
||||
image-rendering: optimizequality;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $purple-200;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
@@ -217,21 +182,6 @@
|
||||
margin: 0.75rem auto 0.75rem 0.25rem;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
.red-link {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
color: $maroon-50;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.small {
|
||||
color: $gray-200;
|
||||
margin: auto 0.5rem auto 0.25rem;
|
||||
@@ -242,29 +192,21 @@
|
||||
import { mapState } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import closeX from '../ui/closeX';
|
||||
|
||||
import copyIcon from '@/assets/svg/copy.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
closeX,
|
||||
},
|
||||
mixins: [notifications],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
copy: copyIcon,
|
||||
}),
|
||||
seeking: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
mounted () {
|
||||
this.seeking = Boolean(this.user.party.seeking);
|
||||
},
|
||||
methods: {
|
||||
async createParty () {
|
||||
const group = {
|
||||
@@ -281,10 +223,7 @@ export default {
|
||||
});
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||
await this.$router.push('/party');
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||
this.$router.push('/party');
|
||||
},
|
||||
copyUsername () {
|
||||
if (navigator.clipboard) {
|
||||
@@ -299,12 +238,6 @@ export default {
|
||||
}
|
||||
this.text(this.$t('usernameCopied'));
|
||||
},
|
||||
seekParty () {
|
||||
this.$store.dispatch('user:set', {
|
||||
'party.seeking': !this.user.party.seeking ? new Date() : null,
|
||||
});
|
||||
this.seeking = !this.seeking;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -542,8 +542,7 @@ export default {
|
||||
await this.$store.dispatch('guilds:leave', data);
|
||||
|
||||
if (this.isParty) {
|
||||
await this.$router.push({ name: 'tasks' });
|
||||
window.location.reload(true);
|
||||
this.$router.push({ name: 'tasks' });
|
||||
}
|
||||
},
|
||||
upgradeGroup () {
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
<group-plan-creation-modal />
|
||||
<div>
|
||||
<div class="header">
|
||||
<h1 v-once class="text-center">
|
||||
{{ $t('groupPlanTitle') }}
|
||||
<h1 class="text-center">
|
||||
Need more for your Group?
|
||||
</h1>
|
||||
<div class="row">
|
||||
<div class="col-8 offset-2 text-center">
|
||||
<h2 v-once class="sub-text">
|
||||
<h2 class="sub-text">
|
||||
{{ $t('groupBenefitsDescription') }}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -24,8 +24,8 @@
|
||||
src="~@/assets/images/group-plans/group-14@3x.png"
|
||||
>
|
||||
<hr>
|
||||
<h2 v-once> {{ $t('teamBasedTasks') }} </h2>
|
||||
<p v-once> {{ $t('teamBasedTasksListDesc') }} </p>
|
||||
<h2>{{ $t('teamBasedTasks') }}</h2>
|
||||
<p>Set up an easily-viewed shared task list for the group. Assign tasks to your fellow group members, or let them claim their own tasks to make it clear what everyone is working on!</p><!-- eslint-disable-line max-len -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@@ -35,8 +35,8 @@
|
||||
src="~@/assets/images/group-plans/group-12@3x.png"
|
||||
>
|
||||
<hr>
|
||||
<h2 v-once> {{ $t('groupManagementControls') }} </h2>
|
||||
<p v-once> {{ $t('groupManagementControlsDesc') }} </p>
|
||||
<h2>Group Management Controls</h2>
|
||||
<p>Use task approvals to verify that a task that was really completed, add Group Managers to share responsibilities, and enjoy a private group chat for all team members.</p><!-- eslint-disable-line max-len -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@@ -46,8 +46,8 @@
|
||||
src="~@/assets/images/group-plans/group-13@3x.png"
|
||||
>
|
||||
<hr>
|
||||
<h2 v-once> {{ $t('inGameBenefits') }} </h2>
|
||||
<p v-once> {{ $t('inGameBenefitsDesc') }} </p>
|
||||
<h2>In-Game Benefits</h2>
|
||||
<p>Group members get an exclusive Jackalope Mount, as well as full subscription benefits, including special monthly equipment sets and the ability to buy gems with gold.</p><!-- eslint-disable-line max-len -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
a:not([href]) {
|
||||
color: $blue-10;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div
|
||||
v-if="seekers.length > 0"
|
||||
class="fit-content mx-auto mt-4"
|
||||
>
|
||||
<div class="d-flex align-items-center">
|
||||
<h1 v-once class="my-auto mr-auto"> {{ $t('findPartyMembers') }}</h1>
|
||||
<div
|
||||
class="btn btn-secondary btn-sync ml-auto my-auto pl-2 pr-3 d-flex"
|
||||
@click="refreshList()"
|
||||
>
|
||||
<div class="svg-icon icon-16 color my-auto mr-2" v-html="icons.sync"></div>
|
||||
<div class="ml-auto"> {{ $t('refreshList') }} </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap seeker-list">
|
||||
<div
|
||||
v-for="(seeker, index) in seekers"
|
||||
:key="seeker._id"
|
||||
class="seeker"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<avatar
|
||||
:member="seeker"
|
||||
:hideClassBadge="true"
|
||||
@click.native="showMemberModal(seeker._id)"
|
||||
class="mr-3 mb-2"
|
||||
/>
|
||||
<div class="card-data">
|
||||
<user-link
|
||||
:user-id="seeker._id"
|
||||
:name="seeker.profile.name"
|
||||
:backer="seeker.backer"
|
||||
:contributor="seeker.contributor"
|
||||
/>
|
||||
<div class="small-with-border pb-2 mb-2">
|
||||
@{{ seeker.auth.local.username }} • {{ $t('level') }} {{ seeker.stats.lvl }}
|
||||
</div>
|
||||
<div
|
||||
class="d-flex"
|
||||
>
|
||||
<strong v-once> {{ $t('classLabel') }} </strong>
|
||||
<span
|
||||
class="svg-icon d-inline-block icon-16 my-auto mx-2"
|
||||
v-html="icons[seeker.stats.class]"
|
||||
>
|
||||
</span>
|
||||
<strong
|
||||
:class="`${seeker.stats.class}-color`"
|
||||
>
|
||||
{{ $t(seeker.stats.class) }}
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<strong v-once class="mr-2"> {{ $t('checkinsLabel') }} </strong>
|
||||
{{ seeker.loginIncentives }}
|
||||
</div>
|
||||
<div>
|
||||
<strong v-once class="mr-2"> {{ $t('languageLabel') }} </strong>
|
||||
{{ displayLanguage(seeker.preferences.language) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<strong
|
||||
v-if="!seeker.invited"
|
||||
@click="inviteUser(seeker._id, index)"
|
||||
class="btn btn-primary w-100"
|
||||
>
|
||||
{{ $t('inviteToParty') }}
|
||||
</strong>
|
||||
<div
|
||||
v-else
|
||||
@click="rescindInvite(seeker._id, index)"
|
||||
class="btn btn-success w-100"
|
||||
v-html="$t('invitedToYourParty')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<mugen-scroll
|
||||
v-show="loading"
|
||||
:handler="infiniteScrollTrigger"
|
||||
:should-handle="!loading && canLoadMore"
|
||||
:threshold="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="seekers.length === 0 && !loading"
|
||||
class="d-flex flex-column empty-state text-center my-5"
|
||||
>
|
||||
<div class="gray-circle mb-3 mx-auto d-flex">
|
||||
<div
|
||||
class="svg-icon icon-32 color m-auto"
|
||||
v-html="icons.users"
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<strong class="mb-1"> {{ $t('findMorePartyMembers') }} </strong>
|
||||
<div v-html="$t('noOneLooking')"></div>
|
||||
</div>
|
||||
</div>
|
||||
<h2
|
||||
v-show="loading"
|
||||
class="loading"
|
||||
:class="seekers.length === 0 ? 'mt-3' : 'mt-0'"
|
||||
>
|
||||
{{ $t('loading') }}
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
h1 {
|
||||
color: $purple-300;
|
||||
}
|
||||
|
||||
strong {
|
||||
line-height: 1.71;
|
||||
}
|
||||
.avatar {
|
||||
background-color: $gray-600;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
box-shadow: none;
|
||||
color: $green-1;
|
||||
font-weight: normal;
|
||||
|
||||
&:not(:disabled):not(.disabled):active {
|
||||
color: $green-1;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-sync {
|
||||
min-width: 128px;
|
||||
max-height: 32px;
|
||||
|
||||
.svg-icon {
|
||||
color: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
.card-data {
|
||||
width: 267px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
color: $gray-100;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
.fit-content {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.gray-circle {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
color: $gray-600;
|
||||
background-color: $gray-200;
|
||||
border-radius: 100px;
|
||||
|
||||
.icon-32 {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: $purple-300;
|
||||
}
|
||||
|
||||
.seeker-list {
|
||||
max-width: 920px;
|
||||
|
||||
@media (max-width: 962px) {
|
||||
max-width: 464px;
|
||||
};
|
||||
|
||||
.seeker {
|
||||
width: 448px;
|
||||
margin-bottom: 24px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
background-color: $white;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 963px) {
|
||||
&:nth-child(2) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
&:nth-child(even) {
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.small-with-border {
|
||||
border-bottom: 1px solid $gray-500;
|
||||
color: $gray-100;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.healer-color {
|
||||
color: $yellow-10;
|
||||
}
|
||||
|
||||
.rogue-color {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
.warrior-color {
|
||||
color: $red-50;
|
||||
}
|
||||
|
||||
.wizard-color {
|
||||
color: $blue-10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
import MugenScroll from 'vue-mugen-scroll';
|
||||
import Avatar from '../avatar';
|
||||
import userLink from '../userLink';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import syncIcon from '@/assets/svg/sync-2.svg';
|
||||
import usersIcon from '@/assets/svg/users.svg';
|
||||
import warriorIcon from '@/assets/svg/warrior.svg';
|
||||
import rogueIcon from '@/assets/svg/rogue.svg';
|
||||
import healerIcon from '@/assets/svg/healer.svg';
|
||||
import wizardIcon from '@/assets/svg/wizard.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Avatar,
|
||||
MugenScroll,
|
||||
userLink,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
canLoadMore: true,
|
||||
loading: true,
|
||||
page: 0,
|
||||
party: {},
|
||||
seekers: [],
|
||||
icons: Object.freeze({
|
||||
warrior: warriorIcon,
|
||||
rogue: rogueIcon,
|
||||
healer: healerIcon,
|
||||
sync: syncIcon,
|
||||
users: usersIcon,
|
||||
wizard: wizardIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
availableLanguages: 'i18n.availableLanguages',
|
||||
user: 'user.data',
|
||||
}),
|
||||
},
|
||||
async mounted () {
|
||||
try {
|
||||
this.party = await this.$store.dispatch('guilds:getGroup', { groupId: this.user.party._id });
|
||||
} catch {
|
||||
this.$router.push('/');
|
||||
}
|
||||
if (!this.party._id || this.party.leader._id !== this.user._id) {
|
||||
this.$router.push('/');
|
||||
} else {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('lookingForPartyTitle'),
|
||||
});
|
||||
this.seekers = await this.$store.dispatch('party:lookingForParty');
|
||||
this.canLoadMore = this.seekers.length === 30;
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
displayLanguage (languageCode) {
|
||||
const language = this.availableLanguages.find(lang => lang.code === languageCode);
|
||||
if (language) {
|
||||
return language.name;
|
||||
}
|
||||
return languageCode;
|
||||
},
|
||||
infiniteScrollTrigger () {
|
||||
if (this.canLoadMore) {
|
||||
this.loading = true;
|
||||
}
|
||||
|
||||
this.loadMore();
|
||||
},
|
||||
async inviteUser (userId, index) {
|
||||
await this.$store.dispatch('guilds:invite', {
|
||||
invitationDetails: {
|
||||
inviter: this.user.profile.name,
|
||||
uuids: [userId],
|
||||
},
|
||||
groupId: this.party._id,
|
||||
});
|
||||
this.seekers[index].invited = true;
|
||||
},
|
||||
loadMore: debounce(async function loadMoreDebounce () {
|
||||
this.page += 1;
|
||||
const addlSeekers = await this.$store.dispatch('party:lookingForParty', { page: this.page });
|
||||
this.seekers = this.seekers.concat(addlSeekers);
|
||||
this.canLoadMore = this.seekers.length % 30 === 0;
|
||||
this.loading = false;
|
||||
}, 1000),
|
||||
async refreshList () {
|
||||
this.loading = true;
|
||||
this.page = 0;
|
||||
this.seekers = await this.$store.dispatch('party:lookingForParty');
|
||||
this.canLoadMore = this.seekers.length === 30;
|
||||
this.loading = false;
|
||||
},
|
||||
async rescindInvite (userId, index) {
|
||||
await this.$store.dispatch('members:removeMember', {
|
||||
memberId: userId,
|
||||
groupId: this.party._id,
|
||||
});
|
||||
this.seekers[index].invited = false;
|
||||
},
|
||||
showMemberModal (userId) {
|
||||
this.$router.push({ name: 'userProfile', params: { userId } });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -245,6 +245,10 @@
|
||||
text-align: center;
|
||||
|
||||
color: $gray-100;
|
||||
|
||||
a {
|
||||
color: $blue-10;
|
||||
}
|
||||
}
|
||||
|
||||
#quest-detail-modal {
|
||||
|
||||
@@ -377,9 +377,11 @@
|
||||
|
||||
.members-invited {
|
||||
min-height: 1rem;
|
||||
color: $blue-10;
|
||||
margin: 0;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: $blue-10;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="sidebar px-4">
|
||||
<div>
|
||||
<div class="buttons-wrapper">
|
||||
<div class="button-container d-flex">
|
||||
<div class="button-container button-with-menu-row">
|
||||
<button
|
||||
v-if="!isMember"
|
||||
class="btn btn-success btn-success"
|
||||
@@ -203,6 +203,10 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.button-with-menu-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.menuIcon {
|
||||
width: 4px;
|
||||
height: 1rem;
|
||||
|
||||
@@ -340,13 +340,12 @@
|
||||
<li>
|
||||
<a
|
||||
v-once
|
||||
href="https://tools.habitica.com/"
|
||||
href="https://oldgods.net/habitrpg/habitrpg_user_data_display.html"
|
||||
target="_blank"
|
||||
>{{ $t('dataDisplayTool') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href=""
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal()"
|
||||
>
|
||||
@@ -522,6 +521,21 @@
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
// formats the report a bug link to match the others
|
||||
a:not([href]) {
|
||||
&:not([role=button]) {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
a:not([href]):hover {
|
||||
&:not([role=button]) {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.tier1-icon, .tier2-icon {
|
||||
width: 11px;
|
||||
}
|
||||
@@ -745,7 +759,6 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { goToModForm } from '@/libs/modform';
|
||||
|
||||
@@ -822,23 +835,22 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
currentEvent: 'worldState.data.currentEvent',
|
||||
}),
|
||||
questData () {
|
||||
if (!this.group.quest) return {};
|
||||
return quests.quests[this.group.quest.key];
|
||||
},
|
||||
imageURLs () {
|
||||
const currentEvent = find(this.currentEventList, event => Boolean(event.season));
|
||||
if (!currentEvent) {
|
||||
if (!this.currentEvent || !this.currentEvent.season) {
|
||||
return {
|
||||
background: 'url(/static/npc/normal/tavern_background.png)',
|
||||
npc: 'url(/static/npc/normal/tavern_npc.png)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: `url(/static/npc/${currentEvent.season}/tavern_background.png)`,
|
||||
npc: `url(/static/npc/${currentEvent.season}/tavern_npc.png)`,
|
||||
background: `url(/static/npc/${this.currentEvent.season}/tavern_background.png)`,
|
||||
npc: `url(/static/npc/${this.currentEvent.season}/tavern_npc.png)`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -258,22 +258,13 @@
|
||||
:key="hero._id"
|
||||
>
|
||||
<td>
|
||||
<div
|
||||
<user-link
|
||||
v-if="hasPermission(hero, 'userSupport')"
|
||||
class="width-content"
|
||||
>
|
||||
<user-link
|
||||
:id="hero._id"
|
||||
:user="hero"
|
||||
/>
|
||||
<b-popover
|
||||
:target="hero._id"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('gamemaster')"
|
||||
/>
|
||||
</div>
|
||||
:user="hero"
|
||||
:popover="$t('gamemaster')"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
/>
|
||||
<user-link
|
||||
v-else
|
||||
:user="hero"
|
||||
@@ -311,10 +302,6 @@
|
||||
h4.expand-toggle::after {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.width-content {
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
<template>
|
||||
<base-banner
|
||||
banner-id="birthday-banner"
|
||||
class="birthday-banner"
|
||||
:show="showBirthdayBanner"
|
||||
height="3rem"
|
||||
:can-close="false"
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
:aria-label="$t('celebrateBirthday')"
|
||||
class="content d-flex justify-content-around align-items-center ml-auto mr-auto"
|
||||
@click="showBirthdayModal"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gifts left-gift"
|
||||
v-html="icons.giftsBirthday"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-ten-birthday"
|
||||
v-html="icons.tenBirthday"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="announce-text"
|
||||
v-html="$t('celebrateBirthday')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gifts right-gift"
|
||||
v-html="icons.giftsBirthday"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</base-banner>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.announce-text {
|
||||
color: $purple-50;
|
||||
}
|
||||
|
||||
.birthday-banner {
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 8px;
|
||||
background-image: linear-gradient(90deg,
|
||||
rgba(255,190,93,0) 0%,
|
||||
rgba(255,190,93,1) 25%,
|
||||
rgba(255,190,93,1) 75%,
|
||||
rgba(255,190,93,0) 100%),
|
||||
url('~@/assets/images/glitter.png');
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.left-gift {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.right-gift {
|
||||
margin: auto auto auto 8px;
|
||||
filter: flipH;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.svg-gifts {
|
||||
width: 85px;
|
||||
}
|
||||
|
||||
.svg-ten-birthday {
|
||||
width: 192.5px;
|
||||
margin-left: 8px;
|
||||
margin-right: 8.5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import { mapState } from '@/libs/store';
|
||||
import BaseBanner from './base';
|
||||
|
||||
import giftsBirthday from '@/assets/svg/gifts-birthday.svg';
|
||||
import tenBirthday from '@/assets/svg/10th-birthday-linear.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseBanner,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
giftsBirthday,
|
||||
tenBirthday,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
showBirthdayBanner () {
|
||||
return Boolean(find(this.currentEventList, event => Boolean(event.event === 'birthday10')));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showBirthdayModal () {
|
||||
this.$root.$emit('bv::show::modal', 'birthday-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
@@ -51,20 +51,20 @@
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="no-party d-none d-md-flex justify-content-center text-center mr-4"
|
||||
class="no-party d-none d-md-flex justify-content-center text-center mr-4"
|
||||
>
|
||||
<div class="align-self-center">
|
||||
<h3>{{ user.party._id ? $t('questWithOthers') : $t('battleWithFriends') }}</h3>
|
||||
<h3>{{ $t('battleWithFriends') }}</h3>
|
||||
<span
|
||||
class="small-text"
|
||||
v-html="user.party._id ? $t('inviteFriendsParty') : $t('startPartyDetail')"
|
||||
v-html="$t('inviteFriendsParty')"
|
||||
></span>
|
||||
<br>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="createOrInviteParty()"
|
||||
>
|
||||
{{ user.party._id ? $t('findPartyMembers') : $t('getStarted') }}
|
||||
{{ user.party._id ? $t('inviteFriends') : $t('startAParty') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +122,6 @@
|
||||
|
||||
<script>
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import { mapGetters, mapActions } from '@/libs/store';
|
||||
import MemberDetails from '../memberDetails';
|
||||
import createPartyModal from '../groups/createPartyModal';
|
||||
@@ -233,24 +232,10 @@ export default {
|
||||
this.expandedMember = memberId;
|
||||
}
|
||||
},
|
||||
async createOrInviteParty () {
|
||||
createOrInviteParty () {
|
||||
if (this.user.party._id) {
|
||||
await Analytics.track({
|
||||
eventName: 'Header Party CTA',
|
||||
eventAction: 'Header Party CTA',
|
||||
eventCategory: 'behavior',
|
||||
hitType: 'event',
|
||||
state: 'Find Party Members',
|
||||
});
|
||||
this.$router.push('/looking-for-party');
|
||||
this.$root.$emit('inviteModal::inviteToGroup', this.user.party);
|
||||
} else {
|
||||
await Analytics.track({
|
||||
eventName: 'Header Party CTA',
|
||||
eventAction: 'Header Party CTA',
|
||||
eventCategory: 'behavior',
|
||||
hitType: 'event',
|
||||
state: 'Get Started',
|
||||
});
|
||||
this.$root.$emit('bv::show::modal', 'create-party-modal');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<b-nav-item
|
||||
v-if="user.party._id && user._id !== partyLeaderId"
|
||||
v-if="user.party._id"
|
||||
class="topbar-item"
|
||||
:class="{'active': $route.path.startsWith('/party')}"
|
||||
tag="li"
|
||||
@@ -156,36 +156,6 @@
|
||||
>
|
||||
{{ $t('party') }}
|
||||
</b-nav-item>
|
||||
<li
|
||||
v-if="user.party._id && user._id === partyLeaderId"
|
||||
class="topbar-item droppable"
|
||||
:class="{'active': $route.path.startsWith('/party')}"
|
||||
>
|
||||
<div
|
||||
class="chevron rotate"
|
||||
@click="dropdownMobile($event)"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="chevron-icon-down"
|
||||
v-html="icons.chevronDown"
|
||||
></div>
|
||||
</div>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'party'}"
|
||||
>
|
||||
{{ $t('party') }}
|
||||
</router-link>
|
||||
<div class="topbar-dropdown">
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'lookingForParty'}"
|
||||
>
|
||||
{{ $t('lookingForPartyTitle') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</li>
|
||||
<b-nav-item
|
||||
v-if="!user.party._id"
|
||||
class="topbar-item"
|
||||
@@ -798,7 +768,6 @@ export default {
|
||||
return {
|
||||
isUserDropdownOpen: false,
|
||||
menuIsOpen: false,
|
||||
partyLeaderId: null,
|
||||
icons: Object.freeze({
|
||||
gem: gemIcon,
|
||||
gold: goldIcon,
|
||||
@@ -827,9 +796,8 @@ export default {
|
||||
};
|
||||
},
|
||||
},
|
||||
async mounted () {
|
||||
await this.getUserGroupPlans();
|
||||
await this.getUserParty();
|
||||
mounted () {
|
||||
this.getUserGroupPlans();
|
||||
Array.from(document.getElementById('menu_collapse').getElementsByTagName('a')).forEach(link => {
|
||||
link.addEventListener('click', this.closeMenu);
|
||||
});
|
||||
@@ -837,9 +805,6 @@ export default {
|
||||
link.addEventListener('mouseenter', this.dropdownDesktop);
|
||||
link.addEventListener('mouseleave', this.dropdownDesktop);
|
||||
});
|
||||
this.$root.$on('update-party', () => {
|
||||
this.getUserParty();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
modForm () {
|
||||
@@ -851,12 +816,6 @@ export default {
|
||||
async getUserGroupPlans () {
|
||||
await this.$store.dispatch('guilds:getGroupPlans');
|
||||
},
|
||||
async getUserParty () {
|
||||
if (this.user.party._id) {
|
||||
await this.$store.dispatch('party:getParty');
|
||||
this.partyLeaderId = this.$store.state.party.data.leader._id;
|
||||
}
|
||||
},
|
||||
openPartyModal () {
|
||||
this.$root.$emit('bv::show::modal', 'create-party-modal');
|
||||
},
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<base-notification
|
||||
:can-remove="canRemove"
|
||||
:has-icon="true"
|
||||
:notification="notification"
|
||||
:read-after-click="true"
|
||||
@click="action"
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
>
|
||||
<strong> {{ notification.data.title }} </strong>
|
||||
<span> {{ notification.data.text }} </span>
|
||||
</div>
|
||||
<div
|
||||
slot="icon"
|
||||
class="mt-3"
|
||||
:class="notification.data.icon"
|
||||
></div>
|
||||
</base-notification>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseNotification from './base';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseNotification,
|
||||
},
|
||||
props: {
|
||||
notification: {
|
||||
type: Object,
|
||||
default (data) {
|
||||
return data;
|
||||
},
|
||||
},
|
||||
canRemove: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
action () {
|
||||
if (!this.notification || !this.notification.data) {
|
||||
return;
|
||||
}
|
||||
if (this.notification.data.destination === 'backgrounds') {
|
||||
this.$store.state.avatarEditorOptions.editingUser = true;
|
||||
this.$store.state.avatarEditorOptions.startingPage = 'backgrounds';
|
||||
this.$store.state.avatarEditorOptions.subpage = '2023';
|
||||
this.$root.$emit('bv::show::modal', 'avatar-modal');
|
||||
} else {
|
||||
this.$router.push({ name: this.notification.data.destination || 'items' });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -5,14 +5,7 @@
|
||||
:notification="notification"
|
||||
>
|
||||
<div slot="content">
|
||||
<div
|
||||
v-html="$t('invitedToPartyBy', {
|
||||
userId: notification.data.inviter,
|
||||
userName: invitingUser.auth ? invitingUser.auth.local.username : null,
|
||||
party: notification.data.name,
|
||||
})"
|
||||
>
|
||||
</div>
|
||||
<div v-html="$t('invitedToParty', {party: notification.data.name})"></div>
|
||||
<div class="notifications-buttons">
|
||||
<div
|
||||
class="btn btn-small btn-success"
|
||||
@@ -34,38 +27,15 @@
|
||||
<script>
|
||||
import BaseNotification from './base';
|
||||
import { mapState } from '@/libs/store';
|
||||
import sync from '@/mixins/sync';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseNotification,
|
||||
},
|
||||
mixins: [sync],
|
||||
props: {
|
||||
notification: {
|
||||
type: Object,
|
||||
default (data) {
|
||||
return data;
|
||||
},
|
||||
},
|
||||
canRemove: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
invitingUser: {},
|
||||
};
|
||||
},
|
||||
props: ['notification', 'canRemove'],
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
async mounted () {
|
||||
this.invitingUser = await this.$store.dispatch('members:fetchMember', {
|
||||
memberId: this.notification.data.inviter,
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
async accept () {
|
||||
const group = this.notification.data;
|
||||
@@ -75,7 +45,6 @@ export default {
|
||||
}
|
||||
|
||||
await this.$store.dispatch('guilds:join', { groupId: group.id, type: 'party' });
|
||||
this.sync();
|
||||
this.$router.push('/party');
|
||||
},
|
||||
reject () {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
{{ $t('notifications') }}
|
||||
</h4>
|
||||
<a
|
||||
class="small-link"
|
||||
class="small-link standard-link"
|
||||
:disabled="notificationsCount === 0"
|
||||
@click="dismissAll"
|
||||
>{{ $t('dismissAll') }}</a>
|
||||
@@ -123,24 +123,23 @@ import successImage from '@/assets/svg/success.svg';
|
||||
import starBadge from '@/assets/svg/star-badge.svg';
|
||||
|
||||
// Notifications
|
||||
import CARD_RECEIVED from './notifications/cardReceived';
|
||||
import CHALLENGE_INVITATION from './notifications/challengeInvitation';
|
||||
import GIFT_ONE_GET_ONE from './notifications/g1g1';
|
||||
import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned';
|
||||
import GROUP_TASK_CLAIMED from './notifications/groupTaskClaimed';
|
||||
import NEW_STUFF from './notifications/newStuff';
|
||||
import GROUP_TASK_NEEDS_WORK from './notifications/groupTaskNeedsWork';
|
||||
import GUILD_INVITATION from './notifications/guildInvitation';
|
||||
import ITEM_RECEIVED from './notifications/itemReceived';
|
||||
import NEW_CHAT_MESSAGE from './notifications/newChatMessage';
|
||||
import NEW_INBOX_MESSAGE from './notifications/newPrivateMessage';
|
||||
import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems';
|
||||
import NEW_STUFF from './notifications/newStuff';
|
||||
import ONBOARDING_COMPLETE from './notifications/onboardingComplete';
|
||||
import PARTY_INVITATION from './notifications/partyInvitation';
|
||||
import CHALLENGE_INVITATION from './notifications/challengeInvitation';
|
||||
import QUEST_INVITATION from './notifications/questInvitation';
|
||||
import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned';
|
||||
import GROUP_TASK_CLAIMED from './notifications/groupTaskClaimed';
|
||||
import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints';
|
||||
import VERIFY_USERNAME from './notifications/verifyUsername';
|
||||
import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems';
|
||||
import CARD_RECEIVED from './notifications/cardReceived';
|
||||
import NEW_INBOX_MESSAGE from './notifications/newPrivateMessage';
|
||||
import NEW_CHAT_MESSAGE from './notifications/newChatMessage';
|
||||
import WORLD_BOSS from './notifications/worldBoss';
|
||||
import VERIFY_USERNAME from './notifications/verifyUsername';
|
||||
import ONBOARDING_COMPLETE from './notifications/onboardingComplete';
|
||||
import GIFT_ONE_GET_ONE from './notifications/g1g1';
|
||||
import OnboardingGuide from './onboardingGuide';
|
||||
|
||||
export default {
|
||||
@@ -148,25 +147,24 @@ export default {
|
||||
MenuDropdown,
|
||||
MessageCount,
|
||||
// One component for each type
|
||||
CARD_RECEIVED,
|
||||
CHALLENGE_INVITATION,
|
||||
GIFT_ONE_GET_ONE,
|
||||
GROUP_TASK_ASSIGNED,
|
||||
GROUP_TASK_CLAIMED,
|
||||
NEW_STUFF,
|
||||
GROUP_TASK_NEEDS_WORK,
|
||||
GUILD_INVITATION,
|
||||
ITEM_RECEIVED,
|
||||
NEW_CHAT_MESSAGE,
|
||||
NEW_INBOX_MESSAGE,
|
||||
NEW_MYSTERY_ITEMS,
|
||||
NEW_STUFF,
|
||||
ONBOARDING_COMPLETE,
|
||||
PARTY_INVITATION,
|
||||
CHALLENGE_INVITATION,
|
||||
QUEST_INVITATION,
|
||||
GROUP_TASK_ASSIGNED,
|
||||
GROUP_TASK_CLAIMED,
|
||||
UNALLOCATED_STATS_POINTS,
|
||||
VERIFY_USERNAME,
|
||||
NEW_MYSTERY_ITEMS,
|
||||
CARD_RECEIVED,
|
||||
NEW_INBOX_MESSAGE,
|
||||
NEW_CHAT_MESSAGE,
|
||||
WorldBoss: WORLD_BOSS,
|
||||
VERIFY_USERNAME,
|
||||
OnboardingGuide,
|
||||
ONBOARDING_COMPLETE,
|
||||
GIFT_ONE_GET_ONE,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -187,7 +185,6 @@ export default {
|
||||
// NOTE: Those not listed here won't be shown in the notification panel!
|
||||
handledNotifications: [
|
||||
'NEW_STUFF',
|
||||
'ITEM_RECEIVED',
|
||||
'GIFT_ONE_GET_ONE',
|
||||
'GROUP_TASK_NEEDS_WORK',
|
||||
'GUILD_INVITATION',
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
>{{ $t('editAvatar') }}</a>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item dropdown-separated"
|
||||
@click="showAvatar('backgrounds', '2023')"
|
||||
@click="showAvatar('backgrounds', '2022')"
|
||||
>{{ $t('backgrounds') }}</a>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
|
||||
@@ -879,7 +879,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.user.preferences.suppressModals.hatchPet) {
|
||||
if (this.user.preferences.suppressModals.raisePet) {
|
||||
this.hatchPet(pet);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,9 +171,8 @@ export default {
|
||||
getPetItemClass () {
|
||||
if (this.isOwned() && some(
|
||||
this.currentEventList,
|
||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'teaShop',
|
||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'virtual',
|
||||
)) {
|
||||
if (this.isSpecial()) return `Pet ${this.foolPet(this.item.key)}`;
|
||||
const petString = `${this.item.eggKey}-${this.item.key}`;
|
||||
return `Pet ${this.foolPet(petString)}`;
|
||||
}
|
||||
|
||||
@@ -139,8 +139,6 @@
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import renderWithMentions from '@/libs/renderWithMentions';
|
||||
import { mapState } from '@/libs/store';
|
||||
import userLink from '../userLink';
|
||||
@@ -152,7 +150,6 @@ export default {
|
||||
components: {
|
||||
userLink,
|
||||
},
|
||||
mixins: [externalLinks],
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(value).fromNow();
|
||||
@@ -182,10 +179,6 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.$emit('message-card-mounted');
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
report () {
|
||||
|
||||
@@ -1,877 +0,0 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="birthday-modal"
|
||||
:hide-header="true"
|
||||
:hide-footer="true"
|
||||
>
|
||||
<div class="modal-content">
|
||||
<div
|
||||
class="modal-close"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon svg-close"
|
||||
v-html="icons.close"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="svg-confetti svg-icon"
|
||||
v-html="icons.confetti"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<img
|
||||
src="~@/assets/images/10-birthday.png"
|
||||
class="ten-birthday"
|
||||
>
|
||||
</div>
|
||||
<div class="limited-wrapper">
|
||||
<div
|
||||
class="svg-gifts svg-icon"
|
||||
v-html="icons.gifts"
|
||||
>
|
||||
</div>
|
||||
<div class="limited-event">
|
||||
{{ $t('limitedEvent') }}
|
||||
</div>
|
||||
<div class="dates">
|
||||
{{ $t('anniversaryLimitedDates') }}
|
||||
</div>
|
||||
<div
|
||||
class="svg-gifts-flip svg-icon"
|
||||
v-html="icons.gifts"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="celebrate d-flex justify-content-center">
|
||||
{{ $t('celebrateAnniversary') }}
|
||||
</div>
|
||||
<h2 class="d-flex justify-content-center">
|
||||
<span
|
||||
class="left-divider"
|
||||
v-html="icons.divider"
|
||||
></span>
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
{{ $t('jubilantGryphatricePromo') }}
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="right-divider"
|
||||
></span>
|
||||
</h2>
|
||||
<!-- gryphatrice info -->
|
||||
<div class="d-flex">
|
||||
<div class="jubilant-gryphatrice d-flex mr-auto">
|
||||
<img
|
||||
src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Gryphatrice-Jubilant-Large.gif"
|
||||
width="156px"
|
||||
height="144px"
|
||||
alt="a pink, purple, and green gryphatrice pet winks at you adorably"
|
||||
>
|
||||
</div>
|
||||
<div class="align-items-center">
|
||||
<div class="limited-edition mr-auto">
|
||||
{{ $t('limitedEdition') }}
|
||||
</div>
|
||||
<div class="gryphatrice-text">
|
||||
{{ $t('anniversaryGryphatriceText') }}
|
||||
</div>
|
||||
<div
|
||||
class="gryphatrice-price"
|
||||
v-html="$t('anniversaryGryphatricePrice')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- beginning of payments -->
|
||||
<!-- buy with money OR gems -->
|
||||
<div
|
||||
v-if="!ownGryphatrice && !gryphBought"
|
||||
>
|
||||
<div
|
||||
v-if="selectedPage !== 'payment-buttons'"
|
||||
id="initial-buttons"
|
||||
class="d-flex justify-content-center"
|
||||
>
|
||||
<button
|
||||
class="btn btn-secondary buy-now-left"
|
||||
:class="{active: selectedPage === 'payment-buttons'}"
|
||||
@click="selectedPage = 'payment-buttons'"
|
||||
>
|
||||
{{ $t('buyNowMoneyButton') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary buy-now-right"
|
||||
@click="buyGryphatriceGems()"
|
||||
>
|
||||
{{ $t('buyNowGemsButton') }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- buy with money -->
|
||||
<div
|
||||
v-else-if="selectedPage === 'payment-buttons'"
|
||||
id="payment-buttons"
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<button
|
||||
class="btn btn-secondary d-flex stripe"
|
||||
@click="redirectToStripe({ sku: 'price_0MPZ6iZCD0RifGXlLah2furv' })"
|
||||
>
|
||||
<span
|
||||
class="svg-stripe"
|
||||
v-html="icons.stripe"
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary d-flex paypal"
|
||||
@click="openPaypal({
|
||||
url: paypalCheckoutLink, type: 'sku', sku: 'Pet-Gryphatrice-Jubilant'
|
||||
})"
|
||||
>
|
||||
<span
|
||||
class="svg-paypal"
|
||||
v-html="icons.paypal"
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
<amazon-button
|
||||
:disabled="disabled"
|
||||
:amazon-data="amazonData"
|
||||
class="btn btn-secondary d-flex amazon"
|
||||
v-html="icons.amazon"
|
||||
/>
|
||||
<div
|
||||
class="pay-with-gems"
|
||||
@click="selectedPage = 'initial-buttons'"
|
||||
>
|
||||
{{ $t('wantToPayWithGemsText') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Own the gryphatrice -->
|
||||
<div
|
||||
v-else
|
||||
class="d-flex"
|
||||
>
|
||||
<button
|
||||
class="own-gryphatrice-button"
|
||||
@click="closeAndRedirect('/inventory/stable')"
|
||||
v-html="$t('ownJubilantGryphatrice')"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<!-- end of payments -->
|
||||
<h2 class="d-flex justify-content-center">
|
||||
<span
|
||||
class="left-divider"
|
||||
v-html="icons.divider"
|
||||
></span>
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
{{ $t('plentyOfPotions') }}
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="right-divider"
|
||||
></span>
|
||||
</h2>
|
||||
<div class="plenty-of-potions d-flex">
|
||||
{{ $t('plentyOfPotionsText') }}
|
||||
</div>
|
||||
<div class="potions">
|
||||
<div class="pot-1">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Porcelain.png">
|
||||
</div>
|
||||
<div class="pot-2">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Vampire.png">
|
||||
</div>
|
||||
<div class="pot-3">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Aquatic.png">
|
||||
</div>
|
||||
<div class="pot-4">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_StainedGlass.png">
|
||||
</div>
|
||||
<div class="pot-5">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Celestial.png">
|
||||
</div>
|
||||
<div class="pot-6">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Glow.png">
|
||||
</div>
|
||||
<div class="pot-7">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_AutumnLeaf.png">
|
||||
</div>
|
||||
<div class="pot-8">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_SandSculpture.png">
|
||||
</div>
|
||||
<div class="pot-9">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Peppermint.png">
|
||||
</div>
|
||||
<div class="pot-10">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Shimmer.png">
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-secondary d-flex justify-content-center visit-the-market"
|
||||
@click="closeAndRedirect('/shops/market')"
|
||||
>
|
||||
{{ $t('visitTheMarketButton') }}
|
||||
</button>
|
||||
<h2 class="d-flex justify-content-center">
|
||||
<span
|
||||
class="left-divider"
|
||||
v-html="icons.divider"
|
||||
></span>
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
{{ $t('fourForFree') }}
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="right-divider"
|
||||
></span>
|
||||
</h2>
|
||||
<div class="four-for-free">
|
||||
{{ $t('fourForFreeText') }}
|
||||
</div>
|
||||
<div class="four-grid d-flex justify-content-center">
|
||||
<div class="day-one-a">
|
||||
<div class="day-text">
|
||||
{{ $t('dayOne') }}
|
||||
</div>
|
||||
<div class="gift d-flex justify-content-center align-items-middle">
|
||||
<img
|
||||
src="~@/assets/images/robes.webp"
|
||||
class="m-auto"
|
||||
width="40px"
|
||||
height="66px"
|
||||
>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ $t('partyRobes') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="day-one-b">
|
||||
<div class="day-text">
|
||||
{{ $t('dayOne') }}
|
||||
</div>
|
||||
<div class="gift d-flex justify-content-center align-items-middle">
|
||||
<div
|
||||
class="svg-gem svg-icon m-auto"
|
||||
v-html="icons.birthdayGems"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ $t('twentyGems') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="day-five">
|
||||
<div class="day-text">
|
||||
{{ $t('dayFive') }}
|
||||
</div>
|
||||
<div class="gift d-flex justify-content-center align-items-middle">
|
||||
<img
|
||||
src="~@/assets/images/habitica-hero-goober.webp"
|
||||
class="m-auto"
|
||||
><!-- Birthday Set -->
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ $t('birthdaySet') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="day-ten">
|
||||
<div class="day-text">
|
||||
{{ $t('dayTen') }}
|
||||
</div>
|
||||
<div class="gift d-flex justify-content-center align-items-middle">
|
||||
<div
|
||||
class="svg-background svg-icon m-auto"
|
||||
v-html="icons.birthdayBackground"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ $t('background') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-bottom">
|
||||
<div class="limitations d-flex justify-content-center">
|
||||
{{ $t('limitations') }}
|
||||
</div>
|
||||
<div class="fine-print">
|
||||
{{ $t('anniversaryLimitations') }}
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#birthday-modal {
|
||||
.modal-body {
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
.modal-content {
|
||||
border-radius: 14px;
|
||||
border: 0px;
|
||||
}
|
||||
.modal-footer {
|
||||
border-radius: 14px;
|
||||
border: 0px;
|
||||
}
|
||||
.amazon {
|
||||
margin-bottom: 16px;
|
||||
|
||||
svg {
|
||||
width: 84px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.amazonpay-button-inner-image {
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/mixins.scss';
|
||||
|
||||
#birthday-modal {
|
||||
h2 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
color: $white;
|
||||
column-gap: 0.5rem;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.modal-body{
|
||||
box-shadow: 0 14px 28px 0 rgba(26, 24, 29, 0.24), 0 10px 10px 0 rgba(26, 24, 29, 0.28);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 566px;
|
||||
padding: 32px 24px 24px;
|
||||
background: linear-gradient(158deg,#6133b4,#4f2a93);
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
.modal-bottom {
|
||||
width: 566px;
|
||||
background-color: $purple-50;
|
||||
color: $purple-500;
|
||||
line-height: 1.33;
|
||||
border-top: 0px;
|
||||
padding: 16px 40px 28px 40px;
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
}
|
||||
.limitations {
|
||||
color: $white;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
margin-top: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
.fine-print {
|
||||
font-size: 0.75rem;
|
||||
color: $purple-500;
|
||||
line-height: 1.33;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ten-birthday {
|
||||
position: relative;
|
||||
width: 268px;
|
||||
height: 244px;
|
||||
margin: 0 125px 16px;
|
||||
}
|
||||
|
||||
.limited-event {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
letter-spacing: 2.4px;
|
||||
margin-top: -8px;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.dates {
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.celebrate {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
margin: 16px 16px 24px 16px;
|
||||
text-align: center;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.jubilant-gryphatrice {
|
||||
height: 176px;
|
||||
width: 204px;
|
||||
border-radius: 12px;
|
||||
background-color: $purple-50;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24px;
|
||||
margin-left: 4px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.limited-wrapper {
|
||||
margin-top: -36px;
|
||||
margin-bottom: -36px;
|
||||
}
|
||||
|
||||
.limited-edition, .gryphatrice-text, .gryphatrice-price {
|
||||
max-width: 274px;
|
||||
}
|
||||
|
||||
.limited-edition {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
line-height:1.33;
|
||||
letter-spacing:2.4px;
|
||||
padding-top: 18px;
|
||||
margin-left: 24px;
|
||||
margin-bottom: 8px;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.gryphatrice-text, .gryphatrice-price {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
margin-left: 24px;
|
||||
margin-right: 4px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.gryphatrice-price {
|
||||
padding-top: 16px;
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
.buy-now-left {
|
||||
width: 243px;
|
||||
margin: 24px 8px 24px 0px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
|
||||
.buy-now-right {
|
||||
width: 243px;
|
||||
margin: 24px 0px 24px 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
|
||||
.stripe {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.paypal {
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.stripe, .paypal, .amazon {
|
||||
width: 506px;
|
||||
height: 32px;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
border-radius: 4px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pay-with-gems {
|
||||
color: $white;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pay-with-gems:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.own-gryphatrice-button {
|
||||
width: 506px;
|
||||
height: 32px;
|
||||
margin: 24px 4px;
|
||||
border-radius: 4px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: $green-100;
|
||||
background-color: $green-100;
|
||||
color: $green-1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plenty-of-potions {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
margin: 0 8px 24px;
|
||||
text-align: center;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.potions {
|
||||
display: grid;
|
||||
grid-template-columns: 5;
|
||||
grid-template-rows: 2;
|
||||
gap: 24px 24px;
|
||||
justify-content: center;
|
||||
|
||||
.pot-1, .pot-2, .pot-3, .pot-4, .pot-5,
|
||||
.pot-6, .pot-7, .pot-8, .pot-9, .pot-10 {
|
||||
height: 68px;
|
||||
width: 68px;
|
||||
border-radius: 8px;
|
||||
background-color: $purple-50;
|
||||
}
|
||||
|
||||
.pot-1 {
|
||||
grid-column: 1 / 1;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-2 {
|
||||
grid-column: 2 / 2;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-3 {
|
||||
grid-column: 3 / 3;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-4 {
|
||||
grid-column: 4 / 4;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-5 {
|
||||
grid-column: 5 / 5;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-6 {
|
||||
grid-column: 1 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
.pot-7 {
|
||||
grid-column: 2 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
.pot-8 {
|
||||
grid-column: 3 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
.pot-9 {
|
||||
grid-column: 4 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
.pot-10 {
|
||||
grid-column: 5 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
|
||||
}
|
||||
.visit-the-market {
|
||||
height: 32px;
|
||||
margin: 24px 4px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.four-for-free {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
margin: 0 36px 24px;
|
||||
text-align: center;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.four-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 4;
|
||||
grid-template-rows: 1;
|
||||
gap: 24px;
|
||||
}
|
||||
.day-one-a, .day-one-b, .day-five, .day-ten {
|
||||
height: 140px;
|
||||
width: 100px;
|
||||
border-radius: 8px;
|
||||
background-color: $purple-50;
|
||||
}
|
||||
|
||||
.day-one-a {
|
||||
grid-column: 1 / 1;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
.day-one-b {
|
||||
grid-column: 2 / 2;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
.day-five {
|
||||
grid-column: 3 / 3;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
.day-ten {
|
||||
grid-column: 4 / 4;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
|
||||
.day-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
letter-spacing: 2.4px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
padding: 4px 0px;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.gift {
|
||||
height: 80px;
|
||||
width: 84px;
|
||||
margin: 0 8px 32px;
|
||||
background-color: $purple-100;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
padding: 8px 0px;
|
||||
margin-top: -32px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
// SVG CSS
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
.svg-close {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
fill: $purple-50;
|
||||
|
||||
& svg path {
|
||||
fill: $purple-50 !important;;
|
||||
}
|
||||
& :hover {
|
||||
fill: $purple-50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.svg-confetti {
|
||||
position: absolute;
|
||||
height: 152px;
|
||||
width: 518px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.svg-gifts, .svg-gifts-flip {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
width: 85px;
|
||||
}
|
||||
|
||||
.svg-gifts {
|
||||
margin-left: 70px;
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
.svg-gifts-flip {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
left: 366px;
|
||||
bottom: 34px;
|
||||
}
|
||||
|
||||
.left-divider, .right-divider {
|
||||
background-image: url('~@/assets/images/fancy-divider.png');
|
||||
background-position: right center;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-flex;
|
||||
flex-grow: 2;
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
|
||||
.right-divider {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.svg-cross {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.svg-gem {
|
||||
height: 48px;
|
||||
width: 58px;
|
||||
}
|
||||
|
||||
.svg-background {
|
||||
height: 68px;
|
||||
width: 68px;
|
||||
}
|
||||
|
||||
.svg-stripe {
|
||||
height: 20px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.svg-paypal {
|
||||
height: 16px;
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
// to check if user owns JG or not
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
// Purchase functionality
|
||||
import buy from '@/mixins/buy';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import payments from '@/mixins/payments';
|
||||
import content from '@/../../common/script/content/index';
|
||||
import amazonButton from '@/components/payments/buttons/amazon';
|
||||
|
||||
// import images
|
||||
import close from '@/assets/svg/new-close.svg';
|
||||
import confetti from '@/assets/svg/confetti.svg';
|
||||
import gifts from '@/assets/svg/gifts-birthday.svg';
|
||||
import cross from '@/assets/svg/cross.svg';
|
||||
import stripe from '@/assets/svg/stripe.svg';
|
||||
import paypal from '@/assets/svg/paypal-logo.svg';
|
||||
import amazon from '@/assets/svg/amazonpay.svg';
|
||||
import birthdayGems from '@/assets/svg/birthday-gems.svg';
|
||||
import birthdayBackground from '@/assets/svg/icon-background-birthday.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
amazonButton,
|
||||
},
|
||||
mixins: [buy, notifications, payments],
|
||||
data () {
|
||||
return {
|
||||
amazonData: {
|
||||
type: 'single',
|
||||
sku: 'Pet-Gryphatrice-Jubilant',
|
||||
},
|
||||
icons: Object.freeze({
|
||||
close,
|
||||
confetti,
|
||||
gifts,
|
||||
cross,
|
||||
stripe,
|
||||
paypal,
|
||||
amazon,
|
||||
birthdayGems,
|
||||
birthdayBackground,
|
||||
}),
|
||||
selectedPage: 'initial-buttons',
|
||||
gryphBought: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
ownGryphatrice () {
|
||||
return Boolean(this.user && this.user.items.pets['Gryphatrice-Jubilant']);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hide () {
|
||||
this.$root.$emit('bv::hide::modal', 'birthday-modal');
|
||||
},
|
||||
buyGryphatriceGems () {
|
||||
const gryphatrice = content.petInfo['Gryphatrice-Jubilant'];
|
||||
if (this.user.balance * 4 < gryphatrice.value) {
|
||||
this.$root.$emit('bv::show::modal', 'buy-gems');
|
||||
return this.hide();
|
||||
}
|
||||
if (!this.confirmPurchase(gryphatrice.currency, gryphatrice.value)) {
|
||||
return null;
|
||||
}
|
||||
this.makeGenericPurchase(gryphatrice);
|
||||
this.gryphBought = true;
|
||||
return this.purchased(gryphatrice.text());
|
||||
},
|
||||
closeAndRedirect (route) {
|
||||
const routeTerminator = route.split('/')[route.split('/').length - 1];
|
||||
if (this.$router.history.current.name !== routeTerminator) {
|
||||
this.$router.push(route);
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'birthday-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -78,7 +78,6 @@ export default {
|
||||
orderReferenceId: null,
|
||||
subscription: null,
|
||||
coupon: null,
|
||||
sku: null,
|
||||
},
|
||||
isAmazonSetup: false,
|
||||
amazonButtonEnabled: false,
|
||||
@@ -175,10 +174,7 @@ export default {
|
||||
storePaymentStatusAndReload (url) {
|
||||
let paymentType;
|
||||
|
||||
if (this.amazonPayments.type === 'single') {
|
||||
if (this.amazonPayments.sku) paymentType = 'sku';
|
||||
else if (!this.amazonPayments.gift) paymentType = 'gems';
|
||||
}
|
||||
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) {
|
||||
@@ -227,7 +223,6 @@ export default {
|
||||
const data = {
|
||||
orderReferenceId: this.amazonPayments.orderReferenceId,
|
||||
gift: this.amazonPayments.gift,
|
||||
sku: this.amazonPayments.sku,
|
||||
};
|
||||
|
||||
if (this.amazonPayments.gemsBlock) {
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: $gray-10;
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</button>
|
||||
<a
|
||||
v-once
|
||||
class="standard-link"
|
||||
@click="close()"
|
||||
>{{ $t('neverMind') }}</a>
|
||||
</div>
|
||||
|
||||