old client structure

This commit is contained in:
Matteo Pagliazzi
2019-10-01 11:44:38 +02:00
parent 5b61f65711
commit b44fbdcb14
906 changed files with 44 additions and 44 deletions
+88
View File
@@ -0,0 +1,88 @@
import axios from 'axios';
const LOCALSTORAGE_AUTH_KEY = 'habit-mobile-settings';
export async function register (store, params) {
let url = '/api/v4/user/auth/local/register';
if (params.groupInvite) url += `?groupInvite=${params.groupInvite}`;
let result = await axios.post(url, {
username: params.username,
email: params.email,
password: params.password,
confirmPassword: params.passwordConfirm,
});
let user = result.data.data;
let userLocalData = JSON.stringify({
auth: {
apiId: user._id,
apiToken: user.apiToken,
},
});
localStorage.setItem(LOCALSTORAGE_AUTH_KEY, userLocalData);
}
export async function login (store, params) {
let url = '/api/v4/user/auth/local/login';
let result = await axios.post(url, {
username: params.username,
// email: params.email,
password: params.password,
});
let user = result.data.data;
let userLocalData = JSON.stringify({
auth: {
apiId: user.id,
apiToken: user.apiToken,
},
});
localStorage.setItem(LOCALSTORAGE_AUTH_KEY, userLocalData);
}
export async function verifyUsername (store, params) {
let url = '/api/v4/user/auth/verify-username';
let result = await axios.post(url, {
username: params.username,
});
return result.data.data;
}
export async function verifyDisplayName (store, params) {
let url = '/api/v4/user/auth/verify-display-name';
let result = await axios.post(url, {
displayName: params.displayName,
});
return result.data.data;
}
export async function socialAuth (store, params) {
let url = '/api/v4/user/auth/social';
let result = await axios.post(url, {
network: params.auth.network,
authResponse: params.auth.authResponse,
});
let user = result.data.data;
let userLocalData = JSON.stringify({
auth: {
apiId: user.id,
apiToken: user.apiToken,
},
});
localStorage.setItem(LOCALSTORAGE_AUTH_KEY, userLocalData);
}
export function logout () {
localStorage.clear();
window.location.href = '/logout-server';
}
@@ -0,0 +1,98 @@
import axios from 'axios';
import omit from 'lodash/omit';
import encodeParams from 'client/libs/encodeParams';
export async function createChallenge (store, payload) {
let response = await axios.post('/api/v4/challenges', payload.challenge);
let newChallenge = response.data.data;
return newChallenge;
}
export async function cloneChallenge (store, payload) {
const response = await axios.post(`/api/v4/challenges/${payload.cloningChallengeId}/clone`, payload.challenge);
const newChallenge = response.data.data.clonedChallenge;
return newChallenge;
}
export async function joinChallenge (store, payload) {
let response = await axios.post(`/api/v4/challenges/${payload.challengeId}/join`);
return response.data.data;
}
export async function leaveChallenge (store, payload) {
let url = `/api/v4/challenges/${payload.challengeId}/leave`;
let response = await axios.post(url, {
keep: payload.keep,
});
return response.data.data;
}
export async function getUserChallenges (store, payload) {
let url = '/api/v4/challenges/user';
let {
member,
page,
search,
categories,
owned,
} = payload;
let query = {};
if (member) query.member = member;
if (page >= 0) query.page = page;
if (search) query.search = search;
if (categories) query.categories = categories;
if (owned) query.owned = owned;
const parms = encodeParams(query);
const response = await axios.get(`${url}?${parms}`);
return response.data.data;
}
export async function getGroupChallenges (store, payload) {
let response = await axios.get(`/api/v4/challenges/groups/${payload.groupId}`);
return response.data.data;
}
export async function getChallenge (store, payload) {
let response = await axios.get(`/api/v4/challenges/${payload.challengeId}`);
return response.data.data;
}
export async function exportChallengeCsv (store, payload) {
let response = await axios.get(`/api/v4/challenges/${payload.challengeId}/export/csv`);
return response.data.data;
}
export async function updateChallenge (store, payload) {
let challengeDataToSend = omit(payload.challenge, ['tasks', 'habits', 'todos', 'rewards', 'group']);
if (challengeDataToSend.leader && challengeDataToSend.leader._id) challengeDataToSend.leader = challengeDataToSend.leader._id;
let response = await axios.put(`/api/v4/challenges/${payload.challenge._id}`, challengeDataToSend);
return response.data.data;
}
export async function deleteChallenge (store, payload) {
const response = await axios.delete(`/api/v4/challenges/${payload.challengeId}`);
if (payload.prize) {
store.state.user.data.balance += payload.prize / 4;
}
return response.data.data;
}
export async function selectChallengeWinner (store, payload) {
let response = await axios.post(`/api/v4/challenges/${payload.challengeId}/selectWinner/${payload.winnerId}`);
return response.data.data;
}
+77
View File
@@ -0,0 +1,77 @@
import axios from 'axios';
import * as Analytics from 'client/libs/analytics';
export async function getChat (store, payload) {
let response = await axios.get(`/api/v4/groups/${payload.groupId}/chat`);
return response.data.data;
}
export async function postChat (store, payload) {
const group = payload.group;
let url = `/api/v4/groups/${group._id}/chat`;
if (payload.previousMsg) {
url += `?previousMsg=${payload.previousMsg}`;
}
if (group.type === 'party') {
Analytics.updateUser({
partyID: group.id,
partySize: group.memberCount,
});
}
let response = await axios.post(url, {
message: payload.message,
});
return response.data.data;
}
export async function deleteChat (store, payload) {
let url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatId}`;
if (payload.previousMsg) {
url += `?previousMsg=${payload.previousMsg}`;
}
let response = await axios.delete(url);
return response.data.data;
}
export async function like (store, payload) {
let url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatId}/like`;
let response = await axios.post(url);
return response.data.data;
}
export async function flag (store, payload) {
let url = '';
if (payload.groupId === 'privateMessage') {
url = `/api/v4/members/flag-private-message/${payload.chatId}`;
} else {
url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatId}/flag`;
}
const response = await axios.post(url, {
comment: payload.comment,
});
return response.data.data;
}
export async function clearFlagCount (store, payload) {
let url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatId}/clearflags`;
let response = await axios.post(url);
return response.data.data;
}
export async function markChatSeen (store, payload) {
if (store.state.user.newMessages) delete store.state.user.newMessages[payload.groupId];
let url = `/api/v4/groups/${payload.groupId}/chat/seen`;
let response = await axios.post(url);
return response.data.data;
}
@@ -0,0 +1,32 @@
import axios from 'axios';
import equipOp from 'common/script/ops/equip';
import hatchOp from 'common/script/ops/hatch';
import feedOp from 'common/script/ops/feed';
export function equip (store, params) {
const user = store.state.user.data;
equipOp(user, {params});
axios
.post(`/api/v4/user/equip/${params.type}/${params.key}`);
// TODO
// .then((res) => console.log('equip', res))
// .catch((err) => console.error('equip', err));
}
export function hatch (store, params) {
const user = store.state.user.data;
hatchOp(user, {params});
axios
.post(`/api/v4/user/hatch/${params.egg}/${params.hatchingPotion}`);
// TODO
// .then((res) => console.log('equip', res))
// .catch((err) => console.error('equip', err));
}
export async function feed (store, params) {
const user = store.state.user.data;
feedOp(user, {params});
const response = await axios
.post(`/api/v4/user/feed/${params.pet}/${params.food}`);
return response.data;
}
+206
View File
@@ -0,0 +1,206 @@
import axios from 'axios';
import omit from 'lodash/omit';
import findIndex from 'lodash/findIndex';
import * as Analytics from 'client/libs/analytics';
export async function getPublicGuilds (store, payload) {
let params = {
type: 'publicGuilds',
paginate: true,
page: payload.page,
};
if (payload.categories) params.categories = payload.categories;
if (payload.minMemberCount) params.minMemberCount = payload.minMemberCount;
if (payload.maxMemberCount) params.maxMemberCount = payload.maxMemberCount;
if (payload.leader) params.leader = payload.leader;
if (payload.member) params.member = payload.member;
if (payload.search) params.search = payload.search;
let response = await axios.get('/api/v4/groups', {
params,
});
return response.data.data;
}
export async function getMyGuilds (store) {
let params = {
type: 'guilds',
};
let response = await axios.get('/api/v4/groups', { params });
let guilds = response.data.data;
store.state.myGuilds = guilds;
return response.data.data;
}
export async function getGroup (store, payload) {
let response = await axios.get(`/api/v4/groups/${payload.groupId}`);
// @TODO: should we store the active group for modifying?
// let guilds = response.data.data;
// store.state.myGuilds = guilds;
// @TODO: Populate with members, challenges, and invites
return response.data.data;
}
export async function join (store, payload) {
const groupId = payload.groupId;
const type = payload.type;
const user = store.state.user.data;
const invitations = user.invitations;
let response;
try {
response = await axios.post(`/api/v4/groups/${groupId}/join`);
} catch (err) {
alert(err.response.data.message);
return;
}
if (type === 'guild') {
const invitationI = invitations.guilds.findIndex(i => i.id === groupId);
if (invitationI !== -1) invitations.guilds.splice(invitationI, 1);
user.guilds.push(groupId);
store.state.myGuilds.push(response.data.data);
} else if (type === 'party') {
const invitationI = invitations.parties.findIndex(i => i.id === groupId);
if (invitationI !== -1) invitations.parties.splice(invitationI, 1);
user.party._id = groupId;
Analytics.updateUser({partyID: groupId});
}
return response.data.data;
}
export async function leave (store, payload) {
// @TODO: is the dafault for keepChallenges 'remain-in-challenges'
let data = {
keep: payload.keep,
keepChallenges: payload.keepChallenges,
};
let response = await axios.post(`/api/v4/groups/${payload.groupId}/leave`, data);
// @TODO: update for party
let index = store.state.user.data.guilds.indexOf(payload.groupId);
store.state.user.data.guilds.splice(index, 1);
if (payload.type === 'myGuilds') {
let guildIndex = findIndex(store.state.myGuilds, (guild) => {
return guild._id === payload.groupId;
});
store.state.myGuilds.splice(guildIndex, 1);
} else if (payload.type === 'party') {
store.state.user.data.party._id = null;
store.state.party.data = {};
store.state.party.status = 'NOT_LOADED';
}
return response.data.data;
}
export async function create (store, payload) {
let response = await axios.post('/api/v4/groups/', payload.group);
let newGroup = response.data.data;
if (newGroup.leader._id === store.state.user.data._id || newGroup.privacy === 'private') {
store.state.myGuilds.push(newGroup);
}
store.state.user.data.guilds.push(newGroup._id);
return newGroup;
}
export async function update (store, payload) {
// Remove populated fields
let groupDetailsToSend = omit(payload.group, ['chat', 'challenges', 'members', 'invites']);
if (groupDetailsToSend.leader && groupDetailsToSend.leader._id) groupDetailsToSend.leader = groupDetailsToSend.leader._id;
let response = await axios.put(`/api/v4/groups/${payload.group.id}`, groupDetailsToSend);
let updatedGroup = response.data.data;
return updatedGroup;
}
export async function rejectInvite (store, payload) {
const groupId = payload.groupId;
const type = payload.type;
const user = store.state.user.data;
const invitations = user.invitations;
let response = await axios.post(`/api/v4/groups/${groupId}/reject-invite`);
if (type === 'guild') {
const invitationI = invitations.guilds.findIndex(i => i.id === groupId);
if (invitationI !== -1) invitations.guilds.splice(invitationI, 1);
} else if (type === 'party') {
const invitationI = invitations.parties.findIndex(i => i.id === groupId);
if (invitationI !== -1) invitations.parties.splice(invitationI, 1);
}
return response;
}
export async function removeMember (store, payload) {
let response = await axios.post(`/api/v4/groups/${payload.groupId}/removeMember/${payload.memberId}`, {
message: payload.message,
});
// @TODO: find guild and remove member
return response;
}
export async function invite (store, payload) {
let response = await axios.post(`/api/v4/groups/${payload.groupId}/invite`, {
uuids: payload.invitationDetails.uuids,
emails: payload.invitationDetails.emails,
usernames: payload.invitationDetails.usernames,
});
// @TODO: find guild and add invites
return response;
}
export async function inviteToQuest (store, payload) {
let response = await axios.post(`/api/v4/groups/${payload.groupId}/quests/invite/${payload.key}`);
// @TODO: Any updates?
return response;
}
export async function addManager (store, payload) {
let response = await axios.post(`/api/v4/groups/${payload.groupId}/add-manager/`, {
managerId: payload.memberId,
});
// @TODO: Add managers to group or does the component handle?
return response;
}
export async function removeManager (store, payload) {
let response = await axios.post(`/api/v4/groups/${payload.groupId}/remove-manager/`, {
managerId: payload.memberId,
});
// @TODO: Add managers to group or does the component handle?
return response;
}
export async function getGroupPlans () {
let response = await axios.get('/api/v4/group-plans');
return response.data.data;
}
+28
View File
@@ -0,0 +1,28 @@
import axios from 'axios';
export async function getHeroes () {
let url = '/api/v4/hall/heroes';
let response = await axios.get(url);
return response.data.data;
}
export async function getHero (store, payload) {
let url = `/api/v4/hall/heroes/${payload.uuid}`;
let response = await axios.get(url);
return response.data.data;
}
export async function updateHero (store, payload) {
let url = `/api/v4/hall/heroes/${payload.heroDetails._id}`;
let response = await axios.put(url, payload.heroDetails);
return response.data.data;
}
export async function getPatrons (store, payload) {
let page = 0;
if (payload.page) page = payload.page;
let url = `/api/v4/hall/patrons/?page=${page}`;
let response = await axios.get(url);
return response.data.data;
}
+42
View File
@@ -0,0 +1,42 @@
import { flattenAndNamespace } from 'client/libs/store/helpers/internals';
import * as common from './common';
import * as user from './user';
import * as tasks from './tasks';
import * as guilds from './guilds';
import * as party from './party';
import * as members from './members';
import * as auth from './auth';
import * as quests from './quests';
import * as challenges from './challenges';
import * as chat from './chat';
import * as notifications from './notifications';
import * as tags from './tags';
import * as hall from './hall';
import * as shops from './shops';
import * as snackbars from './snackbars';
import * as worldState from './world-state';
// Actions should be named as 'actionName' and can be accessed as 'namespace:actionName'
// Example: fetch in user.js -> 'user:fetch'
const actions = flattenAndNamespace({
common,
user,
tasks,
guilds,
party,
members,
auth,
quests,
challenges,
chat,
notifications,
tags,
hall,
shops,
snackbars,
worldState,
});
export default actions;
+134
View File
@@ -0,0 +1,134 @@
import axios from 'axios';
// import omit from 'lodash/omit';
// import findIndex from 'lodash/findIndex';
let apiv4Prefix = '/api/v4';
export async function getGroupMembers (store, payload) {
let url = `${apiv4Prefix}/groups/${payload.groupId}/members`;
const params = {};
if (payload.includeAllPublicFields) {
params.includeAllPublicFields = true;
}
if (payload.lastMemberId) {
params.lastId = payload.lastMemberId;
}
if (payload.searchTerm) {
params.search = payload.searchTerm;
}
let response = await axios.get(url, { params });
return response.data.data;
}
export async function fetchMember (store, payload) {
let url = `${apiv4Prefix}/members/${payload.memberId}`;
let response = await axios.get(url);
return response;
}
export async function getGroupInvites (store, payload) {
let url = `${apiv4Prefix}/groups/${payload.groupId}/invites`;
if (payload.includeAllPublicFields) {
url += '?includeAllPublicFields=true';
}
let response = await axios.get(url);
return response.data.data;
}
export async function getChallengeMembers (store, payload) {
let url = `${apiv4Prefix}/challenges/${payload.challengeId}/members`;
const params = {};
if (payload.includeAllPublicFields) {
params.includeAllPublicFields = true;
}
if (payload.lastMemberId) {
params.lastId = payload.lastMemberId;
}
if (payload.searchTerm) {
params.search = payload.searchTerm;
}
let response = await axios.get(url, { params });
return response.data.data;
}
export async function getChallengeMemberProgress (store, payload) {
let url = `${apiv4Prefix}/challenges/${payload.challengeId}/members/${payload.memberId}`;
let response = await axios.get(url);
return response;
}
export async function sendPrivateMessage (store, payload) {
let url = `${apiv4Prefix}/members/send-private-message`;
let data = {
message: payload.message,
toUserId: payload.toUserId,
};
let response = await axios.post(url, data);
return response;
}
export async function transferGems (store, payload) {
let url = `${apiv4Prefix}/members/transfer-gems`;
let data = {
message: payload.message,
toUserId: payload.toUserId,
gemAmount: payload.gemAmount,
};
let response = await axios.post(url, data);
store.state.user.data.balance -= payload.gemAmount / 4;
return response;
}
export async function removeMember (store, payload) {
let url = `${apiv4Prefix}/groups/${payload.groupId}/removeMember/${payload.memberId}`;
let data = {
message: payload.message,
};
let response = await axios.post(url, data);
return response;
}
// export async function selectMember (uid) {
// let memberIsReady = _checkIfMemberIsReady(members[uid]);
//
// if (memberIsReady) {
// _prepareMember(members[uid], self);
// return
// } else {
// fetchMember(uid)
// .then(function (response) {
// var member = response.data.data;
// addToMembersList(member); // lazy load for later
// _prepareMember(member, self);
// deferred.resolve();
// });
// }
// }
// function addToMembersList (member) {
// if (member._id) {
// members[member._id] = member;
// }
// }
// function _checkIfMemberIsReady (member) {
// return member && member.items && member.items.weapon;
// }
//
// function _prepareMember(member, self) {
// self.selectedMember = members[member._id];
// }
//
// $rootScope.$on('userUpdated', function(event, user){
// addToMembersList(user);
// })
@@ -0,0 +1,29 @@
import axios from 'axios';
export async function readNotification (store, payload) {
let url = `/api/v4/notifications/${payload.notificationId}/read`;
let response = await axios.post(url);
return response.data.data;
}
export async function readNotifications (store, payload) {
let url = '/api/v4/notifications/read';
let response = await axios.post(url, {
notificationIds: payload.notificationIds,
});
return response.data.data;
}
export async function seeNotification (store, payload) {
let url = `/api/v4/notifications/${payload.notificationId}/see`;
let response = await axios.post(url);
return response.data.data;
}
export async function seeNotifications (store, payload) {
let url = '/api/v4/notifications/see';
let response = await axios.post(url, {
notificationIds: payload.notificationIds,
});
return response.data.data;
}
+25
View File
@@ -0,0 +1,25 @@
import { loadAsyncResource } from 'client/libs/asyncResource';
export function getMembers (store, forceLoad = false) {
return loadAsyncResource({
store,
path: 'partyMembers',
url: '/api/v4/groups/party/members?includeAllPublicFields=true',
deserialize (response) {
return response.data.data;
},
forceLoad,
});
}
export function getParty (store, forceLoad = false) {
return loadAsyncResource({
store,
path: 'party',
url: '/api/v4/groups/party',
deserialize (response) {
return response.data.data;
},
forceLoad,
});
}
@@ -0,0 +1,30 @@
import axios from 'axios';
import * as Analytics from 'client/libs/analytics';
// export async function initQuest (store) {
// }
export async function sendAction (store, payload) {
// @TODO: Maybe move this to server
let partyData = {};
if (store.state.party && store.state.party.data) {
partyData = {
partyID: store.state.party.data._id,
partySize: store.state.party.data.memberCount,
};
} else {
partyData = {
partyID: store.state.user.data.party._id,
partySize: store.state.partyMembers.data.length,
};
}
Analytics.updateUser(partyData);
let response = await axios.post(`/api/v4/groups/${payload.groupId}/${payload.action}`);
// @TODO: Update user?
// User.sync();
return response.data.quest || response.data.data;
}
+196
View File
@@ -0,0 +1,196 @@
import axios from 'axios';
import buyOp from 'common/script/ops/buy/buy';
import content from 'common/script/content/index';
import hourglassPurchaseOp from 'common/script/ops/buy/hourglassPurchase';
import sellOp from 'common/script/ops/sell';
import unlockOp from 'common/script/ops/unlock';
import rerollOp from 'common/script/ops/reroll';
import releasePetsOp from 'common/script/ops/releasePets';
import releaseMountsOp from 'common/script/ops/releaseMounts';
import releaseBothOp from 'common/script/ops/releaseBoth';
import { getDropClass } from 'client/libs/notifications';
// @TODO: Purchase means gems and buy means gold. That wording is misused below, but we should also change
// the generic buy functions to something else. Or have a Gold Vendor and Gem Vendor, etc
function buyItem (store, params) {
const quantity = params.quantity || 1;
const user = store.state.user.data;
let opResult = buyOp(user, {params, quantity});
return {
result: opResult,
httpCall: axios.post(`/api/v4/user/buy/${params.key}`),
};
}
export function buyQuestItem (store, params) {
const quantity = params.quantity || 1;
const user = store.state.user.data;
let opResult = buyOp(user, {
params,
type: 'quest',
quantity,
});
return {
result: opResult,
httpCall: axios.post(`/api/v4/user/buy/${params.key}`, {type: 'quest', quantity}),
};
}
async function buyArmoire (store, params) {
const quantity = params.quantity || 1;
let armoire = content.armoire;
// We need the server result because Armoire has random item in the result
let result = await axios.post('/api/v4/user/buy/armoire', {
type: 'armoire',
quantity,
});
let buyResult = result.data.data;
if (buyResult) {
const resData = buyResult;
const item = resData.armoire;
const message = result.data.message;
const isExperience = item.type === 'experience';
if (item.type === 'gear') {
store.state.user.data.items.gear.owned[item.dropKey] = true;
}
if (item.type === 'food') {
if (!store.state.user.data.items.food[item.dropKey]) store.state.user.data.items.food[item.dropKey] = 0;
store.state.user.data.items.food[item.dropKey] += 1;
}
store.state.user.data.stats.gp -= armoire.value;
// @TODO: We might need to abstract notifications to library rather than mixin
const notificationOptions = isExperience ?
{
text: `+ ${item.value}`,
type: 'xp',
flavorMessage: message,
} :
{
text: message,
type: 'drop',
icon: getDropClass({type: item.type, key: item.dropKey}),
};
store.dispatch('snackbars:add', {
title: '',
timeout: true,
...notificationOptions,
});
}
}
export function purchase (store, params) {
const quantity = params.quantity || 1;
const user = store.state.user.data;
let opResult = buyOp(user, {params, quantity});
return {
result: opResult,
httpCall: axios.post(`/api/v4/user/purchase/${params.type}/${params.key}`, {quantity}),
};
}
export function purchaseMysterySet (store, params) {
const user = store.state.user.data;
let opResult = buyOp(user, {params, type: 'mystery'});
return {
result: opResult,
httpCall: axios.post(`/api/v4/user/buy/${params.key}`, {type: 'mystery'}),
};
}
export function purchaseHourglassItem (store, params) {
const quantity = params.quantity || 1;
const user = store.state.user.data;
let opResult = hourglassPurchaseOp(user, {params, quantity});
return {
result: opResult,
httpCall: axios.post(`/api/v4/user/purchase-hourglass/${params.type}/${params.key}`, {quantity}),
};
}
export function unlock (store, params) {
const user = store.state.user.data;
let opResult = unlockOp(user, params);
return {
result: opResult,
httpCall: axios.post(`/api/v4/user/unlock?path=${params.query.path}`),
};
}
export async function genericPurchase (store, params) {
switch (params.pinType) {
case 'mystery_set':
return purchaseMysterySet(store, params);
case 'armoire': // eslint-disable-line
await buyArmoire(store, params);
return;
case 'fortify': {
let rerollResult = rerollOp(store.state.user.data, store.state.tasks.data);
await axios.post('/api/v4/user/reroll');
await Promise.all([
store.dispatch('user:fetch', {forceLoad: true}),
store.dispatch('tasks:fetchUserTasks', {forceLoad: true}),
]);
return rerollResult;
}
case 'rebirth_orb':
return store.dispatch('user:rebirth');
case 'potion':
case 'marketGear':
// 'marketGear' gets `type`= `gear` which is used for gem-purchasable gear
// resetting type to pinType only here
return buyItem(store, {...params, type: params.pinType});
case 'background':
return unlock(store, {
query: {
path: `background.${params.key}`,
},
});
default:
if (params.pinType === 'quests' && params.currency === 'gold') {
return buyQuestItem(store, params);
} else if (params.currency === 'hourglasses') {
return purchaseHourglassItem(store, params);
} else {
return purchase(store, params);
}
}
}
export function sellItems (store, params) {
const user = store.state.user.data;
sellOp(user, {params, query: {amount: params.amount}});
axios.post(`/api/v4/user/sell/${params.type}/${params.key}?amount=${params.amount}`);
}
export function releasePets (store, params) {
releasePetsOp(params.user);
axios.post('/api/v4/user/release-pets');
}
export function releaseMounts (store, params) {
releaseMountsOp(params.user);
axios.post('/api/v4/user/release-mounts');
}
export function releaseBoth (store, params) {
releaseBothOp(params.user);
axios.post('/api/v4/user/release-both');
}
@@ -0,0 +1,13 @@
import uuid from 'uuid';
export function add (store, payload) {
let notification = Object.assign({}, payload);
notification.uuid = uuid();
store.state.notificationStore.push(notification);
}
export function remove (store, payload) {
store.state.notificationStore = store.state.notificationStore.filter(notification => {
return notification.uuid !== payload.uuid;
});
}
+44
View File
@@ -0,0 +1,44 @@
import axios from 'axios';
export async function getTags () {
let url = 'api/v4/tags';
let response = await axios.get(url);
return response.data.data;
}
export async function createTag (store, payload) {
let url = 'api/v4/tags';
let response = await axios.post(url, {
tagDetails: payload.tagDetails,
});
return response.data.data;
}
export async function getTag (store, payload) {
let url = `api/v4/tags/${payload.tagId}`;
let response = await axios.get(url);
return response.data.data;
}
export async function updateTag (store, payload) {
let url = `api/v4/tags/${payload.tagId}`;
let response = await axios.put(url, {
tagDetails: payload.tagDetails,
});
return response.data.data;
}
export async function sortTag (store, payload) {
let url = 'api/v4/reorder-tags';
let response = await axios.post(url, {
tagId: payload.tagId,
to: payload.to,
});
return response.data.data;
}
export async function deleteTag (store, payload) {
let url = `api/v4/tags/${payload.tagId}`;
let response = await axios.delete(url);
return response.data.data;
}
+232
View File
@@ -0,0 +1,232 @@
import { loadAsyncResource } from 'client/libs/asyncResource';
import axios from 'axios';
import compact from 'lodash/compact';
import omit from 'lodash/omit';
export function fetchUserTasks (store, options = {}) {
return loadAsyncResource({
store,
path: 'tasks',
url: '/api/v4/tasks/user',
deserialize (response) {
// Wait for the user to be loaded before deserializing
// because user.tasksOrder is necessary
return store.dispatch('user:fetch').then((userResource) => {
return store.dispatch('tasks:order', [response.data.data, userResource.data.tasksOrder]);
});
},
forceLoad: options.forceLoad,
});
}
export async function fetchCompletedTodos (store) {
// Wait for the user to be loaded before deserializing
// because user.tasksOrder is necessary
await store.dispatch('tasks:fetchUserTasks');
const loadStatus = store.state.completedTodosStatus;
if (loadStatus !== 'LOADING') {
store.state.completedTodosStatus = 'LOADING';
const response = await axios.get('/api/v4/tasks/user?type=completedTodos');
const completedTodos = response.data.data;
const tasks = store.state.tasks.data;
// Remove existing completed todos
tasks.todos = tasks.todos.filter(t => !t.completed);
tasks.todos.push(...completedTodos);
store.state.completedTodosStatus = 'LOADED';
}
}
export async function clearCompletedTodos (store) {
await axios.post('/api/v4/tasks/clearCompletedTodos');
store.state.tasks.data.todos = store.state.tasks.data.todos.filter(task => {
return !task.completed;
});
}
export function order (store, [rawTasks, tasksOrder]) {
const tasks = {
habits: [],
dailys: [],
todos: [],
rewards: [],
};
rawTasks.forEach(task => {
tasks[`${task.type}s`].push(task);
});
Object.keys(tasks).forEach((type) => {
let tasksOfType = tasks[type];
const orderOfType = tasksOrder[type];
const orderedTasks = new Array(tasksOfType.length);
const unorderedTasks = []; // what we want to add later
tasksOfType.forEach((task, index) => {
const taskId = task._id;
const i = orderOfType[index] === taskId ? index : orderOfType.indexOf(taskId);
if (i === -1) {
unorderedTasks.push(task);
} else {
orderedTasks[i] = task;
}
});
tasks[type] = compact(orderedTasks).concat(unorderedTasks);
});
return tasks;
}
function sanitizeChecklist (task) {
if (task.checklist) {
task.checklist = task.checklist.filter((i) => {
return Boolean(i.text);
});
}
}
// Supply an array to create multiple tasks
export async function create (store, createdTask) {
// Treat all create actions as if we are adding multiple tasks
const payload = Array.isArray(createdTask) ? createdTask : [createdTask];
payload.forEach(t => {
const type = `${t.type}s`;
const list = store.state.tasks.data[type];
sanitizeChecklist(t);
list.unshift(t);
store.state.user.data.tasksOrder[type].unshift(t._id);
});
const response = await axios.post('/api/v4/tasks/user', payload);
const data = Array.isArray(response.data.data) ? response.data.data : [response.data.data];
data.forEach(taskRes => {
const taskData = store.state.tasks.data[`${taskRes.type}s`].find(t => t._id === taskRes._id);
Object.assign(taskData, taskRes);
});
}
export async function save (store, editedTask) {
const taskId = editedTask._id;
const type = editedTask.type;
const originalTask = store.state.tasks.data[`${type}s`].find(t => t._id === taskId);
sanitizeChecklist(editedTask);
if (originalTask) Object.assign(originalTask, editedTask);
const taskDataToSend = omit(editedTask, ['history']);
const response = await axios.put(`/api/v4/tasks/${taskId}`, taskDataToSend);
if (originalTask) Object.assign(originalTask, response.data.data);
}
export async function scoreChecklistItem (store, {taskId, itemId}) {
await axios.post(`/api/v4/tasks/${taskId}/checklist/${itemId}/score`);
}
export async function collapseChecklist (store, task) {
task.collapseChecklist = !task.collapseChecklist;
await axios.put(`/api/v4/tasks/${task._id}`, {
collapseChecklist: task.collapseChecklist,
});
}
export async function destroy (store, task) {
const list = store.state.tasks.data[`${task.type}s`];
const taskIndex = list.findIndex(t => t._id === task._id);
if (taskIndex > -1) {
list.splice(taskIndex, 1);
}
await axios.delete(`/api/v4/tasks/${task._id}`);
}
export async function getChallengeTasks (store, payload) {
let response = await axios.get(`/api/v4/tasks/challenge/${payload.challengeId}`);
return response.data.data;
}
export async function createChallengeTasks (store, payload) {
let response = await axios.post(`/api/v4/tasks/challenge/${payload.challengeId}`, payload.tasks);
return response.data.data;
}
export async function getGroupTasks (store, payload) {
let response = await axios.get(`/api/v4/tasks/group/${payload.groupId}`);
return response.data.data;
}
export async function getCompletedGroupTasks (store, payload) {
let response = await axios.get(`/api/v4/tasks/group/${payload.groupId}?type=completedTodos`);
return response.data.data;
}
export async function createGroupTasks (store, payload) {
let response = await axios.post(`/api/v4/tasks/group/${payload.groupId}`, payload.tasks);
return response.data.data;
}
export async function assignTask (store, payload) {
let response = await axios.post(`/api/v4/tasks/${payload.taskId}/assign/${payload.userId}`);
return response.data.data;
}
export async function unassignTask (store, payload) {
let response = await axios.post(`/api/v4/tasks/${payload.taskId}/unassign/${payload.userId}`);
return response.data.data;
}
export async function needsWork (store, payload) {
let response = await axios.post(`/api/v4/tasks/${payload.taskId}/needs-work/${payload.userId}`);
return response.data.data;
}
export async function getGroupApprovals (store, payload) {
let response = await axios.get(`/api/v4/approvals/group/${payload.groupId}`);
return response.data.data;
}
export async function approve (store, payload) {
let response = await axios.post(`/api/v4/tasks/${payload.taskId}/approve/${payload.userId}`);
return response.data.data;
}
export async function unlinkOneTask (store, payload) {
if (!payload.keep) payload.keep = 'keep';
let task = payload.task;
const list = store.state.tasks.data[`${task.type}s`];
const taskIndex = list.findIndex(t => t._id === task._id);
if (taskIndex > -1) {
list.splice(taskIndex, 1);
}
let response = await axios.post(`/api/v4/tasks/unlink-one/${payload.task._id}?keep=${payload.keep}`);
return response.data.data;
}
export async function unlinkAllTasks (store, payload) {
if (!payload.keep) payload.keep = 'keep-all';
let response = await axios.post(`/api/v4/tasks/unlink-all/${payload.challengeId}?keep=${payload.keep}`);
return response.data.data;
}
export async function move (store, payload) {
let response = await axios.post(`/api/v4/tasks/${payload.taskId}/move/to/${payload.position}`);
return response.data.data;
}
export async function moveGroupTask (store, payload) {
let response = await axios.post(`/api/v4/group-tasks/${payload.taskId}/move/to/${payload.position}`);
return response.data.data;
}
+165
View File
@@ -0,0 +1,165 @@
import { loadAsyncResource } from 'client/libs/asyncResource';
import setProps from 'lodash/set';
import axios from 'axios';
import { togglePinnedItem as togglePinnedItemOp } from 'common/script/ops/pinnedGearUtils';
import changeClassOp from 'common/script/ops/changeClass';
import disableClassesOp from 'common/script/ops/disableClasses';
import openMysteryItemOp from 'common/script/ops/openMysteryItem';
export function fetch (store, options = {}) { // eslint-disable-line no-shadow
return loadAsyncResource({
store,
path: 'user',
url: '/api/v4/user',
deserialize (response) {
return response.data.data;
},
forceLoad: options.forceLoad,
});
}
export async function set (store, changes) {
const user = store.state.user.data;
for (let key in changes) {
if (key === 'tags') {
// Keep challenge and group tags
const oldTags = user.tags.filter(t => {
return t.group;
});
user.tags = changes[key].concat(oldTags);
// Remove deleted tags from tasks
const userTasksByType = (await store.dispatch('tasks:fetchUserTasks')).data; // eslint-disable-line no-await-in-loop
Object.keys(userTasksByType).forEach(taskType => {
userTasksByType[taskType].forEach(task => {
const tagsIndexesToRemove = [];
task.tags.forEach((tagId, tagIndex) => {
if (user.tags.find(tag => tag.id === tagId)) return; // eslint-disable-line max-nested-callbacks
tagsIndexesToRemove.push(tagIndex);
});
tagsIndexesToRemove.forEach(i => task.tags.splice(i, 1));
});
});
} else {
setProps(user, key, changes[key]);
}
}
let response = await axios.put('/api/v4/user', changes);
return response.data.data;
}
export async function sleep (store) {
const user = store.state.user.data;
user.preferences.sleep = !user.preferences.sleep;
let response = await axios.post('/api/v4/user/sleep');
return response.data.data;
}
export async function addWebhook (store, payload) {
let response = await axios.post('/api/v4/user/webhook', payload.webhookInfo);
return response.data.data;
}
export async function updateWebhook (store, payload) {
let response = await axios.put(`/api/v4/user/webhook/${payload.webhook.id}`, payload.webhook);
return response.data.data;
}
export async function deleteWebhook (store, payload) {
let response = await axios.delete(`/api/v4/user/webhook/${payload.webhook.id}`);
return response.data.data;
}
export async function changeClass (store, params) {
const user = store.state.user.data;
changeClassOp(user, params);
user.flags.classSelected = true;
let response = await axios.post(`/api/v4/user/change-class?class=${params.query.class}`);
return response.data.data;
}
export async function disableClasses (store) {
const user = store.state.user.data;
disableClassesOp(user);
let response = await axios.post('/api/v4/user/disable-classes');
return response.data.data;
}
export function togglePinnedItem (store, params) {
const user = store.state.user.data;
let addedItem = togglePinnedItemOp(user, params);
axios.get(`/api/v4/user/toggle-pinned-item/${params.type}/${params.path}`);
// TODO
// .then((res) => console.log('equip', res))
// .catch((err) => console.error('equip', err));
return addedItem;
}
export async function movePinnedItem (store, params) {
let response = await axios.post(`/api/v4/user/move-pinned-item/${params.path}/move/to/${params.position}`);
return response.data.data;
}
export function castSpell (store, params) {
let spellUrl = `/api/v4/user/class/cast/${params.key}`;
const data = {};
if (params.targetId) spellUrl += `?targetId=${params.targetId}`;
if (params.quantity) data.quantity = params.quantity;
return axios.post(spellUrl, data);
}
export async function openMysteryItem (store) {
let user = store.state.user.data;
openMysteryItemOp(user);
return axios.post('/api/v4/user/open-mystery-item');
}
export function newStuffLater (store) {
store.state.user.data.flags.newStuff = false;
return axios.post('/api/v4/news/tell-me-later');
}
export async function rebirth () {
let result = await axios.post('/api/v4/user/rebirth');
return result;
}
export async function togglePrivateMessagesOpt (store) {
let response = await axios.put('/api/v4/user',
{
'inbox.optOut': !store.state.user.data.inbox.optOut,
}
);
store.state.user.data.inbox.optOut = !store.state.user.data.inbox.optOut;
return response;
}
export async function userLookup (store, params) {
let response;
if (params.uuid) {
response = await axios.get(`/api/v4/members/${params.uuid}`);
}
if (params.username) {
response = await axios.get(`/api/v4/members/username/${params.username}`);
}
return response;
}
@@ -0,0 +1,7 @@
import axios from 'axios';
export async function getWorldState () {
const url = '/api/v4/world-state';
const response = await axios.get(url);
return response.data.data;
}
+19
View File
@@ -0,0 +1,19 @@
import { flattenAndNamespace } from 'client/libs/store/helpers/internals';
import * as user from './user';
import * as shops from './shops';
import * as tasks from './tasks';
import * as party from './party';
import * as members from './members';
// Getters should be named as 'getterName' and can be accessed as 'namespace:getterName'
// Example: gems in user.js -> 'user:gems'
const getters = flattenAndNamespace({
user,
tasks,
party,
members,
shops,
});
export default getters;
@@ -0,0 +1,12 @@
import memberHasClass from 'common/script/libs/hasClass';
export function isBuffed () {
return (member) => {
const buffs = member.stats.buffs;
return buffs.str || buffs.per || buffs.con || buffs.int;
};
}
export function hasClass () {
return memberHasClass;
}
@@ -0,0 +1,3 @@
export function members (store) {
return store.state.partyMembers.data;
}
@@ -0,0 +1,7 @@
export function market (store) {
return store.state.shops.market;
}
export function quests (store) {
return store.state.shops.market;
}
+162
View File
@@ -0,0 +1,162 @@
import { shouldDo } from 'common/script/cron';
// Library / Utility function
import { orderSingleTypeTasks } from 'client/libs/store/helpers/orderTasks.js';
import { getActiveFilter } from 'client/libs/store/helpers/filterTasks.js';
import sortBy from 'lodash/sortBy';
// Return all the tags belonging to an user task
export function getTagsFor (store) {
return (task) => {
return store.state.user.data.tags
.filter(tag => task.tags && task.tags.indexOf(tag.id) !== -1)
.map(tag => tag.name);
};
}
function getTaskColor (task) {
if (task.type === 'reward' || task.byHabitica) return 'purple';
const value = task.value;
if (value < -20) {
return 'worst';
} else if (value < -10) {
return 'worse';
} else if (value < -1) {
return 'bad';
} else if (value < 1) {
return 'neutral';
} else if (value < 5) {
return 'good';
} else if (value < 10) {
return 'better';
} else {
return 'best';
}
}
export function canDelete () {
return (task) => {
let isUserChallenge = Boolean(task.userId);
let activeChallenge = isUserChallenge && task.challenge && task.challenge.id && !task.challenge.broken;
return !activeChallenge;
};
}
export function getTaskClasses (store) {
const userPreferences = store.state.user.data.preferences;
// Purpose can be one of the following strings:
// Edit Modal: edit-modal-bg, edit-modal-text, edit-modal-icon
// Create Modal: create-modal-bg, create-modal-text, create-modal-icon
// Control: 'control'
return (task, purpose, dueDate) => {
if (!dueDate) dueDate = new Date();
const type = task.type;
const color = getTaskColor(task);
switch (purpose) {
case 'edit-modal-bg':
return `task-${color}-modal-bg`;
case 'edit-modal-text':
return `task-${color}-modal-text`;
case 'edit-modal-icon':
return `task-${color}-modal-icon`;
case 'edit-modal-option-disabled':
return `task-${color}-modal-option-disabled`;
case 'edit-modal-habit-control-disabled':
return `task-${color}-modal-habit-control-disabled`;
case 'create-modal-bg':
return 'task-purple-modal-bg';
case 'create-modal-text':
return 'task-purple-modal-text';
case 'create-modal-icon':
return 'task-purple-modal-icon';
case 'create-modal-option-disabled':
return 'task-purple-modal-option-disabled';
case 'create-modal-habit-control-disabled':
return 'task-purple-modal-habit-control-disabled';
case 'control':
if (type === 'todo' || type === 'daily') {
if (task.completed || !shouldDo(dueDate, task, userPreferences) && type === 'daily') {
return {
bg: 'task-disabled-daily-todo-control-bg',
checkbox: 'task-disabled-daily-todo-control-checkbox',
inner: 'task-disabled-daily-todo-control-inner',
content: 'task-disabled-daily-todo-control-content',
};
}
return {
bg: task.group && task.group.id && !task.userId ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`,
checkbox: `task-${color}-control-checkbox`,
inner: `task-${color}-control-inner-daily-todo`,
icon: `task-${color}-control-icon`,
};
} else if (type === 'reward') {
return {
bg: task.group && task.group.id && !task.userId ? 'task-reward-control-bg-noninteractive' : 'task-reward-control-bg',
};
} else if (type === 'habit') {
return {
up: task.up ?
{ bg: task.group && task.group.id && !task.userId ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`, inner: `task-${color}-control-inner-habit`, icon: `task-${color}-control-icon`} :
{ bg: 'task-disabled-habit-control-bg', inner: 'task-disabled-habit-control-inner', icon: `task-${color}-control-icon` },
down: task.down ?
{ bg: task.group && task.group.id && !task.userId ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`, inner: `task-${color}-control-inner-habit`, icon: `task-${color}-control-icon`} :
{ bg: 'task-disabled-habit-control-bg', inner: 'task-disabled-habit-control-inner', icon: `task-${color}-control-icon` },
};
}
break;
default:
return 'not a valid class';
}
};
}
// Returns all list for given task type
export function getUnfilteredTaskList ({state}) {
return (type) => state.tasks.data[`${type}s`];
}
// Returns filtered, sorted, ordered, tag filtered, and search filtered task list
// @TODO: sort task list based on used preferences
export function getFilteredTaskList ({state, getters}) {
return ({
type,
filterType = '',
}) => {
// get requested tasks
// check if task list has been passed as override props
// assumption: type will always be passed as param
let requestedTasks = getters['tasks:getUnfilteredTaskList'](type);
let userPreferences = state.user.data.preferences;
let taskOrderForType = state.user.data.tasksOrder[type];
// order tasks based on user set task order
// Still needs unit test for this..
if (requestedTasks.length > 0 && ['scheduled', 'due'].indexOf(filterType.label) === -1) {
requestedTasks = orderSingleTypeTasks(requestedTasks, taskOrderForType);
}
let selectedFilter = getActiveFilter(type, filterType);
// Pass user preferences to the filter function which uses currying
if (type === 'daily' && (filterType === 'due' || filterType === 'notDue')) {
selectedFilter = {
...selectedFilter,
filterFn: selectedFilter.filterFn(userPreferences),
};
}
requestedTasks = requestedTasks.filter(selectedFilter.filterFn);
if (selectedFilter.sort) {
requestedTasks = sortBy(requestedTasks, selectedFilter.sort);
}
return requestedTasks;
};
}
+19
View File
@@ -0,0 +1,19 @@
export function data (store) {
return store.state.user.data;
}
export function gems (store) {
return store.state.user.data.balance * 4;
}
export function buffs (store) {
return (key) => store.state.user.data.stats.buffs[key];
}
export function preferences (store) {
return store.state.user.data.preferences;
}
export function tasksOrder (store) {
return (type) => store.state.user.tasksOrder[`${type}s`];
}
+131
View File
@@ -0,0 +1,131 @@
import Store from 'client/libs/store';
import deepFreeze from 'client/libs/deepFreeze';
import content from 'common/script/content/index';
import * as commonConstants from 'common/script/constants';
import { DAY_MAPPING } from 'common/script/cron';
import { asyncResourceFactory } from 'client/libs/asyncResource';
import { setUpAxios } from 'client/libs/auth';
import axios from 'axios';
import moment from 'moment';
import actions from './actions';
import getters from './getters';
const IS_TEST = process.env.NODE_ENV === 'test'; // eslint-disable-line no-process-env
// Load user auth parameters and determine if it's logged in
// before trying to load data
let isUserLoggedIn = false;
let browserTimezoneOffset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60)
axios.defaults.headers.common['x-client'] = 'habitica-web';
let AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
if (AUTH_SETTINGS) {
AUTH_SETTINGS = JSON.parse(AUTH_SETTINGS);
isUserLoggedIn = setUpAxios(AUTH_SETTINGS);
}
const i18nData = window && window['habitica-i18n'];
let availableLanguages = [];
let selectedLanguage = {};
if (i18nData) {
availableLanguages = i18nData.availableLanguages;
selectedLanguage = i18nData.language;
}
// Export a function that generates the store and not the store directly
// so that we can regenerate it multiple times for testing, when not testing
// always export the same route
let existingStore;
export default function () {
if (!IS_TEST && existingStore) return existingStore;
existingStore = new Store({
actions,
getters,
state: {
serverAppVersion: '',
title: 'Habitica',
isUserLoggedIn,
isUserLoaded: false, // Means the user and the user's tasks are ready
isAmazonReady: false, // Whether the Amazon Payments lib can be used
user: asyncResourceFactory(),
credentials: isUserLoggedIn ? {
API_ID: AUTH_SETTINGS.auth.apiId,
API_TOKEN: AUTH_SETTINGS.auth.apiToken,
} : {},
// store the timezone offset in case it's different than the one in
// user.preferences.timezoneOffset and change it after the user is synced
// in app.vue
browserTimezoneOffset,
tasks: asyncResourceFactory(), // user tasks
completedTodosStatus: 'NOT_LOADED',
party: asyncResourceFactory(),
partyMembers: asyncResourceFactory(),
shops: {
market: asyncResourceFactory(),
quests: asyncResourceFactory(),
seasonal: asyncResourceFactory(),
'time-travelers': asyncResourceFactory(),
},
myGuilds: [],
groupFormOptions: {
creatingParty: false,
groupId: '',
},
avatarEditorOptions: {
editingUser: false,
startingPage: '',
subPage: '',
},
challengeOptions: {
cloning: false,
tasksToClone: {},
workingChallenge: {},
},
editingGroup: {}, // @TODO move to local state
// content data, frozen to prevent Vue from modifying it since it's static and never changes
// @TODO apply freezing to the entire codebase (the server) and not only to the client side?
// NOTE this takes about 10-15ms on a fast computer
content: deepFreeze(content),
constants: deepFreeze({...commonConstants, DAY_MAPPING}),
i18n: deepFreeze({
availableLanguages,
selectedLanguage,
}),
hideHeader: false,
memberModalOptions: {
viewingMembers: [],
groupId: '',
challengeId: '',
group: {},
},
openedItemRows: [],
spellOptions: {
castingSpell: false,
spellDrawOpen: true,
},
profileOptions: {
startingPage: '',
},
gemModalOptions: {
startingPage: '',
},
rageModalOptions: {
npc: '',
},
profileUser: {},
upgradingGroup: {},
notificationStore: [],
modalStack: [],
equipmentDrawerOpen: true,
groupPlans: [],
isRunningYesterdailies: false,
},
});
return existingStore;
}