mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-14 11:28:57 -05:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95f9479d7a | |||
| af095d8450 | |||
| 470495387c | |||
| bdef1ca23c | |||
| 1835804e86 | |||
| cb58994bdf | |||
| 44f3b73183 | |||
| 7e23fdc22a | |||
| e138d2b67b | |||
| 3e3248fecb | |||
| 78ee60611a | |||
| 3c7aaa605b | |||
| 00343da266 | |||
| 56d09411d9 | |||
| ae0df2242a | |||
| 5b06b28c97 | |||
| c6a3bfb291 | |||
| 7797794cd5 | |||
| d9e09a5f3d | |||
| 4e73c8513e | |||
| 9421fd7ced | |||
| 699de64328 | |||
| 6f9cbf9ca1 | |||
| a097819b72 | |||
| 77f71b5415 | |||
| ced3621dea | |||
| e321d85b3c | |||
| d72b40d5b0 | |||
| 54443a2980 | |||
| 00dc990974 | |||
| 3737aa045d | |||
| b03ddf6f7d | |||
| 4ab89fd3e0 | |||
| f1e200c0f5 | |||
| 218664dfcc | |||
| a0f29e970d | |||
| 200cd66d66 | |||
| dd05a8d608 | |||
| 299e88233c | |||
| 26bde1f766 | |||
| d95836b881 | |||
| fac81bb9ee | |||
| b323abd225 | |||
| b3870e5f34 | |||
| 29dc56c12f | |||
| b62f08d500 | |||
| f62177fb1a | |||
| 885f2998ae | |||
| 2afd96e11c | |||
| cd92f44365 | |||
| 863177902a | |||
| 96974461e5 | |||
| 8895b70ffa | |||
| 03480ebfc7 | |||
| 9b8676f02e | |||
| 3e7738b5b1 | |||
| 33a235b46c | |||
| 137d6c1f9d | |||
| 1a5e820d88 | |||
| 0c7f9ca6bb | |||
| 3e6b3ce3ff | |||
| ea5ba965e7 | |||
| 7215a550b5 | |||
| 3235dfa236 | |||
| 9baf7a7c67 | |||
| cd629ef7fa | |||
| 9ef7c45241 | |||
| fef3d09f2d | |||
| 53c83c585a | |||
| e628c5dc3b | |||
| 9eaa531f66 | |||
| 3ffea4332e | |||
| 4618fd8954 | |||
| 791c19b5f1 | |||
| 7193cc6bae | |||
| 1845bd1e35 | |||
| 5f468d16b7 | |||
| 20a99e526d | |||
| 1e69f42d0f | |||
| 9c2f5213cb | |||
| c06d5107ac | |||
| 89e4cbcffe | |||
| 67564317fb |
@@ -16,7 +16,7 @@ var migrationName = '20140831_increase_gems_for_previous_contributions';
|
||||
* https://github.com/HabitRPG/habitrpg/issues/3933
|
||||
* Increase Number of Gems for Contributors
|
||||
* author: Alys (d904bd62-da08-416b-a816-ba797c9ee265)
|
||||
*
|
||||
*
|
||||
* Increase everyone's gems per their contribution level.
|
||||
* Originally they were given 2 gems per tier.
|
||||
* Now they are given 3 gems per tier for tiers 1,2,3
|
||||
@@ -70,7 +70,7 @@ dbUsers.findEach(query, fields, function(err, user) {
|
||||
var extraGems = tier; // tiers 1,2,3
|
||||
if (tier > 3) { extraGems = 3 + (tier - 3) * 2; }
|
||||
if (tier == 8) { extraGems = 11; }
|
||||
extraBalance = extraGems / 4;
|
||||
var extraBalance = extraGems / 4;
|
||||
set['balance'] = user.balance + extraBalance;
|
||||
|
||||
// Capture current state of user:
|
||||
|
||||
@@ -39,7 +39,7 @@ function findUsers(gt){
|
||||
console.log('User: ', countUsers, user._id);
|
||||
|
||||
var update = {
|
||||
$set: {};
|
||||
$set: {}
|
||||
};
|
||||
|
||||
if(user.auth && user.auth.local) {
|
||||
@@ -60,4 +60,4 @@ function findUsers(gt){
|
||||
});
|
||||
};
|
||||
|
||||
findUsers();
|
||||
findUsers();
|
||||
|
||||
@@ -17,8 +17,12 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
var processUsers = require('./groups/update-groups-with-group-plans');
|
||||
const processUsers = require('./users/account-transfer');
|
||||
processUsers()
|
||||
.catch(function (err) {
|
||||
console.log(err)
|
||||
.then(() => {
|
||||
process.exit();
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
process.exit();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
var migrationName = 'AccountTransfer';
|
||||
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
|
||||
/*
|
||||
* This migraition will copy user data from prod to test
|
||||
*/
|
||||
|
||||
const monk = require('monk');
|
||||
const connectionString = '';
|
||||
const Users = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
import uniq from 'lodash/uniq';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
|
||||
module.exports = async function accountTransfer () {
|
||||
const fromAccountId = '';
|
||||
const toAccountId = '';
|
||||
|
||||
const fromAccount = await Users.findOne({_id: fromAccountId});
|
||||
const toAccount = await Users.findOne({_id: toAccountId});
|
||||
|
||||
const newMounts = Object.assign({}, fromAccount.items.mounts, toAccount.items.mounts);
|
||||
const newPets = Object.assign({}, fromAccount.items.pets, toAccount.items.pets);
|
||||
const newBackgrounds = Object.assign({}, fromAccount.purchased.background, toAccount.purchased.background);
|
||||
|
||||
await Users.update({_id: toAccountId}, {
|
||||
$set: {
|
||||
'items.pets': newPets,
|
||||
'items.mounts': newMounts,
|
||||
'purchased.background': newBackgrounds,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
console.log(result);
|
||||
});
|
||||
};
|
||||
Generated
+391
-521
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.12.0",
|
||||
"version": "4.12.6",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -122,6 +122,7 @@
|
||||
"vue-router": "^3.0.0",
|
||||
"vue-style-loader": "^3.0.0",
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#825a866b6a9c52dd8c588a3e8b900880875ce914",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-merge": "^4.0.0",
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import config from '../../../../../config.json';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
@@ -74,7 +75,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('can unflag a system message', async () => {
|
||||
it('can\'t flag a system message', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'party',
|
||||
@@ -95,13 +96,15 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
await member.post('/user/class/cast/mpheal');
|
||||
|
||||
let [skillMsg] = await member.get(`/groups/${group.id}/chat`);
|
||||
|
||||
await member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`);
|
||||
await admin.post(`/groups/${group._id}/chat/${skillMsg.id}/clearflags`);
|
||||
|
||||
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].id).to.eql(skillMsg.id);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
await expect(member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS.COMMUNITY_MANAGER_EMAIL}),
|
||||
});
|
||||
// let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
// expect(messages[0].id).to.eql(skillMsg.id);
|
||||
// expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
|
||||
describe('GET /coupons/', () => {
|
||||
let user;
|
||||
@@ -19,7 +19,7 @@ describe('GET /coupons/', () => {
|
||||
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('noSudoAccess'),
|
||||
message: apiMessages('noSudoAccess'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import couponCode from 'coupon-code';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
|
||||
describe('POST /coupons/generate/:event', () => {
|
||||
let user;
|
||||
@@ -25,7 +26,7 @@ describe('POST /coupons/generate/:event', () => {
|
||||
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('noSudoAccess'),
|
||||
message: apiMessages('noSudoAccess'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
each,
|
||||
} from 'lodash';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as payments from '../../../../../website/server/libs/payments';
|
||||
|
||||
describe('POST /groups/:groupId/leave', () => {
|
||||
let typesOfGroups = {
|
||||
@@ -264,4 +266,45 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
expect(userWithNonExistentParty.party).to.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
context('Leaving a group plan', () => {
|
||||
it('cancels the free subscription', async () => {
|
||||
// Create group
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Private Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
let leader = groupLeader;
|
||||
let member = members[0];
|
||||
let userWithFreePlan = await User.findById(leader._id).exec();
|
||||
|
||||
// Create subscription
|
||||
let paymentData = {
|
||||
user: userWithFreePlan,
|
||||
groupId: group._id,
|
||||
sub: {
|
||||
key: 'basic_3mo',
|
||||
},
|
||||
customerId: 'customer-id',
|
||||
paymentMethod: 'Payment Method',
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
};
|
||||
await payments.createSubscription(paymentData);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.planId).to.equal('group_plan_auto');
|
||||
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||
|
||||
// Leave
|
||||
await member.post(`/groups/${group._id}/leave`);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.dateTerminated).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,6 +130,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
it('uncompletes todo when direction is down', async () => {
|
||||
await user.post(`/tasks/${todo._id}/score/up`);
|
||||
await user.post(`/tasks/${todo._id}/score/down`);
|
||||
let updatedTask = await user.get(`/tasks/${todo._id}`);
|
||||
|
||||
@@ -137,9 +138,23 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
expect(updatedTask.dateCompleted).to.be.a('undefined');
|
||||
});
|
||||
|
||||
it('scores up todo even if it is already completed'); // Yes?
|
||||
it('doesn\'t let a todo be completed twice', async () => {
|
||||
await user.post(`/tasks/${todo._id}/score/up`);
|
||||
await expect(user.post(`/tasks/${todo._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('sessionOutdated'),
|
||||
});
|
||||
});
|
||||
|
||||
it('scores down todo even if it is already uncompleted'); // Yes?
|
||||
it('doesn\'t let a todo be uncompleted twice', async () => {
|
||||
await expect(user.post(`/tasks/${todo._id}/score/down`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('sessionOutdated'),
|
||||
});
|
||||
});
|
||||
|
||||
context('user stats when direction is up', () => {
|
||||
let updatedUser;
|
||||
@@ -163,23 +178,25 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
context('user stats when direction is down', () => {
|
||||
let updatedUser;
|
||||
let updatedUser, initialUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
await user.post(`/tasks/${todo._id}/score/up`);
|
||||
initialUser = await user.get('/user');
|
||||
await user.post(`/tasks/${todo._id}/score/down`);
|
||||
updatedUser = await user.get('/user');
|
||||
});
|
||||
|
||||
it('decreases user\'s mp', () => {
|
||||
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
|
||||
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
|
||||
});
|
||||
|
||||
it('decreases user\'s exp', () => {
|
||||
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
|
||||
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
|
||||
});
|
||||
|
||||
it('decreases user\'s gold', () => {
|
||||
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
|
||||
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -202,6 +219,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
it('uncompletes daily when direction is down', async () => {
|
||||
await user.post(`/tasks/${daily._id}/score/up`);
|
||||
await user.post(`/tasks/${daily._id}/score/down`);
|
||||
let task = await user.get(`/tasks/${daily._id}`);
|
||||
|
||||
@@ -222,9 +240,22 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
expect(task.nextDue.length).to.eql(6);
|
||||
});
|
||||
|
||||
it('scores up daily even if it is already completed'); // Yes?
|
||||
it('doesn\'t let a daily be completed twice', async () => {
|
||||
await user.post(`/tasks/${daily._id}/score/up`);
|
||||
await expect(user.post(`/tasks/${daily._id}/score/up`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('sessionOutdated'),
|
||||
});
|
||||
});
|
||||
|
||||
it('scores down daily even if it is already uncompleted'); // Yes?
|
||||
it('doesn\'t let a daily be uncompleted twice', async () => {
|
||||
await expect(user.post(`/tasks/${daily._id}/score/down`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('sessionOutdated'),
|
||||
});
|
||||
});
|
||||
|
||||
context('user stats when direction is up', () => {
|
||||
let updatedUser;
|
||||
@@ -248,23 +279,25 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
context('user stats when direction is down', () => {
|
||||
let updatedUser;
|
||||
let updatedUser, initialUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
await user.post(`/tasks/${daily._id}/score/up`);
|
||||
initialUser = await user.get('/user');
|
||||
await user.post(`/tasks/${daily._id}/score/down`);
|
||||
updatedUser = await user.get('/user');
|
||||
});
|
||||
|
||||
it('decreases user\'s mp', () => {
|
||||
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
|
||||
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
|
||||
});
|
||||
|
||||
it('decreases user\'s exp', () => {
|
||||
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
|
||||
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
|
||||
});
|
||||
|
||||
it('decreases user\'s gold', () => {
|
||||
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
|
||||
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+7
@@ -82,6 +82,13 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
it('should update the history', async () => {
|
||||
let newCron = new Date(2015, 11, 20);
|
||||
|
||||
await user.post('/debug/set-cron', {
|
||||
lastCron: newCron,
|
||||
});
|
||||
|
||||
await user.post('/cron');
|
||||
await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
|
||||
|
||||
let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
|
||||
|
||||
@@ -75,15 +75,6 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when non leader tries to create a task', async () => {
|
||||
await expect(member.post(`/tasks/${task._id}/unassign/${member._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanEditTasks'),
|
||||
});
|
||||
});
|
||||
|
||||
it('unassigns a user from a task', async () => {
|
||||
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||
|
||||
@@ -129,4 +120,26 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
||||
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
|
||||
expect(syncedTask).to.not.exist;
|
||||
});
|
||||
|
||||
it('allows a user to unassign themselves', async () => {
|
||||
await member.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||
|
||||
let groupTask = await user.get(`/tasks/group/${guild._id}`);
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
|
||||
expect(syncedTask).to.not.exist;
|
||||
});
|
||||
|
||||
// @TODO: Which do we want? The user to unassign themselves or not. This test was in
|
||||
// here, but then we had a request to allow to unaissgn.
|
||||
xit('returns error when non leader tries to unassign their a task', async () => {
|
||||
await expect(member.post(`/tasks/${task._id}/unassign/${member._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanEditTasks'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -308,7 +308,7 @@ describe('DELETE /user', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('incorrectDeletePhrase'),
|
||||
message: t('incorrectDeletePhrase', {magicWord: 'DELETE'}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import i18n from '../../../../../website/common/script/i18n';
|
||||
import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/ensureAccessRight';
|
||||
import { NotAuthorized } from '../../../../../website/server/libs/errors';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
|
||||
describe('ensure access middlewares', () => {
|
||||
let res, req, next;
|
||||
@@ -42,7 +43,7 @@ describe('ensure access middlewares', () => {
|
||||
|
||||
ensureSudo(req, res, next);
|
||||
|
||||
expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noSudoAccess')));
|
||||
expect(next).to.be.calledWith(new NotAuthorized(apiMessages('noSudoAccess')));
|
||||
});
|
||||
|
||||
it('passes when user is a sudo user', () => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import Vue from 'vue';
|
||||
import MembersModalComponent from 'client/components/groups/membersModal.vue';
|
||||
|
||||
describe('Members Modal Component', () => {
|
||||
describe('Party Sort', () => {
|
||||
let CTor;
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
CTor = Vue.extend(MembersModalComponent);
|
||||
vm = new CTor().$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('should have an empty object as sort-option at start', () => {
|
||||
const defaultData = vm.data();
|
||||
expect(defaultData.sortOption).to.eq({});
|
||||
});
|
||||
|
||||
it('should accept sort-option object', () => {
|
||||
const sortOption = vm.data().sortOption[0];
|
||||
vm.sort(sortOption);
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.data().sortOption).to.eq(sortOption);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -644,7 +644,27 @@ describe('shouldDo', () => {
|
||||
day = moment();
|
||||
dailyTask.repeat[DAY_MAPPING[day.day()]] = true;
|
||||
dailyTask.everyX = 3;
|
||||
let threeWeeksFromToday = day.add(6, 'weeks').day(day.day()).toDate();
|
||||
const threeWeeksFromToday = day.add(6, 'weeks').day(day.day()).toDate();
|
||||
|
||||
expect(shouldDo(threeWeeksFromToday, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('activates Daily on every (x) week on weekday across a year', () => {
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
s: false,
|
||||
f: false,
|
||||
th: false,
|
||||
w: false,
|
||||
t: false,
|
||||
m: false,
|
||||
};
|
||||
|
||||
day = moment('2017-11-19');
|
||||
dailyTask.startDate = day.toDate();
|
||||
dailyTask.repeat[DAY_MAPPING[day.day()]] = true;
|
||||
dailyTask.everyX = 3;
|
||||
const threeWeeksFromToday = moment('2018-01-21');
|
||||
|
||||
expect(shouldDo(threeWeeksFromToday, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
@@ -970,7 +990,7 @@ describe('shouldDo', () => {
|
||||
m: false,
|
||||
};
|
||||
|
||||
let today = moment('2017-01-26');
|
||||
let today = moment('2017-01-26:00:00.000-00:00');
|
||||
let week = today.monthWeek();
|
||||
let dayOfWeek = today.day();
|
||||
dailyTask.startDate = today.toDate();
|
||||
@@ -979,7 +999,7 @@ describe('shouldDo', () => {
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.frequency = 'monthly';
|
||||
|
||||
day = moment('2017-03-24');
|
||||
day = moment('2017-03-24:00:00.000-00:00');
|
||||
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(false);
|
||||
});
|
||||
@@ -995,7 +1015,7 @@ describe('shouldDo', () => {
|
||||
m: false,
|
||||
};
|
||||
|
||||
let today = moment('2017-01-27');
|
||||
let today = moment('2017-01-27:00:00.000-00:00');
|
||||
let week = today.monthWeek();
|
||||
let dayOfWeek = today.day();
|
||||
dailyTask.startDate = today.toDate();
|
||||
@@ -1004,7 +1024,7 @@ describe('shouldDo', () => {
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.frequency = 'monthly';
|
||||
|
||||
day = moment('2017-03-24');
|
||||
day = moment('2017-03-24:00:00.000-00:00');
|
||||
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
@@ -1020,7 +1040,7 @@ describe('shouldDo', () => {
|
||||
m: false,
|
||||
};
|
||||
|
||||
let today = moment('2017-01-27');
|
||||
let today = moment('2017-01-27:00:00.000-00:00');
|
||||
let week = today.monthWeek();
|
||||
let dayOfWeek = today.day();
|
||||
dailyTask.startDate = today.toDate();
|
||||
@@ -1029,7 +1049,7 @@ describe('shouldDo', () => {
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.frequency = 'monthly';
|
||||
|
||||
day = moment('2017-03-24');
|
||||
day = moment('2017-03-24:00:00.000-00:00');
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
|
||||
Regular → Executable
+14
-6
@@ -3,6 +3,14 @@ const path = require('path');
|
||||
const staticAssetsDirectory = './website/static/.'; // The folder where static files (not processed) live
|
||||
const prodEnv = require('./prod.env');
|
||||
const devEnv = require('./dev.env');
|
||||
const nconf = require('nconf');
|
||||
const setupNconf = require('../../website/server/libs/setupNconf');
|
||||
|
||||
let configFile = path.join(path.resolve(__dirname, '../../config.json'));
|
||||
|
||||
setupNconf(configFile);
|
||||
|
||||
const DEV_BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
module.exports = {
|
||||
build: {
|
||||
@@ -33,25 +41,25 @@ module.exports = {
|
||||
assetsPublicPath: '/',
|
||||
staticAssetsDirectory,
|
||||
proxyTable: {
|
||||
// proxy all requests starting with /api/v3 to localhost:3000
|
||||
// proxy all requests starting with /api/v3 to IP:PORT as specified in the top-level config
|
||||
'/api/v3': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/stripe': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/amazon': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/paypal': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/logout': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
const nconf = require('nconf');
|
||||
const { join, resolve } = require('path');
|
||||
const setupNconf = require('../../website/server/libs/setupNconf');
|
||||
|
||||
const PATH_TO_CONFIG = join(resolve(__dirname, '../../config.json'));
|
||||
let configFile = PATH_TO_CONFIG;
|
||||
|
||||
nconf
|
||||
.argv()
|
||||
.env()
|
||||
.file('user', configFile);
|
||||
|
||||
nconf.set('IS_PROD', nconf.get('NODE_ENV') === 'production');
|
||||
nconf.set('IS_DEV', nconf.get('NODE_ENV') === 'development');
|
||||
nconf.set('IS_TEST', nconf.get('NODE_ENV') === 'test');
|
||||
setupNconf(configFile);
|
||||
|
||||
// @TODO: Check if we can import from client. Items like admin emails can be imported
|
||||
// and that should be prefered
|
||||
|
||||
+14
-12
@@ -1,5 +1,6 @@
|
||||
<template lang="pug">
|
||||
#app(:class='{"casting-spell": castingSpell}')
|
||||
amazon-payments-modal
|
||||
snackbars
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
@@ -82,6 +83,7 @@ import BuyModal from './components/shops/buyModal.vue';
|
||||
import SelectMembersModal from 'client/components/selectMembersModal.vue';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import { setup as setupPayments } from 'client/libs/payments';
|
||||
import amazonPaymentsModal from 'client/components/payments/amazonModal';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
@@ -94,6 +96,7 @@ export default {
|
||||
snackbars,
|
||||
BuyModal,
|
||||
SelectMembersModal,
|
||||
amazonPaymentsModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -296,7 +299,6 @@ export default {
|
||||
const modalId = bvEvent.target.id;
|
||||
|
||||
let modalStackLength = this.$store.state.modalStack.length;
|
||||
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
|
||||
let modalSecondToTop = this.$store.state.modalStack[modalStackLength - 2];
|
||||
// Don't remove modal if hid was called from main app
|
||||
// @TODO: I'd reather use this, but I don't know how to pass data to hidden event
|
||||
@@ -308,13 +310,15 @@ export default {
|
||||
|
||||
// Recalculate and show the last modal if there is one
|
||||
modalStackLength = this.$store.state.modalStack.length;
|
||||
modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
|
||||
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
|
||||
if (modalOnTop) this.$root.$emit('bv::show::modal', modalOnTop, {fromRoot: true});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
// @TODO: Do we need this? I think selecting a new item
|
||||
// overwrites. @negue might know
|
||||
if (!$event && this.selectedItemToBuy.purchaseType !== 'card') {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
@@ -332,21 +336,19 @@ export default {
|
||||
},
|
||||
customPurchase (item) {
|
||||
if (item.purchaseType === 'card') {
|
||||
if (this.user.party._id) {
|
||||
this.selectedSpellToBuy = item;
|
||||
this.selectedSpellToBuy = item;
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'buy-modal');
|
||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
||||
} else {
|
||||
this.error(this.$t('errorNotInParty'));
|
||||
}
|
||||
this.$root.$emit('bv::hide::modal', 'buy-modal');
|
||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
||||
}
|
||||
},
|
||||
async memberSelected (member) {
|
||||
this.$store.dispatch('user:castSpell', {key: this.selectedSpellToBuy.key, targetId: member.id});
|
||||
this.selectedSpellToBuy = null;
|
||||
|
||||
this.$store.dispatch('party:getMembers', {forceLoad: true});
|
||||
if (this.user.party._id) {
|
||||
this.$store.dispatch('party:getMembers', {forceLoad: true});
|
||||
}
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'select-member-modal');
|
||||
},
|
||||
@@ -382,4 +384,4 @@ export default {
|
||||
<style src="assets/css/sprites/spritesmith-main-18.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-19.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-20.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
.promo_mystery_201711 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -783px 0px;
|
||||
background-position: -499px -202px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_potions_thunderstorm {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -499px 0px;
|
||||
background-position: -842px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -783px -295px;
|
||||
background-position: -641px -202px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.promo_turkey_day_2017 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -641px 0px;
|
||||
background-position: 0px -515px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
@@ -34,3 +34,9 @@
|
||||
width: 302px;
|
||||
height: 264px;
|
||||
}
|
||||
.scene_money {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -499px 0px;
|
||||
width: 342px;
|
||||
height: 201px;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 334 KiB After Width: | Height: | Size: 334 KiB |
@@ -139,27 +139,6 @@
|
||||
}
|
||||
|
||||
&-better {
|
||||
background: $blue-50;
|
||||
|
||||
&-color {
|
||||
color: darken($blue-50, 12%);
|
||||
}
|
||||
|
||||
&-control-habit {
|
||||
background: darken($blue-50, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $blue-500;
|
||||
color: $blue-50;
|
||||
}
|
||||
|
||||
&-modal-input {
|
||||
color: $blue-500 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-best {
|
||||
background: $teal-50;
|
||||
|
||||
&-color {
|
||||
@@ -180,6 +159,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-best {
|
||||
background: $blue-50;
|
||||
|
||||
&-color {
|
||||
color: darken($blue-50, 12%);
|
||||
}
|
||||
|
||||
&-control-habit {
|
||||
background: darken($blue-50, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $blue-500;
|
||||
color: $blue-50;
|
||||
}
|
||||
|
||||
&-modal-input {
|
||||
color: $blue-500 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-reward {
|
||||
background: #FFF5E5
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// possible values are: normal, fall, habitoween, thanksgiving
|
||||
// more to be added on future seasons
|
||||
|
||||
$npc_market_flavor: 'thanksgiving';
|
||||
$npc_quests_flavor: 'thanksgiving';
|
||||
$npc_seasonal_flavor: 'thanksgiving';
|
||||
$npc_market_flavor: 'normal';
|
||||
$npc_quests_flavor: 'normal';
|
||||
$npc_seasonal_flavor: 'normal';
|
||||
$npc_timetravelers_flavor: 'normal';
|
||||
$npc_tavern_flavor: 'thanksgiving';
|
||||
$npc_tavern_flavor: 'normal';
|
||||
|
||||
@@ -154,6 +154,7 @@ export default {
|
||||
this.$root.$emit('bv::hide::modal', 'choose-class');
|
||||
},
|
||||
clickSelectClass (heroClass) {
|
||||
if (this.user.flags.classSelected && !confirm(this.$t('changeClassConfirmCost'))) return;
|
||||
this.$store.dispatch('user:changeClass', {query: {class: heroClass}});
|
||||
},
|
||||
clickDisableClasses () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
buy-gems-modal(v-if="isUserLoaded")
|
||||
buy-gems-modal(v-if='user')
|
||||
modify-inventory(v-if="isUserLoaded")
|
||||
footer.col-12(:class="{expanded: isExpandedFooter}")
|
||||
.row(v-if="isExpandedFooter")
|
||||
@@ -71,9 +71,13 @@
|
||||
.row
|
||||
.col-10 {{ $t('donateText3') }}
|
||||
.col-2
|
||||
button.btn.btn-donate(@click="donate()")
|
||||
button.btn.btn-contribute(@click="donate()", v-if="user")
|
||||
.svg-icon.heart(v-html="icons.heart")
|
||||
.text {{ $t('companyDonate') }}
|
||||
.btn.btn-contribute(v-else)
|
||||
a(href='http://habitica.wikia.com/wiki/Contributing_to_Habitica', target='_blank')
|
||||
.svg-icon.heart(v-html="icons.heart")
|
||||
.text {{ $t('companyContribute') }}
|
||||
.row
|
||||
.col-12
|
||||
hr
|
||||
@@ -211,7 +215,7 @@
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.btn-donate {
|
||||
.btn-contribute {
|
||||
background: #c3c0c7;
|
||||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
@@ -369,7 +373,7 @@ export default {
|
||||
eventAction: 'click',
|
||||
eventLabel: 'Gems > Donate',
|
||||
});
|
||||
this.$root.$emit('bv::show::modal', 'buy-gems');
|
||||
this.$root.$emit('bv::show::modal', 'buy-gems', {alreadyTracked: true});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
span.head_0
|
||||
span(:class="getGearClass('back_collar')")
|
||||
span(:class="getGearClass('body')")
|
||||
template(v-for="type in ['base', 'bangs', 'mustache', 'beard']")
|
||||
template(v-for="type in ['bangs', 'base', 'mustache', 'beard']")
|
||||
span(:class="'hair_' + type + '_' + member.preferences.hair[type] + '_' + member.preferences.hair.color")
|
||||
span(:class="getGearClass('eyewear')")
|
||||
span(:class="getGearClass('head')")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
challenge-modal(:cloning='cloning' v-on:updatedChallenge='updatedChallenge')
|
||||
leave-challenge-modal(:challengeId='challenge._id')
|
||||
close-challenge-modal(:members='members', :challengeId='challenge._id')
|
||||
challenge-member-progress-modal(:memberId='progressMemberId', :challengeId='challenge._id')
|
||||
|
||||
@@ -33,7 +34,8 @@
|
||||
span.view-progress
|
||||
strong {{ $t('viewProgressOf') }}
|
||||
b-dropdown.create-dropdown(text="Select a Participant")
|
||||
b-dropdown-item(v-for="member in members", :key="member._id", @click="openMemberProgressModal(member._id)")
|
||||
input.form-control(type='text', v-model='searchTerm')
|
||||
b-dropdown-item(v-for="member in memberResults", :key="member._id", @click="openMemberProgressModal(member._id)")
|
||||
| {{ member.profile.name }}
|
||||
span(v-if='isLeader || isAdmin')
|
||||
b-dropdown.create-dropdown(:text="$t('addTaskToChallenge')", :variant="'success'")
|
||||
@@ -189,6 +191,8 @@ import TaskModal from '../tasks/taskModal';
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
import challengeModal from './challengeModal';
|
||||
import challengeMemberProgressModal from './challengeMemberProgressModal';
|
||||
import challengeMemberSearchMixin from 'client/mixins/challengeMemberSearch';
|
||||
import leaveChallengeModal from './leaveChallengeModal';
|
||||
|
||||
import taskDefaults from 'common/script/libs/taskDefaults';
|
||||
|
||||
@@ -198,11 +202,13 @@ import calendarIcon from 'assets/svg/calendar.svg';
|
||||
|
||||
export default {
|
||||
props: ['challengeId'],
|
||||
mixins: [challengeMemberSearchMixin],
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
components: {
|
||||
closeChallengeModal,
|
||||
leaveChallengeModal,
|
||||
challengeModal,
|
||||
challengeMemberProgressModal,
|
||||
TaskColumn: Column,
|
||||
@@ -231,6 +237,8 @@ export default {
|
||||
workingTask: {},
|
||||
taskFormPurpose: 'create',
|
||||
progressMemberId: '',
|
||||
searchTerm: '',
|
||||
memberResults: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -365,19 +373,7 @@ export default {
|
||||
await this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true});
|
||||
},
|
||||
async leaveChallenge () {
|
||||
let keepChallenge = confirm('Do you want to keep challenge tasks?');
|
||||
let keep = 'keep-all';
|
||||
if (!keepChallenge) keep = 'remove-all';
|
||||
|
||||
let index = findIndex(this.user.challenges, (challengeId) => {
|
||||
return challengeId === this.searchId;
|
||||
});
|
||||
this.user.challenges.splice(index, 1);
|
||||
await this.$store.dispatch('challenges:leaveChallenge', {
|
||||
challengeId: this.searchId,
|
||||
keep,
|
||||
});
|
||||
await this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true});
|
||||
this.$root.$emit('bv::show::modal', 'leave-challenge-modal');
|
||||
},
|
||||
closeChallenge () {
|
||||
this.$root.$emit('bv::show::modal', 'close-challenge-modal');
|
||||
|
||||
@@ -57,8 +57,9 @@
|
||||
You do not have enough gems to create a Tavern challenge
|
||||
// @TODO if buy gems button is added, add analytics tracking to it
|
||||
// see https://github.com/HabitRPG/habitica/blob/develop/website/views/options/social/challenges.jade#L134
|
||||
button.btn.btn-primary(v-once, v-if='creating', @click='createChallenge()') {{$t('createChallengeCloneTasks')}}
|
||||
button.btn.btn-primary(v-once, v-if='!creating', @click='updateChallenge()') {{$t('updateChallenge')}}
|
||||
button.btn.btn-primary(v-once, v-if='creating && !cloning', @click='createChallenge()') {{$t('createChallengeAddTasks')}}
|
||||
button.btn.btn-primary(v-once, v-if='cloning', @click='createChallenge()') {{$t('createChallengeCloneTasks')}}
|
||||
button.btn.btn-primary(v-once, v-if='!creating && !cloning', @click='updateChallenge()') {{$t('updateChallenge')}}
|
||||
.col-12.text-center
|
||||
p(v-once) {{$t('challengeMinimum')}}
|
||||
</template>
|
||||
|
||||
@@ -74,10 +74,11 @@ div
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
import challengeMemberSearchMixin from 'client/mixins/challengeMemberSearch';
|
||||
|
||||
export default {
|
||||
props: ['challengeId', 'members'],
|
||||
mixins: [challengeMemberSearchMixin],
|
||||
data () {
|
||||
return {
|
||||
winner: {},
|
||||
@@ -85,14 +86,6 @@ export default {
|
||||
memberResults: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
searchTerm: debounce(function searchTerm (newSearch) {
|
||||
this.searchChallengeMember(newSearch);
|
||||
}, 500),
|
||||
members () {
|
||||
this.memberResults = this.members;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
winnerText () {
|
||||
if (!this.winner.profile) return this.$t('selectMember');
|
||||
@@ -100,12 +93,6 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async searchChallengeMember (search) {
|
||||
this.memberResults = await this.$store.dispatch('members:getChallengeMembers', {
|
||||
challengeId: this.challengeId,
|
||||
searchTerm: search,
|
||||
});
|
||||
},
|
||||
selectMember (member) {
|
||||
this.winner = member;
|
||||
},
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<template lang="pug">
|
||||
b-modal#leave-challenge-modal(:title="$t('leaveChallenge')", size='sm', :hide-footer="true")
|
||||
.modal-body
|
||||
h2 {{ $t('confirmKeepChallengeTasks') }}
|
||||
div
|
||||
button.btn.btn-primary(@click='leaveChallenge("keep")') {{ $t('keepIt') }}
|
||||
button.btn.btn-danger(@click='leaveChallenge("remove-all")') {{ $t('removeIt') }}
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-body {
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
|
||||
export default {
|
||||
props: ['challengeId'],
|
||||
mixins: [notifications],
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
},
|
||||
methods: {
|
||||
async leaveChallenge (keep) {
|
||||
let index = findIndex(this.user.challenges, (id) => {
|
||||
return id === this.challengeId;
|
||||
});
|
||||
this.user.challenges.splice(index, 1);
|
||||
await this.$store.dispatch('challenges:leaveChallenge', {
|
||||
challengeId: this.challengeId,
|
||||
keep,
|
||||
});
|
||||
await this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true});
|
||||
this.close();
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'leave-challenge-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="pug">
|
||||
.col-2.standard-sidebar.hidden-xs-down
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')
|
||||
|
||||
@@ -83,19 +83,19 @@ export default {
|
||||
},
|
||||
{
|
||||
label: 'mental_health',
|
||||
key: 'mental_health ',
|
||||
key: 'mental_health',
|
||||
},
|
||||
{
|
||||
label: 'getting_organized',
|
||||
key: 'getting_organized ',
|
||||
key: 'getting_organized',
|
||||
},
|
||||
{
|
||||
label: 'self_improvement',
|
||||
key: 'self_improvement ',
|
||||
key: 'self_improvement',
|
||||
},
|
||||
{
|
||||
label: 'spirituality',
|
||||
key: 'spirituality ',
|
||||
key: 'spirituality',
|
||||
},
|
||||
{
|
||||
label: 'time_management',
|
||||
|
||||
@@ -24,11 +24,12 @@
|
||||
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
|
||||
.card-body
|
||||
h3.leader(
|
||||
:class='userLevelStyle(cachedProfileData[msg.uuid])'
|
||||
:class='userLevelStyle(msg)',
|
||||
@click="showMemberModal(msg.uuid)",
|
||||
v-b-tooltip.hover.top="('contributor' in msg) ? msg.contributor.text : ''",
|
||||
)
|
||||
| {{msg.user}}
|
||||
.svg-icon(v-html="icons[`tier${cachedProfileData[msg.uuid].contributor.level}`]", v-if='cachedProfileData[msg.uuid] && cachedProfileData[msg.uuid].contributor && cachedProfileData[msg.uuid].contributor.level')
|
||||
.svg-icon(v-html="icons[`tier${msg.contributor.level}`]", v-if='msg.contributor && msg.contributor.level')
|
||||
p.time {{msg.timestamp | timeAgo}}
|
||||
.text(v-markdown='msg.text')
|
||||
hr
|
||||
@@ -41,7 +42,7 @@
|
||||
.svg-icon(v-html="icons.copy")
|
||||
| {{$t('copyAsTodo')}}
|
||||
// @TODO make copyAsTodo work in the inbox
|
||||
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted', @click='report(msg)')
|
||||
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @click='report(msg)')
|
||||
.svg-icon(v-html="icons.report")
|
||||
| {{$t('report')}}
|
||||
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
|
||||
@@ -61,11 +62,12 @@
|
||||
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
|
||||
.card-body
|
||||
h3.leader(
|
||||
:class='userLevelStyle(cachedProfileData[msg.uuid])',
|
||||
:class='userLevelStyle(msg)',
|
||||
@click="showMemberModal(msg.uuid)",
|
||||
v-b-tooltip.hover.top="('contributor' in msg) ? msg.contributor.text : ''",
|
||||
)
|
||||
| {{msg.user}}
|
||||
.svg-icon(v-html="icons[`tier${cachedProfileData[msg.uuid].contributor.level}`]", v-if='cachedProfileData[msg.uuid] && cachedProfileData[msg.uuid].contributor && cachedProfileData[msg.uuid].contributor.level')
|
||||
.svg-icon(v-html="icons[`tier${msg.contributor.level}`]", v-if='msg.contributor && msg.contributor.level')
|
||||
p.time {{msg.timestamp | timeAgo}}
|
||||
.text(v-markdown='msg.text')
|
||||
hr
|
||||
@@ -168,6 +170,7 @@
|
||||
|
||||
h3 { // this is the user name
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
@@ -478,10 +481,10 @@ export default {
|
||||
|
||||
// Open the modal only if the data is available
|
||||
if (profile && !profile.rejected) {
|
||||
// @TODO move to action or anyway move from here because it's super duplicate
|
||||
this.$store.state.profileUser = profile;
|
||||
this.$store.state.profileOptions.startingPage = 'profile';
|
||||
this.$root.$emit('bv::show::modal', 'profile');
|
||||
this.$root.$emit('habitica:show-profile', {
|
||||
user: profile,
|
||||
startingPage: 'profile',
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -177,6 +177,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
span 5
|
||||
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.beard.${baseHair5Keys.join(",hair.beard.")}`)') {{ $t('purchaseAll') }}
|
||||
.col-12.customize-options(v-if='editing')
|
||||
.head_0.option(@click='set({"preferences.hair.mustache": 0})', :class="[{ active: user.preferences.hair.mustache === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
|
||||
.option(v-for='option in baseHair6',
|
||||
:class='{active: option.active, locked: option.locked}')
|
||||
.base.sprite.customize-option(:class="`hair_mustache_${option.key}_${user.preferences.hair.color}`", @click='option.click')
|
||||
@@ -538,12 +539,6 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.option.locked {
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
}
|
||||
|
||||
.option.hide {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -554,8 +549,17 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
padding: .5em;
|
||||
height: 90px;
|
||||
width: 90px;
|
||||
margin-bottom: .5em;
|
||||
margin-right: .5em;
|
||||
margin: 1em .5em .5em 0;
|
||||
border: 4px solid $gray-700;
|
||||
border-radius: 4px;
|
||||
|
||||
&.locked {
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.sprite.customize-option {
|
||||
margin: 0 auto;
|
||||
@@ -587,9 +591,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
}
|
||||
|
||||
.option.active {
|
||||
border: 4px solid $purple-200;
|
||||
border-radius: 4px;
|
||||
margin-top: 1em;
|
||||
border-color: $purple-200;
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
@@ -1023,7 +1025,8 @@ export default {
|
||||
option.key = key;
|
||||
option.active = this.user.preferences.costume ? this.user.items.gear.costume.eyewear === newKey : this.user.items.gear.equipped.eyewear === newKey;
|
||||
option.click = () => {
|
||||
return this.equip(newKey);
|
||||
let type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||
return this.equip(newKey, type);
|
||||
};
|
||||
return option;
|
||||
});
|
||||
@@ -1061,7 +1064,8 @@ export default {
|
||||
option.active = this.user.preferences.costume ? this.user.items.gear.costume.headAccessory === newKey : this.user.items.gear.equipped.headAccessory === newKey;
|
||||
option.locked = locked;
|
||||
option.click = () => {
|
||||
return locked ? this.purchase('gear', newKey) : this.equip(newKey);
|
||||
let type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||
return locked ? this.purchase('gear', newKey) : this.equip(newKey, type);
|
||||
};
|
||||
return option;
|
||||
});
|
||||
@@ -1306,9 +1310,8 @@ export default {
|
||||
set (settings) {
|
||||
this.$store.dispatch('user:set', settings);
|
||||
},
|
||||
equip (key) {
|
||||
this.$store.dispatch('common:equip', {key, type: 'equipped'});
|
||||
this.user.items.gear.equipped[key] = !this.user.items.gear.equipped[key];
|
||||
equip (key, type) {
|
||||
this.$store.dispatch('common:equip', {key, type});
|
||||
},
|
||||
async done () {
|
||||
this.loading = true;
|
||||
|
||||
@@ -137,6 +137,7 @@ export default {
|
||||
|
||||
// Reset the page when filters are updated
|
||||
this.lastPageLoaded = 0;
|
||||
this.hasLoadedAllGuilds = false;
|
||||
this.queryFilters.page = this.lastPageLoaded;
|
||||
|
||||
this.queryFilters.categories = eventData.categories.join(',');
|
||||
@@ -173,10 +174,11 @@ export default {
|
||||
},
|
||||
async fetchGuilds () {
|
||||
// We have the data cached
|
||||
if (this.lastPageLoaded === 0 && this.guilds.length > 0) return;
|
||||
if (this.lastPageLoaded === 0 && this.guilds.length > 0) {
|
||||
this.lastPageLoaded += 1;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
this.queryFilters.page = this.lastPageLoaded;
|
||||
let guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters);
|
||||
if (guilds.length === 0) this.hasLoadedAllGuilds = true;
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
.col-6
|
||||
span.float-left
|
||||
| {{parseFloat(group.quest.progress.hp).toFixed(2)}} / {{parseFloat(questData.boss.hp).toFixed(2)}}
|
||||
.col-6
|
||||
.col-6(v-if='userIsOnQuest')
|
||||
// @TODO: Why do we not sync quset progress on the group doc? Each user could have different progress
|
||||
span.float-right {{parseFloat(user.party.quest.progress.up).toFixed(1) || 0}} pending damage
|
||||
.row.rage-bar-row(v-if='questData.boss.rage')
|
||||
@@ -184,6 +184,16 @@
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
@media (min-width: 1300px) {
|
||||
.standard-page {
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
max-width: 430px !important;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: $purple-200;
|
||||
}
|
||||
@@ -559,6 +569,10 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
userIsOnQuest () {
|
||||
if (!this.group.quest || !this.group.quest.members) return false;
|
||||
return Boolean(this.group.quest.members[this.user._id]);
|
||||
},
|
||||
acceptedCount () {
|
||||
let count = 0;
|
||||
|
||||
@@ -869,9 +883,10 @@ export default {
|
||||
},
|
||||
async showMemberProfile (leader) {
|
||||
let heroDetails = await this.$store.dispatch('members:fetchMember', { memberId: leader._id });
|
||||
this.$store.state.profileUser = heroDetails.data.data;
|
||||
this.$store.state.profileOptions.startingPage = 'profile';
|
||||
this.$root.$emit('bv::show::modal', 'profile');
|
||||
this.$root.$emit('habitica:show-profile', {
|
||||
user: heroDetails.data.data,
|
||||
startingPage: 'profile',
|
||||
});
|
||||
},
|
||||
async questAbort () {
|
||||
if (!confirm(this.$t('sureAbort'))) return;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
select.form-control(v-model="workingGroup.newLeader")
|
||||
option(v-for='potentialLeader in potentialLeaders', :value="potentialLeader._id") {{ potentialLeader.name }}
|
||||
|
||||
.form-group(v-if='!this.workingGroup.id')
|
||||
.form-group
|
||||
label
|
||||
strong(v-once) {{$t('privacySettings')}} *
|
||||
br
|
||||
@@ -35,7 +35,7 @@
|
||||
// @TODO discuss the impact of this with moderators before implementing
|
||||
|
||||
br
|
||||
label.custom-control.custom-checkbox(v-if='!isParty')
|
||||
label.custom-control.custom-checkbox(v-if='!isParty && !this.workingGroup.id')
|
||||
input.custom-control-input(type="checkbox", v-model="workingGroup.privateGuild")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ $t('privateGuild') }}
|
||||
@@ -345,7 +345,13 @@ export default {
|
||||
if (editingGroup.summary) this.workingGroup.summary = editingGroup.summary;
|
||||
if (editingGroup.description) this.workingGroup.description = editingGroup.description;
|
||||
if (editingGroup._id) this.workingGroup.id = editingGroup._id;
|
||||
if (editingGroup.leader._id) this.workingGroup.newLeader = editingGroup.leader._id;
|
||||
|
||||
this.workingGroup.onlyLeaderCreatesChallenges = editingGroup.leaderOnly.challenges;
|
||||
|
||||
if (editingGroup.leader._id) {
|
||||
this.workingGroup.newLeader = editingGroup.leader._id;
|
||||
this.workingGroup.currentLeaderId = editingGroup.leader._id;
|
||||
}
|
||||
if (editingGroup._id) this.getMembers();
|
||||
},
|
||||
},
|
||||
@@ -407,11 +413,9 @@ export default {
|
||||
this.workingGroup.privacy = 'public';
|
||||
}
|
||||
|
||||
if (!this.workingGroup.onlyLeaderCreatesChallenges) {
|
||||
this.workingGroup.leaderOnly = {
|
||||
challenges: true,
|
||||
};
|
||||
}
|
||||
this.workingGroup.leaderOnly = {
|
||||
challenges: this.workingGroup.onlyLeaderCreatesChallenges,
|
||||
};
|
||||
|
||||
let categoryKeys = this.workingGroup.categories;
|
||||
let serverCategories = [];
|
||||
@@ -424,14 +428,19 @@ export default {
|
||||
});
|
||||
this.workingGroup.categories = serverCategories;
|
||||
|
||||
let groupData = Object.assign({}, this.workingGroup);
|
||||
if (groupData.newLeader === this.workingGroup.currentLeaderId) {
|
||||
groupData.leader = this.workingGroup.currentLeaderId;
|
||||
}
|
||||
|
||||
let newgroup;
|
||||
if (this.workingGroup.id) {
|
||||
await this.$store.dispatch('guilds:update', {group: this.workingGroup});
|
||||
if (groupData.id) {
|
||||
await this.$store.dispatch('guilds:update', {group: groupData});
|
||||
this.$root.$emit('updatedGroup', this.workingGroup);
|
||||
// @TODO: this doesn't work because of the async resource
|
||||
// if (updatedGroup.type === 'party') this.$store.state.party = {data: updatedGroup};
|
||||
} else {
|
||||
newgroup = await this.$store.dispatch('guilds:create', {group: this.workingGroup});
|
||||
newgroup = await this.$store.dispatch('guilds:create', {group: groupData});
|
||||
this.$store.state.user.data.balance -= 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template lang="pug">
|
||||
// @TODO: Move to group plans folder
|
||||
div
|
||||
amazon-payments-modal(:amazon-payments-prop='amazonPayments')
|
||||
div
|
||||
.header
|
||||
h1.text-center Need more for your Group?
|
||||
@@ -78,7 +77,7 @@ div
|
||||
div Each Additional
|
||||
div Member
|
||||
|
||||
b-modal#group-plan-modal(title="Empty", size='md', hide-footer=true)
|
||||
b-modal#group-plan-modal(title="Select Payment", size='md', hide-footer=true)
|
||||
.col-12(v-if='activePage === PAGES.CREATE_GROUP')
|
||||
.form-group
|
||||
label.control-label(for='new-group-name') Name
|
||||
@@ -200,6 +199,9 @@ div
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
padding: 2em;
|
||||
text-align: center;
|
||||
display: inline-block !important;
|
||||
vertical-align: bottom;
|
||||
margin-right: 1em;
|
||||
|
||||
img {
|
||||
margin: 0 auto;
|
||||
@@ -321,7 +323,6 @@ div
|
||||
|
||||
<script>
|
||||
import paymentsMixin from '../../mixins/payments';
|
||||
import amazonPaymentsModal from '../payments/amazonModal';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import group from 'assets/svg/group.svg';
|
||||
import amazonpay from 'assets/svg/amazonpay.svg';
|
||||
@@ -329,9 +330,6 @@ import positiveIcon from 'assets/svg/positive.svg';
|
||||
|
||||
export default {
|
||||
mixins: [paymentsMixin],
|
||||
components: {
|
||||
amazonPaymentsModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
amazonPayments: {},
|
||||
@@ -405,6 +403,7 @@ export default {
|
||||
if (this.paymentMethod === this.PAYMENTS.STRIPE) {
|
||||
this.showStripe(paymentData);
|
||||
} else if (this.paymentMethod === this.PAYMENTS.AMAZON) {
|
||||
paymentData.type = 'subscription';
|
||||
this.amazonPaymentsInit(paymentData);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ div
|
||||
.col-5.offset-1
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t('sort')", right=true)
|
||||
b-dropdown-item(v-for='sortOption in sortOptions', @click='sort(sortOption.value)', :key='sortOption.value') {{sortOption.text}}
|
||||
b-dropdown-item(v-for='sortOption in sortOptions', @click='sort(sortOption)', :key='sortOption.value') {{sortOption.text}}
|
||||
.row(v-if='invites.length > 0')
|
||||
.col-6.offset-3.nav
|
||||
.nav-item(@click='viewMembers()', :class="{active: selectedPage === 'members'}") {{ $t('members') }}
|
||||
@@ -36,7 +36,7 @@ div
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.messageIcon")
|
||||
span.text {{$t('sendMessage')}}
|
||||
b-dropdown-item(@click='sort(option.value)', v-if='isLeader')
|
||||
b-dropdown-item(@click='promoteToLeader(member._id)', v-if='isLeader')
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.starIcon")
|
||||
span.text {{$t('promoteToLeader')}}
|
||||
@@ -172,7 +172,9 @@ div
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import sortBy from 'lodash/sortBy';
|
||||
// import sortBy from "lodash/sortBy";
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
import removeMemberModal from 'client/components/members/removeMemberModal';
|
||||
@@ -190,27 +192,83 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
sortOption: '',
|
||||
sortOption: {},
|
||||
selectedPage: 'members',
|
||||
members: [],
|
||||
invites: [],
|
||||
memberToRemove: {},
|
||||
sortOptions: [
|
||||
{
|
||||
value: 'level',
|
||||
text: this.$t('tier'),
|
||||
},
|
||||
{
|
||||
value: 'name',
|
||||
text: this.$t('name'),
|
||||
},
|
||||
{
|
||||
value: 'lvl',
|
||||
text: this.$t('level'),
|
||||
},
|
||||
{
|
||||
value: 'class',
|
||||
text: this.$t('class'),
|
||||
order: 'asc',
|
||||
param: 'stats.class',
|
||||
},
|
||||
{
|
||||
value: 'background',
|
||||
text: this.$t('background'),
|
||||
order: 'asc',
|
||||
param: 'preferences.background',
|
||||
},
|
||||
{
|
||||
value: 'date-joined-asc',
|
||||
text: this.$t('sortDateJoinedAsc'),
|
||||
order: 'asc',
|
||||
param: 'auth.timestamps.created',
|
||||
},
|
||||
{
|
||||
value: 'date-joined-desc',
|
||||
text: this.$t('sortDateJoinedDesc'),
|
||||
order: 'desc',
|
||||
param: 'auth.timestamps.created',
|
||||
},
|
||||
{
|
||||
value: 'login-asc',
|
||||
text: this.$t('sortLoginAsc'),
|
||||
order: 'asc',
|
||||
param: 'auth.timestamps.loggedin',
|
||||
},
|
||||
{
|
||||
value: 'login-desc',
|
||||
text: this.$t('sortLoginDesc'),
|
||||
order: 'desc',
|
||||
param: 'auth.timestamps.loggedin',
|
||||
},
|
||||
{
|
||||
value: 'level-asc',
|
||||
text: this.$t('sortLevelAsc'),
|
||||
order: 'asc',
|
||||
param: 'stats.lvl',
|
||||
},
|
||||
{
|
||||
value: 'level-desc',
|
||||
text: this.$t('sortLevelDesc'),
|
||||
order: 'desc',
|
||||
param: 'stats.lvl',
|
||||
},
|
||||
{
|
||||
value: 'name-asc',
|
||||
text: this.$t('sortNameAsc'),
|
||||
order: 'asc',
|
||||
param: 'profile.name',
|
||||
},
|
||||
{
|
||||
value: 'name-desc',
|
||||
text: this.$t('sortNameDesc'),
|
||||
order: 'desc',
|
||||
param: 'profile.name',
|
||||
},
|
||||
{
|
||||
value: 'tier-asc',
|
||||
text: this.$t('sortTierAsc'),
|
||||
order: 'asc',
|
||||
param: 'contributor.level',
|
||||
},
|
||||
{
|
||||
value: 'tier-desc',
|
||||
text: this.$t('sortTierDesc'),
|
||||
order: 'desc',
|
||||
param: 'contributor.level',
|
||||
},
|
||||
],
|
||||
searchTerm: '',
|
||||
@@ -249,24 +307,18 @@ export default {
|
||||
|
||||
if (this.searchTerm) {
|
||||
sortedMembers = sortedMembers.filter(member => {
|
||||
return member.profile.name.toLowerCase().indexOf(this.searchTerm.toLowerCase()) !== -1;
|
||||
return (
|
||||
member.profile.name
|
||||
.toLowerCase()
|
||||
.indexOf(this.searchTerm.toLowerCase()) !== -1
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.sortOption) return sortedMembers;
|
||||
|
||||
sortedMembers = sortBy(this.members, [(member) => {
|
||||
if (this.sortOption === 'tier') {
|
||||
if (!member.contributor) return;
|
||||
return member.contributor.level;
|
||||
} else if (this.sortOption === 'name') {
|
||||
return member.profile.name;
|
||||
} else if (this.sortOption === 'lvl') {
|
||||
return member.stats.lvl;
|
||||
} else if (this.sortOption === 'class') {
|
||||
return member.stats.class;
|
||||
}
|
||||
}]);
|
||||
if (!isEmpty(this.sortOption)) {
|
||||
// Use the memberlist filtered by searchTerm
|
||||
sortedMembers = orderBy(sortedMembers, [this.sortOption.param], [this.sortOption.order]);
|
||||
}
|
||||
|
||||
return sortedMembers;
|
||||
},
|
||||
@@ -388,6 +440,12 @@ export default {
|
||||
});
|
||||
this.viewMembers();
|
||||
},
|
||||
async promoteToLeader (memberId) {
|
||||
let groupData = Object.assign({}, this.group);
|
||||
groupData.leader = memberId;
|
||||
await this.$store.dispatch('guilds:update', {group: groupData});
|
||||
this.$root.$emit('updatedGroup', groupData);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,7 @@ router-link.card-link(:to="{ name: 'guild', params: { groupId: guild._id } }")
|
||||
p.summary(v-if='guild.summary') {{guild.summary.substr(0, MAX_SUMMARY_SIZE_FOR_GUILDS)}}
|
||||
p.summary(v-else) {{ guild.name }}
|
||||
.col-md-2.cta-container
|
||||
button.btn.btn-danger(v-if='isMember && displayLeave' @click='leave()', v-once) {{ $t('leave') }}
|
||||
button.btn.btn-danger(v-if='isMember && displayLeave' @click.prevent='leave()', v-once) {{ $t('leave') }}
|
||||
button.btn.btn-success(v-if='!isMember' @click='join()', v-once) {{ $t('join') }}
|
||||
div.item-with-icon.gem-bank(v-if='displayGemBank')
|
||||
.svg-icon.gem(v-html="icons.gem")
|
||||
@@ -175,7 +175,7 @@ export default {
|
||||
},
|
||||
async leave () {
|
||||
// @TODO: ask about challenges when we add challenges
|
||||
await this.$store.dispatch('guilds:leave', {guildId: this.guild._id, type: 'myGuilds'});
|
||||
await this.$store.dispatch('guilds:leave', {groupId: this.guild._id, type: 'myGuilds'});
|
||||
},
|
||||
async reject (invitationToReject) {
|
||||
// @TODO: This needs to be in the notifications where users will now accept invites
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
questDialogContent(:item="questData")
|
||||
div.text-center.actions(v-if='canEditQuest')
|
||||
div
|
||||
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
|
||||
button.btn.btn-secondary(v-once, @click="questConfirm()") {{ $t('begin') }}
|
||||
// @TODO don't allow the party leader to start the quest until the leader has accepted or rejected the invitation (users get confused and think "begin" means "join quest")
|
||||
div
|
||||
.cancel(v-once, @click="questCancel()") {{ $t('cancel') }}
|
||||
@@ -52,11 +52,10 @@
|
||||
height: 460px;
|
||||
width: 320px;
|
||||
top: 2.5em;
|
||||
left: -22em;
|
||||
left: -22.8em;
|
||||
z-index: -1;
|
||||
padding: 2em;
|
||||
overflow: scroll;
|
||||
|
||||
overflow-y: auto;
|
||||
h3 {
|
||||
color: $white;
|
||||
}
|
||||
@@ -180,7 +179,8 @@ export default {
|
||||
return quests.quests[this.group.quest.key];
|
||||
},
|
||||
members () {
|
||||
return this.partyMembers.map(member => {
|
||||
let partyMembers = this.partyMembers || [];
|
||||
return partyMembers.map(member => {
|
||||
return {
|
||||
name: member.profile.name,
|
||||
accepted: this.group.quest.members[member._id],
|
||||
@@ -195,6 +195,14 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async questConfirm () {
|
||||
let count = 0;
|
||||
for (let uuid in this.group.quest.members) {
|
||||
if (this.group.quest.members[uuid]) count += 1;
|
||||
}
|
||||
if (!confirm(this.$t('questConfirm', { questmembers: count, totalmembers: this.group.memberCount}))) return;
|
||||
this.questForceStart();
|
||||
},
|
||||
async questForceStart () {
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/force-start'});
|
||||
this.group.quest = quest;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="pug">
|
||||
.standard-sidebar.hidden-xs-down
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')
|
||||
|
||||
|
||||
@@ -199,9 +199,10 @@ export default {
|
||||
},
|
||||
async clickMember (hero) {
|
||||
let heroDetails = await this.$store.dispatch('members:fetchMember', { memberId: hero._id });
|
||||
this.$store.state.profileUser = heroDetails.data.data;
|
||||
this.$store.state.profileOptions.startingPage = 'profile';
|
||||
this.$root.$emit('bv::show::modal', 'profile');
|
||||
this.$root.$emit('habitica:show-profile', {
|
||||
user: heroDetails.data.data,
|
||||
startingPage: 'profile',
|
||||
});
|
||||
},
|
||||
userLevelStyle () {
|
||||
// @TODO: implement
|
||||
|
||||
@@ -46,7 +46,7 @@ div
|
||||
.dropdown-menu
|
||||
router-link.dropdown-item(:to="{name: 'myChallenges'}") {{ $t('myChallenges') }}
|
||||
router-link.dropdown-item(:to="{name: 'findChallenges'}") {{ $t('findChallenges') }}
|
||||
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}", :to="{name: 'faq'}")
|
||||
router-link.nav-item.dropdown(tag="li", :class="{'active': $route.path.startsWith('/help')}", :to="{name: 'faq'}")
|
||||
a.nav-link(v-once) {{ $t('help') }}
|
||||
.dropdown-menu
|
||||
router-link.dropdown-item(:to="{name: 'faq'}") {{ $t('faq') }}
|
||||
|
||||
@@ -15,7 +15,7 @@ menu-dropdown.item-notifications(:right="true")
|
||||
a.dropdown-item(v-if='user.purchased.plan.mysteryItems.length', @click='go("/inventory/items")')
|
||||
span.glyphicon.glyphicon-gift
|
||||
span {{ $t('newSubscriberItem') }}
|
||||
a.dropdown-item(v-for='(party, index) in user.invitations.parties')
|
||||
a.dropdown-item(v-for='(party, index) in user.invitations.parties', :key='party.id')
|
||||
div
|
||||
span.glyphicon.glyphicon-user
|
||||
span {{ $t('invitedTo', {name: party.name}) }}
|
||||
@@ -26,7 +26,7 @@ menu-dropdown.item-notifications(:right="true")
|
||||
span.glyphicon.glyphicon-envelope
|
||||
span {{ $t('cardReceived') }}
|
||||
a.dropdown-item(@click.stop='clearCards()')
|
||||
a.dropdown-item(v-for='(guild, index) in user.invitations.guilds')
|
||||
a.dropdown-item(v-for='(guild, index) in user.invitations.guilds', :key='guild.id')
|
||||
div
|
||||
span.glyphicon.glyphicon-user
|
||||
span {{ $t('invitedTo', {name: guild.name}) }}
|
||||
@@ -34,10 +34,10 @@ menu-dropdown.item-notifications(:right="true")
|
||||
button.btn.btn-primary(@click.stop='accept(guild, index, "guild")') Accept
|
||||
button.btn.btn-primary(@click.stop='reject(guild, index, "guild")') Reject
|
||||
a.dropdown-item(v-if='user.flags.classSelected && !user.preferences.disableClasses && user.stats.points',
|
||||
@click='go("/user/profile")')
|
||||
@click='showProfile()')
|
||||
span.glyphicon.glyphicon-plus-sign
|
||||
span {{ $t('haveUnallocated', {points: user.stats.points}) }}
|
||||
a.dropdown-item(v-for='message in userNewMessages')
|
||||
a.dropdown-item(v-for='message in userNewMessages', :key='message.key')
|
||||
span(@click='navigateToGroup(message.key)')
|
||||
span.glyphicon.glyphicon-comment
|
||||
span {{message.name}}
|
||||
@@ -279,6 +279,12 @@ export default {
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: partyId, action: 'quests/reject'});
|
||||
this.user.party.quest = quest;
|
||||
},
|
||||
showProfile () {
|
||||
this.$root.$emit('habitica:show-profile', {
|
||||
user: this.user,
|
||||
startingPage: 'stats',
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -98,9 +98,10 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'inbox-modal');
|
||||
},
|
||||
showProfile (startingPage) {
|
||||
this.$store.state.profileUser = this.user;
|
||||
this.$store.state.profileOptions.startingPage = startingPage;
|
||||
this.$root.$emit('bv::show::modal', 'profile');
|
||||
this.$root.$emit('habitica:show-profile', {
|
||||
user: this.user,
|
||||
startingPage,
|
||||
});
|
||||
},
|
||||
showBuyGemsModal (startingPage) {
|
||||
this.$store.state.gemModalOptions.startingPage = startingPage;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
.standard-sidebar
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
.row(v-mousePosition="30", @mouseMoved="mouseMoved($event)")
|
||||
.standard-sidebar
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template lang="pug">
|
||||
// @TODO: breakdown to componentes and use some SOLID
|
||||
.row.stable(v-mousePosition="30", @mouseMoved="mouseMoved($event)")
|
||||
.standard-sidebar.col-3.hidden-xs-down
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
div
|
||||
#npmMattStable.npc_matt
|
||||
b-popover(
|
||||
@@ -54,7 +54,7 @@
|
||||
@change="updateHideMissing"
|
||||
)
|
||||
|
||||
.standard-page.col-12.col-sm-9
|
||||
.standard-page
|
||||
.clearfix
|
||||
h1.float-left.mb-4.page-header(v-once) {{ $t('stable') }}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
.member-details(
|
||||
:class="{ condensed, expanded, 'd-flex': isHeader, row: !isHeader, }",
|
||||
:class="{ condensed, expanded, 'd-flex': isHeader, row: !isHeader, }",
|
||||
@click='showMemberModal(member)'
|
||||
)
|
||||
div(:class="{ 'col-4': !isHeader }")
|
||||
@@ -174,7 +174,7 @@
|
||||
border-radius: 0px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -245,9 +245,10 @@ export default {
|
||||
methods: {
|
||||
percent,
|
||||
showMemberModal (member) {
|
||||
this.$store.state.profileUser = member;
|
||||
this.$store.state.profileOptions.startingPage = 'profile';
|
||||
this.$root.$emit('bv::show::modal', 'profile');
|
||||
this.$root.$emit('habitica:show-profile', {
|
||||
user: member,
|
||||
startingPage: 'profile',
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
div
|
||||
yesterdaily-modal(
|
||||
:yesterDailies='yesterDailies',
|
||||
@hide="runYesterDailiesAction()",
|
||||
@run-cron="runYesterDailiesAction()",
|
||||
)
|
||||
armoire-empty
|
||||
new-stuff
|
||||
@@ -210,6 +210,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
baileyShouldShow () {
|
||||
if (this.user.needsCron) return;
|
||||
this.$root.$emit('bv::show::modal', 'new-stuff');
|
||||
},
|
||||
userHp (after, before) {
|
||||
@@ -246,7 +247,7 @@ export default {
|
||||
// Append Bonus
|
||||
if (money > 0 && Boolean(bonus)) {
|
||||
if (bonus < 0.01) bonus = 0.01;
|
||||
this.text(`+ ${this.coins(bonus)} ${this.$t('streakCoins')}`);
|
||||
this.streak(`+ ${this.coins(bonus)}`);
|
||||
delete this.user._tmp.streakBonus;
|
||||
}
|
||||
},
|
||||
@@ -310,11 +311,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
checkUserAchievements () {
|
||||
// List of prompts for user on changes. Sounds like we may need a refactor here, but it is clean for now
|
||||
if (this.user.flags.newStuff) {
|
||||
this.$root.$emit('bv::show::modal', 'new-stuff');
|
||||
}
|
||||
if (this.user.needsCron) return;
|
||||
|
||||
// List of prompts for user on changes. Sounds like we may need a refactor here, but it is clean for now
|
||||
if (!this.user.flags.welcomed) {
|
||||
this.$store.state.avatarEditorOptions.editingUser = false;
|
||||
this.$root.$emit('bv::show::modal', 'avatar-modal');
|
||||
@@ -411,6 +410,8 @@ export default {
|
||||
this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}),
|
||||
]);
|
||||
|
||||
this.$store.state.isRunningYesterdailies = false;
|
||||
|
||||
if (this.levelBeforeYesterdailies > 0 && this.levelBeforeYesterdailies < this.user.stats.lvl) {
|
||||
this.showLevelUpNotifications(this.user.stats.lvl);
|
||||
}
|
||||
@@ -422,10 +423,14 @@ export default {
|
||||
this.$store.state.groupNotifications.push(notification);
|
||||
},
|
||||
async handleUserNotifications (after) {
|
||||
if (!after || after.length === 0 || !Array.isArray(after)) return;
|
||||
|
||||
if (this.$store.state.isRunningYesterdailies) return;
|
||||
|
||||
if (this.user.flags.newStuff) {
|
||||
this.$root.$emit('bv::show::modal', 'new-stuff');
|
||||
}
|
||||
|
||||
if (!after || after.length === 0 || !Array.isArray(after)) return;
|
||||
|
||||
let notificationsToRead = [];
|
||||
let scoreTaskNotification = [];
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
b-modal#amazon-payment(title="Amazon", :hide-footer="true", size='md')
|
||||
h2.text-center Continue with Amazon
|
||||
#AmazonPayButton
|
||||
#AmazonPayWallet(v-if="amazonLoggedIn", style="width: 400px; height: 228px;")
|
||||
#AmazonPayRecurring(v-if="amazonLoggedIn && amazonPayments.type === 'subscription'",
|
||||
#AmazonPayWallet(v-if="amazonPayments.loggedIn", style="width: 400px; height: 228px;")
|
||||
#AmazonPayRecurring(v-if="amazonPayments.loggedIn && amazonPayments.type === 'subscription'",
|
||||
style="width: 400px; height: 140px;")
|
||||
.modal-footer
|
||||
.text-center
|
||||
@@ -30,40 +30,35 @@ import { mapState } from 'client/libs/store';
|
||||
const AMAZON_PAYMENTS = process.env.AMAZON_PAYMENTS; // eslint-disable-line
|
||||
|
||||
export default {
|
||||
props: ['amazonPaymentsProp'],
|
||||
data () {
|
||||
return {
|
||||
amazonPayments: {
|
||||
modal: null,
|
||||
type: null,
|
||||
gift: null,
|
||||
loggedIn: false,
|
||||
paymentSelected: false,
|
||||
billingAgreementId: '',
|
||||
recurringConsent: false,
|
||||
orderReferenceId: null,
|
||||
subscription: null,
|
||||
coupon: null,
|
||||
},
|
||||
OffAmazonPayments: {},
|
||||
isAmazonSetup: false,
|
||||
amazonButtonEnabled: false,
|
||||
amazonPaymentsbillingAgreementId: '',
|
||||
amazonPaymentspaymentSelected: false,
|
||||
amazonPaymentsrecurringConsent: 'false',
|
||||
amazonLoggedIn: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
...mapState(['isAmazonReady']),
|
||||
// @TODO: Eh, idk if we should move data props here or move these props to data. But we shouldn't have both
|
||||
amazonPayments () {
|
||||
let amazonPayments = {
|
||||
type: 'single',
|
||||
loggedIn: this.amazonLoggedIn,
|
||||
};
|
||||
amazonPayments = Object.assign({}, amazonPayments, this.amazonPaymentsProp);
|
||||
return amazonPayments;
|
||||
},
|
||||
amazonPaymentsCanCheckout () {
|
||||
if (this.amazonPayments.type === 'single') {
|
||||
return this.amazonPaymentspaymentSelected === true;
|
||||
return this.amazonPayments.paymentSelected === true;
|
||||
} else if (this.amazonPayments.type === 'subscription') {
|
||||
return this.amazonPaymentspaymentSelected === true &&
|
||||
// Mah.. one is a boolean the other a string...
|
||||
this.amazonPaymentsrecurringConsent === 'true';
|
||||
} else {
|
||||
return false;
|
||||
return this.amazonPayments.paymentSelected && this.amazonPayments.recurringConsent;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
@@ -72,6 +67,21 @@ export default {
|
||||
this.$store.watch(state => state.isAmazonReady, (isAmazonReady) => {
|
||||
if (isAmazonReady) return this.setupAmazon();
|
||||
});
|
||||
|
||||
this.$root.$on('habitica::pay-with-amazon', (amazonPaymentsData) => {
|
||||
if (!amazonPaymentsData) return;
|
||||
|
||||
let amazonPayments = {
|
||||
type: 'single',
|
||||
loggedIn: false,
|
||||
};
|
||||
this.amazonPayments = Object.assign({}, amazonPayments, amazonPaymentsData);
|
||||
|
||||
this.$root.$emit('bv::show::modal', 'amazon-payment');
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('habitica::pay-with-amazon');
|
||||
},
|
||||
methods: {
|
||||
setupAmazon () {
|
||||
@@ -90,25 +100,21 @@ export default {
|
||||
color: 'Gold',
|
||||
size: 'small',
|
||||
agreementType: 'BillingAgreement',
|
||||
|
||||
onSignIn: async (contract) => {
|
||||
this.amazonPaymentsbillingAgreementId = contract.getAmazonBillingAgreementId();
|
||||
this.amazonLoggedIn = true;
|
||||
this.amazonPayments.billingAgreementId = contract.getAmazonBillingAgreementId();
|
||||
|
||||
this.$set(this.amazonPayments, 'loggedIn', true);
|
||||
|
||||
if (this.amazonPayments.type === 'subscription') {
|
||||
this.amazonPaymentsinitWidgets();
|
||||
this.amazonInitWidgets();
|
||||
} else {
|
||||
let url = '/amazon/createOrderReferenceId';
|
||||
let response = await axios.post(url, {
|
||||
billingAgreementId: this.amazonPaymentsbillingAgreementId,
|
||||
billingAgreementId: this.amazonPayments.billingAgreementId,
|
||||
});
|
||||
|
||||
if (response.status <= 400) {
|
||||
this.amazonPayments.orderReferenceId = response.data.data.orderReferenceId;
|
||||
|
||||
// @TODO: Clarify the deifference of these functions by renaming
|
||||
this.amazonPaymentsinitWidgets();
|
||||
this.amazonInitWidgets();
|
||||
return;
|
||||
}
|
||||
@@ -116,7 +122,6 @@ export default {
|
||||
alert(response.message);
|
||||
}
|
||||
},
|
||||
|
||||
authorization: () => {
|
||||
window.amazon.Login.authorize({
|
||||
scope: 'payments:widget',
|
||||
@@ -131,7 +136,6 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onError: this.amazonOnError,
|
||||
});
|
||||
},
|
||||
@@ -141,38 +145,29 @@ export default {
|
||||
design: {
|
||||
designMode: 'responsive',
|
||||
},
|
||||
|
||||
onPaymentSelect: () => {
|
||||
this.amazonPaymentspaymentSelected = true;
|
||||
},
|
||||
|
||||
onPaymentSelect: this.amazonOnPaymentSelect,
|
||||
onError: this.amazonOnError,
|
||||
};
|
||||
|
||||
// @TODO: Check if this is duplicated below
|
||||
if (this.amazonPayments.type === 'subscription') {
|
||||
walletParams.agreementType = 'BillingAgreement';
|
||||
|
||||
walletParams.billingAgreementId = this.amazonPaymentsbillingAgreementId;
|
||||
walletParams.billingAgreementId = this.amazonPayments.billingAgreementId;
|
||||
walletParams.onReady = (billingAgreement) => {
|
||||
this.amazonPaymentsbillingAgreementId = billingAgreement.getAmazonBillingAgreementId();
|
||||
this.amazonPayments.billingAgreementId = billingAgreement.getAmazonBillingAgreementId();
|
||||
|
||||
new this.OffAmazonPayments.Widgets.Consent({
|
||||
sellerId: AMAZON_PAYMENTS.SELLER_ID,
|
||||
amazonBillingAgreementId: this.amazonPaymentsbillingAgreementId,
|
||||
amazonBillingAgreementId: this.amazonPayments.billingAgreementId,
|
||||
design: {
|
||||
designMode: 'responsive',
|
||||
},
|
||||
|
||||
onReady: (consent) => {
|
||||
let getConsent = consent.getConsentStatus;
|
||||
this.amazonPaymentsrecurringConsent = getConsent ? getConsent() : false;
|
||||
this.$set(this.amazonPayments, 'recurringConsent', getConsent ? Boolean(getConsent()) : false);
|
||||
},
|
||||
|
||||
onConsent: (consent) => {
|
||||
this.amazonPaymentsrecurringConsent = consent.getConsentStatus();
|
||||
this.$set(this.amazonPayments, 'recurringConsent', Boolean(consent.getConsentStatus()));
|
||||
},
|
||||
|
||||
onError: this.amazonOnError,
|
||||
}).bind('AmazonPayRecurring');
|
||||
};
|
||||
@@ -191,7 +186,7 @@ export default {
|
||||
let url = '/amazon/checkout';
|
||||
let response = await axios.post(url, {
|
||||
orderReferenceId: this.amazonPayments.orderReferenceId,
|
||||
gift: this.amazonPaymentsgift,
|
||||
gift: this.amazonPayments.gift,
|
||||
});
|
||||
|
||||
if (response.status < 400) {
|
||||
@@ -211,7 +206,7 @@ export default {
|
||||
}
|
||||
|
||||
let response = await axios.post(url, {
|
||||
billingAgreementId: this.amazonPaymentsbillingAgreementId,
|
||||
billingAgreementId: this.amazonPayments.billingAgreementId,
|
||||
subscription: this.amazonPayments.subscription,
|
||||
coupon: this.amazonPayments.coupon,
|
||||
groupId: this.amazonPayments.groupId,
|
||||
@@ -243,67 +238,26 @@ export default {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
amazonPaymentsinitWidgets () {
|
||||
let walletParams = {
|
||||
sellerId: AMAZON_PAYMENTS.SELLER_ID,
|
||||
design: {
|
||||
designMode: 'responsive',
|
||||
},
|
||||
|
||||
onPaymentSelect: () => {
|
||||
this.amazonPayments.paymentSelected = true;
|
||||
},
|
||||
|
||||
onError: this.amazonOnError,
|
||||
};
|
||||
|
||||
if (this.amazonPayments.type === 'subscription') {
|
||||
walletParams.agreementType = 'BillingAgreement';
|
||||
|
||||
walletParams.billingAgreementId = this.amazonPayments.billingAgreementId;
|
||||
walletParams.onReady = (billingAgreement) => {
|
||||
this.amazonPayments.billingAgreementId = billingAgreement.getAmazonBillingAgreementId();
|
||||
|
||||
new this.OffAmazonPayments.Widgets.Consent({
|
||||
sellerId: AMAZON_PAYMENTS.SELLER_ID,
|
||||
amazonBillingAgreementId: this.amazonPayments.billingAgreementId,
|
||||
design: {
|
||||
designMode: 'responsive',
|
||||
},
|
||||
|
||||
onReady: (consent) => {
|
||||
let getConsent = consent.getConsentStatus;
|
||||
this.amazonPayments.recurringConsent = getConsent ? getConsent() : false;
|
||||
},
|
||||
|
||||
onConsent: (consent) => {
|
||||
this.amazonPayments.recurringConsent = consent.getConsentStatus();
|
||||
},
|
||||
|
||||
onError: this.amazonOnError,
|
||||
}).bind('AmazonPayRecurring');
|
||||
};
|
||||
} else {
|
||||
walletParams.amazonOrderReferenceId = this.amazonPayments.orderReferenceId;
|
||||
}
|
||||
|
||||
new this.OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet');
|
||||
amazonOnPaymentSelect () {
|
||||
this.$set(this.amazonPayments, 'paymentSelected', true);
|
||||
},
|
||||
amazonOnError (error) {
|
||||
alert(error.getErrorMessage());
|
||||
this.reset();
|
||||
},
|
||||
reset () {
|
||||
this.amazonPaymentsmodal = null;
|
||||
// @TODO: Ensure we are using all of these
|
||||
// some vars are set in the payments mixin. We should try to edit in one place
|
||||
this.amazonPayments.modal = null;
|
||||
this.amazonPayments.type = null;
|
||||
this.amazonLoggedIn = false;
|
||||
this.amazonPaymentsgift = null;
|
||||
this.amazonPaymentsbillingAgreementId = null;
|
||||
this.amazonPayments.loggedIn = false;
|
||||
this.amazonPayments.gift = null;
|
||||
this.amazonPayments.billingAgreementId = null;
|
||||
this.amazonPayments.orderReferenceId = null;
|
||||
this.amazonPaymentspaymentSelected = false;
|
||||
this.amazonPaymentsrecurringConsent = false;
|
||||
this.amazonPaymentssubscription = null;
|
||||
this.amazonPaymentscoupon = null;
|
||||
this.amazonPayments.paymentSelected = false;
|
||||
this.amazonPayments.recurringConsent = false;
|
||||
this.amazonPayments.subscription = null;
|
||||
this.amazonPayments.coupon = null;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
.col-md-8.align-self-center
|
||||
p=text
|
||||
div(v-if='user')
|
||||
amazon-payments-modal(:amazon-payments-prop='amazonPayments')
|
||||
b-modal(:hide-footer='true', :hide-header='true', :id='"buy-gems"', size='lg')
|
||||
.container-fluid.purple-gradient
|
||||
.gemfall
|
||||
@@ -344,7 +343,6 @@
|
||||
import markdown from 'client/directives/markdown';
|
||||
import planGemLimits from 'common/script/libs/planGemLimits';
|
||||
import paymentsMixin from 'client/mixins/payments';
|
||||
import amazonPaymentsModal from './amazonModal';
|
||||
|
||||
import checkIcon from 'assets/svg/check.svg';
|
||||
import creditCard from 'assets/svg/credit-card.svg';
|
||||
@@ -360,7 +358,6 @@
|
||||
mixins: [paymentsMixin],
|
||||
components: {
|
||||
planGemLimits,
|
||||
amazonPaymentsModal,
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
|
||||
@@ -5,7 +5,6 @@ b-modal#send-gems(:title="title", :hide-footer="true", size='lg')
|
||||
:class="gift.type === 'gems' ? 'panel-primary' : 'transparent'",
|
||||
@click='gift.type = "gems"'
|
||||
)
|
||||
// @TODO the panel does not exists in Bootstrap 4
|
||||
h3.panel-heading.clearfix
|
||||
.float-right
|
||||
span(v-if='gift.gems.fromBalance') {{ $t('sendGiftGemsBalance', {number: userLoggedIn.balance * 4}) }}
|
||||
@@ -51,26 +50,26 @@ b-modal#send-gems(:title="title", :hide-footer="true", size='lg')
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.panel {
|
||||
margin-bottom: 4px;
|
||||
.panel {
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.transparent {
|
||||
.panel-body {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&.transparent {
|
||||
.panel-body {
|
||||
opacity: 0.7;
|
||||
padding: 8px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #C3C0C7;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 8px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #C3C0C7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -92,6 +92,8 @@ import removeIcon from 'assets/members/remove.svg';
|
||||
import messageIcon from 'assets/members/message.svg';
|
||||
import starIcon from 'assets/members/star.svg';
|
||||
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
props: ['group', 'hideBadge', 'item'],
|
||||
components: {
|
||||
@@ -129,11 +131,12 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
sortedMembers () {
|
||||
let sortedMembers = this.members;
|
||||
if (!this.sortOption) return sortedMembers;
|
||||
|
||||
sortedMembers = sortBy(this.members, [(member) => {
|
||||
sortBy(this.members, [(member) => {
|
||||
if (this.sortOption === 'tier') {
|
||||
if (!member.contributor) return;
|
||||
return member.contributor.level;
|
||||
@@ -173,6 +176,10 @@ export default {
|
||||
if (this.$store.state.memberModalOptions.viewingMembers.length > 0) {
|
||||
this.members = this.$store.state.viewingMembers;
|
||||
}
|
||||
|
||||
if (this.members.length === 0 && !this.groupId) {
|
||||
this.members = [this.user];
|
||||
}
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'select-member-modal');
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
.social-delete(v-if='!user.auth.local.email')
|
||||
h4 {{ $t('deleteAccount') }}
|
||||
.modal-body
|
||||
p {{ $t('deleteSocialAccountText') }}
|
||||
p {{ $t('deleteSocialAccountText', {magicWord: 'DELETE'}) }}
|
||||
br
|
||||
.row
|
||||
.col-md-6
|
||||
|
||||
@@ -85,6 +85,10 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const userChangedLevel = this.restoreValues.stats.lvl !== this.user.stats.lvl;
|
||||
const userDidNotChangeExp = this.restoreValues.stats.exp === this.user.stats.exp;
|
||||
if (userChangedLevel && userDidNotChangeExp) this.restoreValues.stats.exp = 0;
|
||||
|
||||
this.user.stats = clone(this.restoreValues.stats);
|
||||
this.user.achievements.streak = clone(this.restoreValues.achievements.streak);
|
||||
|
||||
|
||||
@@ -341,7 +341,6 @@ export default {
|
||||
await axios.put(`/api/v3/user/auth/update-${attribute}`, updates);
|
||||
alert(this.$t(`${attribute}Success`));
|
||||
this.user[attribute] = updates[attribute];
|
||||
updates = {};
|
||||
},
|
||||
openRestoreModal () {
|
||||
this.$root.$emit('bv::show::modal', 'restore');
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template lang="pug">
|
||||
.standard-page
|
||||
amazon-payments-modal(:amazon-payments-prop='amazonPayments')
|
||||
|
||||
h1 {{ $t('subscription') }}
|
||||
.row
|
||||
.col-6
|
||||
@@ -33,7 +31,7 @@
|
||||
span.noninteractive-button.btn-danger {{ $t('canceledSubscription') }}
|
||||
i.glyphicon.glyphicon-time
|
||||
| {{ $t('subCanceled') }}
|
||||
strong {{user.purchased.plan.dateTerminated | date}}
|
||||
strong {{dateTerminated}}
|
||||
tr(v-if='!hasCanceledSubscription'): td
|
||||
h4 {{ $t('subscribed') }}
|
||||
p(v-if='hasPlan && !hasGroupPlan') {{ $t('purchasedPlanId', purchasedPlanIdInfo) }}
|
||||
@@ -70,7 +68,7 @@
|
||||
|
||||
div(v-if='hasSubscription')
|
||||
.btn.btn-primary(v-if='canEditCardDetails', @click='showStripeEdit()') {{ $t('subUpdateCard') }}
|
||||
.btn.btn-sm.btn-danger(v-if='canCancelSubscription', @click='cancelSubscription()') {{ $t('cancelSub') }}
|
||||
.btn.btn-sm.btn-danger(v-if='canCancelSubscription && !loading', @click='cancelSubscription()') {{ $t('cancelSub') }}
|
||||
small(v-if='!canCancelSubscription', v-html='getCancelSubInfo()')
|
||||
|
||||
.subscribe-pay(v-if='!hasSubscription || hasCanceledSubscription')
|
||||
@@ -82,9 +80,8 @@
|
||||
a.purchase(:href='paypalPurchaseLink', :disabled='!subscription.key', target='_blank')
|
||||
img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png', :alt="$t('paypal')")
|
||||
.col-md-4
|
||||
a.btn.btn-secondary.purchase(@click="amazonPaymentsInit({type: 'subscription', subscription:subscription.key, coupon:subscription.coupon})")
|
||||
a.btn.btn-secondary.purchase(@click="payWithAmazon()")
|
||||
img(src='https://payments.amazon.com/gp/cba/button', :alt="$t('amazonPayments')")
|
||||
|
||||
.row
|
||||
.col-6
|
||||
h2 {{ $t('giftSubscription') }}
|
||||
@@ -115,16 +112,13 @@ import { mapState } from 'client/libs/store';
|
||||
|
||||
import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks';
|
||||
import planGemLimits from '../../../common/script/libs/planGemLimits';
|
||||
import amazonPaymentsModal from '../payments/amazonModal';
|
||||
import paymentsMixin from '../../mixins/payments';
|
||||
|
||||
export default {
|
||||
mixins: [paymentsMixin],
|
||||
components: {
|
||||
amazonPaymentsModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
gemCostTranslation: {
|
||||
gemCost: planGemLimits.convRate,
|
||||
gemLimit: planGemLimits.convRate,
|
||||
@@ -132,6 +126,7 @@ export default {
|
||||
subscription: {
|
||||
key: 'basic_earned',
|
||||
},
|
||||
// @TODO: Remove the need for this or move it to mixin
|
||||
amazonPayments: {},
|
||||
paymentMethods: {
|
||||
AMAZON_PAYMENTS: 'Amazon Payments',
|
||||
@@ -143,13 +138,6 @@ export default {
|
||||
},
|
||||
};
|
||||
},
|
||||
filters: {
|
||||
date (value) {
|
||||
if (!value) return '';
|
||||
return moment(value);
|
||||
// return moment(value).format(this.user.preferences.dateFormat); // @TODO make that work (`TypeError: this is undefined`)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data', credentials: 'credentials'}),
|
||||
subscriptionBlocksOrdered () {
|
||||
@@ -237,6 +225,10 @@ export default {
|
||||
amount: this.numberOfMysticHourglasses,
|
||||
};
|
||||
},
|
||||
dateTerminated () {
|
||||
if (!this.user.preferences || !this.user.preferences.dateFormat) return this.user.purchased.plan.dateTerminated;
|
||||
return moment(this.user.purchased.plan.dateTerminated).format(this.user.preferences.dateFormat.toUpperCase());
|
||||
},
|
||||
canCancelSubscription () {
|
||||
return (
|
||||
this.user.purchased.plan.paymentMethod !== this.paymentMethods.GOOGLE &&
|
||||
@@ -247,6 +239,13 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
payWithAmazon () {
|
||||
this.amazonPaymentsInit({
|
||||
type: 'subscription',
|
||||
subscription: this.subscription.key,
|
||||
coupon: this.subscription.coupon,
|
||||
});
|
||||
},
|
||||
async applyCoupon (coupon) {
|
||||
let response = await axios.get(`/api/v3/coupons/validate/${coupon}`);
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
)
|
||||
|
||||
.purchase-amount(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}")
|
||||
.how-many-to-buy(v-if='["fortify", "gear"].indexOf(item.purchaseType) === -1')
|
||||
.how-many-to-buy(v-if='showAmountToBuy(item)')
|
||||
strong {{ $t('howManyToBuy') }}
|
||||
div(v-if='item.purchaseType !== "gear"')
|
||||
.box(v-if='["fortify", "gear"].indexOf(item.purchaseType) === -1')
|
||||
div(v-if='showAmountToBuy(item)')
|
||||
.box
|
||||
input(type='number', min='0', v-model='selectedAmountToBuy')
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
|
||||
span.value(:class="getPriceClass()") {{ item.value }}
|
||||
@@ -285,6 +285,11 @@
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
const hideAmountSelectionForPurchaseTypes = [
|
||||
'gear', 'backgrounds', 'mystery_set', 'card',
|
||||
'rebirth_orb', 'fortify', 'armoire',
|
||||
];
|
||||
|
||||
export default {
|
||||
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
|
||||
components: {
|
||||
@@ -363,6 +368,16 @@
|
||||
this.$emit('change', $event);
|
||||
},
|
||||
buyItem () {
|
||||
if (this.item.currency === 'gems' &&
|
||||
!confirm(this.$t('purchaseFor', { cost: this.item.value }))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.item.currency === 'hourglasses' &&
|
||||
!confirm(this.$t('purchaseForHourglasses', { cost: this.item.value }))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.item.cast) {
|
||||
this.castStart(this.item);
|
||||
} else if (this.genericPurchase) {
|
||||
@@ -403,6 +418,13 @@
|
||||
return 'gold';
|
||||
}
|
||||
},
|
||||
showAmountToBuy (item) {
|
||||
if (hideAmountSelectionForPurchaseTypes.includes(item.purchaseType)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
getAvatarOverrides (item) {
|
||||
switch (item.purchaseType) {
|
||||
case 'gear':
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<template lang="pug">
|
||||
.row.market
|
||||
.standard-sidebar
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
.form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
.form-group
|
||||
@@ -15,7 +14,6 @@
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[category.identifier].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ category.text }}
|
||||
|
||||
div.form-group.clearfix
|
||||
h3.float-left(v-once) {{ $t('hideLocked') }}
|
||||
toggle-switch.float-right.no-margin(
|
||||
@@ -62,7 +60,7 @@
|
||||
|
||||
h1.mb-4.page-header(v-once) {{ $t('market') }}
|
||||
|
||||
.clearfix
|
||||
.clearfix(v-if="viewOptions['equipment'].selected")
|
||||
h2.float-left.mb-3
|
||||
| {{ $t('equipment') }}
|
||||
|
||||
@@ -99,7 +97,8 @@
|
||||
:itemWidth=94,
|
||||
:itemMargin=24,
|
||||
:type="'gear'",
|
||||
:noItemsLabel="$t('noGearItemsOfClass')"
|
||||
:noItemsLabel="$t('noGearItemsOfClass')",
|
||||
v-if="viewOptions['equipment'].selected"
|
||||
)
|
||||
template(slot="item", slot-scope="ctx")
|
||||
shopItem(
|
||||
@@ -247,7 +246,7 @@
|
||||
height: 38px; // button + margin + padding
|
||||
}
|
||||
|
||||
|
||||
|
||||
.icon-48 {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
@@ -457,6 +456,11 @@ export default {
|
||||
...this.market.categories,
|
||||
];
|
||||
|
||||
categories.push({
|
||||
identifier: 'equipment',
|
||||
text: this.$t('equipment'),
|
||||
});
|
||||
|
||||
categories.push({
|
||||
identifier: 'cards',
|
||||
text: this.$t('cards'),
|
||||
@@ -502,9 +506,11 @@ export default {
|
||||
}
|
||||
|
||||
categories.map((category) => {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: true,
|
||||
});
|
||||
if (!this.viewOptions[category.identifier]) {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return categories;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
.row.quests
|
||||
.standard-sidebar
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
.row.seasonal
|
||||
.standard-sidebar
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
|
||||
@@ -225,7 +225,8 @@ div
|
||||
}
|
||||
},
|
||||
limitedString () {
|
||||
return this.$t('limitedOffer', {date: moment(seasonalShopConfig.dateRange.end).format('LL')});
|
||||
return this.item.owned === false ? '' :
|
||||
this.$t('limitedOffer', {date: moment(seasonalShopConfig.dateRange.end).format('LL')});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
.row.timeTravelers
|
||||
.standard-sidebar(v-if="!closed")
|
||||
.standard-sidebar.d-none.d-sm-block(v-if="!closed")
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
currency: 'hourglasses',
|
||||
key: c.identifier,
|
||||
class: `shop_set_mystery_${c.identifier}`,
|
||||
purchaseType: 'set_mystery',
|
||||
purchaseType: 'mystery_set',
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -4,6 +4,12 @@ transition(name="fade")
|
||||
.row(v-if='notification.type === "error"')
|
||||
.text.col-12
|
||||
div(v-html='notification.text')
|
||||
.row(v-if='notification.type === "streak"')
|
||||
.text.col-7.offset-1
|
||||
div {{message}}
|
||||
.icon.col-4
|
||||
div.svg-icon(v-html="icons.gold")
|
||||
div(v-html='notification.text')
|
||||
.row(v-if='["hp", "gp", "xp", "mp"].indexOf(notification.type) !== -1')
|
||||
.text.col-7.offset-1
|
||||
div
|
||||
@@ -145,6 +151,7 @@ export default {
|
||||
if (this.notification.type === 'mp') localeKey += 'Mana';
|
||||
if (this.notification.type === 'xp') localeKey += 'Experience';
|
||||
if (this.notification.type === 'gp') localeKey += 'Gold';
|
||||
if (this.notification.type === 'streak') localeKey = 'streakCoins';
|
||||
return this.$t(localeKey);
|
||||
// This requires eight translatable strings, but that gives the translators the most flexibility for matching gender/number and for using idioms for lost/spent/used/gained.
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
span {{ $t('enterprisePlansDescription') }}
|
||||
.row.row-margin
|
||||
// TODO
|
||||
a.btn.btn-primary.btn-lg.btn-block(:href="'mailto:vicky@habitica.com?subject=' + $t('enterprisePlansEmailSubject')") {{ $t('enterprisePlansButton') }}
|
||||
a.btn.btn-primary.btn-lg.btn-block(:href="'mailto:vicky@habitica.com?subject=' + enterprisePlansEmailSubject") {{ $t('enterprisePlansButton') }}
|
||||
|
||||
br
|
||||
|
||||
@@ -36,11 +36,16 @@
|
||||
<script>
|
||||
import StaticHeader from './header.vue';
|
||||
import * as Analytics from 'client/libs/analytics';
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StaticHeader,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
enterprisePlansEmailSubject: 'Question regarding Enterprise Plans',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
goToNewGroupPage () {
|
||||
if (!this.$store.state.isUserLoggedIn) {
|
||||
@@ -63,7 +68,7 @@
|
||||
eventLabel: 'Contact Us (Plans)',
|
||||
});
|
||||
|
||||
window.location.href = `mailto:vicky@habitica.com?subject=${this.$t('enterprisePlansEmailSubject')}`;
|
||||
window.location.href = `mailto:vicky@habitica.com?subject=${ this.enterprisePlansEmailSubject }`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="pug">
|
||||
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
|
||||
nav.navbar.navbar-inverse.fixed-top.navbar-expand-sm
|
||||
.navbar-header
|
||||
router-link.nav-item(:to='!isUserLoggedIn ? "/static/home" : "/"')
|
||||
.logo.svg-icon(v-html='icons.logo')
|
||||
|
||||
@@ -4,34 +4,26 @@
|
||||
.align-self-center.right-margin(:class='baileyClass')
|
||||
.media-body
|
||||
h1.align-self-center(v-markdown='$t("newStuff")')
|
||||
h2 11/17/2017 - THANKSGIVING IN HABITICA, NOVEMBER SUBSCRIBER ITEMS, AND ANDROID UPDATE
|
||||
h2 11/29/2017 - LAST CHANCE FOR CARPET RIDER SET AND THUNDERSTORM POTIONS, GUILD AND USE CASE SPOTLIGHTS
|
||||
hr
|
||||
.media
|
||||
.promo_turkey_day_2017.right-margin
|
||||
.media-body.d-flex.align-self-center.flex-column
|
||||
h3 Happy Thanksgiving!
|
||||
p It's Thanksgiving in Habitica! On this day Habiticans celebrate by spending time with loved ones, giving thanks, and riding their glorious turkeys into the magnificent sunset. Some of the NPCs are celebrating the occasion!
|
||||
h3 Turkey Pets, Mounts, and Costume!
|
||||
p For the occasion, all Habiticans have received Turkey-themed items! What items? It all depends on how many Habitica Thanksgivings you've celebrated with us. Each Thanksgiving, you'll get a new and exciting Turkey pet, mount, or gear set! Check your Stable and your pinned Rewards to see what you got!
|
||||
p Thank you for using Habitica - we really love you all <3
|
||||
.small by Lemoness
|
||||
.promo_potions_thunderstorm.right-margin
|
||||
.media-body
|
||||
h3 Last Chance for Carpet Rider Set
|
||||
p(v-markdown='"Reminder: there are only two more days to [subscribe](/user/settings/subscription) and receive the Carpet Rider Set! Subscribing also lets you buy gems for gold. The longer your subscription, the more gems you get!"')
|
||||
p Thanks so much for your support! You help keep Habitica running.
|
||||
.small by Beffymaroo
|
||||
h3 Last Chance for Thunderstorm Hatching Potions
|
||||
p(v-markdown='"Reminder: this is the final day to [buy Thunderstorm Hatching Potions!](/shops/market) If they come back, it won\'t be until next year at the earliest, so don\'t delay!"')
|
||||
.small by Balduranne
|
||||
h3 Generating Interest: Guilds and Use Cases for Money Matters
|
||||
p(v-markdown='"There\'s a new [Guild Spotlight on the blog](https://habitica.wordpress.com/2017/11/28/generating-interest-guilds-for-money-matters/) that highlights the Guilds that can help you as work to improve your budgeting and saving habits! Check it out now to find Habitica\'s best money management communities."')
|
||||
.media
|
||||
.media-body
|
||||
h3 November Subscriber Items Revealed!
|
||||
p(v-markdown='"The November Subscriber Items have been revealed: the [Carpet Rider Item Set](https://habitica.com/user/settings/subscription)! You have until November 30 to receive the item set when you subscribe. If you\'re already an active subscriber, reload the site and then head to Inventory > Items to claim your gear!"')
|
||||
p Subscribers also receive the ability to buy gems for gold -- the longer you subscribe, the more gems you can buy per month! There are other perks as well, such as longer access to uncompressed data and a cute Jackalope pet. Best of all, subscriptions let us keep Habitica running. Thank you very much for your support -- it means a lot to us.
|
||||
.small by Lemoness
|
||||
.promo_mystery_201711.left-margin
|
||||
h3 Android App Update
|
||||
p There’s an exciting new update to our Android app!
|
||||
ul
|
||||
li We’ve smashed some pesky bugs, including the issue with task reordering!
|
||||
li You can now view and assign attribute points (or choose auto-allocation)!
|
||||
li We’ve added Equipment to the Market, plus the ability to change your login name and email, reset or delete your account, make changes to your profile, and access Fix Character Values.
|
||||
li You can also request a password reset from the login screen!
|
||||
p We hope this makes your Habitica experience even better. Be sure to download the update now for a better Habitica experience!
|
||||
p If you like the improvements that we’ve been making to our app, please consider reviewing this new version. It really helps us out!
|
||||
.small by Viirus and piyorii
|
||||
p(v-markdown='"This month\'s [Use Case Spotlight](https://habitica.wordpress.com/2017/11/14/3908/) follows the same theme! It features a number of great suggestions submitted by Habiticans in the [Use Case Spotlights Guild](https://habitica.com/groups/guild/1d3a10bf-60aa-4806-a38b-82d1084a59e6). We hope it helps any of you who might be working on saving and budgeting as the holiday season approaches!"')
|
||||
p Plus, we're collecting user submissions for the next spotlight! How do you use Habitica to stay on top of Holiday Housekeeping? We’ll be featuring player-submitted examples in Use Case Spotlights on the Habitica Blog at the start of next month, so post your suggestions in the Use Case Spotlight Guild now. We look forward to learning more about how you use Habitica to improve your life and get things done!
|
||||
.small by Beffymaroo
|
||||
.scene_money.left-margin
|
||||
br
|
||||
</template>
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ div
|
||||
color: #bda8ff;
|
||||
}
|
||||
|
||||
.social-circle, .btn-donate {
|
||||
.social-circle, .btn-contribute {
|
||||
background: #36205d;
|
||||
color: #bda8ff;
|
||||
|
||||
|
||||
@@ -63,15 +63,15 @@ export default {
|
||||
}
|
||||
|
||||
if (assignedUsersLength === 1 && !this.userIsAssigned) {
|
||||
return `Assigned to ${assignedUsersNames[0]}`;
|
||||
return this.$t('assignedToUser', {userName: assignedUsersNames[0]});
|
||||
} else if (assignedUsersLength > 1 && !this.userIsAssigned) {
|
||||
return `Assigned to ${assignedUsersLength} members`;
|
||||
return this.$t('assignedToMembers', {userCount: assignedUsersLength});
|
||||
} else if (assignedUsersLength > 1 && this.userIsAssigned) {
|
||||
return `Assigned to you and ${assignedUsersLength} members`;
|
||||
return this.$t('assignedToYouAndMembers', {userCount: assignedUsersLength});
|
||||
} else if (this.userIsAssigned) {
|
||||
return 'You are assigned to this task';
|
||||
return this.$t('youAreAssigned');
|
||||
} else if (assignedUsersLength === 0) {
|
||||
return 'This task is unassigned';
|
||||
return this.$t('taskIsUnassigned');
|
||||
}
|
||||
},
|
||||
approvalRequested () {
|
||||
@@ -83,7 +83,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async claim () {
|
||||
if (!confirm('Are you sure you want to claim this task?')) return;
|
||||
if (!confirm(this.$t('confirmClaim'))) return;
|
||||
|
||||
let taskId = this.task._id;
|
||||
// If we are on the user task
|
||||
@@ -98,7 +98,7 @@ export default {
|
||||
this.task.group.assignedUsers.push(this.user._id);
|
||||
},
|
||||
async unassign () {
|
||||
if (!confirm('Are you sure you want to unclaim this task?')) return;
|
||||
if (!confirm(this.$t('confirmUnClaim'))) return;
|
||||
|
||||
let taskId = this.task._id;
|
||||
// If we are on the user task
|
||||
@@ -114,7 +114,7 @@ export default {
|
||||
this.task.group.assignedUsers.splice(index, 1);
|
||||
},
|
||||
approve () {
|
||||
if (!confirm('Are you sure you want to approve this task?')) return;
|
||||
if (!confirm(this.$t('confirmApproval'))) return;
|
||||
let userIdToApprove = this.task.group.assignedUsers[0];
|
||||
this.$store.dispatch('tasks:unassignTask', {
|
||||
taskId: this.task._id,
|
||||
|
||||
@@ -27,11 +27,11 @@ export default {
|
||||
let userIsRequesting = this.task.group.approvals && this.task.group.approvals.indexOf(this.user._id) !== -1;
|
||||
|
||||
if (approvalsLength === 1 && !userIsRequesting) {
|
||||
return `${approvals[0].userId.profile.name} requests approval`;
|
||||
return this.$t('youAreRequestingApproval', {userName: approvals[0].userId.profile.name});
|
||||
} else if (approvalsLength > 1 && !userIsRequesting) {
|
||||
return `${approvalsLength} request approval`;
|
||||
return this.$t('youAreRequestingApproval', {userCount: approvalsLength});
|
||||
} else if (approvalsLength === 1 && userIsRequesting) {
|
||||
return 'You are requesting approval';
|
||||
return this.$t('youAreRequestingApproval');
|
||||
}
|
||||
},
|
||||
userIsAdmin () {
|
||||
|
||||
@@ -34,11 +34,10 @@
|
||||
.svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once)
|
||||
h3(v-once) {{$t('theseAreYourTasks', {taskType: $t(types[type].label)})}}
|
||||
.small-text {{$t(`${type}sDesc`)}}
|
||||
.sortable-tasks(
|
||||
draggable(
|
||||
ref="tasksList",
|
||||
v-sortable='activeFilters[type].label !== "scheduled"',
|
||||
@onsort='sorted',
|
||||
data-sortableId
|
||||
@update='sorted',
|
||||
:options='{disabled: activeFilters[type].label === "scheduled"}',
|
||||
)
|
||||
task(
|
||||
v-for="task in taskList",
|
||||
@@ -64,7 +63,6 @@
|
||||
@click.prevent.stop="togglePinned(ctx.item)"
|
||||
)
|
||||
span.svg-icon.inline.icon-12.color(v-html="icons.pin")
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -81,9 +79,20 @@
|
||||
|
||||
|
||||
.reward-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
@supports (display: grid) {
|
||||
display: grid;
|
||||
grid-column-gap: 16px;
|
||||
grid-row-gap: 4px;
|
||||
grid-template-columns: repeat(auto-fill, 94px);
|
||||
}
|
||||
|
||||
@supports not (display: grid) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
& > div {
|
||||
margin: 0 16px 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tasks-list {
|
||||
@@ -235,7 +244,6 @@
|
||||
import Task from './task';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import throttle from 'lodash/throttle';
|
||||
import sortable from 'client/directives/sortable.directive';
|
||||
import buyMixin from 'client/mixins/buy';
|
||||
import { mapState, mapActions } from 'client/libs/store';
|
||||
import shopItem from '../shops/shopItem';
|
||||
@@ -252,6 +260,7 @@ import habitIcon from 'assets/svg/habit.svg';
|
||||
import dailyIcon from 'assets/svg/daily.svg';
|
||||
import todoIcon from 'assets/svg/todo.svg';
|
||||
import rewardIcon from 'assets/svg/reward.svg';
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
mixins: [buyMixin, notifications],
|
||||
@@ -259,9 +268,7 @@ export default {
|
||||
Task,
|
||||
BuyQuestModal,
|
||||
shopItem,
|
||||
},
|
||||
directives: {
|
||||
sortable,
|
||||
draggable,
|
||||
},
|
||||
props: ['type', 'isUser', 'searchText', 'selectedTags', 'taskListOverride', 'group'], // @TODO: maybe we should store the group on state?
|
||||
data () {
|
||||
@@ -334,6 +341,16 @@ export default {
|
||||
user: 'user.data',
|
||||
userPreferences: 'user.data.preferences',
|
||||
}),
|
||||
onUserPage () {
|
||||
let onUserPage = Boolean(this.taskList.length) && (!this.taskListOverride || this.taskListOverride.length === 0);
|
||||
|
||||
if (!onUserPage) {
|
||||
this.activateFilter('daily', this.types.daily.filters[0]);
|
||||
this.types.reward.filters = [];
|
||||
}
|
||||
|
||||
return onUserPage;
|
||||
},
|
||||
taskList () {
|
||||
// @TODO: This should not default to user's tasks. It should require that you pass options in
|
||||
const filter = this.activeFilters[this.type];
|
||||
@@ -399,6 +416,7 @@ export default {
|
||||
if (this.user.preferences.dailyDueDefaultView) {
|
||||
this.activateFilter('daily', this.types.daily.filters[1]);
|
||||
}
|
||||
|
||||
return this.user.preferences.dailyDueDefaultView;
|
||||
},
|
||||
quickAddPlaceholder () {
|
||||
@@ -434,9 +452,12 @@ export default {
|
||||
deep: true,
|
||||
},
|
||||
dailyDueDefaultView () {
|
||||
if (this.user.preferences.dailyDueDefaultView) {
|
||||
this.activateFilter('daily', this.types.daily.filters[1]);
|
||||
}
|
||||
if (!this.dailyDueDefaultView) return;
|
||||
this.activateFilter('daily', this.types.daily.filters[1]);
|
||||
},
|
||||
quickAddFocused (newValue) {
|
||||
if (newValue) this.quickAddRows = this.quickAddText.split('\n').length;
|
||||
if (!newValue) this.quickAddRows = 1;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
@@ -526,6 +547,7 @@ export default {
|
||||
if (type === 'todo' && filter.label === 'complete2') {
|
||||
this.loadCompletedTodos();
|
||||
}
|
||||
|
||||
this.activeFilters[type] = filter;
|
||||
},
|
||||
setColumnBackgroundVisibility () {
|
||||
|
||||
@@ -12,7 +12,7 @@ div(v-if='user.stats.lvl > 10')
|
||||
drawer(
|
||||
:title="$t('skillsTitle')",
|
||||
v-if='user.stats.class && !user.preferences.disableClasses',
|
||||
v-mousePosition="30",
|
||||
v-mousePosition="30",
|
||||
@mouseMoved="mouseMoved($event)",
|
||||
:openStatus='openStatus',
|
||||
@toggled='drawerToggled'
|
||||
@@ -69,6 +69,7 @@ div(v-if='user.stats.lvl > 10')
|
||||
.details {
|
||||
text-align: left;
|
||||
padding-top: .5em;
|
||||
padding-right: .1em;
|
||||
|
||||
.img {
|
||||
display: inline-block;
|
||||
@@ -129,6 +130,7 @@ div(v-if='user.stats.lvl > 10')
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,8 +135,11 @@
|
||||
color: $gray-10;
|
||||
font-weight: normal;
|
||||
margin-bottom: 0px;
|
||||
margin-right: 15px;
|
||||
line-height: 1.43;
|
||||
font-size: 14px;
|
||||
min-width: 0px;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&.has-notes {
|
||||
padding-bottom: 4px;
|
||||
@@ -159,6 +162,7 @@
|
||||
.dropdown-icon {
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
margin-right: 10px;
|
||||
color: $gray-100 !important;
|
||||
}
|
||||
|
||||
@@ -212,7 +216,9 @@
|
||||
.task-notes {
|
||||
color: $gray-100;
|
||||
font-style: normal;
|
||||
padding-right: 6px;
|
||||
padding-right: 20px;
|
||||
min-width: 0px;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&.has-checklist {
|
||||
padding-bottom: 8px;
|
||||
@@ -227,6 +233,7 @@
|
||||
background: $white;
|
||||
border: 1px solid transparent;
|
||||
transition-duration: 0.15;
|
||||
min-width: 0px;
|
||||
|
||||
&.no-right-border {
|
||||
border-right: none !important;
|
||||
@@ -271,6 +278,8 @@
|
||||
min-height: 0px;
|
||||
width: 100%;
|
||||
margin-left: 8px;
|
||||
padding-right: 20px;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&-done {
|
||||
color: $gray-300;
|
||||
@@ -284,6 +293,7 @@
|
||||
.custom-control-description {
|
||||
margin-left: 6px;
|
||||
padding-top: 0px;
|
||||
min-width: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
type="text", :class="[`${cssClass}-modal-input`]",
|
||||
required, v-model="task.text",
|
||||
autofocus, spellcheck="true",
|
||||
:disabled="groupAccessRequiredAndOnPersonalPage"
|
||||
:disabled="groupAccessRequiredAndOnPersonalPage || challengeAccessRequired"
|
||||
)
|
||||
.form-group
|
||||
label(v-once) {{ $t('notes') }}
|
||||
@@ -26,7 +26,11 @@
|
||||
.option(v-if="checklistEnabled")
|
||||
label(v-once) {{ $t('checklist') }}
|
||||
br
|
||||
div(v-sortable='true', @onsort='sortedChecklist')
|
||||
draggable(
|
||||
v-model="checklist",
|
||||
:options="{handle: '.grippy', filter: '.task-dropdown'}",
|
||||
@update="sortedChecklist"
|
||||
)
|
||||
.inline-edit-input-group.checklist-group.input-group(v-for="(item, $index) in checklist")
|
||||
span.grippy
|
||||
input.inline-edit-input.checklist-item.form-control(type="text", v-model="item.text")
|
||||
@@ -34,12 +38,12 @@
|
||||
.svg-icon.destroy-icon(v-html="icons.destroy")
|
||||
input.inline-edit-input.checklist-item.form-control(type="text", :placeholder="$t('newChecklistItem')", @keydown.enter="addChecklistItem($event)", v-model="newChecklistItem")
|
||||
.d-flex.justify-content-center(v-if="task.type === 'habit'")
|
||||
.option-item(:class="optionClass(task.up === true)", @click="task.up = !task.up")
|
||||
.option-item(:class="optionClass(task.up === true)", @click="toggleUpDirection()")
|
||||
.option-item-box
|
||||
.task-control.habit-control(:class="controlClass.up + '-control-habit'")
|
||||
.svg-icon.positive(v-html="icons.positive")
|
||||
.option-item-label(v-once) {{ $t('positive') }}
|
||||
.option-item(:class="optionClass(task.down === true)", @click="task.down = !task.down")
|
||||
.option-item(:class="optionClass(task.down === true)", @click="toggleDownDirection()")
|
||||
.option-item-box
|
||||
.task-control.habit-control(:class="controlClass.down + '-control-habit'")
|
||||
.svg-icon.negative(v-html="icons.negative")
|
||||
@@ -49,19 +53,19 @@
|
||||
span.float-left {{ $t('difficulty') }}
|
||||
// @TODO .svg-icon.info-icon(v-html="icons.information")
|
||||
.d-flex.justify-content-center
|
||||
.option-item(:class="optionClass(task.priority === 0.1)", @click="task.priority = 0.1")
|
||||
.option-item(:class="optionClass(task.priority === 0.1)", @click="setDifficulty(0.1)")
|
||||
.option-item-box
|
||||
.svg-icon.difficulty-trivial-icon(v-html="icons.difficultyTrivial")
|
||||
.option-item-label(v-once) {{ $t('trivial') }}
|
||||
.option-item(:class="optionClass(task.priority === 1)", @click="task.priority = 1")
|
||||
.option-item(:class="optionClass(task.priority === 1)", @click="setDifficulty(1)")
|
||||
.option-item-box
|
||||
.svg-icon.difficulty-normal-icon(v-html="icons.difficultyNormal")
|
||||
.option-item-label(v-once) {{ $t('easy') }}
|
||||
.option-item(:class="optionClass(task.priority === 1.5)", @click="task.priority = 1.5")
|
||||
.option-item(:class="optionClass(task.priority === 1.5)", @click="setDifficulty(1.5)")
|
||||
.option-item-box
|
||||
.svg-icon.difficulty-medium-icon(v-html="icons.difficultyMedium")
|
||||
.option-item-label(v-once) {{ $t('medium') }}
|
||||
.option-item(:class="optionClass(task.priority === 2)", @click="task.priority = 2")
|
||||
.option-item(:class="optionClass(task.priority === 2)", @click="setDifficulty(2)")
|
||||
.option-item-box
|
||||
.svg-icon.difficulty-hard-icon(v-html="icons.difficultyHard")
|
||||
.option-item-label(v-once) {{ $t('hard') }}
|
||||
@@ -72,28 +76,33 @@
|
||||
:clearButton='true',
|
||||
clearButtonIcon='category-select',
|
||||
:clearButtonText='$t("clear")',
|
||||
:todayButton='true',
|
||||
:todayButton='!challengeAccessRequired',
|
||||
todayButtonIcon='category-select',
|
||||
:todayButtonText='$t("today")',
|
||||
:disabled-picker='challengeAccessRequired'
|
||||
)
|
||||
.option(v-if="task.type === 'daily'")
|
||||
label(v-once) {{ $t('startDate') }}
|
||||
datepicker.d-inline-block(
|
||||
v-model="task.startDate",
|
||||
:clearButton='false',
|
||||
:todayButton='true',
|
||||
:todayButton='!challengeAccessRequired',
|
||||
todayButtonIcon='category-select',
|
||||
:todayButtonText='$t("today")',
|
||||
:disabled-picker='challengeAccessRequired'
|
||||
)
|
||||
.option(v-if="task.type === 'daily'")
|
||||
.form-group
|
||||
label(v-once) {{ $t('repeats') }}
|
||||
b-dropdown(:text="$t(task.frequency)")
|
||||
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly', 'yearly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}")
|
||||
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly', 'yearly']",
|
||||
:key="frequency", @click="task.frequency = frequency",
|
||||
:disabled='challengeAccessRequired',
|
||||
:class="{active: task.frequency === frequency}")
|
||||
| {{ $t(frequency) }}
|
||||
.form-group
|
||||
label(v-once) {{ $t('repeatEvery') }}
|
||||
input(type="number", v-model="task.everyX", min="0", required)
|
||||
input(type="number", v-model="task.everyX", min="0", required, :disabled='challengeAccessRequired')
|
||||
| {{ repeatSuffix }}
|
||||
br
|
||||
template(v-if="task.frequency === 'weekly'")
|
||||
@@ -102,7 +111,7 @@
|
||||
:key="dayNumber",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="task.repeat[day]")
|
||||
input.custom-control-input(type="checkbox", v-model="task.repeat[day]", :disabled='challengeAccessRequired')
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ weekdaysMin(dayNumber) }}
|
||||
template(v-if="task.frequency === 'monthly'")
|
||||
@@ -118,7 +127,7 @@
|
||||
.tags-select.option(v-if="isUserTask")
|
||||
.tags-inline
|
||||
label(v-once) {{ $t('tags') }}
|
||||
.category-wrap(@click="showTagsSelect = !showTagsSelect", v-bind:class="{ active: showTagsSelect }")
|
||||
.category-wrap(@click="toggleTagSelect()", v-bind:class="{ active: showTagsSelect }")
|
||||
span.category-select(v-if='task.tags && task.tags.length === 0')
|
||||
.tags-none {{$t('none')}}
|
||||
.dropdown-toggle
|
||||
@@ -137,7 +146,7 @@
|
||||
.option(v-if="task.type === 'daily' && isUserTask && purpose === 'edit'")
|
||||
.form-group
|
||||
label(v-once) {{ $t('restoreStreak') }}
|
||||
input(type="number", v-model="task.streak", min="0", required)
|
||||
input(type="number", v-model="task.streak", min="0", required, :disabled='challengeAccessRequired')
|
||||
|
||||
.option.group-options(v-if='groupId')
|
||||
label(v-once) Assigned To
|
||||
@@ -461,6 +470,7 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: text;
|
||||
.destroy-icon {
|
||||
display: inline-block;
|
||||
color: $gray-200;
|
||||
@@ -513,11 +523,11 @@
|
||||
import TagsPopup from './tagsPopup';
|
||||
import { mapGetters, mapActions, mapState } from 'client/libs/store';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import sortable from 'client/directives/sortable.directive';
|
||||
import clone from 'lodash/clone';
|
||||
import Datepicker from 'vuejs-datepicker';
|
||||
import moment from 'moment';
|
||||
import uuid from 'uuid';
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
import informationIcon from 'assets/svg/information.svg';
|
||||
import difficultyTrivialIcon from 'assets/svg/difficulty-trivial.svg';
|
||||
@@ -534,9 +544,7 @@ export default {
|
||||
TagsPopup,
|
||||
Datepicker,
|
||||
toggleSwitch,
|
||||
},
|
||||
directives: {
|
||||
sortable,
|
||||
draggable,
|
||||
},
|
||||
// purpose is either create or edit, task is the task created or edited
|
||||
props: ['task', 'purpose', 'challengeId', 'groupId'],
|
||||
@@ -598,12 +606,21 @@ export default {
|
||||
dayMapping: 'constants.DAY_MAPPING',
|
||||
}),
|
||||
groupAccessRequiredAndOnPersonalPage () {
|
||||
if (!this.groupId && this.task.group.id) return true;
|
||||
if (!this.groupId && this.task.group && this.task.group.id) return true;
|
||||
return false;
|
||||
},
|
||||
checklistEnabled () {
|
||||
return ['daily', 'todo'].indexOf(this.task.type) > -1 && !this.isOriginalChallengeTask;
|
||||
},
|
||||
isChallengeTask () {
|
||||
return Boolean(this.task.challenge && this.task.challenge.id);
|
||||
},
|
||||
onUserPage () {
|
||||
return !this.challengeId && !this.groupId;
|
||||
},
|
||||
challengeAccessRequired () {
|
||||
return this.onUserPage && this.isChallengeTask;
|
||||
},
|
||||
isOriginalChallengeTask () {
|
||||
let isUserChallenge = Boolean(this.task.userId);
|
||||
return !isUserChallenge && (this.challengeId || this.task.challenge && this.task.challenge.id);
|
||||
@@ -682,6 +699,22 @@ export default {
|
||||
closeTagsPopup () {
|
||||
this.showTagsSelect = false;
|
||||
},
|
||||
setDifficulty (level) {
|
||||
if (this.challengeAccessRequired) return;
|
||||
this.task.priority = level;
|
||||
},
|
||||
toggleUpDirection () {
|
||||
if (this.challengeAccessRequired) return;
|
||||
this.task.up = !this.task.up;
|
||||
},
|
||||
toggleDownDirection () {
|
||||
if (this.challengeAccessRequired) return;
|
||||
this.task.down = !this.task.down;
|
||||
},
|
||||
toggleTagSelect () {
|
||||
if (this.challengeAccessRequired) return;
|
||||
this.showTagsSelect = !this.showTagsSelect;
|
||||
},
|
||||
sortedChecklist (data) {
|
||||
let sorting = clone(this.task.checklist);
|
||||
let movingItem = sorting[data.oldIndex];
|
||||
|
||||
@@ -407,10 +407,13 @@ export default {
|
||||
this.newTag = null;
|
||||
},
|
||||
removeTag (index, key) {
|
||||
const tagId = this.tagsSnap[key][index].id;
|
||||
const indexInSelected = this.selectedTags.indexOf(tagId);
|
||||
if (indexInSelected !== -1) this.$delete(this.selectedTags, indexInSelected);
|
||||
this.$delete(this.tagsSnap[key], index);
|
||||
},
|
||||
saveTags () {
|
||||
if (this.newTag) this.addTag();
|
||||
if (this.newTag) this.addTag(null, 'tags');
|
||||
|
||||
this.tagsByType.user.tags = this.tagsSnap.tags;
|
||||
this.tagsByType.challenges.tags = this.tagsSnap.challenges;
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
|
||||
@media screen and (min-width: 1241px) {
|
||||
max-width: 978px;
|
||||
// 16.67% is the width of the .col-2 sidebar
|
||||
left: calc((100% + 16.67% - 978px) / 2);
|
||||
// 236px is the width of the .standard-sidebar
|
||||
left: calc((100% + 236px - 978px) / 2);
|
||||
right: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,6 +631,18 @@ export default {
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:show-profile', (data) => {
|
||||
if (!data.user || !data.startingPage) return;
|
||||
// @TODO: We may be able to remove the need for store
|
||||
this.$store.state.profileUser = data.user;
|
||||
this.$store.state.profileOptions.startingPage = data.startingPage;
|
||||
this.$root.$emit('bv::show::modal', 'profile');
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('habitica:show-profile');
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
userLoggedIn: 'user.data',
|
||||
@@ -740,7 +752,6 @@ export default {
|
||||
let curVal = this.user.profile[key];
|
||||
if (!curVal || this.editingProfile[key].toString() !== curVal.toString()) {
|
||||
values[`profile.${key}`] = value;
|
||||
this.$set(this.userLoggedIn.profile, key, value);
|
||||
this.$set(this.user.profile, key, value);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,8 +11,11 @@ export default {
|
||||
profile,
|
||||
},
|
||||
mounted () {
|
||||
this.$store.state.profileUser = {};
|
||||
this.$root.$emit('bv::show::modal', 'profile');
|
||||
// @TODO: Do we need this page?
|
||||
this.$root.$emit('habitica:show-profile', {
|
||||
user: {},
|
||||
startingPage: 'profile',
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -78,6 +78,7 @@ export default {
|
||||
methods: {
|
||||
async close () {
|
||||
this.$root.$emit('bv::hide::modal', 'yesterdaily');
|
||||
this.$emit('run-cron');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import Sortable from 'sortablejs';
|
||||
import uuid from 'uuid';
|
||||
|
||||
let emit = (vNode, eventName, data) => {
|
||||
let handlers = vNode.data && vNode.data.on ||
|
||||
vNode.componentOptions && vNode.componentOptions.listeners;
|
||||
|
||||
if (handlers && handlers[eventName]) {
|
||||
handlers[eventName].fns(data);
|
||||
}
|
||||
};
|
||||
|
||||
let sortableReferences = {};
|
||||
|
||||
function createSortable (el, vNode) {
|
||||
let sortableRef = Sortable.create(el, {
|
||||
filter: '.task-dropdown', // do not make the tasks dropdown draggable or it won't work
|
||||
onSort: (evt) => {
|
||||
emit(vNode, 'onsort', {
|
||||
oldIndex: evt.oldIndex,
|
||||
newIndex: evt.newIndex,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
let uniqueId = uuid();
|
||||
sortableReferences[uniqueId] = sortableRef;
|
||||
el.dataset.sortableId = uniqueId;
|
||||
}
|
||||
|
||||
export default {
|
||||
bind (el, binding, vNode) {
|
||||
createSortable(el, vNode);
|
||||
},
|
||||
unbind (el) {
|
||||
if (sortableReferences[el.dataset.sortableId]) sortableReferences[el.dataset.sortableId].destroy();
|
||||
},
|
||||
update (el, vNode) {
|
||||
if (sortableReferences[el.dataset.sortableId] && !vNode.value) {
|
||||
sortableReferences[el.dataset.sortableId].destroy();
|
||||
delete sortableReferences[el.dataset.sortableId];
|
||||
return;
|
||||
}
|
||||
if (!sortableReferences[el.dataset.sortableId]) createSortable(el, vNode);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
// @TODO: How do we require data or make this functional
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
export default {
|
||||
watch: {
|
||||
searchTerm: debounce(function searchTerm (newSearch) {
|
||||
this.challengeMemberSearchMixin_searchChallengeMember(newSearch);
|
||||
}, 500),
|
||||
members () {
|
||||
this.memberResults = this.members;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async challengeMemberSearchMixin_searchChallengeMember (search) { // eslint-disable-line
|
||||
this.memberResults = await this.$store.dispatch('members:getChallengeMembers', {
|
||||
challengeId: this.challengeId,
|
||||
searchTerm: search,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
}));
|
||||
},
|
||||
streak (val) {
|
||||
this.notify(`${this.$t('streaks')}: ${val}`, 'streak', 'glyphicon glyphicon-repeat');
|
||||
this.notify(`${val}`, 'streak');
|
||||
},
|
||||
text (val, onClick) {
|
||||
if (!val) return;
|
||||
|
||||
@@ -169,12 +169,14 @@ export default {
|
||||
this.amazonPayments.gift = data.gift;
|
||||
this.amazonPayments.type = data.type;
|
||||
|
||||
this.$root.$emit('bv::show::modal', 'amazon-payment');
|
||||
this.$root.$emit('habitica::pay-with-amazon', this.amazonPayments);
|
||||
},
|
||||
async cancelSubscription (config) {
|
||||
if (config && config.group && !confirm(this.$t('confirmCancelGroupPlan'))) return;
|
||||
if (!confirm(this.$t('sureCancelSub'))) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
let group;
|
||||
if (config && config.group) {
|
||||
group = config.group;
|
||||
@@ -203,6 +205,9 @@ export default {
|
||||
|
||||
let cancelUrl = `/${paymentMethod}/subscribe/cancel?${encodeParams(queryParams)}`;
|
||||
await axios.get(cancelUrl);
|
||||
|
||||
this.loading = false;
|
||||
|
||||
// Success
|
||||
alert(this.$t('paypalCanceled'));
|
||||
this.$router.push('/');
|
||||
|
||||
@@ -33,7 +33,7 @@ export function buyQuestItem (store, params) {
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
httpCall: axios.post(`/api/v3/user/buy/${params.key}`, {type: 'quest'}),
|
||||
httpCall: axios.post(`/api/v3/user/buy/${params.key}`, {type: 'quest', quantity}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,6 +41,14 @@ async function buyArmoire (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
let armoire = content.armoire;
|
||||
|
||||
buyOp(store.state.user.data, {
|
||||
params: {
|
||||
key: 'armoire',
|
||||
},
|
||||
type: 'armoire',
|
||||
quantity,
|
||||
});
|
||||
|
||||
// We need the server result because Armoire has random item in the result
|
||||
let result = await axios.post('/api/v3/user/buy/armoire', {
|
||||
type: 'armoire',
|
||||
|
||||
@@ -81,6 +81,8 @@ 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/v3/user/change-class?class=${params.query.class}`);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
@@ -386,8 +386,8 @@
|
||||
"armorSpecialDandySuitNotes": "С това определено ще изглеждате като някой от висшата класа! Увеличава усета с <%= per %>.",
|
||||
"armorSpecialSamuraiArmorText": "Самурайска броня",
|
||||
"armorSpecialSamuraiArmorNotes": "Частите на тази здрава броня са свързани с изящни копринени нишки. Увеличава усета с <%= per %>.",
|
||||
"armorSpecialTurkeyArmorBaseText": "Turkey Armor",
|
||||
"armorSpecialTurkeyArmorBaseNotes": "Keep your drumsticks warm and cozy in this feathery armor! Confers no benefit.",
|
||||
"armorSpecialTurkeyArmorBaseText": "Пуешка броня",
|
||||
"armorSpecialTurkeyArmorBaseNotes": "С тази пухена броня ще Ви бъде много топло и удобно! Не променя показателите.",
|
||||
"armorSpecialYetiText": "Мантия на укротител на йетита",
|
||||
"armorSpecialYetiNotes": "Пухкава и жестока. Увеличава якостта с <%= con %>. Ограничена серия: Зимна екипировка 2013-2014 г.",
|
||||
"armorSpecialSkiText": "Анорак на ски-убиец",
|
||||
@@ -584,8 +584,8 @@
|
||||
"armorMystery201707Notes": "Тази броня ще Ви помогне да се слеете с морските същества, докато изпълнявате подводните си мисии или преживявате други приключения под водата. Не променя показателите. Предмет за абонати: юли 2017 г.",
|
||||
"armorMystery201710Text": "Облекло на надменно дяволче",
|
||||
"armorMystery201710Notes": "Люспесто, лъскаво и здраво! Не променя показателите. Предмет за абонати: октомври 2017 г.",
|
||||
"armorMystery201711Text": "Carpet Rider Outfit",
|
||||
"armorMystery201711Notes": "This cozy sweater set will help keep you warm as you ride through the sky! Confers no benefit. November 2017 Subscriber Item.",
|
||||
"armorMystery201711Text": "Облекло на килимния летец",
|
||||
"armorMystery201711Notes": "Този удобен пуловер ще Ви държи топло докато се носите из облаците! Не променя показателите. Предмет за абонати: ноември 2017 г.",
|
||||
"armorMystery301404Text": "Изтънчен костюм",
|
||||
"armorMystery301404Notes": "Спретнат и елегантен! Не променя показателите. Предмет за абонати: февруари 3015 г.",
|
||||
"armorMystery301703Text": "Изтънчена паунова рокля",
|
||||
@@ -740,8 +740,8 @@
|
||||
"headSpecialKabutoNotes": "Този шлем е функционален и красив! Враговете Ви ще се разсеят, тъй като ще са заети да му се възхищават. Увеличава интелигентността с <%= int %>.",
|
||||
"headSpecialNamingDay2017Text": "Царствено лилав грифонски шлем",
|
||||
"headSpecialNamingDay2017Notes": "Честит имен ден! Носете този яростен пернат шлем, когато празнувате името на Хабитика. Не променя показателите.",
|
||||
"headSpecialTurkeyHelmBaseText": "Turkey Helm",
|
||||
"headSpecialTurkeyHelmBaseNotes": "Your Turkey Day look will be complete when you don this beaked helm! Confers no benefit.",
|
||||
"headSpecialTurkeyHelmBaseText": "Пуешки шлем",
|
||||
"headSpecialTurkeyHelmBaseNotes": "Облеклото Ви за деня на пуйката ще бъде завършено с този шлем с клюн! Не променя показателите.",
|
||||
"headSpecialNyeText": "Абсурдна купонджийска шапка",
|
||||
"headSpecialNyeNotes": "Получихте абсурдна купонджийска шапка! Носете я с гордост, когато посрещате Нова година! Не променя показателите.",
|
||||
"headSpecialYetiText": "Шлем на укротител на йетита",
|
||||
@@ -1252,8 +1252,8 @@
|
||||
"backSpecialSnowdriftVeilNotes": "С този прозрачен воал ще изглеждате така, сякаш Ви обгръща изящен снежен облак! Не променя показателите.",
|
||||
"backSpecialAetherCloakText": "Етерна мантия",
|
||||
"backSpecialAetherCloakNotes": "Тази мантия някога е принадлежала на самата Изгубената класова повелителка. Увеличава усета с <%= per %>.",
|
||||
"backSpecialTurkeyTailBaseText": "Turkey Tail",
|
||||
"backSpecialTurkeyTailBaseNotes": "Wear your noble Turkey Tail with pride while you celebrate! Confers no benefit.",
|
||||
"backSpecialTurkeyTailBaseText": "Пуешка опашка",
|
||||
"backSpecialTurkeyTailBaseNotes": "Носете гордо пуешката си опашка, докато празнувате! Не променя показателите.",
|
||||
"body": "Аксесоар за тяло",
|
||||
"bodyCapitalized": "Аксесоар за тяло",
|
||||
"bodyBase0Text": "Няма аксесоар за тяло",
|
||||
@@ -1284,8 +1284,8 @@
|
||||
"bodyMystery201705Notes": "Тези свити криле не просто изглеждат страхотно — с тях ще имате бързината и ловкостта на грифон! Не променя показателите. Предмет за абонати: май 2017 г.",
|
||||
"bodyMystery201706Text": "Наметало на парцалив пират",
|
||||
"bodyMystery201706Notes": "Това наметало има тайни джобове, където можете да скриете всичкото злато, което измъкнете от задачите си. Не променя показателите. Предмет за абонати: юни 2017 г.",
|
||||
"bodyMystery201711Text": "Carpet Rider Scarf",
|
||||
"bodyMystery201711Notes": "This soft knitted scarf looks quite majestic blowing in the wind. Confers no benefit. November 2017 Subscriber Item.",
|
||||
"bodyMystery201711Text": "Шал на килимния летец",
|
||||
"bodyMystery201711Notes": "Този мек плетен шал изглежда доста впечатляващо, когато се развява от вятъра. Не променя показателите. Предмет за абонати: ноември 2017 г.",
|
||||
"headAccessory": "аксесоар за глава",
|
||||
"headAccessoryCapitalized": "Аксесоар за глава",
|
||||
"accessories": "Аксесоари",
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
"mysterySet201708": "Комплект на воина на лавата",
|
||||
"mysterySet201709": "Комплект на ученика-магьосник",
|
||||
"mysterySet201710": "Комплект на надменното дяволче",
|
||||
"mysterySet201711": "Carpet Rider Set",
|
||||
"mysterySet201711": "Комплект на килимния летец",
|
||||
"mysterySet301404": "Стандартен изтънчен комплект",
|
||||
"mysterySet301405": "Комплект изтънчени принадлежности",
|
||||
"mysterySet301703": "Изтънчен паунов комплект",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"either": "Beides",
|
||||
"createChallenge": "Wettbewerb erstellen",
|
||||
"createChallengeAddTasks": "Wettbewerbsaufgabe hinzufügen",
|
||||
"createChallengeCloneTasks": "Clone Challenge Tasks",
|
||||
"createChallengeCloneTasks": "Wettbewerbsaufgaben kopieren",
|
||||
"addTaskToChallenge": "Aufgabe hinzufügen",
|
||||
"discard": "Verwerfen",
|
||||
"challengeTitle": "Titel des Wettbewerbs",
|
||||
|
||||
@@ -158,9 +158,9 @@
|
||||
"questEggHippoText": "Nilpferd",
|
||||
"questEggHippoMountText": "Nilpferd",
|
||||
"questEggHippoAdjective": "ein glückliches",
|
||||
"questEggYarnText": "Yarn",
|
||||
"questEggYarnMountText": "Flying Carpet",
|
||||
"questEggYarnAdjective": "woolen",
|
||||
"questEggYarnText": "Wollknäuel",
|
||||
"questEggYarnMountText": "Fliegender Teppich",
|
||||
"questEggYarnAdjective": "wolliges",
|
||||
"eggNotes": "Finde ein Schlüpfelixier, das Du über dieses Ei gießen kannst, damit ein <%= eggAdjective(locale) %> <%= eggText(locale) %> schlüpfen kann.",
|
||||
"hatchingPotionBase": "Normales",
|
||||
"hatchingPotionWhite": "Weißes",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"questsForSale": "Kaufbare Quests",
|
||||
"petQuests": "Haustier- und Reittier-Quests",
|
||||
"unlockableQuests": "Freischaltbare Quests",
|
||||
"goldQuests": "Masterclasser Quest Lines",
|
||||
"goldQuests": "Klassenmeister-Questreihen",
|
||||
"questDetails": "Quest-Details",
|
||||
"questDetailsTitle": "Quest-Details",
|
||||
"questDescription": "Quests erlauben es Spielern, sich gemeinsam mit den Gruppenmitgliedern auf Langzeit-Ziele im Spiel zu konzentrieren. ",
|
||||
|
||||
@@ -544,10 +544,10 @@
|
||||
"questLostMasterclasser4DropBackAccessory": "Aether Cloak (Back Accessory)",
|
||||
"questLostMasterclasser4DropWeapon": "Aether Crystals (Two-Handed Weapon)",
|
||||
"questLostMasterclasser4DropMount": "Invisible Aether Mount",
|
||||
"questYarnText": "A Tangled Yarn",
|
||||
"questYarnText": "Ein verheddertes Knäuel",
|
||||
"questYarnNotes": "It’s such a pleasant day that you decide to take a walk through the Taskan Countryside. As you pass by its famous yarn shop, a piercing scream startles the birds into flight and scatters the butterflies into hiding. You run towards the source and see @Arcosine running up the path towards you. Behind him, a horrifying creature consisting of yarn, pins, and knitting needles is clicking and clacking ever closer.<br><br>The shopkeepers race after him, and @stefalupagus grabs your arm, out of breath. \"Looks like all of his unfinished projects\" <em>gasp gasp</em> \"have transformed the yarn from our Yarn Shop\" <em>gasp gasp</em> \"into a tangled mass of Yarnghetti!\"<br><br>\"Sometimes, life gets in the way and a project is abandoned, becoming ever more tangled and confused,\" says @khdarkwolf. \"The confusion can even spread to other projects, until there are so many half-finished works running around that no one gets anything done!\"<br><br>It’s time to make a choice: complete your stalled projects… or decide to unravel them for good. Either way, you'll have to increase your productivity quickly before the Dread Yarnghetti spreads confusion and discord to the rest of Habitica!",
|
||||
"questYarnCompletion": "With a feeble swipe of a pin-riddled appendage and a weak roar, the Dread Yarnghetti finally unravels into a pile of yarn balls.<br><br>\"Take care of this yarn,\" shopkeeper @JinjooHat says, handing them to you. \"If you feed them and care for them properly, they'll grow into new and exciting projects that just might make your heart take flight…\"",
|
||||
"questYarnBoss": "The Dread Yarnghetti",
|
||||
"questYarnDropYarnEgg": "Yarn (Egg)",
|
||||
"questYarnUnlockText": "Unlocks purchasable Yarn eggs in the Market"
|
||||
"questYarnDropYarnEgg": "Wollknäuel (Ei)",
|
||||
"questYarnUnlockText": "Ermöglicht den Kauf von Wollknäueleiern auf dem Marktplatz"
|
||||
}
|
||||
@@ -129,5 +129,6 @@
|
||||
"locationRequired": "Location of challenge is required ('Add to')",
|
||||
"categoiresRequired": "One or more categories must be selected",
|
||||
"viewProgressOf": "View Progress Of",
|
||||
"selectMember": "Select Member"
|
||||
"selectMember": "Select Member",
|
||||
"confirmKeepChallengeTasks": "Do you want to keep challenge tasks?"
|
||||
}
|
||||
|
||||
@@ -163,6 +163,7 @@
|
||||
"dieText": "You've lost a Level, all your Gold, and a random piece of Equipment. Arise, Habiteer, and try again! Curb those negative Habits, be vigilant in completion of Dailies, and hold death at arm's length with a Health Potion if you falter!",
|
||||
"sureReset": "Are you sure? This will reset your character's class and allocated points (you'll get them all back to re-allocate), and costs 3 Gems.",
|
||||
"purchaseFor": "Purchase for <%= cost %> Gems?",
|
||||
"purchaseForHourglasses": "Purchase for <%= cost %> Hourglasses?",
|
||||
"notEnoughMana": "Not enough mana.",
|
||||
"invalidTarget": "You can't cast a skill on that.",
|
||||
"youCast": "You cast <%= spell %>.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"contribModal": "<%= name %>, you awesome person! You're now a tier <%= level %> contributor for helping Habitica. See",
|
||||
"contribLink": "what prizes you've earned for your contribution!",
|
||||
"contribName": "Contributor",
|
||||
"contribText": "Has contributed to Habitica (code, design, pixel art, legal advice, docs, etc). Want this badge? <a href='http://habitica.wikia.com/wiki/Contributing_to_Habitica' target='_blank'>Read more.</a>",
|
||||
"contribText": "Has contributed to Habitica, whether via code, art, music, writing, or other methods. To learn more, join the Aspiring Legends Guild!",
|
||||
"readMore": "Read More",
|
||||
"kickstartName": "Kickstarter Backer - $<%= key %> Tier",
|
||||
"kickstartText": "Backed the Kickstarter Project",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"companyAbout": "How It Works",
|
||||
"companyBlog": "Blog",
|
||||
"devBlog": "Developer Blog",
|
||||
"companyContribute": "Contribute",
|
||||
"companyDonate": "Donate",
|
||||
"companyPrivacy": "Privacy",
|
||||
"companyTerms": "Terms",
|
||||
@@ -252,7 +253,7 @@
|
||||
"missingNewPassword": "Missing new password.",
|
||||
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
||||
"wrongPassword": "Wrong password.",
|
||||
"incorrectDeletePhrase": "Please type DELETE in all caps to delete your account.",
|
||||
"incorrectDeletePhrase": "Please type <%= magicWord %> in all caps to delete your account.",
|
||||
"notAnEmail": "Invalid email address.",
|
||||
"emailTaken": "Email address is already used in an account.",
|
||||
"newEmailRequired": "Missing new email address.",
|
||||
|
||||
@@ -111,8 +111,8 @@
|
||||
"achievementDilatory": "Savior of Dilatory",
|
||||
"achievementDilatoryText": "Helped defeat the Dread Drag'on of Dilatory during the 2014 Summer Splash Event!",
|
||||
"costumeContest": "Costume Contestant",
|
||||
"costumeContestText": "Participated in the Habitoween Costume Contest. See some of the entries <a href='http://blog.habitrpg.com/tagged/cosplay' target='_blank'>on the Habitica blog</a>!",
|
||||
"costumeContestTextPlural": "Participated in <%= count %> Habitoween Costume Contests. See some of the entries <a href='http://blog.habitrpg.com/tagged/cosplay' target='_blank'>on the Habitica blog</a>!",
|
||||
"costumeContestText": "Participated in the Habitoween Costume Contest. See some of the awesome entries at blog.habitrpg.com!",
|
||||
"costumeContestTextPlural": "Participated in <%= count %> Habitoween Costume Contests. See some of the awesome entries at blog.habitrpg.com!",
|
||||
"memberSince": "- Member since",
|
||||
"lastLoggedIn": "- Last logged in",
|
||||
"notPorted": "This feature is not yet ported from the original site.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user