mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-10-27 19:22:55 +01:00
Compare commits
256 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08d71cc0bb | ||
|
|
c4f870c421 | ||
|
|
ac7c8e0eb6 | ||
|
|
7c9df3b32f | ||
|
|
edefae72bd | ||
|
|
132ecc8bb1 | ||
|
|
ca5b02d0ea | ||
|
|
d8a7cad1a1 | ||
|
|
fa83d1a9cf | ||
|
|
d94851f759 | ||
|
|
2137c190b3 | ||
|
|
428e693711 | ||
|
|
a7aa489960 | ||
|
|
c917c7c4a9 | ||
|
|
e661838ed7 | ||
|
|
af09b6b454 | ||
|
|
f8b3891a0c | ||
|
|
631d18244b | ||
|
|
7883ba8228 | ||
|
|
605c2265c5 | ||
|
|
1bb0319012 | ||
|
|
1f0fd7d8b4 | ||
|
|
6b4b53a430 | ||
|
|
331ea18c42 | ||
|
|
db53d87cba | ||
|
|
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 | ||
|
|
5c4aa664b5 | ||
|
|
184a9df775 | ||
|
|
754d46f1f3 | ||
|
|
683649ff1a | ||
|
|
08ac059a7f | ||
|
|
7c9b0f207c | ||
|
|
f193b8de2c | ||
|
|
812e2132d9 | ||
|
|
282abecd21 | ||
|
|
ba61c91296 | ||
|
|
d49736dd69 | ||
|
|
16b766beef | ||
|
|
8558dcc3a8 | ||
|
|
f8a8b61726 | ||
|
|
067a1de49e | ||
|
|
65ef3bfeca | ||
|
|
af04657856 | ||
|
|
a26c5906d6 | ||
|
|
08a5bff815 | ||
|
|
6089b02746 | ||
|
|
f3f69b1871 | ||
|
|
259f7ef588 | ||
|
|
106a0c9ed8 | ||
|
|
578083dde6 | ||
|
|
9706d7ac64 | ||
|
|
93564c5d52 | ||
|
|
74ba5c0b27 | ||
|
|
bb54a6532d | ||
|
|
3c36c59bb3 | ||
|
|
2308961de6 | ||
|
|
2d71a902f1 | ||
|
|
2e94bfc489 | ||
|
|
1deb903186 | ||
|
|
8c00b91cc6 | ||
|
|
5c8a3f7771 | ||
|
|
70d59be39b | ||
|
|
c562c93158 | ||
|
|
8ac03e311b | ||
|
|
7a430889a8 | ||
|
|
f84b5f163c | ||
|
|
519da49886 | ||
|
|
79d50cb3e0 | ||
|
|
c588c2b2ff | ||
|
|
77a490283c | ||
|
|
e49d26eacd | ||
|
|
7b0fd57eb9 | ||
|
|
7171334e31 | ||
|
|
a3235214b2 | ||
|
|
fca234c45a | ||
|
|
7519023f06 | ||
|
|
ad118095ef | ||
|
|
7ecad94a51 | ||
|
|
328b37322e | ||
|
|
81f7fbc2d5 | ||
|
|
9590ce939a | ||
|
|
fff6fbfbd6 | ||
|
|
52abf8acf3 | ||
|
|
bc61443246 | ||
|
|
cd8594a8b9 | ||
|
|
f3600f64e8 | ||
|
|
752cd57bb1 | ||
|
|
5d6bf131f4 | ||
|
|
8445f45b31 | ||
|
|
df84d7c7b1 | ||
|
|
e837ebec49 | ||
|
|
c7ed693e18 | ||
|
|
e72a25ad02 | ||
|
|
7dab47db16 | ||
|
|
a88ca5a1a8 | ||
|
|
c91d115793 | ||
|
|
e3502bd280 | ||
|
|
8b5ff7c2f9 | ||
|
|
3ac260026b | ||
|
|
80e7fda8ef | ||
|
|
1e7ea399b1 | ||
|
|
9c889a42aa | ||
|
|
952b99599b | ||
|
|
973fa2edc2 | ||
|
|
5e04040f5f | ||
|
|
2c12d5ee29 | ||
|
|
c3f0abadd7 | ||
|
|
adf0a2efca | ||
|
|
e4523c09dc | ||
|
|
91d98b86e1 | ||
|
|
779fb8bce5 | ||
|
|
f0fc83ed85 | ||
|
|
30d2108c78 | ||
|
|
ab68e8a5fe | ||
|
|
2fc9480ae9 | ||
|
|
429afc1e71 | ||
|
|
80da313844 | ||
|
|
31e9100ba2 | ||
|
|
0070f366bb | ||
|
|
2be6865a5c | ||
|
|
db85768e9d | ||
|
|
3d40413882 | ||
|
|
cc88e75950 | ||
|
|
de057dc1b2 | ||
|
|
ff860b04fc | ||
|
|
45dedbbdaa | ||
|
|
b6359ad032 | ||
|
|
a5ae3e5877 | ||
|
|
60ed9d2944 | ||
|
|
91fc4235aa | ||
|
|
42e8dd1361 | ||
|
|
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 |
@@ -87,5 +87,5 @@
|
||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||
"REDIS_PORT": "1234",
|
||||
"REDIS_PASSWORD": "12345678",
|
||||
"TRUSTED_DOMAINS": "https://localhost,https://habitica.com"
|
||||
"TRUSTED_DOMAINS": "localhost,habitica.com"
|
||||
}
|
||||
|
||||
Submodule habitica-images updated: 306496f9de...69724d88ef
79
migrations/archive/2023/20230718_summer_splash_orcas.js
Normal file
79
migrations/archive/2023/20230718_summer_splash_orcas.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230718_summer_splash_orcas';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = { migration: MIGRATION_NAME };
|
||||
const push = {};
|
||||
|
||||
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||
return;
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||
set['items.pets.Orca-Base'] = 5;
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_orca_pet',
|
||||
title: 'Orcas for Summer Splash!',
|
||||
text: 'To celebrate Summer Splash, we\'ve given you an Orca Pet!',
|
||||
destination: 'stable',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
} else {
|
||||
set['items.mounts.Orca-Base'] = true;
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_orca_mount',
|
||||
title: 'Orcas for Summer Splash!',
|
||||
text: 'To celebrate Summer Splash, we\'ve given you an Orca Mount!',
|
||||
destination: 'stable',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await user.updateOne({ $set: set, $push: push }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2023-06-18')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
155
migrations/archive/2023/20230731_naming_day.js
Normal file
155
migrations/archive/2023/20230731_naming_day.js
Normal file
@@ -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
|
||||
}
|
||||
};
|
||||
72
migrations/archive/2023/20230808_guild_gems.js
Normal file
72
migrations/archive/2023/20230808_guild_gems.js
Normal file
@@ -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
|
||||
}
|
||||
};
|
||||
62
migrations/archive/2023/20230808_guild_gems_fix.js
Normal file
62
migrations/archive/2023/20230808_guild_gems_fix.js
Normal file
@@ -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
|
||||
}
|
||||
};
|
||||
144
migrations/archive/2023/20230808_veteran_pet_ladder.js
Normal file
144
migrations/archive/2023/20230808_veteran_pet_ladder.js
Normal file
@@ -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
|
||||
}
|
||||
};
|
||||
2424
package-lock.json
generated
2424
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.273.0",
|
||||
"version": "5.4.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.8",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/register": "^7.21.0",
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/register": "^7.22.5",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.1.3",
|
||||
"@slack/webhook": "^6.1.0",
|
||||
@@ -15,7 +15,7 @@
|
||||
"amplitude": "^6.0.0",
|
||||
"apidoc": "^0.54.0",
|
||||
"apple-auth": "^1.0.9",
|
||||
"bcrypt": "^5.1.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"bootstrap": "^4.6.0",
|
||||
"compression": "^1.7.4",
|
||||
@@ -42,7 +42,7 @@
|
||||
"image-size": "^1.0.2",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^5.0.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsonwebtoken": "^9.0.1",
|
||||
"jwks-rsa": "^2.1.5",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -61,22 +61,22 @@
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"pp-ipn": "^1.1.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"rate-limiter-flexible": "^2.4.0",
|
||||
"rate-limiter-flexible": "^2.4.2",
|
||||
"redis": "^3.1.2",
|
||||
"regenerator-runtime": "^0.13.11",
|
||||
"remove-markdown": "^0.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.2.2",
|
||||
"stripe": "^12.6.0",
|
||||
"superagent": "^8.0.9",
|
||||
"stripe": "^12.9.0",
|
||||
"superagent": "^8.1.2",
|
||||
"universal-analytics": "^0.5.3",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.9.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^3.8.2",
|
||||
"winston": "^3.10.0",
|
||||
"winston-loggly-bulk": "^3.2.1",
|
||||
"xml2js": "^0.5.0"
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
@@ -114,7 +114,7 @@
|
||||
"chai": "^4.3.7",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
"chalk": "^5.2.0",
|
||||
"chalk": "^5.3.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
@@ -122,7 +122,7 @@
|
||||
"monk": "^7.3.4",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.7",
|
||||
"sinon": "^15.1.0",
|
||||
"sinon": "^15.2.0",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
149
website/client/package-lock.json
generated
149
website/client/package-lock.json
generated
@@ -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",
|
||||
@@ -16901,9 +16852,9 @@
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.30.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz",
|
||||
"integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg=="
|
||||
"version": "3.32.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.1.tgz",
|
||||
"integrity": "sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ=="
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.11.0",
|
||||
@@ -21436,9 +21387,9 @@
|
||||
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="
|
||||
},
|
||||
"intro.js": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.0.1.tgz",
|
||||
"integrity": "sha512-1oqz6aOz9cGQ3CrtVYhCSo6AkjnXUn302kcIWLaZ3TI4kKssRXDwDSz4VRoGcfC1jN+WfaSJXRBrITz+QVEBzg=="
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.2.0.tgz",
|
||||
"integrity": "sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ=="
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
@@ -27366,9 +27317,9 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.62.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz",
|
||||
"integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==",
|
||||
"version": "1.63.4",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.63.4.tgz",
|
||||
"integrity": "sha512-Sx/+weUmK+oiIlI+9sdD0wZHsqpbgQg8wSwSnGBjwb5GwqFhYNwwnI+UWZtLjKvKyFlKkatRK235qQ3mokyPoQ==",
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
@@ -27802,9 +27753,9 @@
|
||||
}
|
||||
},
|
||||
"smartbanner.js": {
|
||||
"version": "1.19.2",
|
||||
"resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.2.tgz",
|
||||
"integrity": "sha512-hwcGNp5Hza5PJHTmqP6H8q0XBYhloIQyJgdzv0ldz3HQSeEuKB2riVraQXdKuquE6ZU/0M0yubno53xJ/ZiQQg=="
|
||||
"version": "1.19.3",
|
||||
"resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.3.tgz",
|
||||
"integrity": "sha512-30JaYaPHO0VRC8MXGeUGWm1jF3+kCwKgV2GW9uLa8J3zOuv9D9ZhZo0IKf/xIcX0HQBRisOU4RPhEj7UrNt8Sw=="
|
||||
},
|
||||
"snapdragon": {
|
||||
"version": "0.8.2",
|
||||
@@ -30630,6 +30581,76 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"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==",
|
||||
"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==",
|
||||
"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=="
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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-mugen-scroll": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz",
|
||||
@@ -31340,9 +31361,9 @@
|
||||
}
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA=="
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"chai": "^4.3.7",
|
||||
"core-js": "^3.30.2",
|
||||
"core-js": "^3.32.1",
|
||||
"dompurify": "^3.0.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
@@ -41,14 +41,14 @@
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"inspectpack": "^4.7.1",
|
||||
"intro.js": "^7.0.1",
|
||||
"intro.js": "^7.2.0",
|
||||
"jquery": "^3.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nconf": "^0.12.0",
|
||||
"sass": "^1.62.1",
|
||||
"sass": "^1.63.4",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.19.2",
|
||||
"smartbanner.js": "^1.19.3",
|
||||
"stopword": "^2.0.8",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"svg-url-loader": "^7.1.1",
|
||||
|
||||
BIN
website/client/public/static/npc/normal/pixel_border.png
Normal file
BIN
website/client/public/static/npc/normal/pixel_border.png
Normal file
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;
|
||||
@@ -630,6 +635,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_baobab_forest {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_baobab_forest.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_bayou {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_bayou.png');
|
||||
width: 141px;
|
||||
@@ -710,6 +720,16 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_boardwalk_into_sunset {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_boardwalk_into_sunset.png');
|
||||
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;
|
||||
@@ -785,6 +805,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_colorful_coral {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_colorful_coral.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_coral_reef {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_coral_reef.png');
|
||||
width: 141px;
|
||||
@@ -800,6 +825,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_covered_bridge_in_autumn {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_covered_bridge_in_autumn.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_cozy_barn {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_cozy_barn.png');
|
||||
width: 141px;
|
||||
@@ -910,6 +940,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;
|
||||
@@ -1544,6 +1579,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_moving_day {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_moving_day.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_mystical_observatory {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_mystical_observatory.png');
|
||||
width: 141px;
|
||||
@@ -1579,6 +1619,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_on_a_paddlewheel_boat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_on_a_paddlewheel_boat.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_on_tree_branch {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_on_tree_branch.png');
|
||||
width: 141px;
|
||||
@@ -1714,6 +1759,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;
|
||||
@@ -2341,6 +2391,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_baobab_forest {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_baobab_forest.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_bayou {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_bayou.png');
|
||||
width: 68px;
|
||||
@@ -2421,6 +2476,16 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_boardwalk_into_sunset {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_boardwalk_into_sunset.png');
|
||||
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;
|
||||
@@ -2501,6 +2566,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_colorful_coral {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_colorful_coral.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_coral_reef {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_coral_reef.png');
|
||||
width: 68px;
|
||||
@@ -2516,6 +2586,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_covered_bridge_in_autumn {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_covered_bridge_in_autumn.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.icon_background_cozy_barn {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_cozy_barn.png');
|
||||
width: 68px;
|
||||
@@ -2626,6 +2701,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;
|
||||
@@ -3260,6 +3340,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_moving_day {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_moving_day.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_mystical_observatory {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_mystical_observatory.png');
|
||||
width: 68px;
|
||||
@@ -3295,6 +3380,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_on_a_paddlewheel_boat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_on_a_paddlewheel_boat.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_on_tree_branch {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_on_tree_branch.png');
|
||||
width: 68px;
|
||||
@@ -3430,6 +3520,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;
|
||||
@@ -18405,6 +18500,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;
|
||||
@@ -18415,6 +18555,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_admiralsUniform {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_admiralsUniform.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_armoire_alchemistsRobe {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_alchemistsRobe.png');
|
||||
width: 114px;
|
||||
@@ -18660,6 +18805,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;
|
||||
@@ -18920,6 +19070,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_admiralsBicorne {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_admiralsBicorne.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_armoire_alchemistsHat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_alchemistsHat.png');
|
||||
width: 114px;
|
||||
@@ -19400,6 +19555,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_bucket {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_bucket.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_chocolateFood {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_chocolateFood.png');
|
||||
width: 90px;
|
||||
@@ -19710,6 +19870,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_armor_armoire_admiralsUniform {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_admiralsUniform.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_armoire_alchemistsRobe {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_alchemistsRobe.png');
|
||||
width: 68px;
|
||||
@@ -19955,6 +20120,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;
|
||||
@@ -20185,6 +20355,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;
|
||||
@@ -20230,6 +20445,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_head_armoire_admiralsBicorne {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_armoire_admiralsBicorne.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_head_armoire_alchemistsHat {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_armoire_alchemistsHat.png');
|
||||
width: 68px;
|
||||
@@ -20710,6 +20930,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_armoire_bucket {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_armoire_bucket.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_armoire_chocolateFood {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_armoire_chocolateFood.png');
|
||||
width: 68px;
|
||||
@@ -21105,6 +21330,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_cleaningCloth {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_cleaningCloth.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_clubOfClubs {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_clubOfClubs.png');
|
||||
width: 68px;
|
||||
@@ -21295,6 +21525,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_mop {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_mop.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_mythmakerSword {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_mythmakerSword.png');
|
||||
width: 68px;
|
||||
@@ -21485,6 +21720,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.slim_armor_armoire_admiralsUniform {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_admiralsUniform.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_armoire_alchemistsRobe {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_alchemistsRobe.png');
|
||||
width: 114px;
|
||||
@@ -21730,6 +21970,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;
|
||||
@@ -22035,6 +22280,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_cleaningCloth {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_cleaningCloth.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_clubOfClubs {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_clubOfClubs.png');
|
||||
width: 114px;
|
||||
@@ -22225,6 +22475,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_mop {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_mop.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_mythmakerSword {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_mythmakerSword.png');
|
||||
width: 90px;
|
||||
@@ -22490,6 +22745,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;
|
||||
@@ -22675,6 +22935,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;
|
||||
@@ -22850,6 +23115,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;
|
||||
@@ -23080,6 +23350,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;
|
||||
@@ -28030,11 +28305,6 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.set_mystery_202304 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/set_mystery_202304.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_mystery_202304 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_mystery_202304.png');
|
||||
width: 68px;
|
||||
@@ -28045,6 +28315,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_set_mystery_202304 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202304.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.slim_armor_mystery_202304 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202304.png');
|
||||
width: 114px;
|
||||
@@ -28105,6 +28380,86 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_mystery_202307 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202307.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.headAccessory_mystery_202307 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_mystery_202307.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.shop_armor_mystery_202307 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_mystery_202307.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_mystery_202307 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_mystery_202307.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_set_mystery_202307 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202307.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.slim_armor_mystery_202307 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202307.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.eyewear_mystery_202308 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/eyewear_mystery_202308.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_mystery_202308 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202308.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_eyewear_mystery_202308 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_mystery_202308.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_head_mystery_202308 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_mystery_202308.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_set_mystery_202308 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202308.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.back_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_202309.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.headAccessory_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_mystery_202309.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.shop_back_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_back_mystery_202309.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_mystery_202309.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_set_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202309.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.broad_armor_mystery_301404 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png');
|
||||
width: 90px;
|
||||
@@ -30350,6 +30705,26 @@
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.broad_armor_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2023Healer.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.broad_armor_special_summer2023Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2023Mage.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2023Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.broad_armor_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2023Warrior.png');
|
||||
width: 114px;
|
||||
height: 120px;
|
||||
}
|
||||
.broad_armor_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -30540,6 +30915,26 @@
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.head_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2023Healer.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.head_special_summer2023Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2023Mage.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2023Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.head_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2023Warrior.png');
|
||||
width: 114px;
|
||||
height: 120px;
|
||||
}
|
||||
.head_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -30685,6 +31080,21 @@
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.shield_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2023Healer.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.shield_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2023Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.shield_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2023Warrior.png');
|
||||
width: 114px;
|
||||
height: 120px;
|
||||
}
|
||||
.shield_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -30860,6 +31270,26 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summer2023Healer.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_summer2023Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summer2023Mage.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summer2023Rogue.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summer2023Warrior.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_summerHealer.png');
|
||||
width: 68px;
|
||||
@@ -31080,6 +31510,26 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_head_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summer2023Healer.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_head_special_summer2023Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summer2023Mage.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_head_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summer2023Rogue.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_head_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summer2023Warrior.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_head_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_summerHealer.png');
|
||||
width: 68px;
|
||||
@@ -31225,6 +31675,21 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_summer2023Healer.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_summer2023Rogue.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_summer2023Warrior.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_summerHealer.png');
|
||||
width: 68px;
|
||||
@@ -31400,6 +31865,26 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summer2023Healer.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_summer2023Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summer2023Mage.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summer2023Rogue.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summer2023Warrior.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_summerHealer.png');
|
||||
width: 68px;
|
||||
@@ -31580,6 +32065,26 @@
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.slim_armor_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2023Healer.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.slim_armor_special_summer2023Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2023Mage.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2023Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.slim_armor_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2023Warrior.png');
|
||||
width: 114px;
|
||||
height: 120px;
|
||||
}
|
||||
.slim_armor_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -31760,6 +32265,26 @@
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.weapon_special_summer2023Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2023Healer.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.weapon_special_summer2023Mage {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2023Mage.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_summer2023Rogue {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2023Rogue.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.weapon_special_summer2023Warrior {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2023Warrior.png');
|
||||
width: 114px;
|
||||
height: 120px;
|
||||
}
|
||||
.weapon_special_summerHealer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summerHealer.png');
|
||||
width: 90px;
|
||||
@@ -33997,6 +34522,11 @@
|
||||
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;
|
||||
@@ -34102,6 +34632,11 @@
|
||||
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;
|
||||
@@ -35488,6 +36023,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;
|
||||
@@ -35593,6 +36163,46 @@
|
||||
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;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_orca_pet {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_orca_pet.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.npc_bailey {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/npc_bailey.png');
|
||||
width: 60px;
|
||||
@@ -54748,6 +55358,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}`;
|
||||
|
||||
@@ -183,10 +183,8 @@
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[0].items"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-2"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
@@ -195,6 +193,13 @@
|
||||
>
|
||||
<div class="small-rectangle"></div>
|
||||
</div>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -211,16 +216,21 @@
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[2].items"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
class="background"
|
||||
:class="`background_${bg.key}`"
|
||||
></div>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -236,10 +246,8 @@
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[1].items"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="!user.purchased.background[bg.key]
|
||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||
>
|
||||
@@ -270,6 +278,13 @@
|
||||
:pinned="isBackgroundPinned(bg)"
|
||||
/>
|
||||
</span>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -302,10 +317,8 @@
|
||||
<div
|
||||
v-for="bg in set.items"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="!user.purchased.background[bg.key]
|
||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||
>
|
||||
@@ -336,6 +349,13 @@
|
||||
:pinned="isBackgroundPinned(bg)"
|
||||
/>
|
||||
</span>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!ownsSet('background', set.items) && set.identifier !== 'incentiveBackgrounds'"
|
||||
@@ -358,16 +378,21 @@
|
||||
<div
|
||||
v-for="(bg) in ownedBackgrounds"
|
||||
:key="bg.key"
|
||||
:id="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
class="background"
|
||||
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
||||
></div>
|
||||
<b-popover
|
||||
:target="bg.key"
|
||||
triggers="hover focus"
|
||||
placement="bottom"
|
||||
:prevent-overflow="false"
|
||||
:content="bg.notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
background-color: $white;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 24px;
|
||||
|
||||
@@ -258,13 +258,22 @@
|
||||
:key="hero._id"
|
||||
>
|
||||
<td>
|
||||
<user-link
|
||||
<div
|
||||
v-if="hasPermission(hero, 'userSupport')"
|
||||
:user="hero"
|
||||
:popover="$t('gamemaster')"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
/>
|
||||
class="width-content"
|
||||
>
|
||||
<user-link
|
||||
:id="hero._id"
|
||||
:user="hero"
|
||||
/>
|
||||
<b-popover
|
||||
:target="hero._id"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('gamemaster')"
|
||||
/>
|
||||
</div>
|
||||
<user-link
|
||||
v-else
|
||||
:user="hero"
|
||||
@@ -302,6 +311,10 @@
|
||||
h4.expand-toggle::after {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.width-content {
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
64
website/client/src/components/header/banners/chatBanner.vue
Normal file
64
website/client/src/components/header/banners/chatBanner.vue
Normal file
@@ -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;
|
||||
|
||||
@@ -128,7 +128,10 @@
|
||||
<hr>
|
||||
</div>
|
||||
<div>
|
||||
<div class="checkbox">
|
||||
<div
|
||||
class="checkbox"
|
||||
id="preferenceAdvancedCollapsed"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.advancedCollapsed"
|
||||
@@ -136,17 +139,22 @@
|
||||
class="mr-2"
|
||||
@change="set('advancedCollapsed')"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('startAdvCollapsedPop')"
|
||||
>{{ $t('startAdvCollapsed') }}</span>
|
||||
<span class="hint">
|
||||
{{ $t('startAdvCollapsed') }}
|
||||
</span>
|
||||
<b-popover
|
||||
target="preferenceAdvancedCollapsed"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('startAdvCollapsedPop')"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="party.memberCount === 1"
|
||||
class="checkbox"
|
||||
id="preferenceDisplayInviteAtOneMember"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
@@ -155,12 +163,9 @@
|
||||
class="mr-2"
|
||||
@change="set('displayInviteToPartyWhenPartyIs1')"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('displayInviteToPartyWhenPartyIs1')"
|
||||
>{{ $t('displayInviteToPartyWhenPartyIs1') }}</span>
|
||||
<span class="hint">
|
||||
{{ $t('displayInviteToPartyWhenPartyIs1') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
@@ -201,32 +206,47 @@
|
||||
</div>
|
||||
<hr>
|
||||
<button
|
||||
id="buttonShowBailey"
|
||||
class="btn btn-primary mr-2 mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('showBaileyPop')"
|
||||
@click="showBailey()"
|
||||
>
|
||||
{{ $t('showBailey') }}
|
||||
<b-popover
|
||||
target="buttonShowBailey"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('showBaileyPop')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
id="buttonFCV"
|
||||
class="btn btn-primary mr-2 mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('fixValPop')"
|
||||
@click="openRestoreModal()"
|
||||
>
|
||||
{{ $t('fixVal') }}
|
||||
<b-popover
|
||||
target="buttonFCV"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('fixValPop')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="user.preferences.disableClasses == true"
|
||||
id="buttonEnableClasses"
|
||||
class="btn btn-primary mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('enableClassPop')"
|
||||
@click="changeClassForUser(false)"
|
||||
>
|
||||
{{ $t('enableClass') }}
|
||||
<b-popover
|
||||
target="buttonEnableClasses"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('enableClassPop')"
|
||||
/>
|
||||
</button>
|
||||
<hr>
|
||||
<day-start-adjustment />
|
||||
@@ -516,6 +536,10 @@
|
||||
input {
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: fit-content;
|
||||
}
|
||||
.usersettings h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
.limitedTime {
|
||||
height: 32px;
|
||||
width: calc(100% + 30px);
|
||||
margin: 0 -15px; // the modal content has its own padding
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-primary"
|
||||
class="btn btn-primary mb-4"
|
||||
:class="{'notEnough': !enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
|
||||
:disabled="numberInvalid"
|
||||
@click="buyItem()"
|
||||
@@ -141,7 +141,6 @@
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
display: block;
|
||||
margin-top: 24px;
|
||||
padding: 16px 24px;
|
||||
align-content: center;
|
||||
|
||||
|
||||
@@ -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>
|
||||
615
website/client/src/components/static/chatSunsetFaq.vue
Normal file
615
website/client/src/components/static/chatSunsetFaq.vue
Normal file
@@ -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>
|
||||
@@ -7,13 +7,19 @@
|
||||
<br>
|
||||
<p class="text-center">
|
||||
<button
|
||||
id="buttonClearBrowserData"
|
||||
class="btn btn-lg btn-danger"
|
||||
popover-trigger="mouseover"
|
||||
:popover="$t('localStorageClearExplanation')"
|
||||
@click="clearLocalStorage()"
|
||||
>
|
||||
{{ $t('localStorageClear') }}
|
||||
</button>
|
||||
<b-popover
|
||||
target="buttonClearBrowserData"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('localStorageClearExplanation')"
|
||||
/>
|
||||
</p>
|
||||
<br>
|
||||
<p v-html="$t('localStorageTryNext', localStorageTryNext) "></p>
|
||||
|
||||
@@ -1,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: {
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
}, controlClass.up.inner]"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="$t('scoreUp')"
|
||||
:aria-disabled="showTaskLockIcon || (!task.up && !showTaskLockIcon)"
|
||||
@click="score('up')"
|
||||
@keypress.enter="score('up')"
|
||||
>
|
||||
@@ -63,6 +66,7 @@
|
||||
controlClass.inner,
|
||||
]"
|
||||
tabindex="0"
|
||||
role="checkbox"
|
||||
@click="score(showCheckIcon ? 'down' : 'up' )"
|
||||
@keypress.enter="score(showCheckIcon ? 'down' : 'up' )"
|
||||
>
|
||||
@@ -244,7 +248,7 @@
|
||||
class="svg-icon calendar my-auto"
|
||||
v-html="icons.calendar"
|
||||
></div>
|
||||
<span>{{ $t('due') }} {{ formatDueDate() }}</span>
|
||||
<span>{{ formatDueDate() }}</span>
|
||||
</div>
|
||||
<div class="icons-right d-flex justify-content-end">
|
||||
<div
|
||||
@@ -359,6 +363,9 @@
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
}, controlClass.down.inner]"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="$t('scoreDown')"
|
||||
:aria-disabled="showTaskLockIcon || (!task.down && !showTaskLockIcon)"
|
||||
@click="score('down')"
|
||||
@keypress.enter="score('down')"
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,17 +146,19 @@
|
||||
:key="stat"
|
||||
class="row"
|
||||
>
|
||||
<div class="col-4">
|
||||
<span
|
||||
class="hint"
|
||||
:popover-title="$t(statInfo.title)"
|
||||
popover-placement="right"
|
||||
:popover="$t(statInfo.popover)"
|
||||
popover-trigger="mouseenter"
|
||||
>
|
||||
<strong>{{ $t(statInfo.title) }}</strong>
|
||||
</span>
|
||||
<div
|
||||
class="col-4"
|
||||
:id="statInfo.title"
|
||||
>
|
||||
<strong> {{ $t(statInfo.title)}} </strong>
|
||||
<strong>: {{ statsComputed[stat] }}</strong>
|
||||
<b-popover
|
||||
:target="statInfo.title"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t(statInfo.popover)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<ul class="bonus-stats">
|
||||
@@ -183,27 +185,38 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="user.stats.buffs.stealth">
|
||||
<div
|
||||
v-if="user.stats.buffs.stealth"
|
||||
id="stealthBuff"
|
||||
>
|
||||
<strong
|
||||
v-once
|
||||
class="hint"
|
||||
:popover-title="$t('stealth')"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('stealthNewDay')"
|
||||
>{{ $t('stealth') }}</strong>
|
||||
<strong>: {{ user.stats.buffs.stealth }} </strong>
|
||||
<b-popover
|
||||
target="stealthBuff"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('stealthNewDay')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="user.stats.buffs.streaks">
|
||||
<div
|
||||
v-if="user.stats.buffs.streaks"
|
||||
id="streaksFrozenBuff"
|
||||
>
|
||||
<div>
|
||||
<strong
|
||||
class="hint"
|
||||
popover-title="$t('streaksFrozen')"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('streaksFrozenText')"
|
||||
></strong>
|
||||
{{ $t('streaksFrozen') }}
|
||||
<strong>
|
||||
{{ $t('streaksFrozen') }}
|
||||
</strong>
|
||||
<b-popover
|
||||
target="streaksFrozenBuff"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('streaksFrozenText')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -237,19 +250,27 @@
|
||||
>
|
||||
{{ $t('noMoreAllocate') }}
|
||||
</p>
|
||||
<p v-if="user.stats.points || userLevel100Plus">
|
||||
<p
|
||||
v-if="user.stats.points || userLevel100Plus"
|
||||
id="pointAllocation"
|
||||
>
|
||||
<strong class="inline">{{ user.stats.points }} </strong>
|
||||
<strong
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('levelPopover')"
|
||||
>{{ $t('unallocated') }}</strong>
|
||||
<strong> {{ $t('unallocated') }} </strong>
|
||||
<b-popover
|
||||
target="pointAllocation"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('levelPopover')"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<fieldset class="auto-allocate">
|
||||
<div class="checkbox">
|
||||
<div
|
||||
id="preferenceAutomaticAllocation"
|
||||
class="checkbox"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.automaticAllocation"
|
||||
@@ -259,19 +280,24 @@
|
||||
'preferences.allocationMode': 'taskbased'
|
||||
})"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('autoAllocationPop')"
|
||||
>{{ $t('autoAllocation') }}</span>
|
||||
<b-popover
|
||||
target="preferenceAutomaticAllocation"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('autoAllocationPop')"
|
||||
/>
|
||||
{{ $t('autoAllocation') }}
|
||||
</label>
|
||||
</div>
|
||||
<form
|
||||
v-if="user.preferences.automaticAllocation"
|
||||
style="margin-left:1em"
|
||||
>
|
||||
<div class="radio">
|
||||
<div
|
||||
id="optionFlatAllocation"
|
||||
class="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.allocationMode"
|
||||
@@ -280,15 +306,22 @@
|
||||
value="flat"
|
||||
@change="set({'preferences.allocationMode': 'flat'})"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('evenAllocationPop')"
|
||||
>{{ $t('evenAllocation') }}</span>
|
||||
<span class="hint">
|
||||
{{ $t('evenAllocation') }}
|
||||
</span>
|
||||
<b-popover
|
||||
target="optionFlatAllocation"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('evenAllocationPop')"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<div
|
||||
id="optionClassAllocation"
|
||||
class="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.allocationMode"
|
||||
@@ -297,47 +330,63 @@
|
||||
value="classbased"
|
||||
@change="set({'preferences.allocationMode': 'classbased'})"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('classAllocationPop')"
|
||||
>{{ $t('classAllocation') }}</span>
|
||||
<span class="hint">
|
||||
{{ $t('classAllocation') }}
|
||||
</span>
|
||||
<b-popover
|
||||
target="optionClassAllocation"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('classAllocationPop')"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<div
|
||||
id="optionTaskAllocation"
|
||||
class="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.allocationMode"
|
||||
type="radio"
|
||||
name="allocationMode"
|
||||
value="taskbased"
|
||||
value="classbased"
|
||||
@change="set({'preferences.allocationMode': 'taskbased'})"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('taskAllocationPop')"
|
||||
>{{ $t('taskAllocation') }}</span>
|
||||
<span class="hint">
|
||||
{{ $t('taskAllocation') }}
|
||||
</span>
|
||||
<b-popover
|
||||
target="optionTaskAllocation"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('taskAllocationPop')"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
v-if="user.preferences.automaticAllocation
|
||||
&& !(user.preferences.allocationMode === 'taskbased') && (user.stats.points > 0)"
|
||||
id="buttonDistributePoints"
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary btn-xs"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('distributePointsPop')"
|
||||
@click="allocateNow({})"
|
||||
>
|
||||
<span class="glyphicon glyphicon-download"></span>
|
||||
|
||||
{{ $t('distributePoints') }}
|
||||
</button>
|
||||
<b-popover
|
||||
target="buttonDistributePoints"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('distributePointsPop')"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
@@ -346,28 +395,35 @@
|
||||
:key="stat"
|
||||
class="row"
|
||||
>
|
||||
<div class="col-8">
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t(statInfo.popover)"
|
||||
></span>
|
||||
<div
|
||||
:id="`${stat}-info`"
|
||||
class="col-8"
|
||||
>
|
||||
{{ $t(statInfo.title) + user.stats[stat] }}
|
||||
<b-popover
|
||||
:target="`${stat}-info`"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t(statInfo.popover)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="user.stats.points"
|
||||
:id="`${stat}-allocate`"
|
||||
class="col-4"
|
||||
@click="allocate(stat)"
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t(statInfo.allocatepop)"
|
||||
>
|
||||
<button class="btn btn-primary">
|
||||
+
|
||||
</button>
|
||||
<b-popover
|
||||
:target="`${stat}-allocate`"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t(statInfo.allocatePop)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
|
||||
22
website/client/src/router/deprecated-routes.js
Normal file
22
website/client/src/router/deprecated-routes.js
Normal file
@@ -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;
|
||||
|
||||
3
website/client/src/router/shared-route-imports.js
Normal file
3
website/client/src/router/shared-route-imports.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export const NotFoundPage = () => import(/* webpackChunkName: "not-found" */'@/components/404');
|
||||
|
||||
export const ProfilePage = () => import(/* webpackChunkName: "user" */'@/components/userMenu/profilePage');
|
||||
82
website/client/src/router/static-routes.js
Normal file
82
website/client/src/router/static-routes.js
Normal file
@@ -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 },
|
||||
},
|
||||
],
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user