mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-19 19:38:45 -05:00
Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ebebbbaac5 | |||
| 63376b918e | |||
| ba96cd6e24 | |||
| 9e0e2a83be | |||
| 1fa926ac04 | |||
| c71f0b3fda | |||
| e8f5958f77 | |||
| 2803db73e5 | |||
| 0f9adf6675 | |||
| 63414a80fe | |||
| e73e8bfb9e | |||
| 694fe5a273 | |||
| 82ebe71eb4 | |||
| 58d58ff962 | |||
| 04dcb27501 | |||
| c9395ba1ca | |||
| e9845e7b01 | |||
| faa7ff6328 | |||
| 50cc7ee09a | |||
| db56134832 | |||
| 1ea954ab10 | |||
| e405372319 | |||
| 8f64afe9df | |||
| bf0e640fa6 | |||
| a6792a4f08 | |||
| 0007736f5c | |||
| d564944507 | |||
| b679cfb935 | |||
| 464e4f10b2 | |||
| 647b27c55f | |||
| ac4e6490d9 | |||
| a3784e98a3 | |||
| 5931f02692 | |||
| e21aa074e4 | |||
| fd038bd150 | |||
| 699be3a3cc | |||
| bb7e0e22ec | |||
| a79a088a8f | |||
| 60c0e6b3df | |||
| 43c10f75c3 | |||
| d4e3e83d46 | |||
| 013f8bcca7 | |||
| ebd0cb72de | |||
| c44b1670cf | |||
| 39477c6f11 | |||
| 1371b80635 | |||
| 9fa355fbcc | |||
| b594d2bb29 | |||
| a8b52ab656 | |||
| cce6d91611 | |||
| 3109a03055 | |||
| 14518b8213 | |||
| 3ad31c7cd0 | |||
| bfa6d24e47 | |||
| 8c88f56d08 | |||
| 1df3f9d9f3 | |||
| b5a0dad7f7 | |||
| 9b34c3e11a | |||
| 9d61bd724a | |||
| 13c21139dd | |||
| 8e85de53cb | |||
| bf222351e5 | |||
| 6867aab74b | |||
| 0cae808b7e | |||
| 81be8316a0 | |||
| d7071d6b4d | |||
| 150cd16b1c | |||
| 38ac4c53d1 | |||
| 9b8bb99039 | |||
| f8bcc81fe6 | |||
| cc18acd69a | |||
| c5a2e5a2e0 | |||
| 1532f8f774 | |||
| aa4426c800 | |||
| 0140b9beb7 | |||
| 8f92993045 | |||
| 7697d87358 | |||
| 712205d253 | |||
| ede036e94b | |||
| 67c16c137b | |||
| c2ced5c925 | |||
| b09ae3f053 | |||
| 4c60371ebd | |||
| 16be591ed8 | |||
| f75a4f6982 | |||
| 330c3e1bf6 | |||
| 0ba3cd3bdf | |||
| 2cfe11619a | |||
| 7607c67070 | |||
| 652dfa6ecc | |||
| d394858022 | |||
| 26f5ef093f | |||
| 714319d67b | |||
| cbaa3180cc | |||
| b4866fd3b1 | |||
| 86646bbbdb | |||
| 282abecd21 | |||
| ba61c91296 | |||
| d49736dd69 | |||
| 16b766beef | |||
| a26c5906d6 | |||
| 08a5bff815 | |||
| 578083dde6 | |||
| 9706d7ac64 | |||
| 93564c5d52 | |||
| 2e94bfc489 | |||
| 1deb903186 | |||
| 8c00b91cc6 | |||
| 5c8a3f7771 | |||
| 8ac03e311b | |||
| 7a430889a8 | |||
| f84b5f163c | |||
| ad118095ef | |||
| 7ecad94a51 | |||
| 328b37322e | |||
| 81f7fbc2d5 | |||
| 9590ce939a | |||
| fff6fbfbd6 | |||
| 52abf8acf3 | |||
| bc61443246 | |||
| cd8594a8b9 | |||
| f3600f64e8 | |||
| 752cd57bb1 | |||
| 5d6bf131f4 | |||
| 8445f45b31 | |||
| 7dab47db16 | |||
| a88ca5a1a8 | |||
| c91d115793 | |||
| e3502bd280 | |||
| 8b5ff7c2f9 | |||
| 3ac260026b | |||
| 80e7fda8ef | |||
| 1e7ea399b1 | |||
| 9c889a42aa | |||
| 952b99599b | |||
| 973fa2edc2 | |||
| 5e04040f5f | |||
| 2fc9480ae9 | |||
| 429afc1e71 | |||
| 80da313844 | |||
| de057dc1b2 | |||
| ff860b04fc | |||
| 45dedbbdaa | |||
| b6359ad032 | |||
| 658a02bfc3 | |||
| e1398e8d7c | |||
| 0185a1fbd6 | |||
| 3b6c39dc9b | |||
| 7e2a35d7a9 | |||
| 84e5c00be1 | |||
| 187029f44f | |||
| efbc7d1460 | |||
| 36f84d083e | |||
| 2154ba5451 | |||
| cf0e45c68c | |||
| df5d1e95d1 | |||
| 93d9038765 | |||
| f4e8bf9c2e | |||
| 9c7f1ae630 | |||
| 302eabb30f | |||
| 09695f637e | |||
| 97c8138340 | |||
| a65b0d1f4d | |||
| 8d73e2949a | |||
| c0cf647873 | |||
| d20e976176 | |||
| 739016ba01 | |||
| 55d6ee3f7e | |||
| 9ef13dad68 | |||
| 14fa69719b | |||
| 9228b070fa | |||
| 929b0196a4 | |||
| 1b91f620e1 | |||
| 8d9602fb16 |
+1
-1
Submodule habitica-images updated: 0c48d4c818...109539e445
@@ -0,0 +1,155 @@
|
||||
/* 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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/* 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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
/* 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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
/* 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
|
||||
}
|
||||
};
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "4.277.0",
|
||||
"version": "5.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.277.0",
|
||||
"version": "5.2.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
|
||||
@@ -16,60 +16,7 @@ describe('GET /challenges/:challengeId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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', () => {
|
||||
context('Group Plan', () => {
|
||||
let groupLeader;
|
||||
let challengeLeader;
|
||||
let group;
|
||||
@@ -84,14 +31,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 = members[0]; // eslint-disable-line prefer-destructuring
|
||||
otherMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||
[challengeLeader, otherMember] = members;
|
||||
|
||||
challenge = await generateChallenge(challengeLeader, group);
|
||||
});
|
||||
|
||||
@@ -71,42 +71,18 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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: leader._id,
|
||||
id: leader._id,
|
||||
profile: { name: leader.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
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 group = await generateGroup(user, { type: 'party', privacy: 'private', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.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 },
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: anotherUser.auth.local.username,
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
|
||||
-14
@@ -72,20 +72,6 @@ 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,117 +7,7 @@ import {
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('GET challenges/groups/:groupId', () => {
|
||||
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', () => {
|
||||
context('Group Plan', () => {
|
||||
let privateGuild; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
|
||||
@@ -128,6 +18,7 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
@@ -186,68 +77,6 @@ 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;
|
||||
@@ -401,7 +230,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 });
|
||||
@@ -435,5 +264,58 @@ 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,39 +2,44 @@ 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 publicGuild; let userData; let groupData;
|
||||
let user; let member; let nonMember; let challenge; let challenge2; let publicChallenge;
|
||||
let groupPlan; let userData; let groupData; let tavern; let tavernData;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
groupPlan = group;
|
||||
groupData = {
|
||||
_id: publicGuild._id,
|
||||
_id: groupPlan._id,
|
||||
categories: [],
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
id: groupPlan._id,
|
||||
type: groupPlan.type,
|
||||
privacy: groupPlan.privacy,
|
||||
name: groupPlan.name,
|
||||
summary: groupPlan.name,
|
||||
leader: groupPlan.leader._id,
|
||||
};
|
||||
|
||||
user = groupLeader;
|
||||
userData = {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
_id: groupPlan.leader._id,
|
||||
id: groupPlan.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
@@ -46,17 +51,31 @@ 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 nonMember.post(`/challenges/${challenge._id}/join`);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
context('all challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
const challenges = await member.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
@@ -64,11 +83,13 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge.group).to.eql(groupData);
|
||||
});
|
||||
|
||||
it('should not return challenges a non-member has not joined', async () => {
|
||||
it('should return public challenges', async () => {
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
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', async () => {
|
||||
@@ -100,10 +121,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: challenge2._id });
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: publicChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
const newChallenge = await generateChallenge(user, groupPlan);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
@@ -113,52 +134,23 @@ 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: privateChallenge._id });
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._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: privateChallenge._id });
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('my challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await nonMember.get(`/challenges/user?page=0&member=${true}`);
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
@@ -177,6 +169,10 @@ 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 () => {
|
||||
@@ -190,6 +186,10 @@ 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,36 +199,40 @@ 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
|
||||
publicGuild;
|
||||
group;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
summary: 'summary for TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
publicGuild = group;
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
@@ -271,7 +275,7 @@ describe('GET challenges/user', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
const newChallenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
@@ -294,9 +298,10 @@ describe('GET challenges/user', () => {
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
|
||||
@@ -42,26 +42,7 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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 () => {
|
||||
it('returns 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({
|
||||
@@ -77,7 +58,7 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Creating a challenge for a valid group', () => {
|
||||
context('creating a Challenge for a Group Plan', () => {
|
||||
let groupLeader;
|
||||
let group;
|
||||
let groupMember;
|
||||
@@ -94,9 +75,11 @@ 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,6 +18,7 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -10,27 +9,30 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
admin;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
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 = await generateUser();
|
||||
admin = await generateUser({ 'permissions.moderator': true });
|
||||
userThatDidNotCreateChat = members[0]; // eslint-disable-line prefer-destructuring
|
||||
admin = members[1]; // eslint-disable-line prefer-destructuring
|
||||
await admin.update({ permissions: { moderator: true } });
|
||||
});
|
||||
|
||||
context('Chat errors', () => {
|
||||
it('returns an error is message does not exist', async () => {
|
||||
it('returns an error if 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,
|
||||
@@ -56,7 +58,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
nextMessage = nextMessage.message;
|
||||
});
|
||||
|
||||
it('allows creator to delete a their message', async () => {
|
||||
it('allows creator to delete 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,
|
||||
generateGroup,
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -11,48 +11,22 @@ 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 () => {
|
||||
const leader = await generateUser({ balance: 2 });
|
||||
|
||||
group = await generateGroup(leader, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
}, {
|
||||
({ group } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
chat: [
|
||||
'Hello',
|
||||
'Welcome to the Guild',
|
||||
],
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('returns error if user is not member of requested private group', async () => {
|
||||
|
||||
@@ -1,32 +1,42 @@
|
||||
import { find } from 'lodash';
|
||||
import find from 'lodash/find';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
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;
|
||||
group; let members; let userToDelete;
|
||||
const TEST_MESSAGE = 'Test Message';
|
||||
const USER_AGE_FOR_FLAGGING = 3;
|
||||
|
||||
beforeEach(async () => {
|
||||
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, 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,
|
||||
}));
|
||||
|
||||
group = await user.post('/groups', {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
[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(),
|
||||
});
|
||||
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -69,8 +79,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',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
title: 'Flag in Test Guild - (private guild)',
|
||||
title_link: undefined,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
||||
mrkdwn_in: [
|
||||
@@ -78,7 +88,7 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-ensable camelcase */
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
|
||||
it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => {
|
||||
@@ -104,8 +114,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',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
title: 'Flag in Test Guild - (private guild)',
|
||||
title_link: undefined,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
||||
mrkdwn_in: [
|
||||
@@ -113,15 +123,12 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-ensable camelcase */
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
|
||||
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||
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', {
|
||||
const { message } = await userToDelete.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
||||
await userToDelete.del('/user', {
|
||||
password: 'password',
|
||||
});
|
||||
|
||||
|
||||
@@ -6,27 +6,27 @@ import {
|
||||
|
||||
describe('POST /chat/:chatId/like', () => {
|
||||
let user;
|
||||
let groupWithChat;
|
||||
const testMessage = 'Test Message';
|
||||
let anotherUser;
|
||||
let groupWithChat;
|
||||
let members;
|
||||
const testMessage = 'Test Message';
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
({ group: groupWithChat, groupLeader: user, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
});
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
user = groupLeader;
|
||||
groupWithChat = group;
|
||||
anotherUser = members[0]; // eslint-disable-line prefer-destructuring
|
||||
[anotherUser] = members;
|
||||
await anotherUser.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,41 +1,33 @@
|
||||
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 { CHAT_FLAG_FROM_SHADOW_MUTE, MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import { 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: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
user = groupLeader;
|
||||
await user.update({
|
||||
@@ -43,8 +35,7 @@ describe('POST /chat', () => {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
}); // prevent tests accidentally throwing messageGroupChatSpam
|
||||
groupWithChat = group;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
additionalMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||
[member, additionalMember] = members;
|
||||
await member.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
await additionalMember.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
@@ -89,32 +80,12 @@ 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 () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Private Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
const privateGuildMemberWithChatsRevoked = members[0];
|
||||
await privateGuildMemberWithChatsRevoked.update({
|
||||
await member.update({
|
||||
'flags.chatRevoked': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await privateGuildMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
@@ -152,54 +123,12 @@ 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 () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Private Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
const userWithChatShadowMuted = members[0];
|
||||
await userWithChatShadowMuted.update({
|
||||
await member.update({
|
||||
'flags.chatShadowMuted': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
@@ -226,100 +155,9 @@ 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: {
|
||||
@@ -336,37 +174,8 @@ 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 { 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 });
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testBannedWordMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
@@ -383,45 +192,6 @@ 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: {
|
||||
@@ -437,28 +207,17 @@ 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 () => {
|
||||
const brandNewUser = await generateUser();
|
||||
await expect(brandNewUser.post('/groups/habitrpg/chat', { message: 'hi im new' }))
|
||||
await user.update({ 'auth.timestamps.created': new Date() });
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/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 () => {
|
||||
@@ -519,54 +278,42 @@ describe('POST /chat', () => {
|
||||
const mount = 'test-mount';
|
||||
const pet = 'test-pet';
|
||||
const style = 'test-style';
|
||||
const userWithStyle = await generateUser({
|
||||
await user.update({
|
||||
'items.currentMount': mount,
|
||||
'items.currentPet': pet,
|
||||
'preferences.style': style,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
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.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.preferences.background)
|
||||
.to.eql(userWithStyle.preferences.background);
|
||||
.to.eql(user.preferences.background);
|
||||
});
|
||||
|
||||
it('creates equipped to user styles', async () => {
|
||||
const userWithStyle = await generateUser({
|
||||
'preferences.costume': false,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.gear.equipped)
|
||||
.to.eql(userWithStyle.items.gear.equipped);
|
||||
.to.eql(user.items.gear.equipped);
|
||||
expect(message.message.userStyles.items.gear.costume).to.not.exist;
|
||||
});
|
||||
|
||||
it('creates costume to user styles', async () => {
|
||||
const userWithStyle = await generateUser({
|
||||
'preferences.costume': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
await user.update({ 'preferences.costume': true });
|
||||
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.gear.costume).to.eql(userWithStyle.items.gear.costume);
|
||||
expect(message.message.userStyles.items.gear.costume).to.eql(user.items.gear.costume);
|
||||
expect(message.message.userStyles.items.gear.equipped).to.not.exist;
|
||||
});
|
||||
|
||||
@@ -576,12 +323,11 @@ describe('POST /chat', () => {
|
||||
tier: 800,
|
||||
tokensApplied: true,
|
||||
};
|
||||
const backer = await generateUser({
|
||||
await user.update({
|
||||
backer: backerInfo,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await backer.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const messageBackerInfo = message.message.backer;
|
||||
|
||||
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
||||
@@ -661,43 +407,5 @@ 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,18 +12,19 @@ describe('POST /groups/:id/chat/seen', () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
guildLeader = groupLeader;
|
||||
guildMember = members[0]; // eslint-disable-line prefer-destructuring
|
||||
[guildMember] = members;
|
||||
|
||||
guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' });
|
||||
guildMessage = guildMessage.message;
|
||||
|
||||
@@ -2,7 +2,6 @@ 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';
|
||||
@@ -13,21 +12,24 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
admin;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
members: 2,
|
||||
});
|
||||
|
||||
groupWithChat = group;
|
||||
author = groupLeader;
|
||||
nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
admin = await generateUser({ 'permissions.moderator': true });
|
||||
[nonAdmin, admin] = members;
|
||||
await nonAdmin.update({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
await admin.update({ 'permissions.moderator': true });
|
||||
|
||||
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||
message = message.message;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /group-plans', () => {
|
||||
@@ -8,20 +7,15 @@ describe('GET /group-plans', () => {
|
||||
let groupPlan;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser({ balance: 4 });
|
||||
groupPlan = await generateGroup(user,
|
||||
{
|
||||
name: 'public guild - is member',
|
||||
({ group: groupPlan, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'group plan - is member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
{
|
||||
purchased: {
|
||||
plan: {
|
||||
customerId: 'existings',
|
||||
},
|
||||
},
|
||||
});
|
||||
upgradeToGroupPlan: true,
|
||||
leaderDetails: { balance: 4 },
|
||||
}));
|
||||
});
|
||||
|
||||
it('returns group plans for the user', async () => {
|
||||
|
||||
@@ -1,70 +1,63 @@
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
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 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;
|
||||
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;
|
||||
const categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
let publicGuildNotMember;
|
||||
let privateGuildUserIsMemberOf;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
|
||||
const leader = await generateUser({ balance: 10 });
|
||||
user = await generateUser({ balance: 4 });
|
||||
({
|
||||
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 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`);
|
||||
({ group: secondGroup, groupLeader: secondLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] });
|
||||
await secondLeader.post(`/groups/${secondGroup._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${secondGroup._id}/join`);
|
||||
|
||||
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 createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await generateGroup(leader, {
|
||||
@@ -98,172 +91,16 @@ 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_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
});
|
||||
|
||||
it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => {
|
||||
@@ -272,21 +109,21 @@ describe('GET /groups', () => {
|
||||
});
|
||||
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
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.
|
||||
await expect(user.get('/groups?type=privateGuilds,party'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
|
||||
});
|
||||
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
const group = await generateGroup(user, {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
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);
|
||||
});
|
||||
|
||||
// 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);
|
||||
it('filters private guilds by size', async () => {
|
||||
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /groups/:groupId/invites', () => {
|
||||
@@ -71,15 +72,16 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
|
||||
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 { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 31,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
const res = await leader.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
@@ -90,8 +92,16 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
}).timeout(10000);
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -101,8 +111,16 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -112,8 +130,16 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -123,15 +149,16 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
||||
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 { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 31,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
||||
expect(res.length).to.equal(14);
|
||||
@@ -149,17 +176,20 @@ 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
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
({ group, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 32,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
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 expectedIds = invitees.map(generatedInvite => generatedInvite._id);
|
||||
|
||||
const res = await user.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
@@ -75,7 +76,15 @@ describe('GET /groups/:groupId/members', () => {
|
||||
});
|
||||
|
||||
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
||||
const group = await generateGroup(user, { type: 'guild', name: generateUUID() });
|
||||
let group;
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
upgradeToGroupPlan: true,
|
||||
members: 1,
|
||||
}));
|
||||
|
||||
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
|
||||
@@ -206,20 +215,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 leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const { group, groupLeader: leader, members: generatedUsers } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
upgradeToGroupPlan: true,
|
||||
leaderDetails: { balance: 4 },
|
||||
members: 57,
|
||||
});
|
||||
|
||||
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 user.get(`/groups/${group._id}/members`);
|
||||
const res = await leader.get(`/groups/${group._id}/members`);
|
||||
expect(res.length).to.equal(30);
|
||||
const res2 = await user.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`);
|
||||
const res2 = await leader.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,7 +11,6 @@ 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' };
|
||||
|
||||
@@ -24,10 +23,11 @@ describe('GET /groups/:id', () => {
|
||||
const groupData = await createAndPopulateGroup({
|
||||
members: 30,
|
||||
groupDetails,
|
||||
upgradeToGroupPlan: groupDetails.type === 'guild',
|
||||
});
|
||||
|
||||
leader = groupData.groupLeader;
|
||||
member = groupData.members[0]; // eslint-disable-line prefer-destructuring
|
||||
[member] = groupData.members;
|
||||
createdGroup = groupData.group;
|
||||
});
|
||||
|
||||
@@ -49,34 +49,6 @@ 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;
|
||||
@@ -89,6 +61,7 @@ describe('GET /groups/:id', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
@@ -218,7 +191,7 @@ describe('GET /groups/:id', () => {
|
||||
});
|
||||
|
||||
context('Flagged messages', () => {
|
||||
let group;
|
||||
let group; let members;
|
||||
|
||||
const chat1 = {
|
||||
id: 'chat1',
|
||||
@@ -268,7 +241,7 @@ describe('GET /groups/:id', () => {
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
chat: [
|
||||
chat1,
|
||||
chat2,
|
||||
@@ -277,9 +250,11 @@ describe('GET /groups/:id', () => {
|
||||
chat5,
|
||||
],
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
group = groupData.group;
|
||||
({ group, members } = groupData);
|
||||
|
||||
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
|
||||
});
|
||||
@@ -287,8 +262,8 @@ describe('GET /groups/:id', () => {
|
||||
context('non-admin', () => {
|
||||
let nonAdmin;
|
||||
|
||||
beforeEach(async () => {
|
||||
nonAdmin = await generateUser();
|
||||
beforeEach(() => {
|
||||
[nonAdmin] = members;
|
||||
});
|
||||
|
||||
it('does not include messages with a flag count of 2 or greater', async () => {
|
||||
@@ -314,9 +289,8 @@ describe('GET /groups/:id', () => {
|
||||
let admin;
|
||||
|
||||
beforeEach(async () => {
|
||||
admin = await generateUser({
|
||||
'permissions.moderator': true,
|
||||
});
|
||||
[admin] = members;
|
||||
await admin.update({ permissions: { moderator: true } });
|
||||
});
|
||||
|
||||
it('includes all messages', async () => {
|
||||
|
||||
@@ -2,7 +2,6 @@ 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', () => {
|
||||
@@ -35,8 +34,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 Public Guild',
|
||||
type: 'guild',
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
expect(group.leader).to.eql({
|
||||
@@ -51,7 +50,7 @@ describe('POST /group', () => {
|
||||
const name = 'Test Group';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'guild',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
const updatedGroup = await user.get(`/groups/${group._id}`);
|
||||
@@ -64,7 +63,7 @@ describe('POST /group', () => {
|
||||
const summary = 'Test Summary';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'guild',
|
||||
type: 'party',
|
||||
summary,
|
||||
});
|
||||
|
||||
@@ -78,7 +77,7 @@ describe('POST /group', () => {
|
||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1);
|
||||
await expect(user.post('/groups', {
|
||||
name,
|
||||
type: 'guild',
|
||||
type: 'party',
|
||||
summary,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -88,157 +87,6 @@ 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,81 +18,24 @@ describe('POST /group/:groupId/join', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Joining a public guild', () => {
|
||||
let user; let joiningUser; let
|
||||
publicGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
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;
|
||||
let user;
|
||||
let invitedUser;
|
||||
let guild;
|
||||
let invitees;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
({ group: guild, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
[invitedUser] = invitees;
|
||||
});
|
||||
|
||||
it('returns error when user is not invited to private guild', async () => {
|
||||
@@ -182,7 +125,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
|
||||
party = group;
|
||||
user = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
[invitedUser] = invitees;
|
||||
});
|
||||
|
||||
it('returns error when user is not invited to party', async () => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
generateChallenge,
|
||||
checkExistence,
|
||||
createAndPopulateGroup,
|
||||
sleep,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
@@ -14,253 +13,187 @@ import payments from '../../../../../website/server/libs/payments/payments';
|
||||
import calculateSubscriptionTerminationDate from '../../../../../website/server/libs/payments/calculateSubscriptionTerminationDate';
|
||||
|
||||
describe('POST /groups/:groupId/leave', () => {
|
||||
const typesOfGroups = {
|
||||
'public guild': { type: 'guild', privacy: 'public' },
|
||||
'private guild': { type: 'guild', privacy: 'private' },
|
||||
party: { type: 'party', privacy: 'private' },
|
||||
};
|
||||
let groupToLeave;
|
||||
let leader;
|
||||
let member;
|
||||
let members;
|
||||
let memberCount;
|
||||
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a ${groupType}`, () => {
|
||||
let groupToLeave;
|
||||
let leader;
|
||||
let member;
|
||||
let memberCount;
|
||||
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;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
});
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
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'),
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
});
|
||||
|
||||
it(`lets user leave a ${groupType}`, async () => {
|
||||
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 userThatLeftGroup = await member.get('/user');
|
||||
const userWithChallengeTasks = 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);
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
});
|
||||
|
||||
it(`sets a new group leader when leader leaves a ${groupType}`, async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
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' });
|
||||
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
expect(groupToLeave.leader).to.equal(member._id);
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
});
|
||||
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
await sleep(0.5);
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
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;
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
});
|
||||
|
||||
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 group as the last member', () => {
|
||||
context('private guild', () => {
|
||||
let privateGuild;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
context('Leaving a Party', () => {
|
||||
let invitees;
|
||||
let invitedUser;
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
beforeEach(async () => {
|
||||
({
|
||||
group: groupToLeave,
|
||||
groupLeader: leader,
|
||||
members,
|
||||
invitees,
|
||||
} = await createAndPopulateGroup({
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
members: 1,
|
||||
invites: 1,
|
||||
}));
|
||||
|
||||
privateGuild = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
[member] = members;
|
||||
[invitedUser] = invitees;
|
||||
memberCount = groupToLeave.memberCount;
|
||||
await leader.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
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('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'),
|
||||
});
|
||||
});
|
||||
|
||||
context('public guild', () => {
|
||||
let publicGuild;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
it('lets user leave', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
|
||||
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);
|
||||
});
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
});
|
||||
|
||||
context('party', () => {
|
||||
let party;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
it('sets a new group leader when leader leaves', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
expect(groupToLeave.leader).to.equal(member._id);
|
||||
});
|
||||
|
||||
party = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
await member.sync();
|
||||
|
||||
it('removes a group when the last member leaves a party', async () => {
|
||||
await leader.post(`/groups/${party._id}/leave`);
|
||||
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 expect(checkExistence('party', party._id)).to.eventually.equal(false);
|
||||
});
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await member.sync();
|
||||
|
||||
it('removes invitations when the last member leaves a party', async () => {
|
||||
await leader.post(`/groups/${party._id}/leave`);
|
||||
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;
|
||||
});
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
it('removes a party when the last member leaves', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||
});
|
||||
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;
|
||||
});
|
||||
|
||||
it('deletes non existent party from user when user tries to leave', async () => {
|
||||
@@ -275,23 +208,71 @@ 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 () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
({ group: groupWithPlan, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
});
|
||||
leader = groupLeader;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
groupWithPlan = group;
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
[member] = members;
|
||||
const userWithFreePlan = await User.findById(leader._id).exec();
|
||||
|
||||
// Create subscription
|
||||
@@ -321,45 +302,21 @@ 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 () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
({ group: groupWithPlan, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
}));
|
||||
[member] = members;
|
||||
groupWithPlan = group;
|
||||
await member.update({
|
||||
'purchased.plan.extraMonths': extraMonths,
|
||||
});
|
||||
|
||||
@@ -5,43 +5,6 @@ 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;
|
||||
@@ -54,6 +17,7 @@ describe('POST /group/:groupId/reject-invite', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
},
|
||||
invites: 1,
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
@@ -129,9 +130,11 @@ 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.calledOnce;
|
||||
expect(email.sendTxn).to.be.calledTwice;
|
||||
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,24 +3,23 @@ import nconf from 'nconf';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
const INVITES_LIMIT = 100;
|
||||
const PARTY_LIMIT_MEMBERS = 29;
|
||||
const PARTY_LIMIT_MEMBERS = 30;
|
||||
const MAX_EMAIL_INVITES_BY_USER = 200;
|
||||
|
||||
describe('Post /groups/:groupId/invite', () => {
|
||||
let inviter;
|
||||
let group;
|
||||
const groupName = 'Test Public Guild';
|
||||
const groupName = 'Test Party';
|
||||
|
||||
beforeEach(async () => {
|
||||
inviter = await generateUser({ balance: 4 });
|
||||
group = await inviter.post('/groups', {
|
||||
name: groupName,
|
||||
type: 'guild',
|
||||
type: 'party',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,45 +64,44 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('invites a user to a group by username', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||
})).to.eventually.deep.equal([{
|
||||
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);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
.to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites multiple users to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
const response = await (inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [
|
||||
userToInvite.auth.local.lowerCaseUsername,
|
||||
userToInvite2.auth.local.lowerCaseUsername,
|
||||
],
|
||||
})).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.guilds[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -214,42 +212,42 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('invites a user to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
})).to.eventually.deep.equal([{
|
||||
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);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
.to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites multiple users to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
const response = await 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,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
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);
|
||||
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);
|
||||
});
|
||||
|
||||
it('returns an error when inviting multiple users and a user is not found', async () => {
|
||||
@@ -338,12 +336,8 @@ 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/${tmpGroup._id}/invite`, {
|
||||
await expect(inviterWithMax.post(`/groups/${group._id}/invite`, {
|
||||
emails: [testInvite],
|
||||
inviter: 'inviter name',
|
||||
}))
|
||||
@@ -419,15 +413,15 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.guilds[0].id).to.equal(group._id);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(group._id);
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
|
||||
it('invites marks invite with cancelled plan', async () => {
|
||||
const cancelledPlanGroup = await generateGroup(inviter, {
|
||||
type: 'guild',
|
||||
name: generateUUID(),
|
||||
});
|
||||
it('invites user to group with cancelled plan', async () => {
|
||||
let cancelledPlanGroup;
|
||||
({ group: cancelledPlanGroup, groupLeader: inviter } = await createAndPopulateGroup({
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
await cancelledPlanGroup.createCancelledSubscription();
|
||||
|
||||
const newUser = await generateUser();
|
||||
@@ -437,13 +431,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.guilds[0].id).to.equal(cancelledPlanGroup._id);
|
||||
expect(invitedUser.invitations.guilds[0].cancelledPlan).to.be.true;
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(cancelledPlanGroup._id);
|
||||
expect(invitedUser.invitations.parties[0].cancelledPlan).to.be.true;
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('guild invites', () => {
|
||||
describe('party invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||
const userToInvite = await generateUser();
|
||||
@@ -457,103 +451,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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],
|
||||
});
|
||||
|
||||
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`, {
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
@@ -566,13 +470,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/${party._id}/invite`, {
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id, userToInvite2._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${party._id}/join`);
|
||||
await userToInvite2.post(`/groups/${party._id}/join`);
|
||||
await userToInvite.post(`/groups/${group._id}/join`);
|
||||
await userToInvite2.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
@@ -596,7 +500,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
// Invite to first party
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
@@ -609,28 +513,27 @@ 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(party._id);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(group._id);
|
||||
expect(invitedUser.invitations.parties[1].id).to.equal(party2._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user if party id is not associated with a real party', async () => {
|
||||
it('allows 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/${party._id}/invite`, {
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(group._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('party size limits', () => {
|
||||
let party;
|
||||
let partyLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
group = await createAndPopulateGroup({
|
||||
({ group, groupLeader: partyLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
@@ -638,9 +541,7 @@ 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 () => {
|
||||
@@ -651,7 +552,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
expect(await partyLeader.post(`/groups/${party._id}/invite`, {
|
||||
expect(await partyLeader.post(`/groups/${group._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
})).to.be.an('array');
|
||||
}).timeout(10000);
|
||||
@@ -664,13 +565,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
await expect(partyLeader.post(`/groups/${party._id}/invite`, {
|
||||
await expect(partyLeader.post(`/groups/${group._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS + 1 }),
|
||||
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS }),
|
||||
});
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
@@ -17,9 +17,10 @@ describe('POST /group/:groupId/add-manager', () => {
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupToUpdate = group;
|
||||
|
||||
@@ -23,10 +23,11 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
categories: groupCategories,
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
adminUser = await generateUser({ 'permissions.moderator': true });
|
||||
groupToUpdate = group;
|
||||
@@ -106,14 +107,28 @@ describe('PUT /group', () => {
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
it('allows a leader to change leaders', async () => {
|
||||
const updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
it('does not allow a leader to change leader of active group plan', async () => {
|
||||
await expect(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(nonLeader._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(nonLeader.profile.name);
|
||||
expect(updatedGroup.leader._id).to.eql(members[0]._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(members[0].profile.name);
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
@@ -122,15 +137,16 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
const updateGroupDetails = {
|
||||
id: group._id,
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
bannedWordsAllowed: true,
|
||||
};
|
||||
|
||||
@@ -150,9 +166,11 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await groupLeader.update({ permissions: {} });
|
||||
|
||||
const updateGroupDetails = {
|
||||
id: group._id,
|
||||
|
||||
+16
-17
@@ -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,22 +50,21 @@ describe('payments : amazon #subscribeCancel', () => {
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
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(),
|
||||
});
|
||||
({ 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,
|
||||
}));
|
||||
|
||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ describe('payments - amazon - #subscribe', () => {
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
|
||||
+16
-17
@@ -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,22 +48,21 @@ describe('payments - stripe - #subscribeCancel', () => {
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
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(),
|
||||
});
|
||||
({ 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,
|
||||
}));
|
||||
|
||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ 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,6 +43,7 @@ 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,14 +51,13 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
});
|
||||
|
||||
it('does not issue invites for Guilds', async () => {
|
||||
const { group } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'public' },
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
const alternateGroup = group;
|
||||
|
||||
await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
await expect(groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('guildQuestsNotSupported'),
|
||||
|
||||
@@ -52,6 +52,7 @@ 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,6 +52,7 @@ 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,6 +51,7 @@ 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,6 +53,7 @@ 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,6 +1,5 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
@@ -8,8 +7,12 @@ describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({ balance: 1 });
|
||||
guild = await generateGroup(user, { type: 'guild' }, { 'purchased.plan.customerId': 'group-unlimited' });
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
});
|
||||
|
||||
it('can move task to new position', async () => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
find,
|
||||
each,
|
||||
map,
|
||||
} from 'lodash';
|
||||
@@ -198,95 +197,6 @@ 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,6 +51,7 @@ describe('POST /user/purchase/:type/:key', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
@@ -77,6 +78,7 @@ describe('POST /user/purchase/:type/:key', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
|
||||
@@ -714,31 +714,6 @@ 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', () => {
|
||||
|
||||
@@ -665,6 +665,7 @@ 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,6 +127,9 @@ 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,6 +120,9 @@ 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);
|
||||
|
||||
Generated
+79
-49
@@ -13318,31 +13318,11 @@
|
||||
"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=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"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==",
|
||||
"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",
|
||||
@@ -13374,35 +13354,6 @@
|
||||
"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==",
|
||||
"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==",
|
||||
"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==",
|
||||
"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",
|
||||
@@ -30630,6 +30581,85 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 148 B |
@@ -41,6 +41,7 @@
|
||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||
<template v-else>
|
||||
<template v-if="isUserLoaded">
|
||||
<chat-banner />
|
||||
<damage-paused-banner />
|
||||
<gems-promo-banner />
|
||||
<gift-promo-banner />
|
||||
@@ -159,6 +160,7 @@ 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';
|
||||
@@ -198,6 +200,7 @@ export default {
|
||||
AppHeader,
|
||||
AppFooter,
|
||||
birthdayModal,
|
||||
ChatBanner,
|
||||
DamagePausedBanner,
|
||||
GemsPromoBanner,
|
||||
GiftPromoBanner,
|
||||
|
||||
@@ -94,6 +94,12 @@
|
||||
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;
|
||||
}
|
||||
@@ -192,14 +198,6 @@
|
||||
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 */
|
||||
|
||||
@@ -68,6 +68,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.achievement-bonelessBoss2x {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-bonelessBoss2x.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.achievement-boot2x {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-boot2x.png');
|
||||
width: 48px;
|
||||
@@ -715,6 +720,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_bonsai_collection {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_bonsai_collection.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_branches_of_a_holiday_tree {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_branches_of_a_holiday_tree.png');
|
||||
width: 141px;
|
||||
@@ -920,6 +930,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_dreamy_island {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_dreamy_island.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_drifting_raft {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_drifting_raft.png');
|
||||
width: 141px;
|
||||
@@ -1729,6 +1744,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_rock_garden {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_rock_garden.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_rolling_hills {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_rolling_hills.png');
|
||||
width: 141px;
|
||||
@@ -2441,6 +2461,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_bonsai_collection {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_bonsai_collection.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_branches_of_a_holiday_tree {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_branches_of_a_holiday_tree.png');
|
||||
width: 68px;
|
||||
@@ -2651,6 +2676,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_dreamy_island {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_dreamy_island.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_drifting_raft {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_drifting_raft.png');
|
||||
width: 68px;
|
||||
@@ -3460,6 +3490,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_rock_garden {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_rock_garden.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_rolling_hills {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_rolling_hills.png');
|
||||
width: 68px;
|
||||
@@ -18435,6 +18470,51 @@
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.body_armoire_karateBlackBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karateBlackBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_karateBlueBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karateBlueBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_karateBrownBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karateBrownBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_karateGreenBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karateGreenBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_karateOrangeBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karateOrangeBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_karatePurpleBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karatePurpleBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_karateRedBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karateRedBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_karateWhiteBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karateWhiteBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_karateYellowBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_karateYellowBelt.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_armoire_lifeguardWhistle {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_armoire_lifeguardWhistle.png');
|
||||
width: 114px;
|
||||
@@ -18695,6 +18775,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_karateGi {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_karateGi.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_lamplightersGreatcoat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_lamplightersGreatcoat.png');
|
||||
width: 114px;
|
||||
@@ -20000,6 +20085,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_armoire_karateGi {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_karateGi.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_armoire_lamplightersGreatcoat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_lamplightersGreatcoat.png');
|
||||
width: 68px;
|
||||
@@ -20230,6 +20320,51 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karateBlackBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karateBlackBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karateBlueBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karateBlueBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karateBrownBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karateBrownBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karateGreenBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karateGreenBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karateOrangeBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karateOrangeBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karatePurpleBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karatePurpleBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karateRedBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karateRedBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karateWhiteBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karateWhiteBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_karateYellowBelt {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_karateYellowBelt.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_armoire_lifeguardWhistle {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_armoire_lifeguardWhistle.png');
|
||||
width: 68px;
|
||||
@@ -21785,6 +21920,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_karateGi {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_karateGi.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_lamplightersGreatcoat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_lamplightersGreatcoat.png');
|
||||
width: 114px;
|
||||
@@ -22545,6 +22685,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_heroicTunic {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_heroicTunic.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_lunarWarriorArmor {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_lunarWarriorArmor.png');
|
||||
width: 90px;
|
||||
@@ -22730,6 +22875,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_heroicTunic {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_heroicTunic.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_lunarWarriorArmor {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_lunarWarriorArmor.png');
|
||||
width: 68px;
|
||||
@@ -22905,6 +23055,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_heroicTunic {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_heroicTunic.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_lunarWarriorArmor {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_lunarWarriorArmor.png');
|
||||
width: 90px;
|
||||
@@ -23135,6 +23290,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_back_special_heroicAureole {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_back_special_heroicAureole.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_back_special_lionTail {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_back_special_lionTail.png');
|
||||
width: 68px;
|
||||
@@ -34229,6 +34389,214 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.headAccessory_special_bearEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_bearEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_bearEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_bearEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_blackHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_blackHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_blueHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_blueHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_cactusEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_cactusEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_cactusEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_cactusEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_foxEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_foxEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_foxEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_foxEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_greenHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_greenHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_heroicCirclet {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_heroicCirclet.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_lionEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pandaEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pandaEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_pandaEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pandaEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pigEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pigEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_pigEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pigEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pinkHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pinkHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_redHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_redHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_tigerEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_tigerEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_tigerEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_tigerEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_whiteHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_whiteHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_wolfEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_wolfEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_wolfEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_wolfEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_yellowHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_yellowHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_headAccessory_special_bearEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_bearEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_blackHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_blackHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_blueHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_blueHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_cactusEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_cactusEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_foxEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_foxEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_greenHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_greenHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_heroicCirclet {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_heroicCirclet.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_lionEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pandaEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pandaEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pigEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pigEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pinkHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pinkHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_redHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_redHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_tigerEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_tigerEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_whiteHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_whiteHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_wolfEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_wolfEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_yellowHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_yellowHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.head_0 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_0.png');
|
||||
width: 90px;
|
||||
@@ -34610,204 +34978,6 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.headAccessory_special_bearEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_bearEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_bearEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_bearEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_blackHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_blackHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_blueHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_blueHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_cactusEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_cactusEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_cactusEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_cactusEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_foxEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_foxEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_foxEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_foxEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_greenHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_greenHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_lionEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pandaEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pandaEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_pandaEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pandaEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pigEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pigEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_pigEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pigEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_pinkHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_pinkHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_redHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_redHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_tigerEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_tigerEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_tigerEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_tigerEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_whiteHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_whiteHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_wolfEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_wolfEars.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.headAccessory_special_wolfEars {
|
||||
background-position: -25px -15px;
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_wolfEars.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.headAccessory_special_yellowHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_yellowHeadband.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_headAccessory_special_bearEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_bearEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_blackHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_blackHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_blueHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_blueHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_cactusEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_cactusEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_foxEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_foxEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_greenHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_greenHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_lionEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pandaEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pandaEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pigEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pigEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_pinkHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_pinkHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_redHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_redHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_tigerEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_tigerEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_whiteHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_whiteHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_wolfEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_wolfEars.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_yellowHeadband {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_yellowHeadband.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shield_healer_1 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_healer_1.png');
|
||||
width: 90px;
|
||||
@@ -35768,6 +35938,41 @@
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.heroic_set_icon {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/heroic_set_icon.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_bear {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_bear.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_dragon {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_dragon.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_fox {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_fox.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_lion {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_lion.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_tiger {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_tiger.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_wolf {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_wolf.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_head_special_nye {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_head_special_nye.png');
|
||||
width: 28px;
|
||||
@@ -35873,6 +36078,36 @@
|
||||
width: 20px;
|
||||
height: 24px;
|
||||
}
|
||||
.notif_namingDay_back {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_namingDay_back.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_namingDay_body {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_namingDay_body.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_namingDay_cake {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_namingDay_cake.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_namingDay_head {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_namingDay_head.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_namingDay_mount {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_namingDay_mount.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_namingDay_pet {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_namingDay_pet.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_orca_mount {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_orca_mount.png');
|
||||
width: 28px;
|
||||
@@ -55038,6 +55273,11 @@
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Veteran {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-Veteran.png');
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-VirtualPet {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-VirtualPet.png');
|
||||
width: 81px;
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<div class="col-6 text-center mx-auto mb-5">
|
||||
<!-- @TODO i18n. How to setup the strings with the router-link inside?-->
|
||||
<img
|
||||
class="not-found-img"
|
||||
:class="retiredChatPage ? 'mt-5' : 'image-404'"
|
||||
src="~@/assets/images/404.png"
|
||||
>
|
||||
<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 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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -37,6 +46,9 @@ export default {
|
||||
}
|
||||
return { name: 'contact' };
|
||||
},
|
||||
retiredChatPage () {
|
||||
return this.$route.fullPath.indexOf('/groups') !== -1;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -44,28 +56,20 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.col-12 {
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.not-found-img {
|
||||
margin-top: 152px;
|
||||
margin-bottom: 42px;
|
||||
}
|
||||
|
||||
h1.not-found {
|
||||
line-height: 1.33;
|
||||
h1, .static-wrapper h1 {
|
||||
color: $purple-200;
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
margin-top: 0px;
|
||||
line-height: 1.33;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h2.not-found {
|
||||
line-height: 1.4;
|
||||
font-weight: normal;
|
||||
color: $gray-200;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.image-404 {
|
||||
margin-top: 104px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<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>
|
||||
@@ -1,58 +0,0 @@
|
||||
<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>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="member.preferences"
|
||||
class="avatar"
|
||||
:style="{width, height, paddingTop}"
|
||||
:class="backgroundClass"
|
||||
@@ -184,9 +185,11 @@ export default {
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
hasClass () {
|
||||
if (!this.member) return false;
|
||||
return this.$store.getters['members:hasClass'](this.member);
|
||||
},
|
||||
isBuffed () {
|
||||
if (!this.member) return false;
|
||||
return this.$store.getters['members:isBuffed'](this.member);
|
||||
},
|
||||
paddingTop () {
|
||||
@@ -197,28 +200,30 @@ export default {
|
||||
let val = '24px';
|
||||
|
||||
if (!this.avatarOnly) {
|
||||
if (this.member.items.currentPet) val = '24px';
|
||||
if (this.member.items.currentMount) val = '0px';
|
||||
if (this.member?.items.currentPet) val = '24px';
|
||||
if (this.member?.items.currentMount) val = '0px';
|
||||
}
|
||||
|
||||
return val;
|
||||
},
|
||||
backgroundClass () {
|
||||
const { background } = this.member.preferences;
|
||||
if (this.member) {
|
||||
const { background } = this.member.preferences;
|
||||
|
||||
const allowToShowBackground = !this.avatarOnly || this.withBackground;
|
||||
const allowToShowBackground = !this.avatarOnly || this.withBackground;
|
||||
|
||||
if (this.overrideAvatarGear && this.overrideAvatarGear.background) {
|
||||
return `background_${this.overrideAvatarGear.background}`;
|
||||
if (this.overrideAvatarGear && this.overrideAvatarGear.background) {
|
||||
return `background_${this.overrideAvatarGear.background}`;
|
||||
}
|
||||
|
||||
if (background && allowToShowBackground) {
|
||||
return `background_${this.member.preferences.background}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (background && allowToShowBackground) {
|
||||
return `background_${this.member.preferences.background}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
visualBuffs () {
|
||||
if (!this.member) return {};
|
||||
return {
|
||||
snowball: `avatar_snowball_${this.member.stats.class}`,
|
||||
spookySparkles: 'ghost',
|
||||
@@ -227,15 +232,16 @@ export default {
|
||||
};
|
||||
},
|
||||
skinClass () {
|
||||
if (!this.member) return '';
|
||||
const baseClass = `skin_${this.member.preferences.skin}`;
|
||||
|
||||
return `${baseClass}${this.member.preferences.sleep ? '_sleep' : ''}`;
|
||||
},
|
||||
costumeClass () {
|
||||
return this.member.preferences.costume ? 'costume' : 'equipped';
|
||||
return this.member?.preferences.costume ? 'costume' : 'equipped';
|
||||
},
|
||||
specialMountClass () {
|
||||
if (!this.avatarOnly && this.member.items.currentMount && this.member.items.currentMount.includes('Kangaroo')) {
|
||||
if (!this.avatarOnly && this.member?.items.currentMount && this.member?.items.currentMount.includes('Kangaroo')) {
|
||||
return 'offset-kangaroo';
|
||||
}
|
||||
|
||||
@@ -248,12 +254,13 @@ export default {
|
||||
)) {
|
||||
return this.foolPet(this.member.items.currentPet);
|
||||
}
|
||||
if (this.member.items.currentPet) return `Pet-${this.member.items.currentPet}`;
|
||||
if (this.member?.items.currentPet) return `Pet-${this.member.items.currentPet}`;
|
||||
return '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getGearClass (gearType) {
|
||||
if (!this.member) return '';
|
||||
let result = this.member.items.gear[this.costumeClass][gearType];
|
||||
|
||||
if (this.overrideAvatarGear && this.overrideAvatarGear[gearType]) {
|
||||
@@ -263,6 +270,7 @@ export default {
|
||||
return result;
|
||||
},
|
||||
hideGear (gearType) {
|
||||
if (!this.member) return true;
|
||||
if (gearType === 'weapon') {
|
||||
const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];
|
||||
|
||||
@@ -288,6 +296,7 @@ export default {
|
||||
this.$root.$emit('castEnd', this.member, 'user', e);
|
||||
},
|
||||
showAvatar () {
|
||||
if (!this.member) return false;
|
||||
if (!this.showVisualBuffs) return true;
|
||||
|
||||
const { buffs } = this.member.stats;
|
||||
|
||||
@@ -8,23 +8,15 @@
|
||||
slot="modal-header"
|
||||
class="bug-report-modal-header"
|
||||
>
|
||||
<h2 v-once>
|
||||
{{ $t('reportBug') }}
|
||||
<h2>
|
||||
{{ question ? $t('askQuestion') : $t('reportBug') }}
|
||||
</h2>
|
||||
|
||||
<div
|
||||
v-once
|
||||
class="report-bug-header-describe"
|
||||
>
|
||||
{{ $t('reportBugHeaderDescribe') }}
|
||||
</div>
|
||||
|
||||
<div class="dialog-close">
|
||||
<close-icon
|
||||
:purple="true"
|
||||
@click="close()"
|
||||
/>
|
||||
<div class="report-bug-header-describe">
|
||||
{{ question ? $t('askQuestionHeaderDescribe') : $t('reportBugHeaderDescribe') }}
|
||||
</div>
|
||||
<close-x
|
||||
@close="close()"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<form
|
||||
@@ -40,11 +32,8 @@
|
||||
>
|
||||
{{ $t('email') }}
|
||||
</label>
|
||||
<div
|
||||
v-once
|
||||
class="mb-2 description-label"
|
||||
>
|
||||
{{ $t('reportEmailText') }}
|
||||
<div class="mb-2 description-label">
|
||||
{{ question ? $t('questionEmailText') : $t('reportEmailText') }}
|
||||
</div>
|
||||
<input
|
||||
id="emailInput"
|
||||
@@ -64,21 +53,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label v-once>
|
||||
{{ $t('reportDescription') }}
|
||||
<label>
|
||||
{{ question ? $t('question') : $t('reportDescription') }}
|
||||
</label>
|
||||
<div
|
||||
v-once
|
||||
class="mb-2 description-label"
|
||||
>
|
||||
{{ $t('reportDescriptionText') }}
|
||||
<div class="mb-2 description-label">
|
||||
{{ question ? $t('questionDescriptionText') : $t('reportDescriptionText') }}
|
||||
</div>
|
||||
<textarea
|
||||
v-model="message"
|
||||
class="form-control"
|
||||
rows="5"
|
||||
:required="true"
|
||||
:placeholder="$t('reportDescriptionPlaceholder')"
|
||||
:placeholder="question ? $t('questionPlaceholder') : $t('reportDescriptionPlaceholder')"
|
||||
:class="{'input-invalid': messageInvalid && this.message.length === 0}"
|
||||
>
|
||||
|
||||
@@ -89,7 +75,7 @@
|
||||
type="submit"
|
||||
:disabled="!message || !emailValid"
|
||||
>
|
||||
{{ $t('submitBugReport') }}
|
||||
{{ question ? $t('submitQuestion') : $t('submitBugReport') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -141,7 +127,7 @@ h2 {
|
||||
.bug-report-modal-header {
|
||||
color: $white;
|
||||
width: 100%;
|
||||
padding: 2rem 3rem 1.5rem 1.5rem;
|
||||
padding: 1.5rem 3rem 1.5rem 1.5rem;
|
||||
|
||||
background-image: linear-gradient(288deg, #{$purple-200}, #{$purple-300});
|
||||
}
|
||||
@@ -182,13 +168,13 @@ label {
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
import closeIcon from '@/components/shared/closeIcon';
|
||||
import closeX from '@/components/ui/closeX';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { MODALS } from '@/libs/consts';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
closeIcon,
|
||||
closeX,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -211,6 +197,7 @@ export default {
|
||||
await axios.post('/api/v4/bug-report', {
|
||||
message: this.message,
|
||||
email: this.email,
|
||||
question: this.question,
|
||||
});
|
||||
|
||||
this.message = '';
|
||||
@@ -233,6 +220,9 @@ export default {
|
||||
if (this.email.length <= 3) return false;
|
||||
return !this.emailValid;
|
||||
},
|
||||
question () {
|
||||
return this.$store.state.bugReportOptions.question;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
const { user } = this;
|
||||
|
||||
@@ -418,6 +418,9 @@ export default {
|
||||
methods: {
|
||||
async shown () {
|
||||
this.groups = await this.$store.dispatch('guilds:getMyGuilds');
|
||||
this.groups = this.groups.filter(group => !(
|
||||
group.leaderOnly.challenges && group.leader !== this.user._id
|
||||
));
|
||||
|
||||
if (this.user.party && this.user.party._id) {
|
||||
await this.$store.dispatch('party:getParty');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<challenge-modal
|
||||
:group-id="groupId"
|
||||
:group-id="group._id"
|
||||
@createChallenge="challengeCreated"
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
v-if="user._id !== msg.uuid && msg.uuid !== 'system'"
|
||||
class="avatar-left"
|
||||
:class="{ invisible: avatarUnavailable(msg) }"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid]"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:override-top-padding="'14px'"
|
||||
@@ -58,7 +58,7 @@
|
||||
<avatar
|
||||
v-if="user._id === msg.uuid"
|
||||
:class="{ invisible: avatarUnavailable(msg) }"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid]"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:override-top-padding="'14px'"
|
||||
|
||||
@@ -50,7 +50,6 @@ 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';
|
||||
|
||||
@@ -89,9 +88,7 @@ export default {
|
||||
createTask: 'tasks:create',
|
||||
}),
|
||||
groupPath () {
|
||||
if (this.groupId === TAVERN_ID) {
|
||||
return `${baseUrl}/groups/tavern`;
|
||||
} if (this.groupType === 'party') {
|
||||
if (this.groupType === 'party') {
|
||||
return `${baseUrl}/party`;
|
||||
}
|
||||
return `${baseUrl}/groups/guild/${this.groupId}`;
|
||||
|
||||
@@ -6,13 +6,10 @@
|
||||
:style="{height}"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
<div
|
||||
<close-x
|
||||
v-if="canClose"
|
||||
class="close-icon svg-icon icon-12"
|
||||
|
||||
@click="close()"
|
||||
v-html="icons.close"
|
||||
></div>
|
||||
@close="close()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -30,32 +27,24 @@ body.modal-open .habitica-top-banner {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.625rem;
|
||||
z-index: 1300;
|
||||
}
|
||||
|
||||
.close-icon.svg-icon {
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: 0;
|
||||
opacity: 0.48;
|
||||
|
||||
& ::v-deep svg path {
|
||||
stroke: $white !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.75;
|
||||
.modal-close {
|
||||
position: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import closeIcon from '@/assets/svg/close.svg';
|
||||
import closeX from '@/components/ui/closeX';
|
||||
import {
|
||||
clearBannerSetting, hideBanner, isBannerHidden, updateBannerHeight,
|
||||
} from '@/libs/banner.func';
|
||||
import { EVENTS } from '@/libs/events';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
closeX,
|
||||
},
|
||||
props: {
|
||||
bannerId: {
|
||||
type: String,
|
||||
@@ -82,9 +71,6 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: closeIcon,
|
||||
}),
|
||||
hidden: false,
|
||||
};
|
||||
},
|
||||
@@ -119,8 +105,6 @@ export default {
|
||||
close () {
|
||||
hideBanner(this.bannerId);
|
||||
this.hidden = true;
|
||||
|
||||
this.$root.$emit(EVENTS.BANNER_HIDDEN, this.bannerId);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<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,48 +194,6 @@
|
||||
>
|
||||
{{ $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="{
|
||||
@@ -354,22 +312,18 @@
|
||||
>
|
||||
{{ $t('reportBug') }}
|
||||
</a>
|
||||
<router-link
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
to="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a"
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
>
|
||||
{{ $t('askQuestion') }}
|
||||
</router-link>
|
||||
</a>
|
||||
<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"
|
||||
@@ -661,6 +615,7 @@ body.modal-open #habitica-menu {
|
||||
|
||||
&:hover {
|
||||
background: $purple-300;
|
||||
text-decoration: none;
|
||||
|
||||
&:last-child {
|
||||
border-bottom-right-radius: 5px;
|
||||
@@ -830,9 +785,11 @@ export default {
|
||||
async mounted () {
|
||||
await this.getUserGroupPlans();
|
||||
await this.getUserParty();
|
||||
Array.from(document.getElementById('menu_collapse').getElementsByTagName('a')).forEach(link => {
|
||||
link.addEventListener('click', this.closeMenu);
|
||||
});
|
||||
if (document.getElementById('menu_collapse')) {
|
||||
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);
|
||||
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
}
|
||||
|
||||
await this.$store.dispatch('guilds:join', { groupId: group.id, type: 'guild' });
|
||||
this.$router.push({ name: 'guild', params: { groupId: group.id } });
|
||||
this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { groupId: group.id } });
|
||||
},
|
||||
reject () {
|
||||
this.$store.dispatch('guilds:rejectInvite', { groupId: this.notification.data.id, type: 'guild' });
|
||||
|
||||
@@ -44,13 +44,13 @@ export default {
|
||||
if (!this.notification || !this.notification.data) {
|
||||
return;
|
||||
}
|
||||
if (this.notification.data.destination === 'backgrounds') {
|
||||
if (this.notification.data.destination.indexOf('backgrounds') !== -1) {
|
||||
this.$store.state.avatarEditorOptions.editingUser = true;
|
||||
this.$store.state.avatarEditorOptions.startingPage = 'backgrounds';
|
||||
this.$store.state.avatarEditorOptions.subpage = '2023';
|
||||
this.$root.$emit('bv::show::modal', 'avatar-modal');
|
||||
} else {
|
||||
this.$router.push({ name: this.notification.data.destination || 'items' });
|
||||
this.$router.push(this.notification.data.destination || '/inventory/items');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -270,6 +270,9 @@ export default {
|
||||
methods: {
|
||||
percent,
|
||||
showMemberModal (member) {
|
||||
if (this.$route.name === 'userProfile' && this.$route.params?.userId === member._id) {
|
||||
return;
|
||||
}
|
||||
this.$router.push({ name: 'userProfile', params: { userId: member._id } });
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
<low-health />
|
||||
<level-up />
|
||||
<choose-class />
|
||||
<testing />
|
||||
<testingletiant />
|
||||
<rebirth-enabled />
|
||||
<contributor />
|
||||
<won-challenge />
|
||||
@@ -127,8 +125,6 @@ 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';
|
||||
@@ -269,8 +265,6 @@ export default {
|
||||
armoireEmpty,
|
||||
questCompleted,
|
||||
questInvitation,
|
||||
testing,
|
||||
testingletiant,
|
||||
rebirthEnabled,
|
||||
contributor,
|
||||
loginIncentives,
|
||||
@@ -300,7 +294,6 @@ export default {
|
||||
// general notifications
|
||||
'CRON',
|
||||
'FIRST_DROPS',
|
||||
'GUILD_PROMPT',
|
||||
'LOGIN_INCENTIVE',
|
||||
'NEW_CONTRIBUTOR_LEVEL',
|
||||
'ONBOARDING_COMPLETE',
|
||||
@@ -705,14 +698,6 @@ 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;
|
||||
|
||||
@@ -106,6 +106,7 @@ export default {
|
||||
preventMultipleWatchExecution: false,
|
||||
eventPromoBannerHeight: null,
|
||||
sleepingBannerHeight: null,
|
||||
warningBannerHeight: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -135,6 +136,10 @@ export default {
|
||||
notificationBannerHeight () {
|
||||
let scrollPosToCheck = 56;
|
||||
|
||||
if (this.warningBannerHeight) {
|
||||
scrollPosToCheck += this.warningBannerHeight;
|
||||
}
|
||||
|
||||
if (this.sleepingBannerHeight) {
|
||||
scrollPosToCheck += this.sleepingBannerHeight;
|
||||
}
|
||||
@@ -361,6 +366,7 @@ export default {
|
||||
|
||||
updateBannerHeightAndScrollY () {
|
||||
this.updateEventBannerHeight();
|
||||
this.warningBannerHeight = getBannerHeight('chat-warning');
|
||||
this.sleepingBannerHeight = getBannerHeight('damage-paused');
|
||||
this.updateScrollY();
|
||||
},
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<div class="container-fluid text-center">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-3">
|
||||
<h1>{{ $t('checkOutMobileApps') }}</h1>
|
||||
<div
|
||||
class="promo_habitica"
|
||||
style="border-radius:25px;margin:auto;margin-bottom:30px"
|
||||
></div>
|
||||
<a
|
||||
href="https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"
|
||||
>
|
||||
<img
|
||||
alt="Get it on Google Play"
|
||||
src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png"
|
||||
style="width:139px;height:45px;image-rendering:auto;vertical-align:top"
|
||||
>
|
||||
</a>
|
||||
<a
|
||||
href="https://geo.itunes.apple.com/us/app/habitica/id994882113?mt=8"
|
||||
style="display:inline-block;overflow:hidden;background:url(https://linkmaker.itunes.apple.com/images/badges/en-us/badge_appstore-lrg.svg#svgView) no-repeat;background-size:100%;width:152px;height:45px;margin-left:20px;image-rendering:auto"
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,615 @@
|
||||
<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>
|
||||
@@ -1,99 +1,61 @@
|
||||
<template>
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid w-75 mx-auto">
|
||||
<h1>{{ $t('communityGuidelines') }}</h1>
|
||||
<hr>
|
||||
<p>{{ $t('lastUpdated') }} February 8, 2023</p>
|
||||
<p>{{ $t('lastUpdated') }} June 8, 2023</p>
|
||||
<h2 id="welcome">
|
||||
{{ $t('commGuideHeadingWelcome') }}
|
||||
</h2>
|
||||
<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>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/intro.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara001')"></p>
|
||||
<p v-html="$t('commGuidePara002')"></p>
|
||||
<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 v-html="$t('commGuideList02N')"></li>
|
||||
<li v-html="$t('commGuideList02A')"></li>
|
||||
<li v-html="$t('commGuideList02B')"></li>
|
||||
<li v-html="$t('commGuideList02G')"></li>
|
||||
<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('commGuideList02G')"></li>
|
||||
<li v-html="$t('commGuideList02D')"></li>
|
||||
<li v-html="$t('commGuideList02E')"></li>
|
||||
<li v-html="$t('commGuideList02F')"></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('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>
|
||||
<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>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/infractions.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara050')"></p>
|
||||
<p v-html="$t('commGuidePara051')"></p>
|
||||
<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>
|
||||
@@ -101,59 +63,34 @@
|
||||
<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>
|
||||
<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>
|
||||
<p v-html="$t('commGuidePara059')"></p>
|
||||
<img src="~@/assets/images/community-guidelines/consequences.png">
|
||||
<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('commGuideList10F')"></li>
|
||||
<li v-html="$t('commGuideList10G')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingMinorConsequences') }}</h4>
|
||||
<ul>
|
||||
@@ -166,56 +103,53 @@
|
||||
<h3 id="restoration">
|
||||
{{ $t('commGuideHeadingRestoration') }}
|
||||
</h3>
|
||||
<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>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/restoration.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara061')"></p>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<p v-html="$t('commGuidePara013')"></p>
|
||||
<p>
|
||||
{{ $t('commGuidePara014') }}<br>
|
||||
@@ -223,7 +157,6 @@
|
||||
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">
|
||||
@@ -235,16 +168,12 @@
|
||||
{{ $t('commGuideHeadingLinks') }}
|
||||
</h2>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideLink01')"></li>
|
||||
<li v-html="$t('commGuideLink02')"></li>
|
||||
<li><a href="/static/faq">{{ $t('faq') }}</a></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 class="list-2col list-unstyled">
|
||||
<li>Beffymaroo</li>
|
||||
<ul>
|
||||
<li>Breadstrings</li>
|
||||
<li>Draayder</li>
|
||||
<li>Kiwibot</li>
|
||||
@@ -258,12 +187,3 @@
|
||||
</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"
|
||||
href="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a"
|
||||
>Habitica Help guild</a>
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
> {{ $t('askQuestion') }}</a>
|
||||
<br>
|
||||
{{ $t('businessInquiries') }}
|
||||
:
|
||||
|
||||
@@ -94,6 +94,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
position: relative;
|
||||
top: 2rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.container-fluid {
|
||||
margin: auto;
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
</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,12 +12,15 @@
|
||||
<p v-markdown="$t(`webStep${step}Text`, stepVars[step])"></p>
|
||||
<hr>
|
||||
</div>
|
||||
<p
|
||||
v-markdown="$t('overviewQuestions', {
|
||||
faqUrl: '/static/faq/',
|
||||
helpGuildUrl: '/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a'
|
||||
})"
|
||||
></p>
|
||||
<p>
|
||||
<span v-html="$t('overviewQuestionsRevised')"></span>
|
||||
<a
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
>
|
||||
{{ $t('askQuestion') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,11 +38,13 @@
|
||||
|
||||
<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,11 +1,12 @@
|
||||
<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 />
|
||||
@@ -243,11 +244,13 @@
|
||||
|
||||
<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: {
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.modal-close {
|
||||
color: $black;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
@@ -22,10 +25,10 @@
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
opacity: 0.75;
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,10 +190,14 @@
|
||||
class="col-12 col-md-6"
|
||||
>
|
||||
<div class="row col-12 stats-column">
|
||||
<div
|
||||
:id="`${stat}-information`"
|
||||
class="col-12 col-md-4 attribute-label"
|
||||
>
|
||||
<div class="col-12 col-md-4 attribute-label">
|
||||
<span
|
||||
class="hint"
|
||||
:popover-title="$t(statInfo.title)"
|
||||
popover-placement="right"
|
||||
:popover="$t(statInfo.popover)"
|
||||
popover-trigger="mouseenter"
|
||||
></span>
|
||||
<div
|
||||
class="stat-title"
|
||||
:class="stat"
|
||||
@@ -202,13 +206,6 @@
|
||||
</div>
|
||||
<strong class="number">{{ totalStatPoints(stat) | floorWholeNumber }}</strong>
|
||||
</div>
|
||||
<b-popover
|
||||
:target="`${stat}-information`"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t(statInfo.popover)"
|
||||
/>
|
||||
<div class="col-12 col-md-6">
|
||||
<ul class="bonus-stats">
|
||||
<li>
|
||||
@@ -358,7 +355,7 @@ export default {
|
||||
},
|
||||
|
||||
allocateStatsList: {
|
||||
str: { title: 'allocateStr', popover: 'strText', allocatepop: 'allocateStrPop' },
|
||||
str: { title: 'allocateStr', popover: 'strengthText', allocatepop: 'allocateStrPop' },
|
||||
int: { title: 'allocateInt', popover: 'intText', allocatepop: 'allocateIntPop' },
|
||||
con: { title: 'allocateCon', popover: 'conText', allocatepop: 'allocateConPop' },
|
||||
per: { title: 'allocatePer', popover: 'perText', allocatepop: 'allocatePerPop' },
|
||||
@@ -367,7 +364,7 @@ export default {
|
||||
stats: {
|
||||
str: {
|
||||
title: 'strength',
|
||||
popover: 'strText',
|
||||
popover: 'strengthText',
|
||||
},
|
||||
int: {
|
||||
title: 'intelligence',
|
||||
|
||||
@@ -53,15 +53,6 @@ export default {
|
||||
hideNavigation: true,
|
||||
},
|
||||
]],
|
||||
tavern: [[
|
||||
{
|
||||
orphan: true,
|
||||
intro: this.$t('tourTavernPage'),
|
||||
final: true,
|
||||
proceed: this.$t('awesome'),
|
||||
hideNavigation: true,
|
||||
},
|
||||
]],
|
||||
party: [[
|
||||
{
|
||||
orphan: true,
|
||||
@@ -71,11 +62,6 @@ export default {
|
||||
hideNavigation: true,
|
||||
},
|
||||
]],
|
||||
guilds: [[
|
||||
{
|
||||
intro: this.$t('tourGuildsPage'),
|
||||
},
|
||||
]],
|
||||
challenges: [[
|
||||
{
|
||||
intro: this.$t('tourChallengesPage'),
|
||||
@@ -129,9 +115,7 @@ export default {
|
||||
switch (this.$route.name) { // eslint-disable-line default-case
|
||||
// case 'options.profile.avatar': return goto('intro', 5);
|
||||
case 'stats': return this.goto('stats', 0);
|
||||
case 'tavern': return this.goto('tavern', 0);
|
||||
case 'party': return this.goto('party', 0);
|
||||
case 'guildsDiscovery': return this.goto('guilds', 0);
|
||||
case 'challenges': return this.goto('challenges', 0);
|
||||
case 'patrons': return this.goto('hall', 0);
|
||||
case 'items': return this.goto('market', 0);
|
||||
|
||||
@@ -2,7 +2,8 @@ import { MODALS } from '@/libs/consts';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
openBugReportModal () {
|
||||
openBugReportModal (questionMode = false) {
|
||||
this.$store.state.bugReportOptions.question = questionMode;
|
||||
this.$root.$emit('bv::show::modal', MODALS.BUG_REPORT);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NotFoundPage } from './shared-route-imports';
|
||||
|
||||
export const DEPRECATED_ROUTES = {
|
||||
path: '/groups',
|
||||
component: NotFoundPage,
|
||||
children: [
|
||||
{ name: 'tavern', path: 'tavern' },
|
||||
{
|
||||
name: 'myGuilds',
|
||||
path: 'myGuilds',
|
||||
},
|
||||
{
|
||||
name: 'guildsDiscovery',
|
||||
path: 'discovery',
|
||||
},
|
||||
{
|
||||
name: 'guild',
|
||||
path: 'guild/:groupId',
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -4,51 +4,17 @@ import * as Analytics from '@/libs/analytics';
|
||||
import getStore from '@/store';
|
||||
import handleRedirect from './handleRedirect';
|
||||
|
||||
import ParentPage from '@/components/parentPage';
|
||||
import { PAGES } from '@/libs/consts';
|
||||
import { STATIC_ROUTES } from './static-routes';
|
||||
import { USER_ROUTES } from './user-routes';
|
||||
import { DEPRECATED_ROUTES } from '@/router/deprecated-routes';
|
||||
import { ProfilePage } from './shared-route-imports';
|
||||
|
||||
// NOTE: when adding a page make sure to implement the `common:setTitle` action
|
||||
|
||||
// Static Pages
|
||||
const StaticWrapper = () => import(/* webpackChunkName: "entry" */'@/components/static/staticWrapper');
|
||||
const HomePage = () => import(/* webpackChunkName: "entry" */'@/components/static/home');
|
||||
|
||||
const AppPage = () => import(/* webpackChunkName: "static" */'@/components/static/app');
|
||||
const AppleRedirectPage = () => import(/* webpackChunkName: "static" */'@/components/static/appleRedirect');
|
||||
const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'@/components/static/clearBrowserData');
|
||||
const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'@/components/static/communityGuidelines');
|
||||
const ContactPage = () => import(/* webpackChunkName: "static" */'@/components/static/contact');
|
||||
const FAQPage = () => import(/* webpackChunkName: "static" */'@/components/static/faq');
|
||||
const FeaturesPage = () => import(/* webpackChunkName: "static" */'@/components/static/features');
|
||||
const GroupPlansPage = () => import(/* webpackChunkName: "static" */'@/components/static/groupPlans');
|
||||
// Commenting out merch page see
|
||||
// https://github.com/HabitRPG/habitica/issues/12039
|
||||
// const MerchPage = () => import(/* webpackChunkName: "static" */'@/components/static/merch');
|
||||
const NewsPage = () => import(/* webpackChunkName: "static" */'@/components/static/newStuff');
|
||||
const OverviewPage = () => import(/* webpackChunkName: "static" */'@/components/static/overview');
|
||||
const PressKitPage = () => import(/* webpackChunkName: "static" */'@/components/static/pressKit');
|
||||
const PrivacyPage = () => import(/* webpackChunkName: "static" */'@/components/static/privacy');
|
||||
const TermsPage = () => import(/* webpackChunkName: "static" */'@/components/static/terms');
|
||||
|
||||
const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'@/components/auth/registerLoginReset');
|
||||
const Logout = () => import(/* webpackChunkName: "auth" */'@/components/auth/logout');
|
||||
|
||||
// User Pages
|
||||
// const StatsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/stats');
|
||||
// const AchievementsPage =
|
||||
// () => import(/* webpackChunkName: "user" */'./components/userMenu/achievements');
|
||||
const ProfilePage = () => import(/* webpackChunkName: "user" */'@/components/userMenu/profilePage');
|
||||
|
||||
// Settings
|
||||
const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index');
|
||||
const API = () => import(/* webpackChunkName: "settings" */'@/components/settings/api');
|
||||
const DataExport = () => import(/* webpackChunkName: "settings" */'@/components/settings/dataExport');
|
||||
const Notifications = () => import(/* webpackChunkName: "settings" */'@/components/settings/notifications');
|
||||
const PromoCode = () => import(/* webpackChunkName: "settings" */'@/components/settings/promoCode');
|
||||
const Site = () => import(/* webpackChunkName: "settings" */'@/components/settings/site');
|
||||
const Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription');
|
||||
const Transactions = () => import(/* webpackChunkName: "settings" */'@/components/settings/purchaseHistory');
|
||||
|
||||
// Hall
|
||||
const HallPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/index');
|
||||
const PatronsPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/patrons');
|
||||
@@ -74,10 +40,6 @@ const EquipmentPage = () => import(/* webpackChunkName: "inventory" */'@/compone
|
||||
const StablePage = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/stable/index');
|
||||
|
||||
// Guilds & Parties
|
||||
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/index');
|
||||
const TavernPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/tavern');
|
||||
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/myGuilds');
|
||||
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/discovery');
|
||||
const GroupPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/group');
|
||||
const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/groupPlan');
|
||||
const LookingForParty = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/lookingForParty');
|
||||
@@ -102,7 +64,6 @@ const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'@/componen
|
||||
const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'@/components/shops/seasonal/index');
|
||||
const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'@/components/shops/timeTravelers/index');
|
||||
|
||||
const NotFoundPage = () => import(/* webpackChunkName: "not-found" */'@/components/404');
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@@ -187,29 +148,7 @@ const router = new VueRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/groups',
|
||||
component: GuildIndex,
|
||||
children: [
|
||||
{ name: 'tavern', path: 'tavern', component: TavernPage },
|
||||
{
|
||||
name: 'myGuilds',
|
||||
path: 'myGuilds',
|
||||
component: MyGuilds,
|
||||
},
|
||||
{
|
||||
name: 'guildsDiscovery',
|
||||
path: 'discovery',
|
||||
component: GuildsDiscoveryPage,
|
||||
},
|
||||
{
|
||||
name: 'guild',
|
||||
path: 'guild/:groupId',
|
||||
component: GroupPage,
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
DEPRECATED_ROUTES,
|
||||
{ path: PAGES.PRIVATE_MESSAGES, name: 'privateMessages', component: MessagesIndex },
|
||||
{
|
||||
name: 'challenges',
|
||||
@@ -234,119 +173,8 @@ const router = new VueRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
component: ParentPage,
|
||||
children: [
|
||||
{ name: 'stats', path: 'stats', component: ProfilePage },
|
||||
{ name: 'achievements', path: 'achievements', component: ProfilePage },
|
||||
{ name: 'profile', path: 'profile', component: ProfilePage },
|
||||
{
|
||||
name: 'settings',
|
||||
path: 'settings',
|
||||
component: Settings,
|
||||
children: [
|
||||
{
|
||||
name: 'site',
|
||||
path: 'site',
|
||||
component: Site,
|
||||
},
|
||||
{
|
||||
name: 'api',
|
||||
path: 'api',
|
||||
component: API,
|
||||
},
|
||||
{
|
||||
name: 'dataExport',
|
||||
path: 'data-export',
|
||||
component: DataExport,
|
||||
},
|
||||
{
|
||||
name: 'promoCode',
|
||||
path: 'promo-code',
|
||||
component: PromoCode,
|
||||
},
|
||||
{
|
||||
name: 'subscription',
|
||||
path: 'subscription',
|
||||
component: Subscription,
|
||||
},
|
||||
{
|
||||
name: 'transactions',
|
||||
path: 'transactions',
|
||||
component: Transactions,
|
||||
meta: {
|
||||
privilegeNeeded: [
|
||||
'userSupport',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'notifications',
|
||||
path: 'notifications',
|
||||
component: Notifications,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/static',
|
||||
component: StaticWrapper,
|
||||
children: [
|
||||
{
|
||||
name: 'app', path: 'app', component: AppPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'appleRedirect', path: 'apple-redirect', component: AppleRedirectPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'communityGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'contact', path: 'contact', component: ContactPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'faq', path: 'faq', component: FAQPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'features', path: 'features', component: FeaturesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'home', path: 'home', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'news', path: 'new-stuff', component: NewsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'overview', path: 'overview', component: OverviewPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'plans', path: 'plans', component: GroupPlansPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'privacy', path: 'privacy', component: PrivacyPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'terms', path: 'terms', component: TermsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'notFound', path: 'not-found', component: NotFoundPage, meta: { requiresLogin: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
USER_ROUTES,
|
||||
STATIC_ROUTES,
|
||||
{
|
||||
path: '/hall',
|
||||
component: HallPage,
|
||||
@@ -382,6 +210,7 @@ const router = new VueRouter({
|
||||
|
||||
// Only used to handle some redirects
|
||||
// See router.beforeEach
|
||||
{ path: '/static/faq/tavern-and-guilds', redirect: '/static/tavern-and-guilds' },
|
||||
{ path: '/redirect/:redirect', name: 'redirect' },
|
||||
{ path: '*', redirect: { name: 'notFound' } },
|
||||
],
|
||||
@@ -462,6 +291,21 @@ router.beforeEach(async (to, from, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Redirect from Guild link to Group Plan where possible
|
||||
if (to.name === 'guild') {
|
||||
await store.dispatch('guilds:getGroupPlans');
|
||||
const { groupPlans } = store.state;
|
||||
const groupPlanIds = groupPlans.data.map(groupPlan => groupPlan._id);
|
||||
if (groupPlanIds.indexOf(to.params.groupId) !== -1) {
|
||||
return next({
|
||||
name: 'groupPlanDetailInformation',
|
||||
params: {
|
||||
groupId: to.params.groupId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect old challenge urls
|
||||
if (to.hash.indexOf('#/options/groups/challenges/') !== -1) {
|
||||
const splits = to.hash.split('/');
|
||||
@@ -509,4 +353,10 @@ router.beforeEach(async (to, from, next) => {
|
||||
return next();
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
if (from.name === 'chatSunsetFaq') {
|
||||
document.body.style.background = '#f9f9f9';
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const NotFoundPage = () => import(/* webpackChunkName: "not-found" */'@/components/404');
|
||||
|
||||
export const ProfilePage = () => import(/* webpackChunkName: "user" */'@/components/userMenu/profilePage');
|
||||
@@ -0,0 +1,82 @@
|
||||
// NOTE: when adding a page make sure to implement the `common:setTitle` action
|
||||
|
||||
import { NotFoundPage } from './shared-route-imports';
|
||||
|
||||
const StaticWrapper = () => import(/* webpackChunkName: "entry" */'@/components/static/staticWrapper');
|
||||
const HomePage = () => import(/* webpackChunkName: "entry" */'@/components/static/home');
|
||||
|
||||
const AppleRedirectPage = () => import(/* webpackChunkName: "static" */'@/components/static/appleRedirect');
|
||||
const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'@/components/static/clearBrowserData');
|
||||
const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'@/components/static/communityGuidelines');
|
||||
const ContactPage = () => import(/* webpackChunkName: "static" */'@/components/static/contact');
|
||||
const FAQPage = () => import(/* webpackChunkName: "static" */'@/components/static/faq');
|
||||
const FeaturesPage = () => import(/* webpackChunkName: "static" */'@/components/static/features');
|
||||
const GroupPlansPage = () => import(/* webpackChunkName: "static" */'@/components/static/groupPlans');
|
||||
// Commenting out merch page see
|
||||
// https://github.com/HabitRPG/habitica/issues/12039
|
||||
// const MerchPage = () => import(/* webpackChunkName: "static" */'@/components/static/merch');
|
||||
const NewsPage = () => import(/* webpackChunkName: "static" */'@/components/static/newStuff');
|
||||
const OverviewPage = () => import(/* webpackChunkName: "static" */'@/components/static/overview');
|
||||
const PressKitPage = () => import(/* webpackChunkName: "static" */'@/components/static/pressKit');
|
||||
const PrivacyPage = () => import(/* webpackChunkName: "static" */'@/components/static/privacy');
|
||||
const ChatSunsetFaq = () => import(/* webpackChunkName: "static" */'@/components/static/chatSunsetFaq');
|
||||
const TermsPage = () => import(/* webpackChunkName: "static" */'@/components/static/terms');
|
||||
|
||||
|
||||
export const STATIC_ROUTES = {
|
||||
path: '/static',
|
||||
component: StaticWrapper,
|
||||
children: [
|
||||
{
|
||||
name: 'appleRedirect', path: 'apple-redirect', component: AppleRedirectPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'communityGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'contact', path: 'contact', component: ContactPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'faq', path: 'faq', component: FAQPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'chatSunsetFaq', path: 'tavern-and-guilds', component: ChatSunsetFaq, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'features', path: 'features', component: FeaturesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'home', path: 'home', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'news', path: 'new-stuff', component: NewsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'overview', path: 'overview', component: OverviewPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'plans', path: 'plans', component: GroupPlansPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'privacy', path: 'privacy', component: PrivacyPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'terms', path: 'terms', component: TermsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'notFound', path: 'not-found', component: NotFoundPage, meta: { requiresLogin: false },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import ParentPage from '@/components/parentPage.vue';
|
||||
import { ProfilePage } from './shared-route-imports';
|
||||
|
||||
|
||||
// Settings
|
||||
const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index');
|
||||
const API = () => import(/* webpackChunkName: "settings" */'@/components/settings/api');
|
||||
const DataExport = () => import(/* webpackChunkName: "settings" */'@/components/settings/dataExport');
|
||||
const Notifications = () => import(/* webpackChunkName: "settings" */'@/components/settings/notifications');
|
||||
const PromoCode = () => import(/* webpackChunkName: "settings" */'@/components/settings/promoCode');
|
||||
const Site = () => import(/* webpackChunkName: "settings" */'@/components/settings/site');
|
||||
const Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription');
|
||||
const Transactions = () => import(/* webpackChunkName: "settings" */'@/components/settings/purchaseHistory');
|
||||
|
||||
|
||||
export const USER_ROUTES = {
|
||||
path: '/user',
|
||||
component: ParentPage,
|
||||
children: [
|
||||
{ name: 'stats', path: 'stats', component: ProfilePage },
|
||||
{ name: 'achievements', path: 'achievements', component: ProfilePage },
|
||||
{ name: 'profile', path: 'profile', component: ProfilePage },
|
||||
{
|
||||
name: 'settings',
|
||||
path: 'settings',
|
||||
component: Settings,
|
||||
children: [
|
||||
{
|
||||
name: 'site',
|
||||
path: 'site',
|
||||
component: Site,
|
||||
},
|
||||
{
|
||||
name: 'api',
|
||||
path: 'api',
|
||||
component: API,
|
||||
},
|
||||
{
|
||||
name: 'dataExport',
|
||||
path: 'data-export',
|
||||
component: DataExport,
|
||||
},
|
||||
{
|
||||
name: 'promoCode',
|
||||
path: 'promo-code',
|
||||
component: PromoCode,
|
||||
},
|
||||
{
|
||||
name: 'subscription',
|
||||
path: 'subscription',
|
||||
component: Subscription,
|
||||
},
|
||||
{
|
||||
name: 'transactions',
|
||||
path: 'transactions',
|
||||
component: Transactions,
|
||||
meta: {
|
||||
privilegeNeeded: [
|
||||
'userSupport',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'notifications',
|
||||
path: 'notifications',
|
||||
component: Notifications,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -148,6 +148,9 @@ export default function () {
|
||||
egg: '',
|
||||
hatchingPotion: '',
|
||||
},
|
||||
bugReportOptions: {
|
||||
question: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -336,5 +336,7 @@
|
||||
"hatchingPotionWindup": "الزنبرك",
|
||||
"hatchingPotionPorcelain": "الخزف الملون",
|
||||
"hatchingPotionBirchBark": "لحاء الشجر",
|
||||
"hatchingPotionRuby": "الياقوت"
|
||||
"hatchingPotionRuby": "الياقوت",
|
||||
"foodPieRed": "فطيرة الكرز الأحمر",
|
||||
"hatchingPotionTeaShop": "متجر الشاي"
|
||||
}
|
||||
|
||||
@@ -115,5 +115,21 @@
|
||||
"achievementSeasonalSpecialistText": "Dokončil/a jsi všechny Jarní a Zimní sezónní úkoly: Honba za vajíčky, Pastičkář Santa, a najdi Cuba!",
|
||||
"achievementWildBlueYonderText": "Ochočil/a všechny zvířata z Modré Cukrové Vaty.",
|
||||
"achievementWildBlueYonderModalText": "Ochočil/a jsi všechny mazlíčky z Modré Cukrové Vaty!",
|
||||
"achievementDomesticatedText": "Vylíhl/a všechna standardní zbarvení domácích zvířat: Fretka, morče, kohout, létající prasátko, krysa, králík, kůň a kráva!"
|
||||
"achievementDomesticatedText": "Vylíhl/a všechna standardní zbarvení domácích zvířat: Fretka, morče, kohout, létající prasátko, krysa, králík, kůň a kráva!",
|
||||
"achievementWildBlueYonder": "Divoký modrý zázrak",
|
||||
"achievementGroupsBeta2022": "Interaktivní beta tester",
|
||||
"achievementGroupsBeta2022Text": "Vy a vaše skupina jste poskytli neocenitelnou zpětnou vazbu, která pomohla společnosti Habitica při testování.",
|
||||
"achievementWoodlandWizard": "Lesní čaroděj",
|
||||
"achievementWoodlandWizardModalText": "Nasbírali jste všechna lesní zvířátka!",
|
||||
"achievementReptacularRumbleModalText": "Nasbíral jsi všechny plazí mazlíčky!",
|
||||
"achievementBirdsOfAFeather": "Ptáci s pírkem",
|
||||
"achievementBirdsOfAFeatherModalText": "Sesbíral jsi všechna létající zvířátka!",
|
||||
"achievementBoneToPick": "Kosti k vybírání",
|
||||
"achievementZodiacZookeeperModalText": "Nasbíral jsi všechna zvířata zvěrokruhu!",
|
||||
"achievementShadyCustomer": "Stínový zákazník",
|
||||
"achievementShadyCustomerText": "Shromáždil všechna stínová zvířata.",
|
||||
"achievementShadyCustomerModalText": "Sesbíral jsi všechna stínová zvířátka!",
|
||||
"achievementShadeOfItAll": "Odstín toho všeho",
|
||||
"achievementShadeOfItAllText": "Zkrotil všechny stínové držáky.",
|
||||
"achievementShadeOfItAllModalText": "Zkrotil jsi všechny stínové držáky!"
|
||||
}
|
||||
|
||||
@@ -570,7 +570,7 @@
|
||||
"backgroundMysticalObservatoryNotes": "Deine Bestimmung steht in den Sternen; vom Mystischen Observatorium aus kannst Du sie lesen.",
|
||||
"backgroundMysticalObservatoryText": "Mystisches Observatorium",
|
||||
"backgrounds112020": "Set 78: Veröffentlicht im November 2020",
|
||||
"backgroundHolidayHearthNotes": "Entspanne, wärme und trockne deine Körperteile an einem Feierlichen Feuer.",
|
||||
"backgroundHolidayHearthNotes": "Entspanne, wärme und trockne deine Körperteile an einem feierlichen Feuer.",
|
||||
"backgroundHolidayHearthText": "Feierliches Feuer",
|
||||
"backgroundInsideAnOrnamentNotes": "Lasse Deine Festtagsstimmung aus dem Inneren dieses Baumschmuckes erstrahlen.",
|
||||
"backgroundInsideAnOrnamentText": "Im Baumschmuck",
|
||||
@@ -794,5 +794,12 @@
|
||||
"backgroundBirthdayBashNotes": "Habitica feiert eine Geburtstagsparty und alle sind eingeladen!",
|
||||
"eventBackgrounds": "Ereignis-Hintergründe",
|
||||
"backgroundBirthdayBashText": "Geburtstagsparty",
|
||||
"backgroundInsideACrystalNotes": "Schaue aus einem Kristall hinaus."
|
||||
"backgroundInsideACrystalNotes": "Schaue aus einem Kristall hinaus.",
|
||||
"backgrounds072023": "SET 110: Veröffentlicht im Juli 2023",
|
||||
"backgroundOnAPaddlewheelBoatText": "Auf einem Schaufelradboot",
|
||||
"backgroundOnAPaddlewheelBoatNotes": "Fahre mit einem Schaufelradboot.",
|
||||
"backgroundColorfulCoralText": "Kunterbunte Koralle",
|
||||
"backgroundColorfulCoralNotes": "Tauche zwischen kunterbunten Korallen.",
|
||||
"backgroundBoardwalkIntoSunsetText": "Promenade im Sonnenuntergang",
|
||||
"backgroundBoardwalkIntoSunsetNotes": "Schlendere über die Promenade in den Sonnenuntergang."
|
||||
}
|
||||
|
||||
@@ -186,5 +186,6 @@
|
||||
"chatCastSpellParty": "<%= username %> wendet <%= spell %> auf Deine Party an.",
|
||||
"chatCastSpellUser": "<%= username %> wendet <%= spell %> auf <%= target %> an.",
|
||||
"purchasePetItemConfirm": "Dieser Einkauf würde die Anzahl der Gegenstände überschreiten, die Du zum Schlüpfen aller möglichen <%= itemText %>-Tiere benötigst. Bist du sicher?",
|
||||
"notEnoughGold": "Nicht genügend Gold."
|
||||
"notEnoughGold": "Nicht genügend Gold.",
|
||||
"chatCastSpellPartyTimes": "<%= username %> verwendet <%= spell %> <%= times %> Male für Deine Party <%= times %>."
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"commGuideList02D": "<strong>Halte die Diskussionen für alle Altersgruppen angemessen</strong>. Das heißt, Erwachsenenthemen in öffentlichen Bereichen zu vermeiden. Viele junge Habiticaner und Menschen mit verschiedenen Hintergründen nutzen diese Seite. Wir wollen unsere Gemeinschaft so angenehm und inklusiv wie möglich gestalten.",
|
||||
"commGuideList02E": "<strong>Vermeide vulgäre Ausdrücke. Dazu gehören auch mildere, religiöse Ausdrücke, die anderswo möglicherweise akzeptiert werden, oder verschleierte Schimpfwörter</strong>. Unter uns sind Menschen aus allen religiösen und kulturellen Hintergründen und wir wollen, dass sich alle im öffentlichen Raum wohl fühlen. <strong>Wenn Dir ein Moderator oder Mitarbeiter mitteilt, dass ein bestimmter Ausdruck in Habitica nicht erlaubt ist, selbst wenn er Dir vielleicht nicht problematisch vorkommt, ist diese Entscheidung endgültig</strong>. Zusätzlich werden verbale Angriffe jeder Art strenge Konsequenzen haben, da sie auch unsere Nutzungsbedingungen verletzen.",
|
||||
"commGuideList02F": "Vermeide längere Diskussionen über spaltende Themen in der Taverne und wenn sie außerhalb des Themenbereichs liegen. Wenn jemand etwas sagt, das zwar von den Richtlinien her erlaubt ist, das Dich aber verletzt, dann ist es in Ordnung, diese Person höflich darauf hinzuweisen. Wenn Dir eine Person sagt, dass ihr Dein Verhalten unangenehm ist, nimm Dir Zeit, darüber zu reflektieren, anstatt im Zorn zu antworten. Aber wenn Du das Gefühl hast, dass ein Gespräch hitzig, übermäßig emotional, oder verletzend wird, dann <strong>lass dich nicht darauf ein. Melde stattdessen die Beiträge, um uns darüber in Kenntnis zu setzen.</strong> Moderatoren werden so schnell wie möglich antworten. Du kannst auch eine E-Mail an <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> senden und gegebenenfalls Screenshots anhängen.",
|
||||
"commGuideList02G": "<strong>Erfülle alle Moderator-Anfragen sofort</strong>. Diese könnten Folgendes beinhalten, ist aber nicht darauf beschränkt: Dich aufzufordern, deine Beiträge in einem bestimmten Bereich zu begrenzen, dein Profil zu bearbeiten, um ungeeignete Inhalte zu entfernen, dich zu bitten, deine Diskussion in einen geeigneteren Bereich zu verschieben, etc. Diskutiere nicht mit Moderatoren. Solltest du mit einer Entscheidung unzufrieden sein, oder anderes Feedback zur Moderation haben, sende eine E-mail an <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> um unseren Community Manager zu kontaktieren.",
|
||||
"commGuideList02G": "<strong>Erfülle alle Mitarbeitenden-Anfragen sofort</strong>. Diese könnten Folgendes beinhalten, ist aber nicht darauf beschränkt: Dich aufzufordern, deine Beiträge in einem bestimmten Bereich zu begrenzen, dein Profil zu bearbeiten, um ungeeignete Inhalte zu entfernen, dich zu bitten, deine Diskussion in einen geeigneteren Bereich zu verschieben, etc. Diskutiere nicht mit Mitarbeitenden. Solltest du mit einer Entscheidung unzufrieden sein, oder anderes Feedback zur Mitarbeitenden haben, sende eine E-mail an <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> um unseren Community Manager zu kontaktieren.",
|
||||
"commGuideList02J": "<strong>Poste keinen Spam</strong>. Spamming kann Folgendes beinhalten, ist aber nicht beschränkt auf: das Posten desselben Kommentars oder derselben Frage an mehreren Stellen, <strong>das Posten von Links ohne Erklärung oder Kontext</strong>, das Posten unsinniger Nachrichten, das Posten mehrerer Werbebotschaften für eine Gilde, Party, oder Herausforderung, oder das Posten vieler Nachrichten hintereinander. Wenn Du irgendeinen Nutzen daraus ziehst, wenn jemand auf einen Link klickt, musst Du das im Text Deiner Nachricht offenlegen, sonst wird sie auch als Spam betrachtet. Mods können gegebenenfalls nach ihrem Ermessen entscheiden, was Spam ausmacht.",
|
||||
"commGuideList02K": "<strong>Bitte vermeide große Überschriften in öffentlichen Chats, vor allem in der Taverne.</strong> Ähnlich wie bei GROSSBUCHSTABEN liest sich der Text, als ob Du schreien würdest, und beeinträchtigt die gemütliche Atmosphäre.",
|
||||
"commGuideList02L": "<strong>Wir raten Dir dringend davon ab, persönliche Informationen - besonders solche, mit denen Du identifiziert werden könntest - in öffentlichen Chats zu teilen.</strong> Zu den identifizierenden Informationen gehören unter anderem: Deine Adresse, Deine E-Mail-Adresse und Dein API-Token/Passwort. Dies dient nur Deiner Sicherheit! Mitarbeiter oder Moderatoren werden solche Beiträge nach eigenem Ermessen entfernen. Wenn Du nach persönlichen Informationen in einer privaten Gilde, Party oder per PN gefragt wirst, empfehlen wir dringend, dass Du höflich ablehnst und Mitarbeiter und Moderatoren informierst, indem Du entweder 1) den Beitrag über das Fähnchen meldest, oder 2) eine E-Mail an <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> schreibst und Screenshots anhängst.",
|
||||
@@ -123,7 +123,7 @@
|
||||
"commGuideList01B": "Unzulässig: Jegliche Kommunikation die gewalttätig/aggressiv, bedrohlich, deskrimienierungsverherlichend o.ä. ist. Inklusive Memes, Bilder/Grafiken und Witze.",
|
||||
"commGuideList01A": "Die Allgemeinen Geschäftsbedingungen gelten für alle Bereiche, einschließlich privater Gilden, Party-Chat und Nachrichten.",
|
||||
"commGuideList01C": "Alle Diskussionen müssen für alle Altersgruppen angemessen und frei von Kraftausdrücken sein.",
|
||||
"commGuideList01D": "Haltet euch bitte an die Anweisungen der Moderatoren.",
|
||||
"commGuideList01D": "Haltet euch bitte an die Anweisungen der Mitarbeitenden.",
|
||||
"commGuideList01E": "Fange in der Taverne keine Streitgespräche an und lass Dich nicht auf solche ein.",
|
||||
"commGuideList01F": "Kein Betteln um bezahlte Artikel, kein Spamming oder große Überschriften/Großbuchstaben.",
|
||||
"commGuidePara017": "Hier ist die Kurzfassung, aber wir möchten Dich ermutigen, weiter unten mehr Details zu erfahren:",
|
||||
|
||||
@@ -65,5 +65,14 @@
|
||||
"faqQuestion15": "Wie funktioniert die Suche nach einer Party?",
|
||||
"iosFaqAnswer14": "Wenn du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn du bereits andere Spieler kennst, die eine Party haben, kannst du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst du auch eine neue Party gründen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die dir am besten gefällt.",
|
||||
"webFaqAnswer14": "Wenn du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn du bereits andere Spieler kennst, die eine Party haben, kannst du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst du auch eine neue Party gründen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die dir am besten gefällt.",
|
||||
"androidFaqAnswer14": "Wenn du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn du bereits andere Spieler kennst, die eine Party haben, kannst du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst du auch eine neue Party gründen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die dir am besten gefällt."
|
||||
"androidFaqAnswer14": "Wenn du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn du bereits andere Spieler kennst, die eine Party haben, kannst du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst du auch eine neue Party gründen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die dir am besten gefällt.",
|
||||
"iosFaqAnswer15": "Nachdem du \"Nach einer Party suchen\" ausgewählt hast, wirst du zu einer Liste von Spielern hinzugefügt, die einer Party beitreten möchten. Gruppenleiter können diese Liste einsehen und Einladungen verschicken. Sobald du eine Einladung erhalten hast, kannst du sie über deine Benachrichtigungen annehmen und der Party deiner Wahl beitreten!\n\nDu kannst mehrere Einladungen zu verschiedenen Partys erhalten. Du kannst jedoch immer nur Mitglied einer Party sein.",
|
||||
"webFaqAnswer24": "Wir haben in der Android-Version 4.2 die Möglichkeit hinzugefügt, nach Partys und Partymitgliedern zu suchen. Stelle sicher, dass du die App geupdated hast, um die Funktion verwenden zu können. Einzelspieler können nach einer Party vom Partymenü aus suchen, wenn sie sich nicht bereits in einer Party befinden. Anführer von Partys können die Liste von Spielern, die eine Party suchen, aufrufen, indem sie \"Neue Party-Mitglieder suchen\" über ihrer eigenen Party-Mitglieder-Liste anklicken.",
|
||||
"faqQuestion23": "Wie filtere ich die Liste von Mitgliedern, welche nach einer Party suchen?",
|
||||
"iosFaqAnswer23": "Aktuell gibt es leider keine Möglichkeit, die Liste mit Mitgliedern, welche nach einer Party suchen, zu filtern. Wir arbeiten jedoch aktuell an einem Filtersystem, mit welchem man nach Klasse, Level und Sprache filtern kann.",
|
||||
"androidFaqAnswer23": "Aktuell gibt es keine Möglichkeit, die Liste mit Mitgliedern, welche nach einer Party suchen, zu filtern. Wir arbeiten jedoch an einer Funktion, mit welcher man nach Klasse, Level und Sprache filtern kann.",
|
||||
"webFaqAnswer23": "Aktuell gibt es keine Möglichkeit, die Liste mit Mitgliedern, welche nach einer Party suchen, zu filtern. Wir arbeiten jedoch an einer Funktion, mit welcher man nach Klasse, Level und Sprache filtern kann.",
|
||||
"iosFaqAnswer22": "Sobald du einer Party beigetreten bist, bekommst du keine Einladungen für andere Partys mehr. Wenn du künftige Einladungen und Nachrichten von einem bestimmten Mitglied verhindern möchtest, besuche das betroffene Profil und klicke den \"Blockieren\"-Button an. Auf dem Handy müssen dafür die drei Punkte in der oberen Ecke und dann \"Blockieren\" ausgewählt werden.\n\nWenn du vermutest, dass ein anderer Spieler unsere Community-Richtlinien mittels Benutzername, Profilangaben oder privat versendeten Nachrichten verstößt, melde dies bitte oder wende dich direkt via eMail an uns über admin@habitica.com",
|
||||
"webFaqAnswer22": "Sobald du einer Party beigetreten bist, bekommst du keine Einladungen für andere Partys mehr. Wenn du künftige Einladungen und Nachrichten von einem bestimmten Mitglied verhindern möchtest, besuche das betroffene Profil und klicke den \"Blockieren\"-Button an. Auf dem Handy müssen dafür die drei Punkte in der oberen Ecke und dann \"Blockieren\" ausgewählt werden.\n\nWenn du vermutest, dass ein anderer Spieler unsere Community-Richtlinien mittels Benutzername, Profilangaben oder privat versendeten Nachrichten verstößt, melde dies bitte oder wende dich direkt via eMail an uns über admin@habitica.com",
|
||||
"faqQuestion24": "Wie suche ich nach einer Party auf Android oder iOS?"
|
||||
}
|
||||
|
||||
@@ -134,5 +134,6 @@
|
||||
"newStuffPostedOn": "Veröffentlicht am <%= publishDate %> um <%= publishTime %>",
|
||||
"helpSupportHabitica": "Hilf Habitica zu unterstützen",
|
||||
"groupsPaymentSubBilling": "Dein nächstes Rechnungsdatum ist <strong><%= renewalDate %></strong>.",
|
||||
"groupsPaymentAutoRenew": "Dieses Abonnement läuft automatisch weiter, bis es gekündigt wird. Du kannst es im Gruppen-Abrechnungs-Tab kündigen."
|
||||
"groupsPaymentAutoRenew": "Dieses Abonnement läuft automatisch weiter, bis es gekündigt wird. Du kannst es im Gruppen-Abrechnungs-Tab kündigen.",
|
||||
"sellItems": "Items verkaufen"
|
||||
}
|
||||
|
||||
@@ -150,5 +150,8 @@
|
||||
"achievementPlantParentModalText": "You collected all the Plant Pets!",
|
||||
"achievementDinosaurDynasty": "Dinosaur Dynasty",
|
||||
"achievementDinosaurDynastyText": "Has hatched all standard colors of bird and dinosaur pets: Falcon, Owl, Parrot, Peacock, Penguin, Rooster, Pterodactyl, T-Rex, Triceratops, and Velociraptor!",
|
||||
"achievementDinosaurDynastyModalText": "You collected all the bird and dinosaur pets!"
|
||||
"achievementDinosaurDynastyModalText": "You collected all the bird and dinosaur pets!",
|
||||
"achievementBonelessBoss": "Boneless Boss",
|
||||
"achievementBonelessBossText": "Has hatched all standard colors of invertebrate pets: Beetle, Butterfly, Cuttlefish, Nudibranch, Octopus, Snail, and Spider!",
|
||||
"achievementBonelessBossModalText": "You collected all the invertebrate pets!"
|
||||
}
|
||||
|
||||
@@ -899,6 +899,15 @@
|
||||
"backgroundBoardwalkIntoSunsetText": "Boardwalk into the Sunset",
|
||||
"backgroundBoardwalkIntoSunsetNotes": "Stroll on a Boardwalk into the Sunset.",
|
||||
|
||||
"backgrounds082023": "SET 111: Released August 2023",
|
||||
"backgroundBonsaiCollectionText": "Bonsai Collection",
|
||||
"backgroundBonsaiCollectionNotes": "Admire a gorgeous Bonsai Collection.",
|
||||
"backgroundDreamyIslandText": "Dreamy Island",
|
||||
"backgroundDreamyIslandNotes": "Enjoy the scenery on a Dreamy Island.",
|
||||
"backgroundRockGardenText": "Rock Garden",
|
||||
"backgroundRockGardenNotes": "Relax in a Rock Garden.",
|
||||
|
||||
|
||||
"timeTravelBackgrounds": "Steampunk Backgrounds",
|
||||
"backgroundAirshipText": "Airship",
|
||||
"backgroundAirshipNotes": "Become a sky sailor on board your very own Airship.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user