mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-10-28 11:42:29 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08127cfa67 | ||
|
|
ae9f10420f | ||
|
|
26bddb6383 | ||
|
|
9a157168a9 |
@@ -87,5 +87,5 @@
|
||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||
"REDIS_PORT": "1234",
|
||||
"REDIS_PASSWORD": "12345678",
|
||||
"TRUSTED_DOMAINS": "localhost,habitica.com"
|
||||
"TRUSTED_DOMAINS": "https://localhost,https://habitica.com"
|
||||
}
|
||||
|
||||
Submodule habitica-images updated: 109539e445...dac6a71d1f
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -1,155 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230731_naming_day';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set;
|
||||
let push;
|
||||
const inc = {
|
||||
'items.food.Cake_Base': 1,
|
||||
'items.food.Cake_CottonCandyBlue': 1,
|
||||
'items.food.Cake_CottonCandyPink': 1,
|
||||
'items.food.Cake_Desert': 1,
|
||||
'items.food.Cake_Golden': 1,
|
||||
'items.food.Cake_Red': 1,
|
||||
'items.food.Cake_Shade': 1,
|
||||
'items.food.Cake_Skeleton': 1,
|
||||
'items.food.Cake_White': 1,
|
||||
'items.food.Cake_Zombie': 1,
|
||||
'achievements.habiticaDays': 1,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.back_special_namingDay2020 !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_cake',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you some cake!',
|
||||
destination: '/inventory/items',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.body_special_namingDay2018 !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.gear.owned.back_special_namingDay2020': true };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_back',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Tail and cake!',
|
||||
destination: '/inventory/equipment',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.head_special_namingDay2017 !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.gear.owned.body_special_namingDay2018': true };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_body',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Cloak and cake!',
|
||||
destination: '/inventory/equipment',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else if (user && user.items && user.items.pets && typeof user.items.pets['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.gear.owned.head_special_namingDay2017': true };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_head',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Helm and cake!',
|
||||
destination: '/inventory/equipment',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.pets.Gryphon-RoyalPurple': 5 };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_pet',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Pet and cake!',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
set = { migration: MIGRATION_NAME, 'items.mounts.Gryphon-RoyalPurple': true };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_mount',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Mount and cake!',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (push) {
|
||||
return await user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
|
||||
} else {
|
||||
return await user.updateOne({ $set: set, $inc: inc }).exec();
|
||||
}
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-07-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)
|
||||
.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,72 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import { model as Group } from '../../../website/server/models/group';
|
||||
|
||||
const guildsPerRun = 500;
|
||||
const progressCount = 1000;
|
||||
const guildsQuery = {
|
||||
type: 'guild',
|
||||
};
|
||||
|
||||
let count = 0;
|
||||
async function updateGroup (guild) {
|
||||
count++;
|
||||
if (count % progressCount === 0) {
|
||||
console.warn(`${count} ${guild._id}`);
|
||||
}
|
||||
|
||||
if (guild.hasActiveGroupPlan()) {
|
||||
return console.warn(`Guild ${guild._id} is active Group Plan`);
|
||||
}
|
||||
|
||||
const leader = await User
|
||||
.findOne({ _id: guild.leader })
|
||||
.select({ _id: true })
|
||||
.exec();
|
||||
|
||||
if (!leader) {
|
||||
return console.warn(`Leader not found for Guild ${guild._id}`);
|
||||
}
|
||||
|
||||
if (guild.balance > 0) {
|
||||
await leader.updateBalance(
|
||||
guild.balance,
|
||||
'create_guild',
|
||||
'',
|
||||
`Guild Bank refund for ${guild.name} (${guild._id})`,
|
||||
);
|
||||
}
|
||||
|
||||
return guild.updateOne({ $set: { balance: 0 } }).exec();
|
||||
}
|
||||
|
||||
export default async function processGroups () {
|
||||
const guildFields = {
|
||||
_id: 1,
|
||||
balance: 1,
|
||||
leader: 1,
|
||||
name: 1,
|
||||
purchased: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const foundGroups = await Group // eslint-disable-line no-await-in-loop
|
||||
.find(guildsQuery)
|
||||
.limit(guildsPerRun)
|
||||
.sort({ _id: 1 })
|
||||
.select(guildFields)
|
||||
.exec();
|
||||
|
||||
if (foundGroups.length === 0) {
|
||||
console.warn('All appropriate Guilds found and modified.');
|
||||
console.warn(`\n${count} Guilds processed\n`);
|
||||
break;
|
||||
} else {
|
||||
guildsQuery._id = {
|
||||
$gt: foundGroups[foundGroups.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(foundGroups.map(guild => updateGroup(guild))); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import { TransactionModel as Transaction } from '../../../website/server/models/transaction';
|
||||
|
||||
const transactionsPerRun = 500;
|
||||
const progressCount = 1000;
|
||||
const transactionsQuery = {
|
||||
transactionType: 'create_guild',
|
||||
amount: { $gt: 0 },
|
||||
};
|
||||
|
||||
let count = 0;
|
||||
async function updateTransaction (transaction) {
|
||||
count++;
|
||||
if (count % progressCount === 0) {
|
||||
console.warn(`${count} ${transaction._id}`);
|
||||
}
|
||||
|
||||
const leader = await User
|
||||
.findOne({ _id: transaction.userId })
|
||||
.select({ _id: true })
|
||||
.exec();
|
||||
|
||||
if (!leader) {
|
||||
return console.warn(`User not found for transaction ${transaction._id}`);
|
||||
}
|
||||
|
||||
return leader.updateOne(
|
||||
{ $inc: { balance: transaction.amount }},
|
||||
).exec();
|
||||
}
|
||||
|
||||
export default async function processTransactions () {
|
||||
const transactionFields = {
|
||||
_id: 1,
|
||||
userId: 1,
|
||||
currency: 1,
|
||||
amount: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const foundTransactions = await Transaction // eslint-disable-line no-await-in-loop
|
||||
.find(transactionsQuery)
|
||||
.limit(transactionsPerRun)
|
||||
.sort({ _id: 1 })
|
||||
.select(transactionFields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (foundTransactions.length === 0) {
|
||||
console.warn('All appropriate transactions found and modified.');
|
||||
console.warn(`\n${count} transactions processed\n`);
|
||||
break;
|
||||
} else {
|
||||
transactionsQuery._id = {
|
||||
$gt: foundTransactions[foundTransactions.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(foundTransactions.map(txn => updateTransaction(txn))); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,144 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230808_veteran_pet_ladder';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {};
|
||||
let push = { notifications: { $each: [] }};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
if (user.items.pets['Fox-Veteran']) {
|
||||
set['items.pets.Dragon-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_dragon',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Dragon.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else if (user.items.pets['Bear-Veteran']) {
|
||||
set['items.pets.Fox-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_fox',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Fox.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else if (user.items.pets['Lion-Veteran']) {
|
||||
set['items.pets.Bear-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_bear',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Bear.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else if (user.items.pets['Tiger-Veteran']) {
|
||||
set['items.pets.Lion-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_lion',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Lion.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else if (user.items.pets['Wolf-Veteran']) {
|
||||
set['items.pets.Tiger-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_tiger',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Tiger.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else {
|
||||
set['items.pets.Wolf-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_wolf',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Wolf.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (user.contributor.level > 0) {
|
||||
set['items.gear.owned.armor_special_heroicTunic'] = true;
|
||||
set['items.gear.owned.back_special_heroicAureole'] = true;
|
||||
set['items.gear.owned.headAccessory_special_heroicCirclet'] = true;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'heroic_set_icon',
|
||||
title: 'You’ve received the Heroic Set!',
|
||||
text: 'To commemorate your hard work as a contributor, we’ve awarded you the Heroic Circlet, Heroic Aureole, and Heroic Tunic.',
|
||||
destination: '/inventory/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('2023-07-08') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
migration: 1,
|
||||
contributor: 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
|
||||
}
|
||||
};
|
||||
2907
package-lock.json
generated
2907
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "5.0.1",
|
||||
"version": "4.270.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.20.12",
|
||||
"@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",
|
||||
@@ -14,9 +14,9 @@
|
||||
"amazon-payments": "^0.2.9",
|
||||
"amplitude": "^6.0.0",
|
||||
"apidoc": "^0.54.0",
|
||||
"apple-auth": "^1.0.9",
|
||||
"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",
|
||||
@@ -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",
|
||||
@@ -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": "^11.10.0",
|
||||
"superagent": "^8.0.6",
|
||||
"universal-analytics": "^0.5.3",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.9.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,7 +110,7 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.3.6",
|
||||
"axios": "^1.2.2",
|
||||
"chai": "^4.3.7",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
@@ -122,7 +122,7 @@
|
||||
"monk": "^7.3.4",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.7",
|
||||
"sinon": "^15.1.2",
|
||||
"sinon": "^15.0.1",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -748,19 +748,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 () => {
|
||||
|
||||
@@ -242,7 +242,7 @@ describe('cron middleware', () => {
|
||||
|
||||
sandbox.spy(cronLib, 'recoverCron');
|
||||
|
||||
sandbox.stub(User, 'updateOne')
|
||||
sandbox.stub(User, 'update')
|
||||
.withArgs({
|
||||
_id: user._id,
|
||||
$or: [
|
||||
|
||||
@@ -1732,7 +1732,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 +1740,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 +1753,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 +1819,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 +2088,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 +2173,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 () => {
|
||||
|
||||
@@ -16,7 +16,60 @@ describe('GET /challenges/:challengeId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Group Plan', () => {
|
||||
context('public guild', () => {
|
||||
let groupLeader;
|
||||
let group;
|
||||
let challenge;
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
|
||||
const populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'public' },
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('should return challenge data', async () => {
|
||||
await challenge.sync();
|
||||
const chal = await user.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.memberCount).to.equal(challenge.memberCount);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: { name: groupLeader.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: groupLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
categories: [],
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
summary: group.name,
|
||||
type: group.type,
|
||||
privacy: group.privacy,
|
||||
leader: groupLeader.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('private guild', () => {
|
||||
let groupLeader;
|
||||
let challengeLeader;
|
||||
let group;
|
||||
@@ -31,14 +84,14 @@ describe('GET /challenges/:challengeId', () => {
|
||||
const populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
members = populatedGroup.members;
|
||||
|
||||
[challengeLeader, otherMember] = members;
|
||||
challengeLeader = members[0]; // eslint-disable-line prefer-destructuring
|
||||
otherMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
challenge = await generateChallenge(challengeLeader, group);
|
||||
});
|
||||
|
||||
@@ -71,18 +71,42 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('populates only some fields', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
it('works with challenges belonging to public guild', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(leader, group);
|
||||
await leader.post(`/challenges/${challenge._id}/join`);
|
||||
const res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: { name: user.profile.name },
|
||||
_id: leader._id,
|
||||
id: leader._id,
|
||||
profile: { name: leader.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
username: leader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('populates only some fields', async () => {
|
||||
const anotherUser = await generateUser({ balance: 3 });
|
||||
const group = await generateGroup(anotherUser, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(anotherUser, group);
|
||||
await anotherUser.post(`/challenges/${challenge._id}/join`);
|
||||
const res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: anotherUser._id,
|
||||
id: anotherUser._id,
|
||||
profile: { name: anotherUser.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: anotherUser.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
|
||||
@@ -72,6 +72,20 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('works with challenges belonging to a public guild', async () => {
|
||||
const groupLeader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(groupLeader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
const taskText = 'Test Text';
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{ type: 'habit', text: taskText }]);
|
||||
|
||||
const memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`);
|
||||
expect(memberProgress).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile', 'tasks']);
|
||||
expect(memberProgress.profile).to.have.all.keys(['name']);
|
||||
expect(memberProgress.tasks.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('returns the member tasks for the challenges', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
|
||||
@@ -7,7 +7,117 @@ import {
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('GET challenges/groups/:groupId', () => {
|
||||
context('Group Plan', () => {
|
||||
context('Public Guild', () => {
|
||||
let publicGuild; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
user = groupLeader;
|
||||
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should return group challenges for non member with populated leader', async () => {
|
||||
const challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return group challenges for member with populated leader', async () => {
|
||||
const challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return newest challenges first', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('Private Guild', () => {
|
||||
let privateGuild; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
|
||||
@@ -18,7 +128,6 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
@@ -77,6 +186,68 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let publicGuild; let user; let officialChallenge; let unofficialChallenges;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
publicGuild = group;
|
||||
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
});
|
||||
|
||||
officialChallenge = await generateChallenge(user, group, {
|
||||
categories: [{
|
||||
name: 'habitica_official',
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
// We add 10 extra challenges to test whether the official challenge
|
||||
// (the oldest) makes it to the front page.
|
||||
unofficialChallenges = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const challenge = await generateChallenge(user, group); // eslint-disable-line
|
||||
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
|
||||
unofficialChallenges.push(challenge);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
const challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
|
||||
it('should return newest challenges first, after official ones', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
unofficialChallenges.forEach((chal, index) => {
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
|
||||
expect(foundChallengeIndex).to.eql(10 - index);
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context('Party', () => {
|
||||
let party; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
@@ -230,7 +401,7 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return tavern challenges using ID "habitrpg"', async () => {
|
||||
it('should return tavern challenges using ID "habitrpg', async () => {
|
||||
const challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
@@ -264,58 +435,5 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let officialChallenge; let unofficialChallenges;
|
||||
|
||||
before(async () => {
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
balance: 3,
|
||||
});
|
||||
|
||||
officialChallenge = await generateChallenge(user, tavern, {
|
||||
categories: [{
|
||||
name: 'habitica_official',
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
prize: 1,
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
// We add 10 extra challenges to test whether the official challenge
|
||||
// (the oldest) makes it to the front page.
|
||||
unofficialChallenges = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const challenge = await generateChallenge(user, tavern, { prize: 1 }); // eslint-disable-line
|
||||
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
|
||||
unofficialChallenges.push(challenge);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
const challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
|
||||
it('should return newest challenges first, after official ones', async () => {
|
||||
let challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
unofficialChallenges.forEach((chal, index) => {
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
|
||||
expect(foundChallengeIndex).to.eql(10 - index);
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, tavern, { prize: 1 });
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,44 +2,39 @@ import {
|
||||
generateUser,
|
||||
generateChallenge,
|
||||
createAndPopulateGroup,
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('GET challenges/user', () => {
|
||||
context('no official challenges', () => {
|
||||
let user; let member; let nonMember; let challenge; let challenge2; let publicChallenge;
|
||||
let groupPlan; let userData; let groupData; let tavern; let tavernData;
|
||||
let user; let member; let nonMember; let challenge; let challenge2;
|
||||
let publicGuild; let userData; let groupData;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupPlan = group;
|
||||
publicGuild = group;
|
||||
groupData = {
|
||||
_id: groupPlan._id,
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: groupPlan._id,
|
||||
type: groupPlan.type,
|
||||
privacy: groupPlan.privacy,
|
||||
name: groupPlan.name,
|
||||
summary: groupPlan.name,
|
||||
leader: groupPlan.leader._id,
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
};
|
||||
|
||||
user = groupLeader;
|
||||
userData = {
|
||||
_id: groupPlan.leader._id,
|
||||
id: groupPlan.leader._id,
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
@@ -51,31 +46,17 @@ describe('GET challenges/user', () => {
|
||||
},
|
||||
};
|
||||
|
||||
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||
tavernData = {
|
||||
_id: TAVERN_ID,
|
||||
categories: [],
|
||||
id: TAVERN_ID,
|
||||
type: tavern.type,
|
||||
privacy: tavern.privacy,
|
||||
name: tavern.name,
|
||||
summary: tavern.name,
|
||||
leader: tavern.leader._id,
|
||||
};
|
||||
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.update({ balance: 0.25 });
|
||||
publicChallenge = await generateChallenge(user, tavern, { prize: 1 });
|
||||
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
await nonMember.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
context('all challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await member.get('/challenges/user?page=0');
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
@@ -83,13 +64,11 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge.group).to.eql(groupData);
|
||||
});
|
||||
|
||||
it('should return public challenges', async () => {
|
||||
it('should not return challenges a non-member has not joined', async () => {
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should return challenges user has created', async () => {
|
||||
@@ -121,10 +100,10 @@ describe('GET challenges/user', () => {
|
||||
it('should return newest challenges first', async () => {
|
||||
let challenges = await user.get('/challenges/user?page=0');
|
||||
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: publicChallenge._id });
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
const newChallenge = await generateChallenge(user, groupPlan);
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
@@ -134,23 +113,52 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
const privateChallenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
const privateChallenge = await generateChallenge(groupLeader, group, {
|
||||
categories: [{
|
||||
name: 'academics',
|
||||
slug: 'academics',
|
||||
}],
|
||||
});
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user?page=0&categories=academics&owned=not_owned');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('my challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
const challenges = await nonMember.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
@@ -169,10 +177,6 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
});
|
||||
|
||||
it('should return challenges user has created if filter by owned', async () => {
|
||||
@@ -186,10 +190,6 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
});
|
||||
|
||||
it('should not return challenges user has created if filter by not owned', async () => {
|
||||
@@ -199,40 +199,36 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges in user groups', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return public challenges', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let user; let officialChallenge; let unofficialChallenges; let
|
||||
group;
|
||||
publicGuild;
|
||||
|
||||
before(async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
summary: 'summary for TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
publicGuild = group;
|
||||
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
@@ -275,7 +271,7 @@ describe('GET challenges/user', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, group);
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
@@ -298,10 +294,9 @@ describe('GET challenges/user', () => {
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
|
||||
@@ -42,7 +42,26 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', async () => {
|
||||
it('returns error when creating a challenge in a public guild and you are not a member of it', async () => {
|
||||
const user = await generateUser();
|
||||
const { group } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(user.post('/challenges', {
|
||||
group: group._id,
|
||||
prize: 4,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('mustBeGroupMember'),
|
||||
});
|
||||
});
|
||||
|
||||
it('return error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', async () => {
|
||||
const user = await generateUser();
|
||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1);
|
||||
const group = createAndPopulateGroup({
|
||||
@@ -58,7 +77,7 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('creating a Challenge for a Group Plan', () => {
|
||||
context('Creating a challenge for a valid group', () => {
|
||||
let groupLeader;
|
||||
let group;
|
||||
let groupMember;
|
||||
@@ -75,11 +94,9 @@ describe('POST /challenges', () => {
|
||||
challenges: true,
|
||||
},
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupLeader = await populatedGroup.groupLeader.sync();
|
||||
await groupLeader.update({ permissions: {} });
|
||||
group = populatedGroup.group;
|
||||
groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -9,30 +10,27 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
admin;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupWithChat = group;
|
||||
user = groupLeader;
|
||||
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||
message = message.message;
|
||||
userThatDidNotCreateChat = members[0]; // eslint-disable-line prefer-destructuring
|
||||
admin = members[1]; // eslint-disable-line prefer-destructuring
|
||||
await admin.update({ permissions: { moderator: true } });
|
||||
userThatDidNotCreateChat = await generateUser();
|
||||
admin = await generateUser({ 'permissions.moderator': true });
|
||||
});
|
||||
|
||||
context('Chat errors', () => {
|
||||
it('returns an error if message does not exist', async () => {
|
||||
it('returns an error is message does not exist', async () => {
|
||||
const fakeChatId = generateUUID();
|
||||
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
@@ -58,7 +56,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
nextMessage = nextMessage.message;
|
||||
});
|
||||
|
||||
it('allows creator to delete their message', async () => {
|
||||
it('allows creator to delete a their message', async () => {
|
||||
await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
|
||||
|
||||
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -11,22 +11,48 @@ describe('GET /groups/:groupId/chat', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
context('public Guild', () => {
|
||||
let group;
|
||||
|
||||
before(async () => {
|
||||
const leader = await generateUser({ balance: 2 });
|
||||
|
||||
group = await generateGroup(leader, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}, {
|
||||
chat: [
|
||||
{ text: 'Hello', flags: {}, id: 1 },
|
||||
{ text: 'Welcome to the Guild', flags: {}, id: 2 },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns Guild chat', async () => {
|
||||
const chat = await user.get(`/groups/${group._id}/chat`);
|
||||
|
||||
expect(chat[0].id).to.eql(group.chat[0].id);
|
||||
expect(chat[1].id).to.eql(group.chat[1].id);
|
||||
});
|
||||
});
|
||||
|
||||
context('private Guild', () => {
|
||||
let group;
|
||||
|
||||
before(async () => {
|
||||
({ group } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
const leader = await generateUser({ balance: 2 });
|
||||
|
||||
group = await generateGroup(leader, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
}, {
|
||||
chat: [
|
||||
'Hello',
|
||||
'Welcome to the Guild',
|
||||
],
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error if user is not member of requested private group', async () => {
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
import find from 'lodash/find';
|
||||
import { find } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat/:chatId/flag', () => {
|
||||
let user; let admin; let anotherUser; let newUser; let
|
||||
group; let members; let userToDelete;
|
||||
group;
|
||||
const TEST_MESSAGE = 'Test Message';
|
||||
const USER_AGE_FOR_FLAGGING = 3;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group, groupLeader: user, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate(),
|
||||
},
|
||||
members: 4,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
[admin, anotherUser, newUser, userToDelete] = members;
|
||||
await user.update({ permissions: {} });
|
||||
await admin.update({ permissions: { moderator: true } });
|
||||
await anotherUser.update({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
await newUser.update({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
|
||||
await userToDelete.update({
|
||||
'auth.timestamps.created': moment().subtract(1, 'days').toDate(),
|
||||
'purchased.plan.dateTerminated': moment().subtract(1, 'minutes').toDate(),
|
||||
});
|
||||
|
||||
user = await generateUser({ balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
admin = await generateUser({ balance: 1, 'permissions.moderator': true });
|
||||
anotherUser = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
newUser = await generateUser({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
|
||||
group = await user.post('/groups', {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -79,8 +69,8 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild - (private guild)',
|
||||
title_link: undefined,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
||||
mrkdwn_in: [
|
||||
@@ -88,7 +78,7 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
/* eslint-ensable camelcase */
|
||||
});
|
||||
|
||||
it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => {
|
||||
@@ -114,8 +104,8 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild - (private guild)',
|
||||
title_link: undefined,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
||||
mrkdwn_in: [
|
||||
@@ -123,12 +113,15 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
/* eslint-ensable camelcase */
|
||||
});
|
||||
|
||||
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||
const { message } = await userToDelete.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
||||
await userToDelete.del('/user', {
|
||||
const deletedUser = await generateUser({
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
const { message } = await deletedUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
||||
await deletedUser.del('/user', {
|
||||
password: 'password',
|
||||
});
|
||||
|
||||
|
||||
@@ -6,27 +6,27 @@ import {
|
||||
|
||||
describe('POST /chat/:chatId/like', () => {
|
||||
let user;
|
||||
let anotherUser;
|
||||
let groupWithChat;
|
||||
let members;
|
||||
const testMessage = 'Test Message';
|
||||
let anotherUser;
|
||||
|
||||
before(async () => {
|
||||
({ group: groupWithChat, groupLeader: user, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
[anotherUser] = members;
|
||||
user = groupLeader;
|
||||
groupWithChat = group;
|
||||
anotherUser = members[0]; // eslint-disable-line prefer-destructuring
|
||||
await anotherUser.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import nconf from 'nconf';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
sleep,
|
||||
server,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import {
|
||||
SPAM_MESSAGE_LIMIT,
|
||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import { CHAT_FLAG_FROM_SHADOW_MUTE, MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat', () => {
|
||||
let user; let groupWithChat; let member; let
|
||||
additionalMember;
|
||||
const testMessage = 'Test Message';
|
||||
const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
||||
const testBannedWordMessage1 = 'TESTPLACEHOLDERSWEARWORDHERE1';
|
||||
const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
||||
const testSlurMessage1 = 'TESTPLACEHOLDERSLURWORDHERE1';
|
||||
const bannedWordErrorMessage = t('bannedWordUsed', { swearWordsUsed: testBannedWordMessage });
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
user = groupLeader;
|
||||
await user.update({
|
||||
@@ -35,7 +43,8 @@ describe('POST /chat', () => {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
}); // prevent tests accidentally throwing messageGroupChatSpam
|
||||
groupWithChat = group;
|
||||
[member, additionalMember] = members;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
additionalMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||
await member.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
await additionalMember.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
@@ -80,12 +89,32 @@ describe('POST /chat', () => {
|
||||
member.update({ 'flags.chatRevoked': false });
|
||||
});
|
||||
|
||||
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
|
||||
const userWithChatRevoked = await member.update({ 'flags.chatRevoked': true });
|
||||
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not error when chat privileges are revoked when sending a message to a private guild', async () => {
|
||||
await member.update({
|
||||
'flags.chatRevoked': true,
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Private Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const privateGuildMemberWithChatsRevoked = members[0];
|
||||
await privateGuildMemberWithChatsRevoked.update({
|
||||
'flags.chatRevoked': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await privateGuildMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
@@ -123,12 +152,54 @@ describe('POST /chat', () => {
|
||||
member.update({ 'flags.chatShadowMuted': false });
|
||||
});
|
||||
|
||||
it('creates a chat with flagCount already set and notifies mods when sending a message to a public guild', async () => {
|
||||
const userWithChatShadowMuted = await member.update({ 'flags.chatShadowMuted': true });
|
||||
const message = await userWithChatShadowMuted.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE);
|
||||
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][1]).to.eql('shadow-muted-post-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `@${member.auth.local.username} / ${member.profile.name} posted while shadow-muted`,
|
||||
attachments: [{
|
||||
fallback: 'Shadow-Muted Message',
|
||||
color: 'danger',
|
||||
author_name: `@${member.auth.local.username} ${member.profile.name} (${member.auth.local.email}; ${member._id})`,
|
||||
title: 'Shadow-Muted Post in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
|
||||
text: testMessage,
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
|
||||
it('creates a chat with zero flagCount when sending a message to a private guild', async () => {
|
||||
await member.update({
|
||||
'flags.chatShadowMuted': true,
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Private Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const userWithChatShadowMuted = members[0];
|
||||
await userWithChatShadowMuted.update({
|
||||
'flags.chatShadowMuted': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
@@ -155,9 +226,100 @@ describe('POST /chat', () => {
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('creates a chat with zero flagCount when non-shadow-muted user sends a message to a public guild', async () => {
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('banned word', () => {
|
||||
it('returns an error when chat message contains a banned word in tavern', async () => {
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when chat message contains a banned word in a public guild', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await expect(members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is part of a phrase', async () => {
|
||||
const wordInPhrase = `phrase ${testBannedWordMessage} end`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is surrounded by non alphabet characters', async () => {
|
||||
const wordInPhrase = `_!${testBannedWordMessage}@_`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is typed in mixed case', async () => {
|
||||
const substrLength = Math.floor(testBannedWordMessage.length / 2);
|
||||
const chatMessage = testBannedWordMessage.substring(0, substrLength).toLowerCase()
|
||||
+ testBannedWordMessage.substring(substrLength).toUpperCase();
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedWordUsed', { swearWordsUsed: chatMessage }),
|
||||
});
|
||||
});
|
||||
|
||||
it('checks error message has all the banned words used, regardless of case', async () => {
|
||||
const testBannedWords = [
|
||||
testBannedWordMessage.toUpperCase(),
|
||||
testBannedWordMessage1.toLowerCase(),
|
||||
];
|
||||
const chatMessage = `Mixing ${testBannedWords[0]} and ${testBannedWords[1]} is bad for you.`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected
|
||||
.and.have.property('message')
|
||||
.that.includes(testBannedWords.join(', '));
|
||||
});
|
||||
|
||||
it('does not error when bad word is suffix of a word', async () => {
|
||||
const wordAsSuffix = `prefix${testBannedWordMessage}`;
|
||||
const message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when bad word is prefix of a word', async () => {
|
||||
const wordAsPrefix = `${testBannedWordMessage}suffix`;
|
||||
const message = await user.post('/groups/habitrpg/chat', { message: wordAsPrefix });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when sending a chat message containing a banned word to a party', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
@@ -174,8 +336,37 @@ describe('POST /chat', () => {
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when sending a chat message containing a banned word to a public guild in which banned words are allowed', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
// Update the bannedWordsAllowed property for the group
|
||||
group.update({ bannedWordsAllowed: true });
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
|
||||
const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when sending a chat message containing a banned word to a private guild', async () => {
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testBannedWordMessage });
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
|
||||
const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
@@ -192,6 +383,45 @@ describe('POST /chat', () => {
|
||||
user.update({ 'flags.chatRevoked': false });
|
||||
});
|
||||
|
||||
it('errors and revokes privileges when chat message contains a banned slur', async () => {
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testSlurMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][1]).to.eql('slur-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${user.profile.name} (${user.id}) tried to post a slur`,
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`,
|
||||
title: 'Slur in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
|
||||
text: testSlurMessage,
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
// Chat privileges are revoked
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows slurs in private groups', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
@@ -207,17 +437,28 @@ describe('POST /chat', () => {
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('errors when slur is typed in mixed case', async () => {
|
||||
const substrLength = Math.floor(testSlurMessage1.length / 2);
|
||||
const chatMessage = testSlurMessage1.substring(0, substrLength).toLowerCase()
|
||||
+ testSlurMessage1.substring(substrLength).toUpperCase();
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when user account is too young', async () => {
|
||||
await user.update({ 'auth.timestamps.created': new Date() });
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: 'hi im new' }))
|
||||
const brandNewUser = await generateUser();
|
||||
await expect(brandNewUser.post('/groups/habitrpg/chat', { message: 'hi im new' }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('chatTemporarilyUnavailable'),
|
||||
});
|
||||
await user.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
it('creates a chat', async () => {
|
||||
@@ -278,42 +519,54 @@ describe('POST /chat', () => {
|
||||
const mount = 'test-mount';
|
||||
const pet = 'test-pet';
|
||||
const style = 'test-style';
|
||||
await user.update({
|
||||
const userWithStyle = await generateUser({
|
||||
'items.currentMount': mount,
|
||||
'items.currentPet': pet,
|
||||
'preferences.style': style,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.currentMount).to.eql(user.items.currentMount);
|
||||
expect(message.message.userStyles.items.currentPet).to.eql(user.items.currentPet);
|
||||
expect(message.message.userStyles.preferences.style).to.eql(user.preferences.style);
|
||||
expect(message.message.userStyles.preferences.hair).to.eql(user.preferences.hair);
|
||||
expect(message.message.userStyles.preferences.skin).to.eql(user.preferences.skin);
|
||||
expect(message.message.userStyles.preferences.shirt).to.eql(user.preferences.shirt);
|
||||
expect(message.message.userStyles.preferences.chair).to.eql(user.preferences.chair);
|
||||
expect(message.message.userStyles.items.currentMount).to.eql(userWithStyle.items.currentMount);
|
||||
expect(message.message.userStyles.items.currentPet).to.eql(userWithStyle.items.currentPet);
|
||||
expect(message.message.userStyles.preferences.style).to.eql(userWithStyle.preferences.style);
|
||||
expect(message.message.userStyles.preferences.hair).to.eql(userWithStyle.preferences.hair);
|
||||
expect(message.message.userStyles.preferences.skin).to.eql(userWithStyle.preferences.skin);
|
||||
expect(message.message.userStyles.preferences.shirt).to.eql(userWithStyle.preferences.shirt);
|
||||
expect(message.message.userStyles.preferences.chair).to.eql(userWithStyle.preferences.chair);
|
||||
expect(message.message.userStyles.preferences.background)
|
||||
.to.eql(user.preferences.background);
|
||||
.to.eql(userWithStyle.preferences.background);
|
||||
});
|
||||
|
||||
it('creates equipped to user styles', async () => {
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
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(user.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 () => {
|
||||
await user.update({ 'preferences.costume': true });
|
||||
const userWithStyle = await generateUser({
|
||||
'preferences.costume': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
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(user.items.gear.costume);
|
||||
expect(message.message.userStyles.items.gear.costume).to.eql(userWithStyle.items.gear.costume);
|
||||
expect(message.message.userStyles.items.gear.equipped).to.not.exist;
|
||||
});
|
||||
|
||||
@@ -323,11 +576,12 @@ describe('POST /chat', () => {
|
||||
tier: 800,
|
||||
tokensApplied: true,
|
||||
};
|
||||
await user.update({
|
||||
const backer = await generateUser({
|
||||
backer: backerInfo,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await backer.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const messageBackerInfo = message.message.backer;
|
||||
|
||||
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
||||
@@ -407,5 +661,43 @@ describe('POST /chat', () => {
|
||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
||||
expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id)).to.exist;
|
||||
});
|
||||
|
||||
it('does not notify other users of a new message that is already hidden from shadow-muting', async () => {
|
||||
await user.update({ 'flags.chatShadowMuted': true });
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const memberWithNotification = await member.get('/user');
|
||||
|
||||
await user.update({ 'flags.chatShadowMuted': false });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.not.exist;
|
||||
expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id)).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('Spam prevention', () => {
|
||||
it('Returns an error when the user has been posting too many messages', async () => {
|
||||
// Post as many messages are needed to reach the spam limit
|
||||
for (let i = 0; i < SPAM_MESSAGE_LIMIT; i += 1) {
|
||||
const result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
|
||||
expect(result.message.id).to.exist;
|
||||
}
|
||||
|
||||
await expect(additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageGroupChatSpam'),
|
||||
});
|
||||
});
|
||||
|
||||
it('contributor should not receive spam alert', async () => {
|
||||
const userSocialite = await member.update({ 'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL });
|
||||
|
||||
// Post 1 more message than the spam limit to ensure they do not reach the limit
|
||||
for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i += 1) {
|
||||
const result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
|
||||
expect(result.message.id).to.exist;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,19 +12,18 @@ describe('POST /groups/:id/chat/seen', () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
guildLeader = groupLeader;
|
||||
[guildMember] = members;
|
||||
guildMember = members[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' });
|
||||
guildMessage = guildMessage.message;
|
||||
|
||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import config from '../../../../../config.json';
|
||||
@@ -12,24 +13,21 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
admin;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
members: 2,
|
||||
});
|
||||
|
||||
groupWithChat = group;
|
||||
author = groupLeader;
|
||||
[nonAdmin, admin] = members;
|
||||
await nonAdmin.update({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
await admin.update({ 'permissions.moderator': true });
|
||||
nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
admin = await generateUser({ 'permissions.moderator': true });
|
||||
|
||||
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||
message = message.message;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /group-plans', () => {
|
||||
@@ -7,15 +8,20 @@ describe('GET /group-plans', () => {
|
||||
let groupPlan;
|
||||
|
||||
before(async () => {
|
||||
({ group: groupPlan, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'group plan - is member',
|
||||
user = await generateUser({ balance: 4 });
|
||||
groupPlan = await generateGroup(user,
|
||||
{
|
||||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
leaderDetails: { balance: 4 },
|
||||
}));
|
||||
{
|
||||
purchased: {
|
||||
plan: {
|
||||
customerId: 'existings',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns group plans for the user', async () => {
|
||||
|
||||
@@ -1,63 +1,70 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
resetHabiticaDB,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('GET /groups', () => {
|
||||
let user; let leader; let members;
|
||||
let secondGroup; let secondLeader;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 2;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 3;
|
||||
let user;
|
||||
let userInGuild;
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
||||
const GUILD_PER_PAGE = 30;
|
||||
const categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
let publicGuildNotMember;
|
||||
let privateGuildUserIsMemberOf;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
|
||||
({
|
||||
group: privateGuildUserIsMemberOf,
|
||||
groupLeader: leader,
|
||||
members,
|
||||
} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
categories,
|
||||
},
|
||||
leaderDetails: {
|
||||
balance: 10,
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
[user] = members;
|
||||
await user.update({ balance: 4 });
|
||||
const leader = await generateUser({ balance: 10 });
|
||||
user = await generateUser({ balance: 4 });
|
||||
|
||||
({ group: secondGroup, groupLeader: secondLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
const publicGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
summary: 'ohayou kombonwa',
|
||||
description: 'oyasumi',
|
||||
});
|
||||
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
await secondLeader.post(`/groups/${secondGroup._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${secondGroup._id}/join`);
|
||||
userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] });
|
||||
|
||||
await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
publicGuildNotMember = await generateGroup(leader, {
|
||||
name: 'public guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
summary: 'Natsume Soseki',
|
||||
description: 'Kinnosuke no Hondana',
|
||||
categories,
|
||||
});
|
||||
|
||||
privateGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
categories,
|
||||
});
|
||||
await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
await generateGroup(leader, {
|
||||
name: 'private guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
await generateGroup(leader, {
|
||||
@@ -91,16 +98,172 @@ describe('GET /groups', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only the tavern when tavern passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=tavern'))
|
||||
.to.eventually.have.a.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]')
|
||||
.and.to.have.property('_id', TAVERN_ID);
|
||||
});
|
||||
|
||||
it('returns only the user\'s party when party passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=party'))
|
||||
.to.eventually.have.a.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]');
|
||||
});
|
||||
|
||||
it('returns all public guilds when publicGuilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=publicGuilds'))
|
||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=publicGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(publicGuildNotMember._id);
|
||||
});
|
||||
|
||||
it('returns private guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||
});
|
||||
|
||||
it('filters public guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
const guilds = await user.get('/groups?type=publicGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters private guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters public guilds by leader role', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&leader=true');
|
||||
expect(guilds.length).to.equal(NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER);
|
||||
});
|
||||
|
||||
it('filters public guilds by member role', async () => {
|
||||
const guilds = await userInGuild.get('/groups?type=publicGuilds&member=true');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].name).to.have.string('is member');
|
||||
});
|
||||
|
||||
it('filters public guilds by single-word search term', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=kom');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
|
||||
});
|
||||
|
||||
it('filters public guilds by single-word search term left and right-padded by spaces', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=++++ohayou+kombonwa+++++');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
|
||||
});
|
||||
|
||||
it('filters public guilds by two-words search term separated by multiple spaces', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=kinnosuke+++++hon');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].description).to.have.string('Kinnosuke');
|
||||
});
|
||||
});
|
||||
|
||||
describe('public guilds pagination', () => {
|
||||
it('req.query.paginate must be a boolean string', async () => {
|
||||
await expect(user.get('/groups?paginate=aString&type=publicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('req.query.paginate can only be true when req.query.type includes publicGuilds', async () => {
|
||||
await expect(user.get('/groups?paginate=true&type=notPublicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: apiError('guildsOnlyPaginate'),
|
||||
});
|
||||
});
|
||||
|
||||
it('req.query.page can\'t be negative', async () => {
|
||||
await expect(user.get('/groups?paginate=true&page=-1&type=publicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 30 guilds per page ordered by number of members', async () => {
|
||||
await user.update({ balance: 9000 });
|
||||
const delay = () => new Promise(resolve => setTimeout(resolve, 40));
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < 60; i += 1) {
|
||||
promises.push(generateGroup(user, {
|
||||
name: `public guild ${i} - is member`,
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}));
|
||||
await delay(); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
|
||||
const groups = await Promise.all(promises);
|
||||
|
||||
// update group number 32 and not the first to make sure sorting works
|
||||
await groups[32].update({ name: 'guild with most members', memberCount: 199 });
|
||||
await groups[33].update({ name: 'guild with less members', memberCount: -100 });
|
||||
|
||||
const page0 = await expect(user.get('/groups?type=publicGuilds&paginate=true'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
expect(page0[0].name).to.equal('guild with most members');
|
||||
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
const page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||
// 1 created now, 4 by other tests, -1 for no more tavern.
|
||||
.to.eventually.have.a.lengthOf(1 + 4 - 1);
|
||||
expect(page2[3].name).to.equal('guild with less members');
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
it('makes sure that the tavern doesn\'t show up when guilds is passed as a query', async () => {
|
||||
const guilds = await user.get('/groups?type=guilds');
|
||||
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('makes sure that the tavern doesn\'t show up when publicGuilds is passed as a query', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds');
|
||||
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=guilds'))
|
||||
.to.eventually.have.a
|
||||
.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
});
|
||||
|
||||
it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => {
|
||||
@@ -109,21 +272,21 @@ describe('GET /groups', () => {
|
||||
});
|
||||
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
await expect(user.get('/groups?type=privateGuilds,party'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
|
||||
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW - 1); // -1 for no Tavern.
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
it('returns private guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
const group = await generateGroup(user, {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
it('filters private guilds by size', async () => {
|
||||
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
// search for 'c++ coders'
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=0&search=c%2B%2B+coders'))
|
||||
.to.eventually.have.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]')
|
||||
.and.to.have.property('_id', group._id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /groups/:groupId/invites', () => {
|
||||
@@ -72,16 +71,15 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 31,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||
|
||||
const res = await leader.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
@@ -92,16 +90,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
}).timeout(10000);
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -111,16 +101,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -130,16 +112,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -149,16 +123,15 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 31,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||
|
||||
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
||||
expect(res.length).to.equal(14);
|
||||
@@ -176,20 +149,17 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
}).timeout(30000);
|
||||
|
||||
it('supports using req.query.lastId to get more invites', async function test () {
|
||||
let group; let invitees;
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
({ group, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 32,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const expectedIds = invitees.map(generatedInvite => generatedInvite._id);
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 32; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate); // Group has 32 invites
|
||||
const expectedIds = generatedInvites.map(generatedInvite => generatedInvite._id);
|
||||
await user.post(`/groups/${group._id}/invite`, { uuids: expectedIds });
|
||||
|
||||
const res = await user.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
@@ -76,15 +75,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
});
|
||||
|
||||
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
||||
let group;
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
upgradeToGroupPlan: true,
|
||||
members: 1,
|
||||
}));
|
||||
|
||||
const group = await generateGroup(user, { type: 'guild', name: generateUUID() });
|
||||
const [memberRes] = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
@@ -215,20 +206,20 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
it('supports using req.query.lastId to get more members', async function test () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
const { group, groupLeader: leader, members: generatedUsers } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
upgradeToGroupPlan: true,
|
||||
leaderDetails: { balance: 4 },
|
||||
members: 57,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 57; i += 1) {
|
||||
usersToGenerate.push(generateUser({ guilds: [group._id] }));
|
||||
}
|
||||
// Group has 59 members (1 is the leader)
|
||||
const generatedUsers = await Promise.all(usersToGenerate);
|
||||
const expectedIds = [leader._id].concat(generatedUsers.map(generatedUser => generatedUser._id));
|
||||
|
||||
const res = await leader.get(`/groups/${group._id}/members`);
|
||||
const res = await user.get(`/groups/${group._id}/members`);
|
||||
expect(res.length).to.equal(30);
|
||||
const res2 = await leader.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`);
|
||||
const res2 = await user.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`);
|
||||
expect(res2.length).to.equal(28);
|
||||
|
||||
const resIds = res.concat(res2).map(member => member._id);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
describe('GET /groups/:id', () => {
|
||||
const typesOfGroups = {};
|
||||
typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' };
|
||||
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
|
||||
typesOfGroups.party = { type: 'party', privacy: 'private' };
|
||||
|
||||
@@ -23,11 +24,10 @@ describe('GET /groups/:id', () => {
|
||||
const groupData = await createAndPopulateGroup({
|
||||
members: 30,
|
||||
groupDetails,
|
||||
upgradeToGroupPlan: groupDetails.type === 'guild',
|
||||
});
|
||||
|
||||
leader = groupData.groupLeader;
|
||||
[member] = groupData.members;
|
||||
member = groupData.members[0]; // eslint-disable-line prefer-destructuring
|
||||
createdGroup = groupData.group;
|
||||
});
|
||||
|
||||
@@ -49,6 +49,34 @@ describe('GET /groups/:id', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-member of a public guild', () => {
|
||||
let nonMember; let
|
||||
createdGroup;
|
||||
|
||||
before(async () => {
|
||||
const groupData = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
nonMember = await generateUser();
|
||||
});
|
||||
|
||||
it('returns the group object for a non-member', async () => {
|
||||
const group = await nonMember.get(`/groups/${createdGroup._id}`);
|
||||
|
||||
expect(group._id).to.eql(createdGroup._id);
|
||||
expect(group.name).to.eql(createdGroup.name);
|
||||
expect(group.type).to.eql(createdGroup.type);
|
||||
expect(group.privacy).to.eql(createdGroup.privacy);
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-member of a private guild', () => {
|
||||
let nonMember; let
|
||||
createdGroup;
|
||||
@@ -61,7 +89,6 @@ describe('GET /groups/:id', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
@@ -191,7 +218,7 @@ describe('GET /groups/:id', () => {
|
||||
});
|
||||
|
||||
context('Flagged messages', () => {
|
||||
let group; let members;
|
||||
let group;
|
||||
|
||||
const chat1 = {
|
||||
id: 'chat1',
|
||||
@@ -241,7 +268,7 @@ describe('GET /groups/:id', () => {
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
chat: [
|
||||
chat1,
|
||||
chat2,
|
||||
@@ -250,11 +277,9 @@ describe('GET /groups/:id', () => {
|
||||
chat5,
|
||||
],
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
({ group, members } = groupData);
|
||||
group = groupData.group;
|
||||
|
||||
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
|
||||
});
|
||||
@@ -262,8 +287,8 @@ describe('GET /groups/:id', () => {
|
||||
context('non-admin', () => {
|
||||
let nonAdmin;
|
||||
|
||||
beforeEach(() => {
|
||||
[nonAdmin] = members;
|
||||
beforeEach(async () => {
|
||||
nonAdmin = await generateUser();
|
||||
});
|
||||
|
||||
it('does not include messages with a flag count of 2 or greater', async () => {
|
||||
@@ -289,8 +314,9 @@ describe('GET /groups/:id', () => {
|
||||
let admin;
|
||||
|
||||
beforeEach(async () => {
|
||||
[admin] = members;
|
||||
await admin.update({ permissions: { moderator: true } });
|
||||
admin = await generateUser({
|
||||
'permissions.moderator': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('includes all messages', async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('POST /group', () => {
|
||||
@@ -34,8 +35,8 @@ describe('POST /group', () => {
|
||||
|
||||
it('sets the group leader to the user who created the group', async () => {
|
||||
const group = await user.post('/groups', {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
expect(group.leader).to.eql({
|
||||
@@ -50,7 +51,7 @@ describe('POST /group', () => {
|
||||
const name = 'Test Group';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
const updatedGroup = await user.get(`/groups/${group._id}`);
|
||||
@@ -63,7 +64,7 @@ describe('POST /group', () => {
|
||||
const summary = 'Test Summary';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
summary,
|
||||
});
|
||||
|
||||
@@ -77,7 +78,7 @@ describe('POST /group', () => {
|
||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1);
|
||||
await expect(user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
summary,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -87,6 +88,157 @@ describe('POST /group', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Guilds', () => {
|
||||
it('returns an error when a user with insufficient funds attempts to create a guild', async () => {
|
||||
await user.update({ balance: 0 });
|
||||
|
||||
await expect(
|
||||
user.post('/groups', {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
}),
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageInsufficientGems'),
|
||||
});
|
||||
});
|
||||
|
||||
it('adds guild to user\'s list of guilds', async () => {
|
||||
const guild = await user.post('/groups', {
|
||||
name: 'some guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.guilds).to.include(guild._id);
|
||||
});
|
||||
|
||||
it('awards the Joined Guild achievement', async () => {
|
||||
await user.post('/groups', {
|
||||
name: 'some guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.achievements.joinedGuild).to.eql(true);
|
||||
});
|
||||
|
||||
context('public guild', () => {
|
||||
it('creates a group', async () => {
|
||||
const groupName = 'Test Public Guild';
|
||||
const groupType = 'guild';
|
||||
const groupPrivacy = 'public';
|
||||
|
||||
const publicGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(publicGuild._id).to.exist;
|
||||
expect(publicGuild.name).to.equal(groupName);
|
||||
expect(publicGuild.type).to.equal(groupType);
|
||||
expect(publicGuild.memberCount).to.equal(1);
|
||||
expect(publicGuild.privacy).to.equal(groupPrivacy);
|
||||
expect(publicGuild.leader).to.eql({
|
||||
_id: user._id,
|
||||
profile: {
|
||||
name: user.profile.name,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when a user with no chat privileges attempts to create a public guild', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
|
||||
await expect(
|
||||
user.post('/groups', {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}),
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('private guild', () => {
|
||||
const groupName = 'Test Private Guild';
|
||||
const groupType = 'guild';
|
||||
const groupPrivacy = 'private';
|
||||
|
||||
it('creates a group', async () => {
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild._id).to.exist;
|
||||
expect(privateGuild.name).to.equal(groupName);
|
||||
expect(privateGuild.type).to.equal(groupType);
|
||||
expect(privateGuild.memberCount).to.equal(1);
|
||||
expect(privateGuild.privacy).to.equal(groupPrivacy);
|
||||
expect(privateGuild.leader).to.eql({
|
||||
_id: user._id,
|
||||
profile: {
|
||||
name: user.profile.name,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a private guild when the user has no chat privileges', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild._id).to.exist;
|
||||
});
|
||||
|
||||
it('deducts gems from user and adds them to guild bank', async () => {
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild.balance).to.eql(1);
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.balance).to.eql(user.balance - 1);
|
||||
});
|
||||
|
||||
it('does not deduct the gems from user when guild creation fails', async () => {
|
||||
const stub = sinon.stub(Group.prototype, 'save').rejects();
|
||||
const promise = user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
await expect(promise).to.eventually.be.rejected;
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.balance).to.eql(user.balance);
|
||||
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Parties', () => {
|
||||
const partyName = 'Test Party';
|
||||
const partyType = 'party';
|
||||
|
||||
@@ -18,24 +18,81 @@ describe('POST /group/:groupId/join', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Joining a private guild', () => {
|
||||
let user;
|
||||
let invitedUser;
|
||||
let guild;
|
||||
let invitees;
|
||||
context('Joining a public guild', () => {
|
||||
let user; let joiningUser; let
|
||||
publicGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: guild, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
user = groupLeader;
|
||||
joiningUser = await generateUser();
|
||||
});
|
||||
|
||||
it('allows non-invited users to join public guilds', async () => {
|
||||
const res = await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').to.include(publicGuild._id);
|
||||
expect(res.leader._id).to.eql(user._id);
|
||||
expect(res.leader.profile.name).to.eql(user.profile.name);
|
||||
});
|
||||
|
||||
it('returns an error if user was already a member', async () => {
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('youAreAlreadyInGroup'),
|
||||
});
|
||||
});
|
||||
|
||||
it('promotes joining member in a public empty guild to leader', async () => {
|
||||
await user.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.nested.property('leader._id', joiningUser._id);
|
||||
});
|
||||
|
||||
it('increments memberCount when joining guilds', async () => {
|
||||
const oldMemberCount = publicGuild.memberCount;
|
||||
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
|
||||
});
|
||||
|
||||
it('awards Joined Guild achievement', async () => {
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get('/user')).to.eventually.have.nested.property('achievements.joinedGuild', true);
|
||||
});
|
||||
});
|
||||
|
||||
context('Joining a private guild', () => {
|
||||
let user; let invitedUser; let
|
||||
guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
[invitedUser] = invitees;
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited to private guild', async () => {
|
||||
@@ -125,7 +182,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
|
||||
party = group;
|
||||
user = groupLeader;
|
||||
[invitedUser] = invitees;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited to party', async () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
generateChallenge,
|
||||
checkExistence,
|
||||
createAndPopulateGroup,
|
||||
sleep,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
@@ -13,187 +14,253 @@ import payments from '../../../../../website/server/libs/payments/payments';
|
||||
import calculateSubscriptionTerminationDate from '../../../../../website/server/libs/payments/calculateSubscriptionTerminationDate';
|
||||
|
||||
describe('POST /groups/:groupId/leave', () => {
|
||||
let groupToLeave;
|
||||
let leader;
|
||||
let member;
|
||||
let members;
|
||||
let memberCount;
|
||||
const typesOfGroups = {
|
||||
'public guild': { type: 'guild', privacy: 'public' },
|
||||
'private guild': { type: 'guild', privacy: 'private' },
|
||||
party: { type: 'party', privacy: 'private' },
|
||||
};
|
||||
|
||||
context('Leaving a Group Plan', () => {
|
||||
beforeEach(async () => {
|
||||
({ group: groupToLeave, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
[member] = members;
|
||||
memberCount = groupToLeave.memberCount;
|
||||
await leader.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
it('prevents non members from leaving', async () => {
|
||||
const user = await generateUser();
|
||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('lets user leave', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
});
|
||||
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.be.undefined;
|
||||
});
|
||||
|
||||
context('with challenges', () => {
|
||||
let challenge;
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a ${groupType}`, () => {
|
||||
let groupToLeave;
|
||||
let leader;
|
||||
let member;
|
||||
let memberCount;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
groupToLeave = group;
|
||||
leader = groupLeader;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
memberCount = group.memberCount;
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
it('prevents non members from leaving', async () => {
|
||||
const user = await generateUser();
|
||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
it(`lets user leave a ${groupType}`, async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
it(`sets a new group leader when leader leaves a ${groupType}`, async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
expect(groupToLeave.leader).to.equal(member._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
await sleep(0.5);
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.be.undefined;
|
||||
});
|
||||
|
||||
context('with challenges', () => {
|
||||
let challenge;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await leader.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await sleep(0.5);
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
// @TODO find elegant way to assert against the task existing
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents quest leader from leaving a groupToLeave');
|
||||
it('prevents a user from leaving during an active quest');
|
||||
});
|
||||
});
|
||||
|
||||
context('Leaving a Party', () => {
|
||||
let invitees;
|
||||
let invitedUser;
|
||||
context('Leaving a group as the last member', () => {
|
||||
context('private guild', () => {
|
||||
let privateGuild;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
({
|
||||
group: groupToLeave,
|
||||
groupLeader: leader,
|
||||
members,
|
||||
invitees,
|
||||
} = await createAndPopulateGroup({
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
members: 1,
|
||||
invites: 1,
|
||||
}));
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Private Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
invites: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
});
|
||||
|
||||
[member] = members;
|
||||
[invitedUser] = invitees;
|
||||
memberCount = groupToLeave.memberCount;
|
||||
await leader.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
privateGuild = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
it('prevents non members from leaving', async () => {
|
||||
const user = await generateUser();
|
||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
await leader.post(`/groups/${group._id}/chat`, { message: 'Some message' });
|
||||
});
|
||||
|
||||
it('removes a group when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${privateGuild._id}/leave`);
|
||||
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('removes invitations when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${privateGuild._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.guilds).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('lets user leave', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
context('public guild', () => {
|
||||
let publicGuild;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
publicGuild = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('keeps the group when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
await expect(checkExistence('groups', publicGuild._id)).to.eventually.equal(true);
|
||||
});
|
||||
|
||||
it('keeps the invitations when the last member leaves a public guild', async () => {
|
||||
await leader.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.guilds).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('deletes non existent guild from user when user tries to leave', async () => {
|
||||
const nonExistentGuildId = generateUUID();
|
||||
const userWithNonExistentGuild = await generateUser({ guilds: [nonExistentGuildId] });
|
||||
expect(userWithNonExistentGuild.guilds).to.contain(nonExistentGuildId);
|
||||
|
||||
await expect(userWithNonExistentGuild.post(`/groups/${nonExistentGuildId}/leave`))
|
||||
.to.eventually.be.rejected;
|
||||
|
||||
await userWithNonExistentGuild.sync();
|
||||
|
||||
expect(userWithNonExistentGuild.guilds).to.not.contain(nonExistentGuildId);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets a new group leader when leader leaves', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
context('party', () => {
|
||||
let party;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
expect(groupToLeave.leader).to.equal(member._id);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
await member.sync();
|
||||
party = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
it('removes a group when the last member leaves a party', async () => {
|
||||
await leader.post(`/groups/${party._id}/leave`);
|
||||
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await member.sync();
|
||||
await expect(checkExistence('party', party._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.be.undefined;
|
||||
});
|
||||
it('removes invitations when the last member leaves a party', async () => {
|
||||
await leader.post(`/groups/${party._id}/leave`);
|
||||
|
||||
it('removes a party when the last member leaves', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
await expect(checkExistence('party', groupToLeave._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('removes invitations when the last member leaves a party', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes non existent party from user when user tries to leave', async () => {
|
||||
@@ -208,71 +275,23 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
|
||||
expect(userWithNonExistentParty.party).to.eql({});
|
||||
});
|
||||
|
||||
context('with challenges', () => {
|
||||
let challenge;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const typesOfGroups = {
|
||||
'private guild': { type: 'guild', privacy: 'private' },
|
||||
party: { type: 'party', privacy: 'private' },
|
||||
};
|
||||
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a group plan when the group is a ${groupType}`, () => {
|
||||
if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans
|
||||
let groupWithPlan;
|
||||
let leader;
|
||||
let member;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: groupWithPlan, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
[member] = members;
|
||||
});
|
||||
leader = groupLeader;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
groupWithPlan = group;
|
||||
const userWithFreePlan = await User.findById(leader._id).exec();
|
||||
|
||||
// Create subscription
|
||||
@@ -302,21 +321,45 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.dateTerminated).to.exist;
|
||||
});
|
||||
|
||||
it('preserves the free subscription when leaving a any other group without a plan', async () => {
|
||||
// Joining a guild without a group plan
|
||||
const { group: groupWithNoPlan } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Group Without Plan',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
await member.post(`/groups/${groupWithNoPlan._id}/join`);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.planId).to.equal('group_plan_auto');
|
||||
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||
|
||||
// Leaving the guild without a group plan
|
||||
await member.post(`/groups/${groupWithNoPlan._id}/leave`);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => {
|
||||
if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans
|
||||
const extraMonths = 12;
|
||||
let groupWithPlan;
|
||||
let member;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: groupWithPlan, members } = await createAndPopulateGroup({
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
[member] = members;
|
||||
groupWithPlan = group;
|
||||
await member.update({
|
||||
'purchased.plan.extraMonths': extraMonths,
|
||||
});
|
||||
|
||||
@@ -5,6 +5,43 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /group/:groupId/reject-invite', () => {
|
||||
context('Rejecting a public guild invite', () => {
|
||||
let publicGuild; let
|
||||
invitedUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited', async () => {
|
||||
const userWithoutInvite = await generateUser();
|
||||
|
||||
await expect(userWithoutInvite.post(`/groups/${publicGuild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageGroupRequiresInvite'),
|
||||
});
|
||||
});
|
||||
|
||||
it('clears invitation from user', async () => {
|
||||
await invitedUser.post(`/groups/${publicGuild._id}/reject-invite`);
|
||||
|
||||
await expect(invitedUser.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.guilds')
|
||||
.to.not.include({ id: publicGuild._id });
|
||||
});
|
||||
});
|
||||
|
||||
context('Rejecting a private guild invite', () => {
|
||||
let invitedUser; let
|
||||
guild;
|
||||
@@ -17,7 +54,6 @@ describe('POST /group/:groupId/reject-invite', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
|
||||
@@ -25,7 +25,6 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
},
|
||||
invites: 1,
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
@@ -130,11 +129,9 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
it('sends email to removed user', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledTwice;
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.eql(member._id);
|
||||
expect(email.sendTxn.args[0][1]).to.eql('kicked-from-guild');
|
||||
expect(email.sendTxn.args[1][0]._id).to.eql(member._id);
|
||||
expect(email.sendTxn.args[1][1]).to.eql('group-member-removed');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import nconf from 'nconf';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -13,13 +14,13 @@ const MAX_EMAIL_INVITES_BY_USER = 200;
|
||||
describe('Post /groups/:groupId/invite', () => {
|
||||
let inviter;
|
||||
let group;
|
||||
const groupName = 'Test Party';
|
||||
const groupName = 'Test Public Guild';
|
||||
|
||||
beforeEach(async () => {
|
||||
inviter = await generateUser({ balance: 4 });
|
||||
group = await inviter.post('/groups', {
|
||||
name: groupName,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,44 +65,45 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('invites a user to a group by username', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||
});
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
})).to.eventually.deep.equal([{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
}]);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites multiple users to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
|
||||
const response = await (inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [
|
||||
userToInvite.auth.local.lowerCaseUsername,
|
||||
userToInvite2.auth.local.lowerCaseUsername,
|
||||
],
|
||||
}));
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
expect(response[1]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[1]._id).to.be.a('String');
|
||||
expect(response[1].id).to.eql(group._id);
|
||||
expect(response[1].name).to.eql(groupName);
|
||||
expect(response[1].inviter).to.eql(inviter._id);
|
||||
})).to.eventually.deep.equal([
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -212,42 +214,42 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('invites a user to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
})).to.eventually.deep.equal([{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
}]);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites multiple users to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id, userToInvite2._id],
|
||||
});
|
||||
})).to.eventually.deep.equal([
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
expect(response[1]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[1]._id).to.be.a('String');
|
||||
expect(response[1].id).to.eql(group._id);
|
||||
expect(response[1].name).to.eql(groupName);
|
||||
expect(response[1].inviter).to.eql(inviter._id);
|
||||
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
|
||||
it('returns an error when inviting multiple users and a user is not found', async () => {
|
||||
@@ -336,8 +338,12 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
invitesSent: MAX_EMAIL_INVITES_BY_USER,
|
||||
balance: 4,
|
||||
});
|
||||
const tmpGroup = await inviterWithMax.post('/groups', {
|
||||
name: groupName,
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
await expect(inviterWithMax.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviterWithMax.post(`/groups/${tmpGroup._id}/invite`, {
|
||||
emails: [testInvite],
|
||||
inviter: 'inviter name',
|
||||
}))
|
||||
@@ -413,15 +419,15 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(group._id);
|
||||
expect(invitedUser.invitations.guilds[0].id).to.equal(group._id);
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
|
||||
it('invites user to group with cancelled plan', async () => {
|
||||
let cancelledPlanGroup;
|
||||
({ group: cancelledPlanGroup, groupLeader: inviter } = await createAndPopulateGroup({
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
it('invites marks invite with cancelled plan', async () => {
|
||||
const cancelledPlanGroup = await generateGroup(inviter, {
|
||||
type: 'guild',
|
||||
name: generateUUID(),
|
||||
});
|
||||
await cancelledPlanGroup.createCancelledSubscription();
|
||||
|
||||
const newUser = await generateUser();
|
||||
@@ -431,13 +437,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(cancelledPlanGroup._id);
|
||||
expect(invitedUser.invitations.parties[0].cancelledPlan).to.be.true;
|
||||
expect(invitedUser.invitations.guilds[0].id).to.equal(cancelledPlanGroup._id);
|
||||
expect(invitedUser.invitations.guilds[0].cancelledPlan).to.be.true;
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('party invites', () => {
|
||||
describe('guild invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||
const userToInvite = await generateUser();
|
||||
@@ -451,7 +457,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||
it('returns an error when invited user is already invited to the group', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
@@ -459,6 +465,96 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('userAlreadyInvitedToGroup', { userId: userToInvite._id, username: userToInvite.profile.name }),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user is already in the group', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('userAlreadyInGroup', { userId: userToInvite._id, username: userToInvite.profile.name }),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows 30+ members in a guild', async () => {
|
||||
const invitesToGenerate = [];
|
||||
// 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
|
||||
expect(await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
})).to.be.an('array');
|
||||
}).timeout(10000);
|
||||
|
||||
// @TODO: Add this after we are able to mock the group plan route
|
||||
xit('returns an error when a non-leader invites to a group plan', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const nonGroupLeader = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [nonGroupLeader._id],
|
||||
});
|
||||
await nonGroupLeader.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(nonGroupLeader.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanInviteToGroupPlan'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('party invites', () => {
|
||||
let party;
|
||||
|
||||
beforeEach(async () => {
|
||||
party = await inviter.post('/groups', {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||
const userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
@@ -470,13 +566,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('returns an error when invited user is already in a party of more than 1 member', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id, userToInvite2._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${group._id}/join`);
|
||||
await userToInvite2.post(`/groups/${group._id}/join`);
|
||||
await userToInvite.post(`/groups/${party._id}/join`);
|
||||
await userToInvite2.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
@@ -500,7 +596,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
// Invite to first party
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
@@ -513,27 +609,28 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
const invitedUser = await userToInvite.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties.length).to.equal(2);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(group._id);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(party._id);
|
||||
expect(invitedUser.invitations.parties[1].id).to.equal(party2._id);
|
||||
});
|
||||
|
||||
it('allows inviting a user if party id is not associated with a real party', async () => {
|
||||
it('allow inviting a user if party id is not associated with a real party', async () => {
|
||||
const userToInvite = await generateUser({
|
||||
party: { _id: generateUUID() },
|
||||
});
|
||||
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(group._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('party size limits', () => {
|
||||
let party;
|
||||
let partyLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group, groupLeader: partyLeader } = await createAndPopulateGroup({
|
||||
group = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
@@ -541,7 +638,9 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
},
|
||||
// Generate party with 20 members
|
||||
members: PARTY_LIMIT_MEMBERS - 10,
|
||||
}));
|
||||
});
|
||||
party = group.group;
|
||||
partyLeader = group.groupLeader;
|
||||
});
|
||||
|
||||
it('allows 30 members in a party', async () => {
|
||||
@@ -552,7 +651,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
expect(await partyLeader.post(`/groups/${group._id}/invite`, {
|
||||
expect(await partyLeader.post(`/groups/${party._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
})).to.be.an('array');
|
||||
}).timeout(10000);
|
||||
@@ -565,7 +664,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
await expect(partyLeader.post(`/groups/${group._id}/invite`, {
|
||||
await expect(partyLeader.post(`/groups/${party._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
|
||||
@@ -17,10 +17,9 @@ describe('POST /group/:groupId/add-manager', () => {
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupToUpdate = group;
|
||||
|
||||
@@ -23,11 +23,10 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
categories: groupCategories,
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
adminUser = await generateUser({ 'permissions.moderator': true });
|
||||
groupToUpdate = group;
|
||||
@@ -107,28 +106,14 @@ describe('PUT /group', () => {
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
it('does not allow a leader to change leader of active group plan', async () => {
|
||||
await expect(leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
it('allows a leader to change leaders', async () => {
|
||||
const updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
name: groupUpdatedName,
|
||||
leader: nonLeader._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotChangeLeaderWithActiveGroupPlan'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a leader of a party to change leaders', async () => {
|
||||
const { group: party, groupLeader: partyLeader, members } = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
});
|
||||
const updatedGroup = await partyLeader.put(`/groups/${party._id}`, {
|
||||
name: groupUpdatedName,
|
||||
leader: members[0]._id,
|
||||
});
|
||||
|
||||
expect(updatedGroup.leader._id).to.eql(members[0]._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(members[0].profile.name);
|
||||
expect(updatedGroup.leader._id).to.eql(nonLeader._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(nonLeader.profile.name);
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
@@ -137,16 +122,15 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
const updateGroupDetails = {
|
||||
id: group._id,
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
bannedWordsAllowed: true,
|
||||
};
|
||||
|
||||
@@ -166,11 +150,9 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await groupLeader.update({ permissions: {} });
|
||||
|
||||
const updateGroupDetails = {
|
||||
id: group._id,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
@@ -50,21 +50,22 @@ describe('payments : amazon #subscribeCancel', () => {
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
});
|
||||
|
||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ describe('payments - amazon - #subscribe', () => {
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
@@ -48,21 +48,22 @@ describe('payments - stripe - #subscribeCancel', () => {
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
});
|
||||
|
||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
||||
it('does not accept quest for a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`))
|
||||
|
||||
@@ -43,7 +43,6 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
it('does not force start quest for a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`))
|
||||
|
||||
@@ -51,13 +51,14 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
});
|
||||
|
||||
it('does not issue invites for Guilds', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
const { group } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'public' },
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
const alternateGroup = group;
|
||||
|
||||
await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('guildQuestsNotSupported'),
|
||||
|
||||
@@ -52,7 +52,6 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`))
|
||||
|
||||
@@ -52,7 +52,6 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`))
|
||||
|
||||
@@ -51,7 +51,6 @@ describe('POST /groups/:groupId/quests/leave', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`))
|
||||
|
||||
@@ -53,7 +53,6 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
@@ -7,12 +8,8 @@ describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
user = await generateUser({ balance: 1 });
|
||||
guild = await generateGroup(user, { type: 'guild' }, { 'purchased.plan.customerId': 'group-unlimited' });
|
||||
});
|
||||
|
||||
it('can move task to new position', async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
find,
|
||||
each,
|
||||
map,
|
||||
} from 'lodash';
|
||||
@@ -197,6 +198,95 @@ describe('DELETE /user', () => {
|
||||
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('last member of a private guild', () => {
|
||||
let privateGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
privateGuild = await generateGroup(user, {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes guild when user is the only member', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is leader of', () => {
|
||||
let guild; let oldLeader; let
|
||||
newLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
newLeader = members[0]; // eslint-disable-line prefer-destructuring
|
||||
oldLeader = groupLeader;
|
||||
});
|
||||
|
||||
it('chooses new group leader for any group user was the leader of', async () => {
|
||||
await oldLeader.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
const updatedGuild = await newLeader.get(`/groups/${guild._id}`);
|
||||
|
||||
expect(updatedGuild.leader).to.exist;
|
||||
expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is a part of', () => {
|
||||
let group1; let group2; let userToDelete; let
|
||||
otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
userToDelete = await generateUser({ balance: 10 });
|
||||
|
||||
group1 = await generateGroup(userToDelete, {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
});
|
||||
|
||||
group2 = group;
|
||||
otherUser = members[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
await userToDelete.post(`/groups/${group2._id}/join`);
|
||||
});
|
||||
|
||||
it('removes user from all groups user was a part of', async () => {
|
||||
await userToDelete.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
const updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
|
||||
const updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
|
||||
const userInGroup = find(updatedGroup2Members, member => member._id === userToDelete._id);
|
||||
|
||||
expect(updatedGroup1Members).to.be.empty;
|
||||
expect(updatedGroup2Members).to.not.be.empty;
|
||||
expect(userInGroup).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Google auth', async () => {
|
||||
|
||||
@@ -51,7 +51,6 @@ describe('POST /user/purchase/:type/:key', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
@@ -78,7 +77,6 @@ describe('POST /user/purchase/:type/:key', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
|
||||
@@ -714,6 +714,31 @@ describe('POST /user/auth/local/register', () => {
|
||||
|
||||
expect(user.invitations.party).to.eql({});
|
||||
});
|
||||
|
||||
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
});
|
||||
|
||||
const invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
const user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.guilds[0]).to.eql({
|
||||
id: group._id,
|
||||
name: group.name,
|
||||
inviter: groupLeader._id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login via api', () => {
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -665,7 +665,6 @@ describe('POST /user/auth/local/register', () => {
|
||||
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
const invite = encrypt(JSON.stringify({
|
||||
|
||||
@@ -127,9 +127,6 @@ export async function createAndPopulateGroup (settings = {}) {
|
||||
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
||||
const { groupDetails } = settings;
|
||||
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||
if (upgradeToGroupPlan) {
|
||||
leaderDetails.permissions = { fullAccess: true };
|
||||
}
|
||||
|
||||
const groupLeader = await generateUser(leaderDetails);
|
||||
const group = await generateGroup(groupLeader, groupDetails);
|
||||
|
||||
@@ -120,9 +120,6 @@ export async function createAndPopulateGroup (settings = {}) {
|
||||
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
||||
const { groupDetails } = settings;
|
||||
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||
if (upgradeToGroupPlan) {
|
||||
leaderDetails.permissions = { fullAccess: true };
|
||||
}
|
||||
|
||||
const groupLeader = await generateUser(leaderDetails);
|
||||
const group = await generateGroup(groupLeader, groupDetails);
|
||||
|
||||
229
website/client/package-lock.json
generated
229
website/client/package-lock.json
generated
@@ -1842,9 +1842,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-optional-chaining": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
|
||||
"integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==",
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz",
|
||||
"integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.20.2",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
|
||||
@@ -1870,9 +1870,9 @@
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz",
|
||||
"integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==",
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
|
||||
"integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
@@ -13318,11 +13318,34 @@
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"optional": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"optional": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
|
||||
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
@@ -13354,6 +13377,38 @@
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.8.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
|
||||
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
@@ -16852,9 +16907,9 @@
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.31.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.31.0.tgz",
|
||||
"integrity": "sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ=="
|
||||
"version": "3.27.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz",
|
||||
"integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w=="
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.11.0",
|
||||
@@ -17903,9 +17958,9 @@
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz",
|
||||
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ=="
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
|
||||
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.7.0",
|
||||
@@ -21022,11 +21077,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
|
||||
},
|
||||
"immutable": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
|
||||
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg=="
|
||||
},
|
||||
"import-cwd": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
||||
@@ -21387,9 +21437,9 @@
|
||||
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="
|
||||
},
|
||||
"intro.js": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.0.1.tgz",
|
||||
"integrity": "sha512-1oqz6aOz9cGQ3CrtVYhCSo6AkjnXUn302kcIWLaZ3TI4kKssRXDwDSz4VRoGcfC1jN+WfaSJXRBrITz+QVEBzg=="
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-6.0.0.tgz",
|
||||
"integrity": "sha512-ZUiR6BoLSvPSlLG0boewnWVgji1fE1gBvP/pyw5pgCKXEDQz1mMeUxarggClPNs71UTq364LwSk9zxz17A9gaQ=="
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
@@ -21969,9 +22019,9 @@
|
||||
}
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
|
||||
"integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
|
||||
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
|
||||
},
|
||||
"js-message": {
|
||||
"version": "1.0.5",
|
||||
@@ -27317,19 +27367,17 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.63.4",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.63.4.tgz",
|
||||
"integrity": "sha512-Sx/+weUmK+oiIlI+9sdD0wZHsqpbgQg8wSwSnGBjwb5GwqFhYNwwnI+UWZtLjKvKyFlKkatRK235qQ3mokyPoQ==",
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.34.0.tgz",
|
||||
"integrity": "sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw==",
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
"chokidar": ">=3.0.0 <4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
@@ -27349,18 +27397,18 @@
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"requires": {
|
||||
"anymatch": "~3.1.2",
|
||||
"anymatch": "~3.1.1",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
"readdirp": "~3.5.0"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
@@ -27399,9 +27447,9 @@
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
@@ -27753,9 +27801,9 @@
|
||||
}
|
||||
},
|
||||
"smartbanner.js": {
|
||||
"version": "1.19.2",
|
||||
"resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.2.tgz",
|
||||
"integrity": "sha512-hwcGNp5Hza5PJHTmqP6H8q0XBYhloIQyJgdzv0ldz3HQSeEuKB2riVraQXdKuquE6ZU/0M0yubno53xJ/ZiQQg=="
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.1.tgz",
|
||||
"integrity": "sha512-x3alFTlk6pLuqrm9PrYQv1E+86CrEIgPf/KJ+nP5342BmOWstbdR8OwD3TPmM56zHQm4MEr/eoqbEcfTKdvdKw=="
|
||||
},
|
||||
"snapdragon": {
|
||||
"version": "0.8.2",
|
||||
@@ -28127,9 +28175,9 @@
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"stopword": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.8.tgz",
|
||||
"integrity": "sha512-btlEC2vEuhCuvshz99hSGsY8GzaP5qzDPQm56j6rR/R38p8xdsOXgU5a6tIgvU/4hcCta1Vlo/2FVXA9m0f8XA=="
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.7.tgz",
|
||||
"integrity": "sha512-s+uLKAxrproCLrq0Wcd3JAIjlJLx6l80b2Rzt0u8+ra5SzGkHnNG8PS3DfGmYk2TrKePDVLL4SdKYwKpgSLc+w=="
|
||||
},
|
||||
"store2": {
|
||||
"version": "2.10.0",
|
||||
@@ -30223,9 +30271,9 @@
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"uuid-browser": {
|
||||
"version": "3.1.0",
|
||||
@@ -30581,85 +30629,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.8.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
|
||||
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"optional": true
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"optional": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"optional": true
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
|
||||
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-mugen-scroll": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz",
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"chai": "^4.3.7",
|
||||
"core-js": "^3.31.0",
|
||||
"dompurify": "^3.0.3",
|
||||
"core-js": "^3.27.2",
|
||||
"dompurify": "^2.4.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
@@ -41,20 +41,20 @@
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"inspectpack": "^4.7.1",
|
||||
"intro.js": "^7.0.1",
|
||||
"jquery": "^3.7.0",
|
||||
"intro.js": "^6.0.0",
|
||||
"jquery": "^3.6.3",
|
||||
"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.7",
|
||||
"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",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.9.0",
|
||||
"vue": "^2.7.10",
|
||||
"vue-cli-plugin-storybook": "2.1.0",
|
||||
@@ -66,6 +66,6 @@
|
||||
"webpack": "^4.46.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0"
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.20.7"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 148 B |
@@ -41,7 +41,6 @@
|
||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||
<template v-else>
|
||||
<template v-if="isUserLoaded">
|
||||
<chat-banner />
|
||||
<damage-paused-banner />
|
||||
<gems-promo-banner />
|
||||
<gift-promo-banner />
|
||||
@@ -160,7 +159,6 @@ 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 ChatBanner from './components/header/banners/chatBanner';
|
||||
import DamagePausedBanner from './components/header/banners/damagePaused';
|
||||
import GemsPromoBanner from './components/header/banners/gemsPromo';
|
||||
import GiftPromoBanner from './components/header/banners/giftPromo';
|
||||
@@ -200,7 +198,6 @@ export default {
|
||||
AppHeader,
|
||||
AppFooter,
|
||||
birthdayModal,
|
||||
ChatBanner,
|
||||
DamagePausedBanner,
|
||||
GemsPromoBanner,
|
||||
GiftPromoBanner,
|
||||
|
||||
@@ -94,12 +94,6 @@
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.back_special_heroicAureole {
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_special_heroicAureole.gif") no-repeat;
|
||||
}
|
||||
|
||||
.head_special_0 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-ShadeHelmet.gif") no-repeat;
|
||||
}
|
||||
@@ -198,6 +192,14 @@
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_steamworks.gif") no-repeat;
|
||||
}
|
||||
|
||||
/* FIXME figure out how to handle customize menu!!
|
||||
.customize-menu .f_head_0 {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-position: -1917px -9px;
|
||||
}
|
||||
*/
|
||||
|
||||
[class*="Mount_Head_"],
|
||||
[class*="Mount_Body_"] {
|
||||
margin-top:18px; /* Sprite accommodates 105x123 box */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,35 +1,26 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-6 text-center mx-auto mb-5">
|
||||
<div class="col-12 text-center">
|
||||
<!-- @TODO i18n. How to setup the strings with the router-link inside?-->
|
||||
<img
|
||||
:class="retiredChatPage ? 'mt-5' : 'image-404'"
|
||||
class="not-found-img"
|
||||
src="~@/assets/images/404.png"
|
||||
>
|
||||
<div v-if="retiredChatPage">
|
||||
<h1>
|
||||
{{ $t('tavernDiscontinued') }}
|
||||
</h1>
|
||||
<p>{{ $t('tavernDiscontinuedDetail') }}</p>
|
||||
<p v-html="$t('tavernDiscontinuedLinks')"></p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h1>
|
||||
Sometimes even the bravest adventurer gets lost.
|
||||
</h1>
|
||||
<p class="mb-0">
|
||||
Looks like this link is broken or the page may have moved, sorry!
|
||||
</p>
|
||||
<p>
|
||||
Head back to the
|
||||
<router-link to="/">
|
||||
Homepage
|
||||
</router-link>or
|
||||
<router-link :to="contactUsLink">
|
||||
Contact Us
|
||||
</router-link>about the issue.
|
||||
</p>
|
||||
</div>
|
||||
<h1 class="not-found">
|
||||
Sometimes even the bravest adventurer gets lost.
|
||||
</h1>
|
||||
<h2 class="not-found">
|
||||
Looks like this link is broken or the page may have moved, sorry!
|
||||
</h2>
|
||||
<h2 class="not-found">
|
||||
Head back to the
|
||||
<router-link to="/">
|
||||
Homepage
|
||||
</router-link>or
|
||||
<router-link :to="contactUsLink">
|
||||
Contact Us
|
||||
</router-link>about the issue.
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -46,9 +37,6 @@ export default {
|
||||
}
|
||||
return { name: 'contact' };
|
||||
},
|
||||
retiredChatPage () {
|
||||
return this.$route.fullPath.indexOf('/groups') !== -1;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -56,20 +44,28 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
h1, .static-wrapper h1 {
|
||||
color: $purple-200;
|
||||
.col-12 {
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.not-found-img {
|
||||
margin-top: 152px;
|
||||
margin-bottom: 42px;
|
||||
}
|
||||
|
||||
h1.not-found {
|
||||
line-height: 1.33;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
color: $purple-200;
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
h2.not-found {
|
||||
line-height: 1.4;
|
||||
font-weight: normal;
|
||||
color: $gray-200;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.image-404 {
|
||||
margin-top: 104px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
60
website/client/src/components/achievements/testing.vue
Normal file
60
website/client/src/components/achievements/testing.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="testing"
|
||||
:title="$t('guildReminderTitle')"
|
||||
size="lg"
|
||||
:hide-footer="true"
|
||||
>
|
||||
<div class="modal-body text-center">
|
||||
<br>
|
||||
<div class="scene_guilds"></div>
|
||||
<br>
|
||||
<h4>{{ $t('guildReminderText1') }}</h4>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6 text-center">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('guildReminderDismiss') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="col-6 text-center"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
class="btn btn-primary"
|
||||
@click="takeMethere()"
|
||||
>
|
||||
{{ $t('guildReminderCTA') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.scene_guilds {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'testing');
|
||||
},
|
||||
takeMethere () {
|
||||
this.$router.push('/groups/discovery');
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="testingletiant"
|
||||
:title="$t('guildReminderTitle')"
|
||||
size="lg"
|
||||
:hide-footer="true"
|
||||
>
|
||||
<div class="modal-content"></div>
|
||||
<div class="modal-body text-center">
|
||||
<br>
|
||||
<div class="scene_guilds"></div>
|
||||
<br>
|
||||
<h4>{{ $t('guildReminderText2') }}</h4>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6 text-center">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('guildReminderDismiss') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-6 text-center">
|
||||
<div
|
||||
class="btn btn-primary"
|
||||
@click="takeMethere()"
|
||||
>
|
||||
{{ $t('guildReminderCTA') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.scene_guilds {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'testingletiant');
|
||||
},
|
||||
takeMethere () {
|
||||
this.$router.push('/groups/discovery');
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -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
|
||||
@@ -54,16 +46,9 @@
|
||||
Perk offset months:
|
||||
<strong>{{ hero.purchased.plan.consecutive.offset }}</strong>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div>
|
||||
Perk month count:
|
||||
<input
|
||||
v-model="hero.purchased.plan.perkMonthCount"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="2"
|
||||
step="1"
|
||||
>
|
||||
<strong>{{ hero.purchased.plan.perkMonthCount }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Next Mystic Hourglass:
|
||||
|
||||
@@ -8,15 +8,23 @@
|
||||
slot="modal-header"
|
||||
class="bug-report-modal-header"
|
||||
>
|
||||
<h2>
|
||||
{{ question ? $t('askQuestion') : $t('reportBug') }}
|
||||
<h2 v-once>
|
||||
{{ $t('reportBug') }}
|
||||
</h2>
|
||||
<div class="report-bug-header-describe">
|
||||
{{ question ? $t('askQuestionHeaderDescribe') : $t('reportBugHeaderDescribe') }}
|
||||
|
||||
<div
|
||||
v-once
|
||||
class="report-bug-header-describe"
|
||||
>
|
||||
{{ $t('reportBugHeaderDescribe') }}
|
||||
</div>
|
||||
|
||||
<div class="dialog-close">
|
||||
<close-icon
|
||||
:purple="true"
|
||||
@click="close()"
|
||||
/>
|
||||
</div>
|
||||
<close-x
|
||||
@close="close()"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<form
|
||||
@@ -32,8 +40,11 @@
|
||||
>
|
||||
{{ $t('email') }}
|
||||
</label>
|
||||
<div class="mb-2 description-label">
|
||||
{{ question ? $t('questionEmailText') : $t('reportEmailText') }}
|
||||
<div
|
||||
v-once
|
||||
class="mb-2 description-label"
|
||||
>
|
||||
{{ $t('reportEmailText') }}
|
||||
</div>
|
||||
<input
|
||||
id="emailInput"
|
||||
@@ -53,18 +64,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
{{ question ? $t('question') : $t('reportDescription') }}
|
||||
<label v-once>
|
||||
{{ $t('reportDescription') }}
|
||||
</label>
|
||||
<div class="mb-2 description-label">
|
||||
{{ question ? $t('questionDescriptionText') : $t('reportDescriptionText') }}
|
||||
<div
|
||||
v-once
|
||||
class="mb-2 description-label"
|
||||
>
|
||||
{{ $t('reportDescriptionText') }}
|
||||
</div>
|
||||
<textarea
|
||||
v-model="message"
|
||||
class="form-control"
|
||||
rows="5"
|
||||
:required="true"
|
||||
:placeholder="question ? $t('questionPlaceholder') : $t('reportDescriptionPlaceholder')"
|
||||
:placeholder="$t('reportDescriptionPlaceholder')"
|
||||
:class="{'input-invalid': messageInvalid && this.message.length === 0}"
|
||||
>
|
||||
|
||||
@@ -75,7 +89,7 @@
|
||||
type="submit"
|
||||
:disabled="!message || !emailValid"
|
||||
>
|
||||
{{ question ? $t('submitQuestion') : $t('submitBugReport') }}
|
||||
{{ $t('submitBugReport') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -127,7 +141,7 @@ h2 {
|
||||
.bug-report-modal-header {
|
||||
color: $white;
|
||||
width: 100%;
|
||||
padding: 1.5rem 3rem 1.5rem 1.5rem;
|
||||
padding: 2rem 3rem 1.5rem 1.5rem;
|
||||
|
||||
background-image: linear-gradient(288deg, #{$purple-200}, #{$purple-300});
|
||||
}
|
||||
@@ -168,13 +182,13 @@ label {
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
import closeX from '@/components/ui/closeX';
|
||||
import closeIcon from '@/components/shared/closeIcon';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { MODALS } from '@/libs/consts';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
closeX,
|
||||
closeIcon,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -197,7 +211,6 @@ export default {
|
||||
await axios.post('/api/v4/bug-report', {
|
||||
message: this.message,
|
||||
email: this.email,
|
||||
question: this.question,
|
||||
});
|
||||
|
||||
this.message = '';
|
||||
@@ -220,9 +233,6 @@ export default {
|
||||
if (this.email.length <= 3) return false;
|
||||
return !this.emailValid;
|
||||
},
|
||||
question () {
|
||||
return this.$store.state.bugReportOptions.question;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
const { user } = this;
|
||||
|
||||
@@ -50,6 +50,7 @@ import notificationsMixin from '@/mixins/notifications';
|
||||
import Task from '@/components/tasks/task';
|
||||
|
||||
import taskDefaults from '@/../../common/script/libs/taskDefaults';
|
||||
import { TAVERN_ID } from '@/../../common/script/constants';
|
||||
|
||||
const baseUrl = 'https://habitica.com';
|
||||
|
||||
@@ -88,7 +89,9 @@ export default {
|
||||
createTask: 'tasks:create',
|
||||
}),
|
||||
groupPath () {
|
||||
if (this.groupType === 'party') {
|
||||
if (this.groupId === TAVERN_ID) {
|
||||
return `${baseUrl}/groups/tavern`;
|
||||
} if (this.groupType === 'party') {
|
||||
return `${baseUrl}/party`;
|
||||
}
|
||||
return `${baseUrl}/groups/guild/${this.groupId}`;
|
||||
|
||||
@@ -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,13 +195,6 @@
|
||||
>
|
||||
<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
|
||||
@@ -216,21 +211,16 @@
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[2].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="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>
|
||||
@@ -246,8 +236,10 @@
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[1].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)"
|
||||
>
|
||||
@@ -278,13 +270,6 @@
|
||||
:pinned="isBackgroundPinned(bg)"
|
||||
/>
|
||||
</span>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -317,8 +302,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 +336,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 +358,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>
|
||||
|
||||
@@ -188,7 +188,6 @@
|
||||
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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
:style="{height}"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
<close-x
|
||||
<div
|
||||
v-if="canClose"
|
||||
@close="close()"
|
||||
/>
|
||||
class="close-icon svg-icon icon-12"
|
||||
|
||||
@click="close()"
|
||||
v-html="icons.close"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -27,24 +30,32 @@ body.modal-open .habitica-top-banner {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.625rem;
|
||||
z-index: 1300;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: unset;
|
||||
.close-icon.svg-icon {
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: 0;
|
||||
opacity: 0.48;
|
||||
|
||||
& ::v-deep svg path {
|
||||
stroke: $white !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import closeX from '@/components/ui/closeX';
|
||||
import closeIcon from '@/assets/svg/close.svg';
|
||||
import {
|
||||
clearBannerSetting, hideBanner, isBannerHidden, updateBannerHeight,
|
||||
} from '@/libs/banner.func';
|
||||
import { EVENTS } from '@/libs/events';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
closeX,
|
||||
},
|
||||
props: {
|
||||
bannerId: {
|
||||
type: String,
|
||||
@@ -71,6 +82,9 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: closeIcon,
|
||||
}),
|
||||
hidden: false,
|
||||
};
|
||||
},
|
||||
@@ -105,6 +119,8 @@ export default {
|
||||
close () {
|
||||
hideBanner(this.bannerId);
|
||||
this.hidden = true;
|
||||
|
||||
this.$root.$emit(EVENTS.BANNER_HIDDEN, this.bannerId);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<base-banner
|
||||
banner-id="chat-warning"
|
||||
banner-class="chat-banner"
|
||||
class="chat-banner"
|
||||
height="3rem"
|
||||
v-if="showChatWarning"
|
||||
:class="{faq: faqPage}"
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
class="w-100 text-center"
|
||||
v-html="$t('chatSunsetWarning')"
|
||||
>
|
||||
</div>
|
||||
</base-banner>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.chat-banner {
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 8px;
|
||||
color: $orange-1;
|
||||
background-color: $orange-100;
|
||||
line-height: 1.71;
|
||||
|
||||
a {
|
||||
color: $orange-1;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: $orange-1;
|
||||
}
|
||||
}
|
||||
|
||||
&.faq {
|
||||
position: fixed;
|
||||
top: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import BaseBanner from './base';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseBanner,
|
||||
},
|
||||
computed: {
|
||||
faqPage () {
|
||||
return (this.$route.fullPath.indexOf('/faq')) !== -1;
|
||||
},
|
||||
showChatWarning () {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
@@ -194,6 +194,48 @@
|
||||
>
|
||||
{{ $t('party') }}
|
||||
</b-nav-item>
|
||||
<li
|
||||
class="topbar-item droppable"
|
||||
:class="{
|
||||
'active': $route.path.startsWith('/groups')}"
|
||||
>
|
||||
<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: 'tavern'}"
|
||||
>
|
||||
{{ $t('guilds') }}
|
||||
</router-link>
|
||||
<div class="topbar-dropdown">
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'tavern'}"
|
||||
>
|
||||
{{ $t('tavern') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'myGuilds'}"
|
||||
>
|
||||
{{ $t('myGuilds') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'guildsDiscovery'}"
|
||||
>
|
||||
{{ $t('guildsDiscovery') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="topbar-item droppable"
|
||||
:class="{
|
||||
@@ -312,18 +354,22 @@
|
||||
>
|
||||
{{ $t('reportBug') }}
|
||||
</a>
|
||||
<a
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
to="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a"
|
||||
>
|
||||
{{ $t('askQuestion') }}
|
||||
</a>
|
||||
</router-link>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
||||
target="_blank"
|
||||
>{{ $t('requestFeature') }}</a>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
href="https://habitica.fandom.com/wiki/Contributing_to_Habitica"
|
||||
target="_blank"
|
||||
>{{ $t('contributing') }}</a>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
href="https://habitica.fandom.com/wiki/Habitica_Wiki"
|
||||
@@ -615,7 +661,6 @@ body.modal-open #habitica-menu {
|
||||
|
||||
&:hover {
|
||||
background: $purple-300;
|
||||
text-decoration: none;
|
||||
|
||||
&:last-child {
|
||||
border-bottom-right-radius: 5px;
|
||||
@@ -785,11 +830,9 @@ export default {
|
||||
async mounted () {
|
||||
await this.getUserGroupPlans();
|
||||
await this.getUserParty();
|
||||
if (document.getElementById('menu_collapse')) {
|
||||
Array.from(document.getElementById('menu_collapse').getElementsByTagName('a')).forEach(link => {
|
||||
link.addEventListener('click', this.closeMenu);
|
||||
});
|
||||
}
|
||||
Array.from(document.getElementById('menu_collapse').getElementsByTagName('a')).forEach(link => {
|
||||
link.addEventListener('click', this.closeMenu);
|
||||
});
|
||||
Array.from(document.getElementsByClassName('topbar-item')).forEach(link => {
|
||||
link.addEventListener('mouseenter', this.dropdownDesktop);
|
||||
link.addEventListener('mouseleave', this.dropdownDesktop);
|
||||
|
||||
@@ -44,13 +44,13 @@ export default {
|
||||
if (!this.notification || !this.notification.data) {
|
||||
return;
|
||||
}
|
||||
if (this.notification.data.destination.indexOf('backgrounds') !== -1) {
|
||||
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(this.notification.data.destination || '/inventory/items');
|
||||
this.$router.push({ name: this.notification.data.destination || 'items' });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
<low-health />
|
||||
<level-up />
|
||||
<choose-class />
|
||||
<testing />
|
||||
<testingletiant />
|
||||
<rebirth-enabled />
|
||||
<contributor />
|
||||
<won-challenge />
|
||||
@@ -125,6 +127,8 @@ import chooseClass from './achievements/chooseClass';
|
||||
import armoireEmpty from './achievements/armoireEmpty';
|
||||
import questCompleted from './achievements/questCompleted';
|
||||
import questInvitation from './achievements/questInvitation';
|
||||
import testing from './achievements/testing';
|
||||
import testingletiant from './achievements/testingletiant';
|
||||
import rebirthEnabled from './achievements/rebirthEnabled';
|
||||
import contributor from './achievements/contributor';
|
||||
import invitedFriend from './achievements/invitedFriend';
|
||||
@@ -265,6 +269,8 @@ export default {
|
||||
armoireEmpty,
|
||||
questCompleted,
|
||||
questInvitation,
|
||||
testing,
|
||||
testingletiant,
|
||||
rebirthEnabled,
|
||||
contributor,
|
||||
loginIncentives,
|
||||
@@ -294,6 +300,7 @@ export default {
|
||||
// general notifications
|
||||
'CRON',
|
||||
'FIRST_DROPS',
|
||||
'GUILD_PROMPT',
|
||||
'LOGIN_INCENTIVE',
|
||||
'NEW_CONTRIBUTOR_LEVEL',
|
||||
'ONBOARDING_COMPLETE',
|
||||
@@ -698,6 +705,14 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'first-drops');
|
||||
}
|
||||
break;
|
||||
case 'GUILD_PROMPT':
|
||||
// @TODO: I'm pretty sure we can find better names for these
|
||||
if (notification.data.textletiant === -1) {
|
||||
this.$root.$emit('bv::show::modal', 'testing');
|
||||
} else {
|
||||
this.$root.$emit('bv::show::modal', 'testingletiant');
|
||||
}
|
||||
break;
|
||||
case 'REBIRTH_ENABLED':
|
||||
this.$root.$emit('bv::show::modal', 'rebirth-enabled');
|
||||
break;
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
<!-- the word "total" -->
|
||||
<div class="buy-gem-total">
|
||||
{{ $t('sendTotal') }}
|
||||
{{ $t('sendGiftTotal') }}
|
||||
</div>
|
||||
|
||||
<!-- the actual dollar amount -->
|
||||
|
||||
@@ -128,10 +128,7 @@
|
||||
<hr>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="checkbox"
|
||||
id="preferenceAdvancedCollapsed"
|
||||
>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.advancedCollapsed"
|
||||
@@ -139,22 +136,33 @@
|
||||
class="mr-2"
|
||||
@change="set('advancedCollapsed')"
|
||||
>
|
||||
<span class="hint">
|
||||
{{ $t('startAdvCollapsed') }}
|
||||
</span>
|
||||
<b-popover
|
||||
target="preferenceAdvancedCollapsed"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('startAdvCollapsedPop')"
|
||||
/>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('startAdvCollapsedPop')"
|
||||
>{{ $t('startAdvCollapsed') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.dailyDueDefaultView"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('dailyDueDefaultView')"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('dailyDueDefaultViewPop')"
|
||||
>{{ $t('dailyDueDefaultView') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="party.memberCount === 1"
|
||||
class="checkbox"
|
||||
id="preferenceDisplayInviteAtOneMember"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
@@ -163,9 +171,12 @@
|
||||
class="mr-2"
|
||||
@change="set('displayInviteToPartyWhenPartyIs1')"
|
||||
>
|
||||
<span class="hint">
|
||||
{{ $t('displayInviteToPartyWhenPartyIs1') }}
|
||||
</span>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('displayInviteToPartyWhenPartyIs1')"
|
||||
>{{ $t('displayInviteToPartyWhenPartyIs1') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
@@ -206,47 +217,32 @@
|
||||
</div>
|
||||
<hr>
|
||||
<button
|
||||
id="buttonShowBailey"
|
||||
class="btn btn-primary mr-2 mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('showBaileyPop')"
|
||||
@click="showBailey()"
|
||||
>
|
||||
{{ $t('showBailey') }}
|
||||
<b-popover
|
||||
target="buttonShowBailey"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('showBaileyPop')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
id="buttonFCV"
|
||||
class="btn btn-primary mr-2 mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('fixValPop')"
|
||||
@click="openRestoreModal()"
|
||||
>
|
||||
{{ $t('fixVal') }}
|
||||
<b-popover
|
||||
target="buttonFCV"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('fixValPop')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="user.preferences.disableClasses == true"
|
||||
id="buttonEnableClasses"
|
||||
class="btn btn-primary mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('enableClassPop')"
|
||||
@click="changeClassForUser(false)"
|
||||
>
|
||||
{{ $t('enableClass') }}
|
||||
<b-popover
|
||||
target="buttonEnableClasses"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('enableClassPop')"
|
||||
/>
|
||||
</button>
|
||||
<hr>
|
||||
<day-start-adjustment />
|
||||
@@ -536,10 +532,6 @@
|
||||
input {
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: fit-content;
|
||||
}
|
||||
.usersettings h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
<template>
|
||||
<div class="item-cost">
|
||||
<span
|
||||
class="cost"
|
||||
:class="getPriceClass()"
|
||||
>
|
||||
<span
|
||||
class="svg-icon inline icon-24"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/mixins.scss';
|
||||
|
||||
.item-cost {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.cost {
|
||||
height: 40px;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
vertical-align: middle;
|
||||
|
||||
&.gems {
|
||||
color: $gems-color;
|
||||
border-radius: 20px;
|
||||
padding: 8px 20px 8px 20px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
background-color: rgba(36, 204, 143, 0.15);
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $gold-color;
|
||||
border-radius: 20px;
|
||||
padding: 8px 20px 8px 20px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
background-color: rgba(255, 190, 93, 0.15);
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
border-radius: 20px;
|
||||
padding: 8px 20px 8px 20px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
background-color: rgba(41, 149, 205, 0.15);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import svgGold from '@/assets/svg/gold.svg';
|
||||
import svgGem from '@/assets/svg/gem.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: svgClose,
|
||||
gold: svgGold,
|
||||
gems: svgGem,
|
||||
}),
|
||||
selectedAmountToBuy: 1,
|
||||
selectedAmount: 1,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getPriceClass () {
|
||||
if (this.priceType && this.icons[this.priceType]) {
|
||||
return this.priceType;
|
||||
} if (this.item.currency && this.icons[this.item.currency]) {
|
||||
return this.item.currency;
|
||||
}
|
||||
return 'gold';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,137 +0,0 @@
|
||||
<template>
|
||||
<div class="d-flex flex-row align-items-center justify-content-center number-increment">
|
||||
<!-- buy modal -->
|
||||
<div
|
||||
class="gray-circle"
|
||||
@click="quantity <= 0
|
||||
? quantity = 0
|
||||
: quantity--"
|
||||
>
|
||||
<div
|
||||
class="icon-negative"
|
||||
v-html="icons.svgNegative"
|
||||
></div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="align-items-center">
|
||||
</div>
|
||||
<input
|
||||
v-model="quantity"
|
||||
class="form-control alignment"
|
||||
step="1"
|
||||
type="number"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="gray-circle"
|
||||
@click="quantity++"
|
||||
>
|
||||
<div
|
||||
class="icon-positive"
|
||||
v-html="icons.svgPositive"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.number-increment {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.alignment {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
width: 94px;
|
||||
height: 32px;
|
||||
width: 48px;
|
||||
margin: 0px 16px 0px 16px;
|
||||
padding: 0;
|
||||
border-radius: 2px;
|
||||
border: solid 1px $gray-400;
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
.gray-circle {
|
||||
border-radius: 100%;
|
||||
border: solid 2px $gray-300;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: $purple-300;
|
||||
}
|
||||
}
|
||||
|
||||
.gray-circle:hover{
|
||||
.icon-positive, .icon-negative {
|
||||
& ::v-deep svg path {
|
||||
fill: $purple-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-positive, .icon-negative {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 4px auto;
|
||||
|
||||
& ::v-deep svg path {
|
||||
fill: $gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// icons
|
||||
import svgGem from '@/assets/svg/gem.svg';
|
||||
import svgGold from '@/assets/svg/gold.svg';
|
||||
import svgPositive from '@/assets/svg/positive.svg';
|
||||
import svgNegative from '@/assets/svg/negative.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
svgGem,
|
||||
svgGold,
|
||||
svgPositive,
|
||||
svgNegative,
|
||||
}),
|
||||
item: { },
|
||||
quantity: 1,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
watch: {
|
||||
quantity () {
|
||||
this.$emit('updateQuantity', this.quantity);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setDefaults () {
|
||||
this.input = 1;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
@@ -22,11 +22,10 @@
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
span {
|
||||
font-size: 0.75rem;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: $gray-100;
|
||||
margin-bottom: 16px;
|
||||
margin-top: -4px;
|
||||
color: $gray-200;
|
||||
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</span>
|
||||
<div>
|
||||
<span
|
||||
class="svg-icon close-icon icon-16 color"
|
||||
class="svg-icon icon-12 close-icon"
|
||||
aria-hidden="true"
|
||||
tabindex="0"
|
||||
@click="hideDialog()"
|
||||
@@ -45,13 +45,6 @@
|
||||
:sprites-margin="'0px auto 0px -24px'"
|
||||
/>
|
||||
</div>
|
||||
<item
|
||||
v-else-if="item.key === 'gem'"
|
||||
class="flat bordered-item"
|
||||
:item="item"
|
||||
:item-content-class="item.class"
|
||||
:show-popover="false"
|
||||
/>
|
||||
<item
|
||||
v-else-if="item.key != 'gem'"
|
||||
class="flat bordered-item"
|
||||
@@ -60,20 +53,10 @@
|
||||
:show-popover="false"
|
||||
/>
|
||||
</slot>
|
||||
<div
|
||||
v-if="!showAvatar && user.items[item.purchaseType]"
|
||||
class="owned"
|
||||
:class="totalOwned"
|
||||
>
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span class="owned-text">{{ $t('owned') }}: <span class="user-amount">{{ totalOwned }}</span></span>
|
||||
</div>
|
||||
<h4 class="title">
|
||||
{{ itemText }}
|
||||
</h4>
|
||||
<div class="item-notes">
|
||||
{{ itemNotes }}
|
||||
</div>
|
||||
<div v-html="itemNotes"></div>
|
||||
<slot
|
||||
name="additionalInfo"
|
||||
:item="item"
|
||||
@@ -86,61 +69,60 @@
|
||||
/>
|
||||
</slot>
|
||||
<div
|
||||
v-if="item.value > 0 && !(item.key === 'gem' && gemsLeft < 1)"
|
||||
v-if="item.value > 0"
|
||||
class="purchase-amount"
|
||||
>
|
||||
<!-- this is where the pretty item cost element lives -->
|
||||
<div class="item-cost">
|
||||
<span
|
||||
class="cost"
|
||||
:class="getPriceClass()"
|
||||
>
|
||||
<span
|
||||
class="svg-icon inline icon-24"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showAmountToBuy(item)"
|
||||
class="how-many-to-buy"
|
||||
>
|
||||
{{ $t('howManyToBuy') }}
|
||||
<strong>{{ $t('howManyToBuy') }}</strong>
|
||||
</div>
|
||||
<div
|
||||
v-if="showAmountToBuy(item)"
|
||||
>
|
||||
<number-increment
|
||||
class="number-increment"
|
||||
@updateQuantity="selectedAmountToBuy = $event"
|
||||
/>
|
||||
<div
|
||||
:class="{'notEnough': notEnoughCurrency}"
|
||||
class="total"
|
||||
>
|
||||
<span class="total-text">{{ $t('sendTotal') }}</span>
|
||||
<div v-if="showAmountToBuy(item)">
|
||||
<div class="box">
|
||||
<input
|
||||
v-model.number="selectedAmountToBuy"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
>
|
||||
</div>
|
||||
<span :class="{'notEnough': notEnoughCurrency}">
|
||||
<span
|
||||
class="svg-icon total icon-24"
|
||||
class="svg-icon inline icon-32"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
></span>
|
||||
<span
|
||||
class="total-text"
|
||||
class="cost"
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value * selectedAmountToBuy }}</span>
|
||||
</div>
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="d-flex align-items-middle"
|
||||
>
|
||||
<span
|
||||
class="svg-icon inline icon-32 ml-auto my-auto"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
></span>
|
||||
<span
|
||||
class="cost mr-auto my-auto"
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.key === 'gem' && gemsLeft < 1"
|
||||
class="no-more-gems"
|
||||
v-if="item.key === 'gem'"
|
||||
class="gems-left"
|
||||
>
|
||||
<strong v-if="gemsLeft > 0">{{ gemsLeft }} {{ $t('gemsRemaining') }}</strong>
|
||||
<strong v-if="gemsLeft === 0">{{ $t('maxBuyGems') }}</strong>
|
||||
</div>
|
||||
<div v-if="attemptingToPurchaseMoreGemsThanAreLeft">
|
||||
{{ $t('notEnoughGemsToBuy') }}
|
||||
</div>
|
||||
<div
|
||||
@@ -165,7 +147,7 @@
|
||||
{{ $t('viewSubscriptions') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="!(item.key === 'gem' && gemsLeft < 1)"
|
||||
v-else
|
||||
class="btn btn-primary"
|
||||
:disabled="item.key === 'gem' && gemsLeft === 0 ||
|
||||
attemptingToPurchaseMoreGemsThanAreLeft || numberInvalid || item.locked ||
|
||||
@@ -183,7 +165,6 @@
|
||||
<countdown-banner
|
||||
v-if="item.event && item.owned == null"
|
||||
:end-date="endDate"
|
||||
class="limitedTime available"
|
||||
/>
|
||||
<div
|
||||
v-if="item.key === 'rebirth_orb' && item.value > 0 && user.stats.lvl >= 100"
|
||||
@@ -197,32 +178,13 @@
|
||||
<span v-html="$t('nextFreeRebirth', {days: nextFreeRebirth})"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.key === 'gem'"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<div
|
||||
v-if="gemsLeft > 0"
|
||||
class="gems-left d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<strong>{{ $t('monthlyGems') }} </strong>
|
||||
{{ gemsLeft }} / {{ totalGems }} {{ $t('gemsRemaining') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="gemsLeft === 0"
|
||||
class="out-of-gems-banner d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<strong>{{ $t('monthlyGems') }} </strong>
|
||||
{{ gemsLeft }} / {{ totalGems }} {{ $t('gemsRemaining') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
slot="modal-footer"
|
||||
class="clearfix"
|
||||
class="d-flex"
|
||||
>
|
||||
<span class="user-balance float-left">{{ $t('yourBalance') }}</span>
|
||||
<span class="balance mr-auto">{{ $t('yourBalance') }}</span>
|
||||
<balanceInfo
|
||||
class="currency-totals"
|
||||
class="ml-auto"
|
||||
:currency-needed="getPriceClass()"
|
||||
:amount-needed="item.value"
|
||||
/>
|
||||
@@ -238,47 +200,11 @@
|
||||
@include centeredModal();
|
||||
|
||||
.modal-body {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
height: 48px;
|
||||
background-color: $gray-700;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
display: block;
|
||||
margin: 24px 0 0 0;
|
||||
padding: 16px 24px;
|
||||
align-content: center;
|
||||
|
||||
.user-balance {
|
||||
width: 150px;
|
||||
height: 16px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: $gray-100;
|
||||
margin-bottom: 16px;
|
||||
margin-top: -4px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.currency-totals {
|
||||
margin-right: -8px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 448px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.badge-dialog {
|
||||
left: -8px;
|
||||
top: -8px;
|
||||
width: 330px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
@@ -286,71 +212,8 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.owned {
|
||||
height: 32px;
|
||||
width: 141px;
|
||||
margin-top: -36px;
|
||||
margin-left: 153px;
|
||||
padding-top: 6px;
|
||||
background-color: $gray-600;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.owned-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
.user-amount {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
transform: scale(1.45, 1.45);
|
||||
top: -25.67px;
|
||||
left: 1px;
|
||||
|
||||
&.shop_gem {
|
||||
transform: scale(1.45, 1.45);
|
||||
top: -2px;
|
||||
left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 28px;
|
||||
color: $gray-10;
|
||||
font-size: 1.25rem;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.item-notes {
|
||||
margin-top: 8px;
|
||||
padding-left: 48.5px;
|
||||
padding-right: 48.5px;
|
||||
line-height: 1.71;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
width: 448px;
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
@@ -358,22 +221,15 @@
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
margin: 32px auto auto;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
margin-top: 16px;
|
||||
margin: 33px auto auto;
|
||||
width: 282px;
|
||||
}
|
||||
|
||||
.purchase-amount {
|
||||
margin-top: 0px;
|
||||
margin-top: 24px;
|
||||
|
||||
.how-many-to-buy {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.number-increment {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.box {
|
||||
@@ -399,105 +255,31 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.no-more-gems {
|
||||
color: $yellow-5;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.33;
|
||||
margin: 16px 48px 0 48px;
|
||||
}
|
||||
|
||||
// for cost icon of a single item
|
||||
span.svg-icon.inline.icon-24 {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 4px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
// for the total user cost
|
||||
span.svg-icon.total.icon-24 {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-left: 6px;
|
||||
span.svg-icon.inline.icon-32 {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
|
||||
margin-right: 8px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
span.svg-icon.icon-16 {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
color: $gray-200;
|
||||
stroke-width: 0px;
|
||||
|
||||
&:hover {
|
||||
color: $gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
.attributes-group {
|
||||
margin: 32px;
|
||||
border-radius: 4px;
|
||||
line-height: 1.71;
|
||||
font-size: 0.875;
|
||||
}
|
||||
|
||||
.attributesGrid {
|
||||
margin-top: 28px;
|
||||
border-radius: 2px;
|
||||
background-color: $gray-500;
|
||||
}
|
||||
|
||||
.item-cost {
|
||||
display: inline-flex;
|
||||
margin: 16px 0;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cost {
|
||||
display: inline-block;
|
||||
font-family: sans-serif;
|
||||
font-size: 1.25rem;
|
||||
width: 28px;
|
||||
height: 32px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding: 6px 20px;
|
||||
line-height: 1.4;
|
||||
border-radius: 20px;
|
||||
line-height: 1.33;
|
||||
|
||||
vertical-align: middle;
|
||||
|
||||
&.gems {
|
||||
color: $green-10;
|
||||
background-color: rgba(36, 204, 143, 0.15);
|
||||
align-items: center;
|
||||
color: $gems-color;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
background-color: rgba(255, 190, 93, 0.15);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
background-color: rgba(41, 149, 205, 0.15);
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.total {
|
||||
font-weight: bold;
|
||||
font-size: 0.875rem;
|
||||
padding-top: 2px;
|
||||
margin-top: 4px;
|
||||
|
||||
&.gems {
|
||||
color: $green-10;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
color: $gold-color;
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
@@ -505,84 +287,62 @@
|
||||
}
|
||||
}
|
||||
|
||||
.total-text {
|
||||
color: $gray-50;
|
||||
font-weight: bold;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
|
||||
&.gems {
|
||||
color: $green-10;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
}
|
||||
}
|
||||
|
||||
button.btn.btn-primary {
|
||||
margin-top: 16px;
|
||||
padding: 4px 16px;
|
||||
height: 32px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
min-width: 6rem;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
.notEnough {
|
||||
pointer-events: none;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.balance {
|
||||
width: 74px;
|
||||
height: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
.free-rebirth {
|
||||
background-color: $yellow-5;
|
||||
color: $white;
|
||||
height: 2rem;
|
||||
line-height: 16px;
|
||||
margin: auto -1rem -1rem;
|
||||
}
|
||||
.modal-footer {
|
||||
height: 48px;
|
||||
background-color: $gray-700;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// .pt-015 {
|
||||
// padding-top: 0.15rem;
|
||||
// }
|
||||
.notEnough {
|
||||
pointer-events: none;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.attributesGrid {
|
||||
margin-top: 8px;
|
||||
border-radius: 2px;
|
||||
background-color: $gray-500;
|
||||
|
||||
margin: 10px 0 24px;
|
||||
}
|
||||
|
||||
.gems-left {
|
||||
height: 32px;
|
||||
background-color: $green-100;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 24px;
|
||||
color: $green-1;
|
||||
width: 100%;
|
||||
margin-bottom: -24px;
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.out-of-gems-banner {
|
||||
height: 32px;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 24px;
|
||||
background-color: $yellow-100;
|
||||
color: $yellow-1;
|
||||
width: 100%;
|
||||
margin-bottom: -24px;
|
||||
}
|
||||
|
||||
.limitedTime {
|
||||
height: 32px;
|
||||
width: 446px;
|
||||
font-size: 0.75rem;
|
||||
margin: 24px 0 0 0;
|
||||
background-color: $purple-300;
|
||||
.free-rebirth {
|
||||
background-color: $yellow-5;
|
||||
color: $white;
|
||||
margin-bottom: -24px;
|
||||
height: 2rem;
|
||||
line-height: 16px;
|
||||
margin: auto -1rem -1rem;
|
||||
}
|
||||
|
||||
.pt-015 {
|
||||
padding-top: 0.15rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -610,8 +370,6 @@ import svgGem from '@/assets/svg/gem.svg';
|
||||
import svgHourglasses from '@/assets/svg/hourglass.svg';
|
||||
import svgClock from '@/assets/svg/clock.svg';
|
||||
import svgWhiteClock from '@/assets/svg/clock-white.svg';
|
||||
import svgPositive from '@/assets/svg/positive.svg';
|
||||
import svgNegative from '@/assets/svg/negative.svg';
|
||||
|
||||
import BalanceInfo from './balanceInfo.vue';
|
||||
import PinBadge from '@/components/ui/pinBadge';
|
||||
@@ -619,7 +377,6 @@ import CountdownBanner from './countdownBanner';
|
||||
import currencyMixin from './_currencyMixin';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import buyMixin from '@/mixins/buy';
|
||||
import numberIncrement from '@/components/shared/numberIncrement';
|
||||
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
@@ -650,17 +407,14 @@ export default {
|
||||
Avatar,
|
||||
PinBadge,
|
||||
CountdownBanner,
|
||||
numberIncrement,
|
||||
},
|
||||
mixins: [buyMixin, currencyMixin, notifications, numberInvalid, spellsMixin],
|
||||
props: {
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
item: {
|
||||
type: Object,
|
||||
},
|
||||
priceType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
withPin: {
|
||||
type: Boolean,
|
||||
@@ -679,14 +433,10 @@ export default {
|
||||
hourglasses: svgHourglasses,
|
||||
clock: svgClock,
|
||||
whiteClock: svgWhiteClock,
|
||||
positive: svgPositive,
|
||||
negative: svgNegative,
|
||||
}),
|
||||
|
||||
selectedAmountToBuy: 1,
|
||||
selectedAmount: 1,
|
||||
isPinned: false,
|
||||
quantity: 1,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -724,11 +474,6 @@ export default {
|
||||
return planGemLimits.convCap
|
||||
+ this.user.purchased.plan.consecutive.gemCapExtra - this.user.purchased.plan.gemsBought;
|
||||
},
|
||||
totalGems () {
|
||||
if (!this.user.purchased.plan) return 0;
|
||||
return planGemLimits.convCap
|
||||
+ this.user.purchased.plan.consecutive.gemCapExtra;
|
||||
},
|
||||
attemptingToPurchaseMoreGemsThanAreLeft () {
|
||||
if (this.item && this.item.key && this.item.key === 'gem' && this.selectedAmountToBuy > this.gemsLeft) return true;
|
||||
return false;
|
||||
@@ -745,9 +490,6 @@ export default {
|
||||
endDate () {
|
||||
return moment(this.item.event.end);
|
||||
},
|
||||
totalOwned () {
|
||||
return this.user.items[this.item.purchaseType][this.item.key] || 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
item: function itemChanged () {
|
||||
@@ -758,9 +500,7 @@ export default {
|
||||
methods: {
|
||||
onChange ($event) {
|
||||
this.$emit('change', $event);
|
||||
this.selectedAmountToBuy = 1;
|
||||
},
|
||||
|
||||
buyItem () {
|
||||
// @TODO: I think we should buying to the items.
|
||||
// Turn the items into classes, and use polymorphism
|
||||
@@ -857,7 +597,6 @@ export default {
|
||||
}
|
||||
},
|
||||
hideDialog () {
|
||||
this.selectedAmountToBuy = 1;
|
||||
this.$root.$emit('bv::hide::modal', 'buy-modal');
|
||||
},
|
||||
getPriceClass () {
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
.limitedTime {
|
||||
height: 32px;
|
||||
width: calc(100% + 30px);
|
||||
margin: 0 -15px; // the modal content has its own padding
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
:hide-header="true"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<div>
|
||||
<div class="close">
|
||||
<span
|
||||
class="svg-icon close-icon icon-16 color"
|
||||
class="svg-icon inline icon-10"
|
||||
aria-hidden="true"
|
||||
@click="hideDialog()"
|
||||
v-html="icons.close"
|
||||
@@ -14,73 +14,60 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="item"
|
||||
class="content bordered-item"
|
||||
class="content"
|
||||
>
|
||||
<div class="inner-content">
|
||||
<item
|
||||
class="flat bordered-item"
|
||||
class="flat"
|
||||
:item="item"
|
||||
:item-content-class="itemContextToSell.itemClass"
|
||||
:show-popover="false"
|
||||
/>
|
||||
<span class="owned">
|
||||
{{ $t('owned') }}: <span class="user-amount">{{ itemContextToSell.itemCount }}</span>
|
||||
</span>
|
||||
>
|
||||
<countBadge
|
||||
slot="itemBadge"
|
||||
:show="true"
|
||||
:count="itemContextToSell.itemCount"
|
||||
/>
|
||||
</item>
|
||||
<h4 class="title">
|
||||
{{ itemContextToSell.itemName }}
|
||||
</h4>
|
||||
<div v-if="item.key === 'Saddle'">
|
||||
<div class="item-notes">
|
||||
<div class="text">
|
||||
{{ item.sellWarningNote() }}
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>
|
||||
<div class="item-notes">
|
||||
<div class="text">
|
||||
{{ item.notes() }}
|
||||
</div>
|
||||
<div class="item-cost">
|
||||
<span class="cost gold">
|
||||
<span
|
||||
class="svg-icon inline icon-24"
|
||||
aria-hidden="true"
|
||||
v-html="icons.gold"
|
||||
></span>
|
||||
<span>{{ item.value }}</span>
|
||||
</span>
|
||||
<div>
|
||||
<b class="how-many-to-sell">{{ $t('howManyToSell') }}</b>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class="how-many-to-sell"
|
||||
>
|
||||
{{ $t('howManyToSell') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<number-increment
|
||||
@updateQuantity="selectedAmountToSell = $event"
|
||||
<b-input
|
||||
v-model="selectedAmountToSell"
|
||||
class="itemsToSell"
|
||||
type="number"
|
||||
:max="itemContextToSell.itemCount"
|
||||
min="1"
|
||||
step="1"
|
||||
@keyup.native="preventNegative($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span class="total-text">
|
||||
{{ $t('sendTotal') }}
|
||||
</span>
|
||||
<span
|
||||
class="svg-icon total icon-24"
|
||||
class="svg-icon inline icon-32"
|
||||
aria-hidden="true"
|
||||
v-html="icons.gold"
|
||||
></span>
|
||||
<span class="total-text gold">
|
||||
{{ item.value * selectedAmountToSell }}
|
||||
</span>
|
||||
<span class="value">{{ item.value }}</span>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="selectedAmountToSell > itemContextToSell.itemCount"
|
||||
@click="sellItems()"
|
||||
>
|
||||
{{ $t('sellItems') }}
|
||||
{{ $t('sell') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,10 +77,8 @@
|
||||
slot="modal-footer"
|
||||
class="clearfix"
|
||||
>
|
||||
<span class="user-balance float-left">{{ $t('yourBalance') }}</span>
|
||||
<balanceInfo
|
||||
class="float-right currency-totals"
|
||||
/>
|
||||
<span class="balance float-left">{{ $t('yourBalance') }}</span>
|
||||
<balanceInfo class="float-right" />
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
@@ -110,13 +95,51 @@
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 448px;
|
||||
width: 330px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 0px;
|
||||
.content {
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
.inner-content {
|
||||
margin: 33px auto auto;
|
||||
width: 282px;
|
||||
}
|
||||
|
||||
span.svg-icon.inline.icon-32 {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
|
||||
margin-left: 24px;
|
||||
margin-right: 8px;
|
||||
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.value {
|
||||
width: 28px;
|
||||
height: 32px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: #df911e;
|
||||
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
button.btn.btn-primary {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.balance {
|
||||
width: 74px;
|
||||
height: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
@@ -125,215 +148,29 @@
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
display: block;
|
||||
margin: 24px 0 0;
|
||||
padding: 16px 24px;
|
||||
align-content: center;
|
||||
|
||||
.user-balance {
|
||||
width: 150px;
|
||||
height: 16px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: $gray-100;
|
||||
margin-bottom: 16px;
|
||||
margin-top: -4px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.currency-totals {
|
||||
margin-right: -8px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
margin: 33px auto auto;
|
||||
width: 282px;
|
||||
}
|
||||
|
||||
.owned {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
background-color: $gray-600;
|
||||
padding: 8px 8px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
.how-many-to-sell {
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
width: 141px;
|
||||
margin-left: 71px;
|
||||
margin-top: -48px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.user-amount {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
cursor: default;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
transform: scale(1.45, 1.45);
|
||||
top: -25px;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $gray-10;
|
||||
font-size: 1.25rem;
|
||||
margin-top: 26px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.item-notes {
|
||||
margin-top: 12px;
|
||||
line-height: 1.71;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
// for cost icon of a single item
|
||||
span.svg-icon.inline.icon-24 {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 4px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
// for the total user cost
|
||||
span.svg-icon.total.icon-24 {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-left: 6px;
|
||||
margin-right: 8px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
span.svg-icon.icon-16 {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
color: $gray-200;
|
||||
stroke-width: 0px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
.item-cost {
|
||||
display: inline-flex;
|
||||
margin: 16px 0;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.cost {
|
||||
display: inline-block;
|
||||
font-family: sans-serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
padding: 6px 20px;
|
||||
line-height: 1.4;
|
||||
border-radius: 20px;
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
background-color: rgba(255, 190, 93, 0.15);
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.how-many-to-sell {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.number-increment {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
font-weight: bold;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 16px;
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
}
|
||||
}
|
||||
|
||||
.total-text {
|
||||
color: $gray-50;
|
||||
font-weight: bold;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
}
|
||||
}
|
||||
|
||||
button.btn.btn-primary {
|
||||
margin-top: 16px;
|
||||
padding: 4px 16px;
|
||||
height: 32px;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
.balance {
|
||||
width: 74px;
|
||||
height: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import svgGold from '@/assets/svg/gold.svg';
|
||||
import svgGem from '@/assets/svg/gem.svg';
|
||||
import svgPositive from '@/assets/svg/positive.svg';
|
||||
import svgNegative from '@/assets/svg/negative.svg';
|
||||
|
||||
import BalanceInfo from '../balanceInfo.vue';
|
||||
import Item from '@/components/inventory/item';
|
||||
import numberIncrement from '@/components/shared/numberIncrement';
|
||||
import CountBadge from '@/components/ui/countBadge';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BalanceInfo,
|
||||
Item,
|
||||
numberIncrement,
|
||||
CountBadge,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -344,8 +181,6 @@ export default {
|
||||
close: svgClose,
|
||||
gold: svgGold,
|
||||
gem: svgGem,
|
||||
svgPositive,
|
||||
svgNegative,
|
||||
}),
|
||||
};
|
||||
},
|
||||
@@ -376,10 +211,6 @@ export default {
|
||||
this.selectedAmountToSell = 0;
|
||||
}
|
||||
},
|
||||
maxOwned () {
|
||||
const maxOwned = this.itemContextToSell.itemCount;
|
||||
return maxOwned;
|
||||
},
|
||||
sellItems () {
|
||||
if (!Number.isInteger(Number(this.selectedAmountToSell))) {
|
||||
this.selectedAmountToSell = 0;
|
||||
|
||||
@@ -33,22 +33,6 @@
|
||||
v-if="!item.locked"
|
||||
class="purchase-amount"
|
||||
>
|
||||
<div class="item-cost">
|
||||
<span
|
||||
class="cost"
|
||||
:class="priceType"
|
||||
>
|
||||
<span
|
||||
class="svg-icon inline icon-24"
|
||||
aria-hidden="true"
|
||||
v-html="icons[priceType]"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
:class="priceType"
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="how-many-to-buy">
|
||||
<strong>{{ $t('howManyToBuy') }}</strong>
|
||||
</div>
|
||||
@@ -58,25 +42,24 @@
|
||||
>
|
||||
{{ item.addlNotes }}
|
||||
</div>
|
||||
<div>
|
||||
<number-increment
|
||||
@updateQuantity="selectedAmountToBuy = $event"
|
||||
/>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span class="total-text">
|
||||
{{ $t('sendTotal') }}
|
||||
</span>
|
||||
<span
|
||||
class="svg-icon inline icon-20"
|
||||
aria-hidden="true"
|
||||
v-html="currencyIcon"
|
||||
></span>
|
||||
<span
|
||||
class="total"
|
||||
:class="priceType"
|
||||
>{{ item.value * selectedAmountToBuy }}</span>
|
||||
<div class="box">
|
||||
<input
|
||||
v-model.number="selectedAmountToBuy"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
>
|
||||
</div>
|
||||
<span
|
||||
class="svg-icon inline icon-32"
|
||||
aria-hidden="true"
|
||||
v-html="currencyIcon"
|
||||
></span>
|
||||
<span
|
||||
class="value"
|
||||
:class="priceType"
|
||||
>{{ item.value }}</span>
|
||||
</div>
|
||||
<button
|
||||
v-if="priceType === 'gems'
|
||||
@@ -89,7 +72,7 @@
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-primary mb-4"
|
||||
class="btn btn-primary"
|
||||
:class="{'notEnough': !enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
|
||||
:disabled="numberInvalid"
|
||||
@click="buyItem()"
|
||||
@@ -129,39 +112,6 @@
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
height: 48px;
|
||||
background-color: $gray-700;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
display: block;
|
||||
padding: 16px 24px;
|
||||
align-content: center;
|
||||
|
||||
.user-balance {
|
||||
width: 150px;
|
||||
height: 16px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: $gray-100;
|
||||
margin-bottom: 16px;
|
||||
margin-top: -4px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.currency-totals {
|
||||
margin-right: -8px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
margin-top: 8%;
|
||||
width: 448px !important;
|
||||
@@ -179,13 +129,8 @@
|
||||
margin: 33px auto auto;
|
||||
}
|
||||
|
||||
.item-notes {
|
||||
height: 48px;
|
||||
margin-top: 8px;
|
||||
padding-left: 48.5px;
|
||||
padding-right: 48.5px;
|
||||
line-height: 1.71;
|
||||
font-size: 0.875rem;
|
||||
.modal-body {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.questInfo {
|
||||
@@ -207,14 +152,16 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
button.btn.btn-primary {
|
||||
margin-top: 14px;
|
||||
padding: 4px 16px;
|
||||
span.svg-icon.inline.icon-32 {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 2px solid black;
|
||||
}
|
||||
button.btn.btn-primary {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.balance {
|
||||
@@ -226,6 +173,19 @@
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
height: 48px;
|
||||
background-color: $gray-700;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
|
||||
&> * {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notEnough {
|
||||
pointer-events: none;
|
||||
opacity: 0.55;
|
||||
@@ -238,108 +198,30 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.item-cost {
|
||||
padding-bottom: 16px;
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: 74px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
margin-right: 24px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cost {
|
||||
height: 40px;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
padding: 8px 20px 8px 20px;
|
||||
|
||||
&.gems {
|
||||
color: $green-10;
|
||||
background-color: rgba(36, 204, 143, 0.15);
|
||||
line-height: 1.4;
|
||||
margin: 0 0 0 -4px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
background-color: rgba(255, 190, 93, 0.15);
|
||||
line-height: 1.4;
|
||||
margin: 0 0 0 -4px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
background-color: rgba(41, 149, 205, 0.15);
|
||||
line-height: 1.4;
|
||||
margin: 0 0 0 -4px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.total-row {
|
||||
font-weight: bold;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.total {
|
||||
font-weight: bold;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 16px;
|
||||
|
||||
&.gems {
|
||||
color: $green-10;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
input::-webkit-contacts-auto-fill-button {
|
||||
visibility: hidden;
|
||||
display: none !important;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.total-text {
|
||||
color: $gray-50;
|
||||
font-weight: bold;
|
||||
font-size: 0.875rem;
|
||||
height: 24px;
|
||||
line-height: 1.71;
|
||||
padding-right: 4px;
|
||||
|
||||
&.gems {
|
||||
color: $green-10;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
}
|
||||
}
|
||||
|
||||
span.svg-icon.inline.icon-20 {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
span.svg-icon.inline.icon-24 {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
span.svg-icon.inline.icon-32 {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.modal-dialog {
|
||||
max-width: 80%;
|
||||
@@ -352,10 +234,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- <style lang="scss" scoped>
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.value {
|
||||
@@ -379,7 +260,7 @@
|
||||
color: $hourglass-color;
|
||||
}
|
||||
}
|
||||
</style> -->
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
@@ -391,8 +272,6 @@ import svgExperience from '@/assets/svg/experience.svg';
|
||||
import svgGem from '@/assets/svg/gem.svg';
|
||||
import svgGold from '@/assets/svg/gold.svg';
|
||||
import svgHourglasses from '@/assets/svg/hourglass.svg';
|
||||
import svgPositive from '@/assets/svg/positive.svg';
|
||||
import svgNegative from '@/assets/svg/negative.svg';
|
||||
|
||||
import BalanceInfo from '../balanceInfo.vue';
|
||||
import currencyMixin from '../_currencyMixin';
|
||||
@@ -401,7 +280,6 @@ import buyMixin from '@/mixins/buy';
|
||||
import numberInvalid from '@/mixins/numberInvalid';
|
||||
import PinBadge from '@/components/ui/pinBadge';
|
||||
import CountdownBanner from '../countdownBanner';
|
||||
import numberIncrement from '@/components/shared/numberIncrement';
|
||||
|
||||
import questDialogContent from './questDialogContent';
|
||||
import QuestRewards from './questRewards';
|
||||
@@ -415,7 +293,6 @@ export default {
|
||||
PinBadge,
|
||||
questDialogContent,
|
||||
CountdownBanner,
|
||||
numberIncrement,
|
||||
},
|
||||
mixins: [buyMixin, currencyMixin, notifications, numberInvalid],
|
||||
props: {
|
||||
@@ -424,7 +301,6 @@ export default {
|
||||
},
|
||||
priceType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
withPin: {
|
||||
type: Boolean,
|
||||
@@ -436,11 +312,9 @@ export default {
|
||||
clock: svgClock,
|
||||
close: svgClose,
|
||||
experience: svgExperience,
|
||||
gems: svgGem,
|
||||
gem: svgGem,
|
||||
gold: svgGold,
|
||||
hourglasses: svgHourglasses,
|
||||
positive: svgPositive,
|
||||
negative: svgNegative,
|
||||
hourglass: svgHourglasses,
|
||||
}),
|
||||
|
||||
isPinned: false,
|
||||
@@ -465,8 +339,8 @@ export default {
|
||||
},
|
||||
currencyIcon () {
|
||||
if (this.priceType === 'gold') return this.icons.gold;
|
||||
if (this.priceType === 'hourglasses') return this.icons.hourglasses;
|
||||
return this.icons.gems;
|
||||
if (this.priceType === 'hourglasses') return this.icons.hourglass;
|
||||
return this.icons.gem;
|
||||
},
|
||||
endDate () {
|
||||
return moment(this.item.event.end);
|
||||
|
||||
@@ -33,17 +33,17 @@
|
||||
|
||||
h3 {
|
||||
color: $gray-10;
|
||||
margin-bottom: 4pxrem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.quest-image {
|
||||
margin: 0 auto;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 1em;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 16px 16px;
|
||||
margin-bottom: 1rem;
|
||||
overflow-y: auto;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -54,10 +54,10 @@
|
||||
line-height: 1.71;
|
||||
color: $gray-50;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
::v-deep .user-label {
|
||||
font-size: 0.875rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -177,6 +177,9 @@ export default {
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.quest-rewards {
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
|
||||
background-color: $gray-700;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,6 @@ export default {
|
||||
preventMultipleWatchExecution: false,
|
||||
eventPromoBannerHeight: null,
|
||||
sleepingBannerHeight: null,
|
||||
warningBannerHeight: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -136,10 +135,6 @@ export default {
|
||||
notificationBannerHeight () {
|
||||
let scrollPosToCheck = 56;
|
||||
|
||||
if (this.warningBannerHeight) {
|
||||
scrollPosToCheck += this.warningBannerHeight;
|
||||
}
|
||||
|
||||
if (this.sleepingBannerHeight) {
|
||||
scrollPosToCheck += this.sleepingBannerHeight;
|
||||
}
|
||||
@@ -366,7 +361,6 @@ export default {
|
||||
|
||||
updateBannerHeightAndScrollY () {
|
||||
this.updateEventBannerHeight();
|
||||
this.warningBannerHeight = getBannerHeight('chat-warning');
|
||||
this.sleepingBannerHeight = getBannerHeight('damage-paused');
|
||||
this.updateScrollY();
|
||||
},
|
||||
|
||||
@@ -1,615 +0,0 @@
|
||||
<template>
|
||||
<div class="top-container mx-auto">
|
||||
<div class="main-text mr-4">
|
||||
<!-- title goes here -->
|
||||
<div class="title-details">
|
||||
<h1 v-once>
|
||||
{{ $t('sunsetFaqTitle') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p v-html="$t('sunsetFaqPara1')"></p> <!-- there's html in here -->
|
||||
<p>{{ $t('sunsetFaqPara2') }}</p>
|
||||
<p>{{ $t('sunsetFaqPara3') }}</p>
|
||||
<p>{{ $t('sunsetFaqPara4') }}</p>
|
||||
<p>{{ $t('sunsetFaqPara5') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Which services are ending -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader1') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p v-html="$t('sunsetFaqPara6')"></p>
|
||||
</div>
|
||||
|
||||
<!-- Why are tavern and guild ending? -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader2') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<ul>
|
||||
<li>{{ $t('sunsetFaqList1') }}</li>
|
||||
<li>{{ $t('sunsetFaqList2') }}</li>
|
||||
<li>{{ $t('sunsetFaqList3') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Can I still talk to my party/group members? -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader3') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p>{{ $t('sunsetFaqPara7') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Pausing dailies -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader4') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p>{{ $t('sunsetFaqPara8') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Accessing group plans -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader5') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p v-html="$t('sunsetFaqPara9')"></p> <!-- there's html in here -->
|
||||
</div>
|
||||
|
||||
<!-- Can I access guild chats? Or banked Gems? -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader12') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p v-html="$t('sunsetFaqPara21')"></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader6') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p>{{ $t('sunsetFaqPara10') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- How can players find groups? -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader7') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p>{{ $t('sunsetFaqPara11') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- What about contributors? -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader8') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p v-html="$t('sunsetFaqPara12')"></p> <!-- there's html in here -->
|
||||
<p v-html="$t('sunsetFaqPara13')"></p> <!-- there's html in here -->
|
||||
<p v-html="$t('sunsetFaqPara14')"></p> <!-- there's html in here -->
|
||||
<p v-html="$t('sunsetFaqPara15')"></p> <!-- there's html in here -->
|
||||
<p v-html="$t('sunsetFaqPara16')"></p> <!-- there's html in here -->
|
||||
<p v-html="$t('sunsetFaqPara17')"></p> <!-- there's html in here -->
|
||||
<p v-html="$t('sunsetFaqPara18')"></p> <!-- there's html in here -->
|
||||
<p v-html="$t('sunsetFaqPara19')"></p> <!-- there's html in here -->
|
||||
</div>
|
||||
|
||||
<!-- Challenges -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader9') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<ul>
|
||||
<li>{{ $t('sunsetFaqList4') }}</li>
|
||||
<li>{{ $t('sunsetFaqList5') }}</li>
|
||||
<li>{{ $t('sunsetFaqList6') }}</li>
|
||||
<li>{{ $t('sunsetFaqList7') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Questions about how to use Habitica -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader10') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<ul>
|
||||
<li v-html="$t('sunsetFaqList8')"></li> <!-- there's html in here -->
|
||||
<li v-html="$t('sunsetFaqList9')"></li> <!-- there's html in here -->
|
||||
<li v-html="$t('sunsetFaqList10')"></li> <!-- there's html in here -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Community Guidelines and TOS -->
|
||||
<div>
|
||||
<h3 class="headings">
|
||||
{{ $t('sunsetFaqHeader11') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="body-text">
|
||||
<p v-html="$t('sunsetFaqPara20')"></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- sidebar -->
|
||||
<div class="sidebar py-4 d-flex flex-column">
|
||||
<!-- staff -->
|
||||
<div class="ml-4">
|
||||
<h2>
|
||||
{{ $t('staff') }}
|
||||
</h2>
|
||||
<div class="d-flex flex-wrap">
|
||||
<div
|
||||
v-for="user in staff"
|
||||
:key="user.uuid"
|
||||
class="staff col-6 p-0"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<router-link
|
||||
class="title"
|
||||
:to="{'name': 'userProfile', 'params': {'userId': user.uuid}}"
|
||||
>
|
||||
{{ user.name }}
|
||||
</router-link>
|
||||
<div
|
||||
v-if="user.type === 'Staff'"
|
||||
class="svg-icon staff-icon ml-1"
|
||||
v-html="icons.tierStaff"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- player tiers -->
|
||||
<div class="ml-4">
|
||||
<h2 class="mt-4 mb-1">
|
||||
{{ $t('playerTiers') }}
|
||||
</h2>
|
||||
<ul class="tier-list">
|
||||
<li
|
||||
v-once
|
||||
class="tier1 d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tier1') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tier1"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="tier2 d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tier2') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tier2"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="tier3 d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tier3') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tier3"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="tier4 d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tier4') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tier4"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="tier5 d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tier5') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tier5"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="tier6 d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tier6') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tier6"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="tier7 d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tier7') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tier7"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="moderator d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tierModerator') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tierMod"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="staff d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tierStaff') }}
|
||||
<div
|
||||
class="svg-icon ml-1"
|
||||
v-html="icons.tierStaff"
|
||||
></div>
|
||||
</li>
|
||||
<li
|
||||
v-once
|
||||
class="npc d-flex justify-content-center"
|
||||
>
|
||||
{{ $t('tierNPC') }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Daniel in sweet, sweet retirement with Jorts -->
|
||||
<div>
|
||||
<div class="gradient">
|
||||
</div>
|
||||
<div
|
||||
class="grassy-meadow-backdrop"
|
||||
:style="{'background-image': imageURLs.background}"
|
||||
>
|
||||
<div
|
||||
class="daniel_front"
|
||||
:style="{'background-image': imageURLs.npc}"
|
||||
></div>
|
||||
<div
|
||||
class="pixel-border"
|
||||
:style="{'background-image': imageURLs.pixel_border}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- email admin -->
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<div class="question mx-auto">
|
||||
{{ $t('anotherQuestion') }}
|
||||
</div>
|
||||
<div
|
||||
class="contact mx-auto"
|
||||
>
|
||||
<p v-html="$t('contactAdmin')"></p> <!-- there's html in here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- final div! -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
h1 {
|
||||
margin-top: 0px;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
li {
|
||||
padding-bottom: 16px;
|
||||
|
||||
&::marker {
|
||||
size: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 21px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.top-container {
|
||||
width: 66.67%;
|
||||
margin-top: 80px;
|
||||
display: flex;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.main-text {
|
||||
.body-text {
|
||||
font-size: 1em;
|
||||
color: $gray-10;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
.headings {
|
||||
font-size: 1.15em;
|
||||
font-weight: 400;
|
||||
line-height: 1.75;
|
||||
color: $purple-200;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
height: fit-content;
|
||||
width: 330px;
|
||||
background-color: $gray-700;
|
||||
border-radius: 16px;
|
||||
|
||||
h2 {
|
||||
color: $gray-10;
|
||||
font-family: Roboto;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
.staff {
|
||||
.staff-icon {
|
||||
width: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 24px;
|
||||
color: $purple-300;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.tier-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 282px;
|
||||
font-size: 1em !important;
|
||||
|
||||
li {
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
border: solid 1px $gray-500;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 4px;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
.tier1 {
|
||||
color: #c42870;
|
||||
.svg-icon {
|
||||
width: 11px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.tier2 {
|
||||
color: #b01515;
|
||||
.svg-icon {
|
||||
width: 11px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.tier3 {
|
||||
color: #d70e14;
|
||||
.svg-icon {
|
||||
width: 13px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.tier4 {
|
||||
color: #c24d00;
|
||||
.svg-icon {
|
||||
width: 13px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.tier5 {
|
||||
color: #9e650f;
|
||||
.svg-icon {
|
||||
width: 8px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.tier6 {
|
||||
color: #2b8363;
|
||||
.svg-icon {
|
||||
width: 8px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.tier7 {
|
||||
color: #167e87;
|
||||
.svg-icon {
|
||||
width: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.moderator {
|
||||
color: #277eab;
|
||||
.svg-icon {
|
||||
width: 13px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.staff {
|
||||
color: #6133b4;
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.npc {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.gradient {
|
||||
position: absolute;
|
||||
width: 330px;
|
||||
height: 100px;
|
||||
margin: -1px 0 116px;
|
||||
background-image: linear-gradient(to bottom, $gray-700 0%, rgba(249, 249, 249, 0) 100%);
|
||||
}
|
||||
|
||||
.grassy-meadow-backdrop {
|
||||
background-repeat: repeat-x;
|
||||
width: 330px;
|
||||
height: 246px;
|
||||
}
|
||||
|
||||
.daniel_front {
|
||||
height: 246px;
|
||||
width: 330px;
|
||||
background-repeat: no-repeat;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pixel-border {
|
||||
width: 330px;
|
||||
height: 30px;
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
margin-top: -30px;
|
||||
}
|
||||
|
||||
.question {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
color: $gray-10;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.contact p {
|
||||
font-size: 1em;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import tier1 from '@/assets/svg/tier-1.svg';
|
||||
import tier2 from '@/assets/svg/tier-2.svg';
|
||||
import tier3 from '@/assets/svg/tier-3.svg';
|
||||
import tier4 from '@/assets/svg/tier-4.svg';
|
||||
import tier5 from '@/assets/svg/tier-5.svg';
|
||||
import tier6 from '@/assets/svg/tier-6.svg';
|
||||
import tier7 from '@/assets/svg/tier-7.svg';
|
||||
import tierMod from '@/assets/svg/tier-mod.svg';
|
||||
import tierNPC from '@/assets/svg/tier-npc.svg';
|
||||
import tierStaff from '@/assets/svg/tier-staff.svg';
|
||||
import staffList from '../../libs/staffList';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
tier1,
|
||||
tier2,
|
||||
tier3,
|
||||
tier4,
|
||||
tier5,
|
||||
tier6,
|
||||
tier7,
|
||||
tierMod,
|
||||
tierNPC,
|
||||
tierStaff,
|
||||
}),
|
||||
group: {
|
||||
chat: [],
|
||||
},
|
||||
sections: {
|
||||
worldBoss: true,
|
||||
},
|
||||
staff: staffList,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
imageURLs () {
|
||||
const currentEvent = find(this.currentEventList, event => Boolean(event.season));
|
||||
if (!currentEvent) {
|
||||
return {
|
||||
background: 'url(/static/npc/normal/tavern_background.png)',
|
||||
npc: 'url(/static/npc/normal/tavern_npc.png)',
|
||||
pixel_border: 'url(/static/npc/normal/pixel_border.png)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: `url(/static/npc/${currentEvent.season}/tavern_background.png)`,
|
||||
npc: `url(/static/npc/${currentEvent.season}/tavern_npc.png)`,
|
||||
pixel_border: 'url(/static/npc/normal/pixel_border.png)',
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
subSection: this.$t('faq/taverns-and-guilds'),
|
||||
});
|
||||
document.body.style.background = '#ffffff';
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -7,19 +7,13 @@
|
||||
<br>
|
||||
<p class="text-center">
|
||||
<button
|
||||
id="buttonClearBrowserData"
|
||||
class="btn btn-lg btn-danger"
|
||||
popover-trigger="mouseover"
|
||||
:popover="$t('localStorageClearExplanation')"
|
||||
@click="clearLocalStorage()"
|
||||
>
|
||||
{{ $t('localStorageClear') }}
|
||||
</button>
|
||||
<b-popover
|
||||
target="buttonClearBrowserData"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('localStorageClearExplanation')"
|
||||
/>
|
||||
</p>
|
||||
<br>
|
||||
<p v-html="$t('localStorageTryNext', localStorageTryNext) "></p>
|
||||
|
||||
@@ -1,61 +1,99 @@
|
||||
<template>
|
||||
<div class="container-fluid w-75 mx-auto">
|
||||
<div class="container-fluid">
|
||||
<h1>{{ $t('communityGuidelines') }}</h1>
|
||||
<hr>
|
||||
<p>{{ $t('lastUpdated') }} June 8, 2023</p>
|
||||
<p>{{ $t('lastUpdated') }} February 8, 2023</p>
|
||||
<h2 id="welcome">
|
||||
{{ $t('commGuideHeadingWelcome') }}
|
||||
</h2>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/intro.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara001')"></p>
|
||||
<p v-html="$t('commGuidePara002')"></p>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/intro.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara001')"></p>
|
||||
<p v-html="$t('commGuidePara002')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara003')"></p>
|
||||
<h2 id="interactions">
|
||||
{{ $t('commGuideHeadingInteractions') }}
|
||||
</h2>
|
||||
<p v-html="$t('commGuidePara015')"></p>
|
||||
<p v-html="$t('commGuidePara016')"></p>
|
||||
<p v-html="$t('commGuidePara017')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList01F')"></li>
|
||||
<li v-html="$t('commGuideList01A')"></li>
|
||||
<li v-html="$t('commGuideList01B')"></li>
|
||||
<li v-html="$t('commGuideList01C')"></li>
|
||||
<li v-html="$t('commGuideList01D')"></li>
|
||||
<li v-html="$t('commGuideList01E')"></li>
|
||||
<li v-html="$t('commGuideList01F')"></li>
|
||||
</ul>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/publicSpaces.png">
|
||||
</div>
|
||||
<ul>
|
||||
<li><strong>{{ $t('commGuideList01A') }}</strong></li>
|
||||
<li v-html="$t('commGuideList02C')"></li>
|
||||
<li v-html="$t('commGuideList02N')"></li>
|
||||
<li v-html="$t('commGuideList02H')"></li>
|
||||
<li v-html="$t('commGuideList02A')"></li>
|
||||
<li v-html="$t('commGuideList02I')"></li>
|
||||
<li v-html="$t('commGuideList02B')"></li>
|
||||
<li v-html="$t('commGuideList02G')"></li>
|
||||
<li v-html="$t('commGuideList02C')"></li>
|
||||
<li v-html="$t('commGuideList02D')"></li>
|
||||
<li v-html="$t('commGuideList02E')"></li>
|
||||
<li v-html="$t('commGuideList02O')"></li>
|
||||
<li v-html="$t('commGuidePara037')"></li>
|
||||
<li v-html="$t('commGuideList02P')"></li>
|
||||
<li v-html="$t('commGuideList02Q')"></li>
|
||||
<li v-html="$t('commGuideList02F')"></li>
|
||||
<li v-html="$t('commGuideList02M')"></li>
|
||||
<li v-html="$t('commGuideList02L')"></li>
|
||||
<li v-html="$t('commGuideList02J')"></li>
|
||||
<li v-html="$t('commGuideList02K')"></li>
|
||||
<li v-html="$t('commGuideList02L')"></li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara019')"></p>
|
||||
<p v-html="$t('commGuidePara020')"></p>
|
||||
<p v-html="$t('commGuidePara020A')"></p>
|
||||
<p v-html="$t('commGuidePara021')"></p>
|
||||
<h3 id="tavern">
|
||||
{{ $t('commGuideHeadingTavern') }}
|
||||
</h3>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/tavern.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara022')"></p>
|
||||
<p v-html="$t('commGuidePara023')"></p>
|
||||
<p v-html="$t('commGuidePara024')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="guilds">
|
||||
{{ $t('commGuideHeadingPublicGuilds') }}
|
||||
</h3>
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara029')"></p>
|
||||
<p v-html="$t('commGuidePara031')"></p>
|
||||
</div>
|
||||
<img src="~@/assets/images/community-guidelines/publicGuilds.png">
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara033')"></p>
|
||||
<p v-html="$t('commGuidePara035')"></p>
|
||||
<p v-html="$t('commGuidePara036')"></p>
|
||||
<p v-html="$t('commGuidePara037')"></p>
|
||||
<p v-html="$t('commGuidePara038')"></p>
|
||||
<h2 id="infractions-consequences-restoration">
|
||||
{{ $t('commGuideHeadingInfractionsEtc') }}
|
||||
</h2>
|
||||
<h3 id="infractions">
|
||||
{{ $t('commGuideHeadingInfractions') }}
|
||||
</h3>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/infractions.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara050')"></p>
|
||||
<p v-html="$t('commGuidePara051')"></p>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/infractions.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara050')"></p>
|
||||
<p v-html="$t('commGuidePara051')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<h4>{{ $t('commGuideHeadingSevereInfractions') }}</h4>
|
||||
<p v-html="$t('commGuidePara052')"></p>
|
||||
<p v-html="$t('commGuidePara053')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList05A')"></li>
|
||||
<li v-html="$t('commGuideList05B')"></li>
|
||||
<li v-html="$t('commGuideList05C')"></li>
|
||||
<li v-html="$t('commGuideList05D')"></li>
|
||||
@@ -63,34 +101,59 @@
|
||||
<li v-html="$t('commGuideList05F')"></li>
|
||||
<li v-html="$t('commGuideList05G')"></li>
|
||||
<li v-html="$t('commGuideList05H')"></li>
|
||||
<li v-html="$t('commGuideList05A')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingModerateInfractions') }}</h4>
|
||||
<p v-html="$t('commGuidePara054')"></p>
|
||||
<p v-html="$t('commGuidePara055')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList06A')"></li>
|
||||
<li v-html="$t('commGuideList06B')"></li>
|
||||
<li v-html="$t('commGuideList06C')"></li>
|
||||
<li v-html="$t('commGuideList06D')"></li>
|
||||
<li v-html="$t('commGuideList06E')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingMinorInfractions') }}</h4>
|
||||
<p v-html="$t('commGuidePara056')"></p>
|
||||
<p v-html="$t('commGuidePara057')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList07A')"></li>
|
||||
<li v-html="$t('commGuideList07B')"></li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara057A')"></p>
|
||||
<h3 id="consequences">
|
||||
{{ $t('commGuideHeadingConsequences') }}
|
||||
</h3>
|
||||
<p v-html="$t('commGuidePara059')"></p>
|
||||
<img src="~@/assets/images/community-guidelines/consequences.png">
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara058')"></p>
|
||||
<p v-html="$t('commGuidePara059')"></p>
|
||||
</div>
|
||||
<img src="~@/assets/images/community-guidelines/consequences.png">
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara060')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList08A')"></li>
|
||||
<li v-html="$t('commGuideList08B')"></li>
|
||||
<li v-html="$t('commGuideList08C')"></li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara060A')"></p>
|
||||
<p v-html="$t('commGuidePara060B')"></p>
|
||||
<h4>{{ $t('commGuideHeadingSevereConsequences') }}</h4>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList09A')"></li>
|
||||
<li v-html="$t('commGuideList09C')"></li>
|
||||
<li v-html="$t('commGuideList09D')"></li>
|
||||
<li v-html="$t('commGuideList09E')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingModerateConsequences') }}</h4>
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('commGuideList10A') }}
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList10A1')"></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-html="$t('commGuideList10D')"></li>
|
||||
<li v-html="$t('commGuideList10G')"></li>
|
||||
<li v-html="$t('commGuideList10F')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingMinorConsequences') }}</h4>
|
||||
<ul>
|
||||
@@ -103,53 +166,56 @@
|
||||
<h3 id="restoration">
|
||||
{{ $t('commGuideHeadingRestoration') }}
|
||||
</h3>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/restoration.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara061')"></p>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/restoration.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara061')"></p>
|
||||
<p v-html="$t('commGuidePara062')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara063')"></p>
|
||||
<h2 id="meet-the-mods">
|
||||
{{ $t('commGuideHeadingMeet') }}
|
||||
</h2>
|
||||
<p v-html="$t('commGuidePara007')"></p>
|
||||
<p v-html="$t('commGuidePara009')"></p>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/staff.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'heyeilatan', realName: 'Natalie'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'CuriousMagpie'}) }})
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'redphoenix', realName: 'Vicky'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'veeeeeee'}) }})
|
||||
- Co-Founder
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Beffymaroo', realName: 'Beth'}) }}
|
||||
- Art, Community Management, Many Hats
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Sabe'}) }}
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Apollo', realName: 'Tressley'}) }}
|
||||
- Designer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Piyo', realName: 'Sara'}) }}
|
||||
- Mobile Designer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
|
||||
- Mobile Developer
|
||||
</li>
|
||||
</ul>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/staff.png">
|
||||
<div class="media-body">
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'heyeilatan', realName: 'Natalie'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'CuriousMagpie'}) }})
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
|
||||
- Mobile Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'redphoenix', realName: 'Vicky'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'veeeeeee'}) }})
|
||||
- Co-Founder
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Beffymaroo', realName: 'Beth'}) }}
|
||||
- Art, Community Management, Many Hats
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Sabe'}) }}
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Apollo', realName: 'Tressley'}) }}
|
||||
- Designer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Piyo', realName: 'Sara'}) }}
|
||||
- Mobile Designer
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara013')"></p>
|
||||
<p>
|
||||
{{ $t('commGuidePara014') }}<br>
|
||||
@@ -157,6 +223,7 @@
|
||||
Lemoness, lefnire, Slappybag, litenull, Shaner, Bobbyroberts99, wc8,
|
||||
Breadstrings, Megan, Blade, Daniel the Bard, deilann, shanaqui, Nakonana,
|
||||
Dewines, Alys, Fox_town, MaybeSteveRogers, and Cantras.
|
||||
|
||||
</em>
|
||||
</p>
|
||||
<h2 id="final">
|
||||
@@ -168,12 +235,16 @@
|
||||
{{ $t('commGuideHeadingLinks') }}
|
||||
</h2>
|
||||
<ul>
|
||||
<li><a href="/static/faq">{{ $t('faq') }}</a></li>
|
||||
<li v-html="$t('commGuideLink01')"></li>
|
||||
<li v-html="$t('commGuideLink02')"></li>
|
||||
<li v-html="$t('commGuideLink03')"></li>
|
||||
<li v-html="$t('commGuideLink04')"></li>
|
||||
<li v-html="$t('commGuideLink06')"></li>
|
||||
<li v-html="$t('commGuideLink07')"></li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara069')"></p>
|
||||
<ul>
|
||||
<ul class="list-2col list-unstyled">
|
||||
<li>Beffymaroo</li>
|
||||
<li>Breadstrings</li>
|
||||
<li>Draayder</li>
|
||||
<li>Kiwibot</li>
|
||||
@@ -187,3 +258,12 @@
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.list-2col {
|
||||
width: 50%;
|
||||
columns: 2;
|
||||
-moz-columns: 2;
|
||||
-webkit-columns: 2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
:
|
||||
<a
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
> {{ $t('askQuestion') }}</a>
|
||||
href="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a"
|
||||
>Habitica Help guild</a>
|
||||
<br>
|
||||
{{ $t('businessInquiries') }}
|
||||
:
|
||||
|
||||
@@ -94,11 +94,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
position: relative;
|
||||
top: 2rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.container-fluid {
|
||||
margin: auto;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<img src="~@/assets/images/marketing/guild.png">
|
||||
<h2>{{ $t('marketing2Lead1Title') }}</h2>
|
||||
<p>{{ $t('marketing2Lead1') }}</p>
|
||||
<img src="~@/assets/images/marketing/vice3.png">
|
||||
|
||||
@@ -12,15 +12,12 @@
|
||||
<p v-markdown="$t(`webStep${step}Text`, stepVars[step])"></p>
|
||||
<hr>
|
||||
</div>
|
||||
<p>
|
||||
<span v-html="$t('overviewQuestionsRevised')"></span>
|
||||
<a
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
>
|
||||
{{ $t('askQuestion') }}
|
||||
</a>
|
||||
</p>
|
||||
<p
|
||||
v-markdown="$t('overviewQuestions', {
|
||||
faqUrl: '/static/faq/',
|
||||
helpGuildUrl: '/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a'
|
||||
})"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,13 +35,11 @@
|
||||
|
||||
<script>
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import reportBug from '@/mixins/reportBug.js';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
mixins: [reportBug],
|
||||
data () {
|
||||
return {
|
||||
stepsNum: ['1', '2', '3'],
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<chat-banner />
|
||||
<static-header
|
||||
v-if="showContentWrap"
|
||||
:class="{
|
||||
'home-header': ['home', 'front'].indexOf($route.name) !== -1,
|
||||
'white-header': this.$route.name === 'plans'
|
||||
}"
|
||||
v-if="showContentWrap"
|
||||
:class="{
|
||||
'home-header': ['home', 'front'].indexOf($route.name) !== -1,
|
||||
'white-header': this.$route.name === 'plans'
|
||||
}"
|
||||
/>
|
||||
<div class="static-wrapper">
|
||||
<router-view />
|
||||
@@ -244,13 +243,11 @@
|
||||
|
||||
<script>
|
||||
import AppFooter from '@/components/appFooter';
|
||||
import ChatBanner from '@/components/header/banners/chatBanner';
|
||||
import StaticHeader from './header.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AppFooter,
|
||||
ChatBanner,
|
||||
StaticHeader,
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -521,12 +521,7 @@ export default {
|
||||
// Get Category Filter Labels
|
||||
this.typeFilters = getFilterLabels(this.type, this.challenge);
|
||||
// Set default filter for task column
|
||||
|
||||
if (this.challenge) {
|
||||
this.activateFilter(this.type);
|
||||
} else {
|
||||
this.activateFilter(this.type, this.user.preferences.tasks.activeFilter[this.type], true);
|
||||
}
|
||||
this.activateFilter(this.type);
|
||||
},
|
||||
mounted () {
|
||||
this.setColumnBackgroundVisibility();
|
||||
@@ -666,7 +661,7 @@ export default {
|
||||
taskSummary (task) {
|
||||
this.$emit('taskSummary', task);
|
||||
},
|
||||
activateFilter (type, filter = '', skipSave = false) {
|
||||
activateFilter (type, filter = '') {
|
||||
// Needs a separate API call as this data may not reside in store
|
||||
if (type === 'todo' && filter === 'complete2') {
|
||||
if (this.group && this.group._id) {
|
||||
@@ -682,16 +677,14 @@ export default {
|
||||
// as default filter for daily
|
||||
// and set the filter as 'due' only when the component first
|
||||
// loads and not on subsequent reloads.
|
||||
if (type === 'daily' && filter === '' && !this.challenge) {
|
||||
if (
|
||||
type === 'daily' && filter === '' && !this.challenge
|
||||
&& this.user.preferences.dailyDueDefaultView
|
||||
) {
|
||||
filter = 'due'; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
this.activeFilter = getActiveFilter(type, filter, this.challenge);
|
||||
|
||||
if (!skipSave && !this.challenge) {
|
||||
const propertyToUpdate = `preferences.tasks.activeFilter.${type}`;
|
||||
this.$store.dispatch('user:set', { [propertyToUpdate]: filter });
|
||||
}
|
||||
},
|
||||
setColumnBackgroundVisibility () {
|
||||
this.$nextTick(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user