Merge branch 'develop' into greenkeeper/karma-4.0.1

This commit is contained in:
Matteo Pagliazzi
2019-03-31 20:05:16 +02:00
291 changed files with 28111 additions and 25741 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
const MIGRATION_NAME = 'mystery_items_201902';
const MYSTERY_ITEMS = ['eyewear_mystery_201902', 'shield_mystery_201902'];
const MIGRATION_NAME = 'mystery_items_201903';
const MYSTERY_ITEMS = ['armor_mystery_201903', 'head_mystery_201903'];
import { model as User } from '../../website/server/models/user';
import { model as UserNotification } from '../../website/server/models/userNotification';
+73
View File
@@ -0,0 +1,73 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190314_pi_day';
import { v4 as uuid } from 'uuid';
import { model as User } from '../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const inc = {
'items.food.Pie_Skeleton': 1,
'items.food.Pie_Base': 1,
'items.food.Pie_CottonCandyBlue': 1,
'items.food.Pie_CottonCandyPink': 1,
'items.food.Pie_Shade': 1,
'items.food.Pie_White': 1,
'items.food.Pie_Golden': 1,
'items.food.Pie_Zombie': 1,
'items.food.Pie_Desert': 1,
'items.food.Pie_Red': 1,
};
const set = {};
set.migration = MIGRATION_NAME;
set['items.gear.owned.head_special_piDay'] = false;
set['items.gear.owned.shield_special_piDay'] = false;
const push = [
{type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid()},
{type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid()},
];
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: {pinnedItems: {$each: push}}}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2019-02-15')},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};
+541 -252
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.85.3",
"version": "4.90.2",
"main": "./website/server/index.js",
"dependencies": {
"@google-cloud/trace-agent": "^3.5.2",
@@ -12,7 +12,7 @@
"amplitude-js": "^4.6.0-beta.2",
"apidoc": "^0.17.5",
"apn": "^2.2.0",
"autoprefixer": "^8.5.0",
"autoprefixer": "^9.4.0",
"aws-sdk": "^2.400.0",
"axios": "^0.18.0",
"axios-progress-bar": "^1.2.0",
@@ -37,7 +37,7 @@
"coupon-code": "^0.4.5",
"cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"csv-stringify": "^4.3.1",
"csv-stringify": "^5.1.0",
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.3",
@@ -54,7 +54,7 @@
"habitica-markdown": "^1.3.0",
"hellojs": "^1.15.1",
"html-webpack-plugin": "^3.2.0",
"image-size": "^0.6.2",
"image-size": "^0.7.0",
"in-app-purchase": "^1.10.2",
"intro.js": "^2.9.3",
"jquery": ">=3.0.0",
@@ -181,11 +181,11 @@
"lcov-result-merger": "^3.0.0",
"mocha": "^5.1.1",
"monk": "^6.0.6",
"nightwatch": "^0.9.21",
"nightwatch": "^1.0.16",
"puppeteer": "^1.5.0",
"require-again": "^2.0.0",
"selenium-server": "^3.12.0",
"sinon": "^6.3.5",
"sinon": "^7.2.4",
"sinon-chai": "^3.0.0",
"sinon-stub-promise": "^4.0.0",
"webpack-bundle-analyzer": "^2.12.0",
+3 -2
View File
@@ -32,6 +32,7 @@ describe('slack', () => {
},
message: {
id: 'chat-id',
username: 'author',
user: 'Author',
uuid: 'author-id',
text: 'some text',
@@ -50,11 +51,11 @@ describe('slack', () => {
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
text: 'flagger (flagger-id; language: flagger-lang) flagged a group message',
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `Author - author@example.com - author-id\n${timestamp}`,
author_name: `@author Author (author@example.com; author-id)\n${timestamp}`,
title: 'Flag in Some group - (private guild)',
title_link: undefined,
text: 'some text',
@@ -63,11 +63,11 @@ describe('POST /chat/:chatId/flag', () => {
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${user.profile.name} (${user.id}; language: en) flagged a message`,
text: `${user.profile.name} (${user.id}; language: en) flagged a group message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `${anotherUser.profile.name} - ${anotherUser.auth.local.email} - ${anotherUser._id}\n${timestamp}`,
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,
@@ -98,11 +98,11 @@ describe('POST /chat/:chatId/flag', () => {
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a message`,
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a group message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `${newUser.profile.name} - ${newUser.auth.local.email} - ${newUser._id}\n${timestamp}`,
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,
@@ -257,7 +257,7 @@ describe('POST /chat', () => {
attachments: [{
fallback: 'Slur Message',
color: 'danger',
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`,
title: 'Slur in Test Guild',
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
text: testSlurMessage,
@@ -310,7 +310,7 @@ describe('POST /chat', () => {
attachments: [{
fallback: 'Slur Message',
color: 'danger',
author_name: `${members[0].profile.name} - ${members[0].auth.local.email} - ${members[0]._id}`,
author_name: `@${members[0].auth.local.username} ${members[0].profile.name} (${members[0].auth.local.email}; ${members[0]._id})`,
title: 'Slur in Party - (private party)',
title_link: undefined,
text: testSlurMessage,
@@ -67,4 +67,10 @@ describe('GET /heroes/:heroId', () => {
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
});
it('returns correct hero using search with difference case', async () => {
await generateUser({}, { username: 'TestUpperCaseName123' });
let heroRes = await user.get('/hall/heroes/TestuPPerCasEName123');
expect(heroRes.auth.local.username).to.equal('TestUpperCaseName123');
});
});
@@ -110,4 +110,22 @@ describe('POST /user/auth/local/login', () => {
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
expect(isValidPassword).to.equal(true);
});
it('user uses social authentication and has no password', async () => {
await user.unset({
'auth.local.hashed_password': 1,
});
await user.sync();
expect(user.auth.local.hashed_password).to.be.undefined;
await expect(api.post(endpoint, {
username: user.auth.local.username,
password: 'any-password',
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('invalidLoginCredentialsLong'),
});
});
});
@@ -0,0 +1,74 @@
import {
generateUser,
translate as t,
} from '../../../helpers/api-integration/v4';
describe('POST /members/flag-private-message/:messageId', () => {
let userToSendMessage;
let messageToSend = 'Test Private Message';
beforeEach(async () => {
userToSendMessage = await generateUser();
});
it('Allows players to flag their own private message', async () => {
let receiver = await generateUser();
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
let senderMessages = await userToSendMessage.get('/inbox/messages');
let sendersMessageInSendersInbox = _.find(senderMessages, (message) => {
return message.uuid === receiver._id && message.text === messageToSend;
});
expect(sendersMessageInSendersInbox).to.exist;
await expect(userToSendMessage.post(`/members/flag-private-message/${sendersMessageInSendersInbox.id}`)).to.eventually.be.ok;
});
it('Flags a private message', async () => {
let receiver = await generateUser();
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
let receiversMessages = await receiver.get('/inbox/messages');
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
return message.uuid === userToSendMessage._id && message.text === messageToSend;
});
expect(sendersMessageInReceiversInbox).to.exist;
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
});
it('Returns an error when user tries to flag a private message that is already flagged', async () => {
let receiver = await generateUser();
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
let receiversMessages = await receiver.get('/inbox/messages');
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
return message.uuid === userToSendMessage._id && message.text === messageToSend;
});
expect(sendersMessageInReceiversInbox).to.exist;
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('messageGroupChatFlagAlreadyReported'),
});
});
});
@@ -4,6 +4,7 @@ import { requester } from './requester';
import {
getDocument as getDocumentFromMongo,
updateDocument as updateDocumentInMongo,
unsetDocument as unsetDocumentInMongo,
} from '../mongo';
import {
assign,
@@ -29,6 +30,18 @@ class ApiObject {
return this;
}
async unset (options) {
if (isEmpty(options)) {
return;
}
await unsetDocumentInMongo(this._docType, this, options);
_updateLocalParameters((this, options));
return this;
}
async sync () {
let updatedDoc = await getDocumentFromMongo(this._docType, this);
@@ -13,10 +13,16 @@ import * as Tasks from '../../../../website/server/models/task';
// parameter, such as the number of wolf eggs the user has,
// , you can do so by passing in the full path as a string:
// { 'items.eggs.Wolf': 10 }
export async function generateUser (update = {}) {
let username = (Date.now() + generateUUID()).substring(0, 20);
let password = 'password';
let email = `${username}@example.com`;
//
// To manually set a username, email or password pass it in as
// an object for the second parameter. Only overrides need to be
// added. Items that don't exist will be autogenerated.
// Example: generateUser({}, { username: 'TestName' }) adds user
// with the 'TestName' username.
export async function generateUser (update = {}, overrides = {}) {
let username = overrides.username || (Date.now() + generateUUID()).substring(0, 20);
let password = overrides.password || 'password';
let email = overrides.email || `${username}@example.com`;
let user = await requester().post('/user/auth/local/register', {
username,
+13
View File
@@ -98,6 +98,19 @@ export async function updateDocument (collectionName, doc, update) {
});
}
// Unset a property in the database.
// Useful for testing.
export async function unsetDocument (collectionName, doc, update) {
let collection = mongoose.connection.db.collection(collectionName);
return new Promise((resolve) => {
collection.updateOne({ _id: doc._id }, { $unset: update }, (updateErr) => {
if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`);
resolve();
});
});
}
export async function getDocument (collectionName, doc) {
let collection = mongoose.connection.db.collection(collectionName);
@@ -1,30 +1,72 @@
.promo_armoire_backgrounds_201902 {
.promo_armoire_backgrounds_201903 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -397px 0px;
background-position: -719px -767px;
width: 423px;
height: 147px;
}
.promo_beffymaroo_wondercon {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 718px;
height: 932px;
}
.promo_celestial_rainbow_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -933px;
width: 423px;
height: 147px;
}
.promo_classes_spring2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -719px -604px;
width: 432px;
height: 162px;
}
.promo_egg_hunt {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -933px;
width: 354px;
height: 147px;
}
.promo_mystery_201902 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -220px;
background-position: -1160px -196px;
width: 240px;
height: 147px;
}
.promo_mythical_marvels_bundle {
.promo_mystery_201903 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -220px;
width: 423px;
background-position: -779px -933px;
width: 351px;
height: 147px;
}
.promo_seasonalshop_spring {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1160px -344px;
width: 162px;
height: 138px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -397px -148px;
background-position: -1160px -483px;
width: 96px;
height: 69px;
}
.scene_cooking {
.scene_dailies {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 396px;
height: 219px;
background-position: -719px -327px;
width: 327px;
height: 276px;
}
.scene_tavern {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -719px 0px;
width: 440px;
height: 326px;
}
.scene_todos {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1160px 0px;
width: 240px;
height: 195px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 968 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 KiB

After

Width:  |  Height:  |  Size: 557 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 KiB

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 115 KiB

@@ -0,0 +1,26 @@
.vdp-datepicker .vdp-datepicker__calendar {
.cell:not(.blank):not(.disabled).day:hover {
border-radius: 2px;
border: 1px solid $purple-400;
}
.cell.selected,
.cell.selected.highlighted,
.cell.selected:hover {
color: $white;
background: $purple-300;
}
.cell.highlighted {
background: rgba($purple-400, 0.24);
color: $purple-200;
}
.cell.highlighted,
.cell.selected {
border-radius: 2px;
font-weight: bold;
}
}
+1 -1
View File
@@ -159,7 +159,7 @@ $bg-disabled-control: #34303a;
width: 18px;
height: 18px;
background-image: url(~client/assets/svg/for-css/checkbox-white.svg);
background-size: 75% 75%;
background-size: 13px 10px;
}
&:active~.custom-control-label::before {
+2
View File
@@ -36,3 +36,5 @@
@import './animals';
@import './iconalert';
@import './tiers';
@import './payments';
@import './datepicker.scss';
+44
View File
@@ -0,0 +1,44 @@
.payments-column {
display: flex;
flex-direction: column;
width: 296px;
justify-content: center;
&.payments-disabled {
opacity: 0.64;
.btn, .btn:hover, .btn:active {
box-shadow: none;
}
.payment-item > *{
cursor: default !important;
}
}
.payment-item {
margin-bottom: 12px;
display: flex;
&.payment-button {
display: flex;
justify-content: center;
align-items: center;
.credit-card-icon {
width: 21.3px;
height: 16px;
margin-right: 8.7px;
}
&.paypal-checkout {
background: #009cde;
img {
width: 157px;
height: 21px;
}
}
}
}
}
+5 -5
View File
@@ -2,8 +2,8 @@
// possible values are: normal, fall, habitoween, thanksgiving, winter, nye, birthday, valentines, spring, summer
// more to be added on future seasons
$npc_market_flavor: 'normal';
$npc_quests_flavor: 'normal';
$npc_seasonal_flavor: 'normal';
$npc_timetravelers_flavor: 'normal';
$npc_tavern_flavor: 'normal';
$npc_market_flavor: 'spring';
$npc_quests_flavor: 'spring';
$npc_seasonal_flavor: 'spring';
$npc_timetravelers_flavor: 'spring';
$npc_tavern_flavor: 'spring';
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="16" viewBox="0 0 22 16">
<path fill="#FFF" fill-rule="nonzero" d="M0 13.872V5.333h21.333v8.539c0 1.176-.747 2.128-1.67 2.128H1.67C.747 16 0 15.047 0 13.872zM19.664 0c.922 0 1.67.918 1.67 2.053v.614H0v-.614C0 .918.747 0 1.67 0h17.994z"/>
</svg>

After

Width:  |  Height:  |  Size: 307 B

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#E1E0E3" fill-rule="evenodd" d="M10.667 10.667L16 8l-5.333-2.667L8 0 5.333 5.333 0 8l5.333 2.667L8 16z"/>
<path class="star-empty" fill="#E1E0E3" fill-rule="evenodd" d="M10.667 10.667L16 8l-5.333-2.667L8 0 5.333 5.333 0 8l5.333 2.667L8 16z"/>
</svg>

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 232 B

@@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<path fill="#E1E0E3" d="M10.667 10.667L16 8l-5.333-2.667L8 0 5.333 5.333 0 8l5.333 2.667L8 16z"/>
<path fill="#FFB445" d="M8 0L5.333 5.333 0 8l5.333 2.667L8 16z"/>
<path class="star-empty" fill="#E1E0E3" d="M10.667 10.667L16 8l-5.333-2.667L8 0 5.333 5.333 0 8l5.333 2.667L8 16z"/>
<path class="star" fill="#FFB445" d="M8 0L5.333 5.333 0 8l5.333 2.667L8 16z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 320 B

After

Width:  |  Height:  |  Size: 352 B

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#FFB445" fill-rule="evenodd" d="M10.667 10.667L16 8l-5.333-2.667L8 0 5.333 5.333 0 8l5.333 2.667L8 16z"/>
<path class="star" fill="#FFB445" fill-rule="evenodd" d="M10.667 10.667L16 8l-5.333-2.667L8 0 5.333 5.333 0 8l5.333 2.667L8 16z"/>
</svg>

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 226 B

@@ -164,30 +164,30 @@ export default {
classGear (heroClass) {
if (heroClass === 'rogue') {
return {
armor: 'armor_rogue_5',
head: 'head_rogue_5',
shield: 'shield_rogue_6',
weapon: 'weapon_rogue_6',
armor: 'armor_special_spring2019Rogue',
head: 'head_special_spring2019Rogue',
shield: 'shield_special_spring2019Rogue',
weapon: 'weapon_special_spring2019Rogue',
};
} else if (heroClass === 'wizard') {
return {
armor: 'armor_wizard_5',
head: 'head_wizard_5',
weapon: 'weapon_wizard_6',
armor: 'armor_special_spring2019Mage',
head: 'head_special_spring2019Mage',
weapon: 'weapon_special_spring2019Mage',
};
} else if (heroClass === 'healer') {
return {
armor: 'armor_healer_5',
head: 'head_healer_5',
shield: 'shield_healer_5',
weapon: 'weapon_healer_6',
armor: 'armor_special_spring2019Healer',
head: 'head_special_spring2019Healer',
shield: 'shield_special_spring2019Healer',
weapon: 'weapon_special_spring2019Healer',
};
} else {
return {
armor: 'armor_warrior_5',
head: 'head_warrior_5',
shield: 'shield_warrior_5',
weapon: 'weapon_warrior_6',
armor: 'armor_special_spring2019Warrior',
head: 'head_special_spring2019Warrior',
shield: 'shield_special_spring2019Warrior',
weapon: 'weapon_special_spring2019Warrior',
};
}
},
+43 -15
View File
@@ -43,6 +43,8 @@
li(v-html='$t("communityForum")')
li
a(href='https://www.facebook.com/Habitica', target='_blank') {{ $t('communityFacebook') }}
li
a(href='https://www.instagram.com/habitica', target='_blank') {{ $t('communityInstagram') }}
li
a(href='https://www.reddit.com/r/habitrpg/', target='_blank') {{ $t('communityReddit') }}
.col-12.col-md-6
@@ -56,23 +58,22 @@
a(:href="getDataDisplayToolUrl", target='_blank') {{ $t('dataDisplayTool') }}
li
a(href='http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths', target='_blank') {{ $t('guidanceForBlacksmiths') }}
li
a(href='http://devs.habitica.com/', target='_blank') {{ $t('devBlog') }}
.col-6.social
h3 {{ $t('footerSocial') }}
a.social-circle(href='https://twitter.com/habitica', target='_blank')
.social-icon.svg-icon(v-html='icons.twitter')
// TODO: Not ready yet. a.social-circle(href='https://www.instagram.com/habitica/', target='_blank')
.social-icon.svg-icon.instagram(v-html='icons.instagram')
a.social-circle(href='https://www.facebook.com/Habitica', target='_blank')
.social-icon.facebook.svg-icon(v-html='icons.facebook')
.icons
a.social-circle(href='https://twitter.com/habitica', target='_blank')
.social-icon.svg-icon(v-html='icons.twitter')
a.social-circle(href='https://www.instagram.com/habitica/', target='_blank')
.social-icon.svg-icon.instagram(v-html='icons.instagram')
a.social-circle(href='https://www.facebook.com/Habitica', target='_blank')
.social-icon.facebook.svg-icon(v-html='icons.facebook')
.row
.col-12.col-md-8 {{ $t('donateText3') }}
.col-12.col-md-4
button.btn.btn-contribute(@click="donate()", v-if="user")
button.btn.btn-contribute.btn-flat(@click="donate()", v-if="user")
.svg-icon.heart(v-html="icons.heart")
.text {{ $t('companyDonate') }}
.btn.btn-contribute(v-else)
.btn.btn-contribute.btn-flat(v-else)
a(href='http://habitica.fandom.com/wiki/Contributing_to_Habitica', target='_blank')
.svg-icon.heart(v-html="icons.heart")
.text {{ $t('companyContribute') }}
@@ -145,6 +146,22 @@
}
}
.icons {
display: flex;
justify-content: flex-end;
flex-shrink: 1;
}
// smaller than desktop
@media only screen and (max-width: 992px) {
.social-circle {
height: 32px !important;
width: 32px !important;
margin-left: 0.75em !important;
}
}
.social-circle {
width: 40px;
height: 40px;
@@ -152,17 +169,20 @@
background-color: #c3c0c7;
display: flex;
margin-left: 1em;
float: right;
&:first-child {
margin-left: 0;
}
&:hover {
background-color: #a5a1ac;
}
.social-icon {
color: #e1e0e3;
width: 16px;
margin: auto;
}
.instagram {
margin-top: .85em;
}
}
.logo {
@@ -185,6 +205,14 @@
box-shadow: none;
border-radius: 4px;
&:hover {
background: #a5a1ac;
.text {
color: white;
}
}
a {
display: flex;
}
+41 -13
View File
@@ -1,8 +1,8 @@
<template lang="pug">
div
.mentioned-icon(v-if='isUserMentioned')
.message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
.message-hidden(v-if='!inbox && msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
.message-hidden(v-if='!inbox && msg.flagCount > 1 && user.contributor.admin') Message hidden
.card-body
user-link(:userId="msg.uuid", :name="msg.user", :backer="msg.backer", :contributor="msg.contributor")
p.time
@@ -11,25 +11,28 @@ div
span(v-b-tooltip="", :title="msg.timestamp | date") {{ msg.timestamp | timeAgo }}&nbsp;
span(v-if="msg.client && user.contributor.level >= 4") ({{ msg.client }})
.text(v-html='atHighlight(parseMarkdown(msg.text))')
.reported(v-if="isMessageReported && (inbox === true)")
span(v-once) {{ $t('reportedMessage')}}
br
span(v-once) {{ $t('canDeleteNow') }}
hr
.d-flex(v-if='msg.id')
.action.d-flex.align-items-center(v-if='!inbox', @click='copyAsTodo(msg)')
.svg-icon(v-html="icons.copy")
div {{$t('copyAsTodo')}}
.action.d-flex.align-items-center(v-if='!inbox && user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @click='report(msg)')
.svg-icon(v-html="icons.report")
div {{$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
.action.d-flex.align-items-center(v-if='(inbox || (user.flags.communityGuidelinesAccepted && msg.uuid !== "system")) && (!isMessageReported || user.contributor.admin)', @click='report(msg)')
.svg-icon(v-html="icons.report", v-once)
div(v-once) {{$t('report')}}
.action.d-flex.align-items-center(v-if='msg.uuid === user._id || inbox || user.contributor.admin', @click='remove()')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
.svg-icon(v-html="icons.delete", v-once)
div(v-once) {{$t('delete')}}
.ml-auto.d-flex(v-b-tooltip="{title: likeTooltip(msg.likes[user._id])}", v-if='!inbox')
.action.d-flex.align-items-center.mr-0(@click='like()', v-if='likeCount > 0', :class='{active: msg.likes[user._id]}')
.svg-icon(v-html="icons.liked", :title='$t("liked")')
| +{{ likeCount }}
.action.d-flex.align-items-center.mr-0(@click='like()', v-if='likeCount === 0', :class='{active: msg.likes[user._id]}')
.svg-icon(v-html="icons.like", :title='$t("like")')
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
span(v-if='!msg.likes[user._id] && !inbox') {{ $t('like') }}
</template>
<style lang="scss">
@@ -111,6 +114,11 @@ div
color: $purple-400;
}
}
.reported {
margin-top: 18px;
color: $red-50;
}
</style>
<script>
@@ -132,8 +140,15 @@ import reportIcon from 'assets/svg/report.svg';
import {highlightUsers} from '../../libs/highlightUsers';
export default {
props: ['msg', 'inbox', 'groupId'],
components: {userLink},
props: {
msg: {},
inbox: {
type: Boolean,
default: false,
},
groupId: {},
},
data () {
return {
icons: Object.freeze({
@@ -143,6 +158,7 @@ export default {
delete: deleteIcon,
liked: likedIcon,
}),
reported: false,
};
},
filters: {
@@ -191,6 +207,9 @@ export default {
}
return likeCount;
},
isMessageReported () {
return this.msg.flags && this.msg.flags[this.user.id] || this.reported;
},
},
methods: {
async like () {
@@ -216,10 +235,18 @@ export default {
copyAsTodo (message) {
this.$root.$emit('habitica::copy-as-todo', message);
},
async report () {
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
this.reported = true;
}
this.$root.$off('habitica:report-result');
});
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: this.groupId,
groupId: this.groupId || 'privateMessage',
});
},
async remove () {
@@ -242,7 +269,8 @@ export default {
return highlightUsers(text, this.user.auth.local.username, this.user.profile.name);
},
parseMarkdown (text) {
return habiticaMarkdown.render(text);
if (!text) return;
return habiticaMarkdown.render(String(text));
},
},
};
@@ -3,7 +3,6 @@
.row
.col-12
copy-as-todo-modal(:group-type='groupType', :group-name='groupName', :group-id='groupId')
report-flag-modal
div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)', :class='{row: inbox}')
.d-flex(v-if='user._id !== msg.uuid', :class='{"flex-grow-1": inbox}')
avatar.avatar-left(
@@ -105,14 +104,21 @@ import findIndex from 'lodash/findIndex';
import Avatar from '../avatar';
import copyAsTodoModal from './copyAsTodoModal';
import reportFlagModal from './reportFlagModal';
import chatCard from './chatCard';
export default {
props: ['chat', 'groupType', 'groupId', 'groupName', 'inbox'],
props: {
chat: {},
inbox: {
type: Boolean,
default: false,
},
groupType: {},
groupId: {},
groupName: {},
},
components: {
copyAsTodoModal,
reportFlagModal,
chatCard,
Avatar,
},
@@ -106,14 +106,16 @@ export default {
this.$root.$emit('bv::hide::modal', 'report-flag');
},
async reportAbuse () {
this.notify('Thank you for reporting this violation. The moderators have been notified.');
this.text(this.$t(this.groupId === 'privateMessage' ? 'pmReported' : 'abuseReported'));
await this.$store.dispatch('chat:flag', {
let result = await this.$store.dispatch('chat:flag', {
groupId: this.groupId,
chatId: this.abuseObject.id,
comment: this.reportComment,
});
this.$root.$emit('habitica:report-result', result);
this.close();
},
async clearFlagCount () {
@@ -139,7 +139,6 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
span 5
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.base.${baseHair4Keys.join(",hair.base.")}`)') {{ $t('purchaseAll') }}
.col-12.customize-options
.head_0.option(@click='set({"preferences.hair.base": 0})', :class="[{ active: user.preferences.hair.base === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
.option(v-for='option in baseHair1',
:class='{active: user.preferences.hair.base === option}')
.base.sprite.customize-option(:class="`hair_base_${option}_${user.preferences.hair.color}`", @click='set({"preferences.hair.base": option})')
@@ -22,11 +22,11 @@
button.btn.btn-primary.btn-lg.btn-block(@click="createGroup()", :disabled="!newGroupIsReady") {{ $t('createGroupPlan') }}
.col-12(v-if='activePage === PAGES.PAY')
h2 {{ $t('choosePaymentMethod') }}
.payment-providers
.box.payment-button(@click='pay(PAYMENTS.STRIPE)')
.svg-icon.credit-card-icon(v-html="icons.creditCard")
.box.payment-button.amazon(@click='pay(PAYMENTS.AMAZON)')
.svg-icon.amazon-pay-icon(v-html="icons.amazonpay")
.payments-column
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }}
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
</template>
<style lang="scss" scoped>
@@ -65,40 +65,29 @@
opacity: 0.7;
}
.amazon-pay-icon {
width: 150px;
}
.credit-card-icon {
width: 150px;
color: #4e4a57;
}
.btn-block {
margin-bottom: 1em;
}
.payment-button.amazon {
margin-bottom: 2em;
}
</style>
<script>
import * as Analytics from 'client/libs/analytics';
import { mapState } from 'client/libs/store';
import paymentsMixin from '../../mixins/payments';
import amazonButton from 'client/components/payments/amazonButton';
import amazonpay from 'assets/svg/amazonpay.svg';
import creditCard from 'assets/svg/credit-card.svg';
import creditCardIcon from 'assets/svg/credit-card-icon.svg';
export default {
mixins: [paymentsMixin],
components: {
amazonButton,
},
data () {
return {
amazonPayments: {},
icons: Object.freeze({
amazonpay,
creditCard,
creditCardIcon,
}),
PAGES: {
CREATE_GROUP: 'create-group',
@@ -159,7 +148,7 @@ export default {
this.showStripe(paymentData);
} else if (this.paymentMethod === this.PAYMENTS.AMAZON) {
paymentData.type = 'subscription';
this.amazonPaymentsInit(paymentData);
return paymentData;
}
},
},
+42 -12
View File
@@ -1,9 +1,10 @@
<template lang="pug">
.row.chat-row
.col-12
h3(v-once) {{ label }}
h3.float-left.label(:class="{accepted: communityGuidelinesAccepted }") {{ label }}
div.float-right(v-markdown='$t("markdownFormattingHelp")')
.row
.row(v-if="communityGuidelinesAccepted")
textarea(:placeholder='placeholder',
v-model='newMessage',
ref='user-entry',
@@ -22,14 +23,14 @@
:caretPosition = 'caretPosition',
:chat='group.chat')
community-guidelines
.row.chat-actions
.col-6.chat-receive-actions
button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
.col-6.chat-send-actions
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
community-guidelines
button.btn.btn-primary.send-chat.float-right(:disabled="!communityGuidelinesAccepted", @click='sendMessage()') {{ $t('send') }}
slot(
name="additionRow",
@@ -46,9 +47,14 @@
import autocomplete from '../chat/autoComplete';
import communityGuidelines from './communityGuidelines';
import chatMessage from '../chat/chatMessages';
import { mapState } from 'client/libs/store';
import markdownDirective from 'client/directives/markdown';
export default {
props: ['label', 'group', 'placeholder'],
directives: {
markdown: markdownDirective,
},
components: {
autocomplete,
communityGuidelines,
@@ -71,9 +77,13 @@
};
},
computed: {
...mapState({user: 'user.data'}),
currentLength () {
return this.newMessage.length;
},
communityGuidelinesAccepted () {
return this.user.flags.communityGuidelinesAccepted;
},
},
methods: {
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
@@ -114,12 +124,22 @@
async sendMessage () {
if (this.sending) return;
this.sending = true;
let response = await this.$store.dispatch('chat:postChat', {
group: this.group,
message: this.newMessage,
});
this.group.chat.unshift(response.message);
this.newMessage = '';
let response;
try {
response = await this.$store.dispatch('chat:postChat', {
group: this.group,
message: this.newMessage,
});
} catch (e) {
// catch exception to allow function to continue
}
if (response) {
this.group.chat.unshift(response.message);
this.newMessage = '';
}
this.sending = false;
// @TODO: I would like to not reload everytime we send. Why are we reloading?
@@ -196,6 +216,16 @@
.chat-row {
position: relative;
.label:not(.accepted) {
color: #a5a1ac;
}
.row {
margin-left: 0;
margin-right: 0;
clear: both;
}
textarea {
min-height: 150px;
width: 100%;
@@ -204,7 +234,7 @@
font-style: italic;
line-height: 1.43;
color: $gray-300;
padding: .5em;
padding: 10px 12px;
}
.user-entry {
@@ -11,17 +11,31 @@
.community-guidelines {
background-color: rgba(135, 129, 144, 0.84);
padding: 1em;
padding-left: 0;
padding-right: 0;
color: $white;
position: absolute;
top: 0;
height: 150px;
margin-top: 2.3em;
width: 100%;
// width: calc(100% - 24px);
border-radius: 4px;
align-items: center;
justify-content: center;
.col {
padding: 20px 24px;
}
a {
text-decoration: underline;
color: $white;
font-weight: bold;
}
button {
margin: 20px 12px;
padding-top: 5px;
padding-bottom: 5px;
}
.btn-follow-guidelines {
white-space: pre-line;
}
+6 -3
View File
@@ -21,12 +21,12 @@
.svg-icon.shield(v-html="icons.silverGuildBadgeIcon", v-if='group.memberCount > 100 && group.memberCount < 999')
.svg-icon.shield(v-html="icons.bronzeGuildBadgeIcon", v-if='group.memberCount < 100')
span.number {{ group.memberCount | abbrNum }}
div.member-list(v-once) {{ $t('memberList') }}
div.member-list.label(v-once) {{ $t('memberList') }}
.col-4(v-if='!isParty')
.item-with-icon(@click='showGroupGems()')
.svg-icon.gem(v-html="icons.gem")
span.number {{group.balance * 4}}
div(v-once) {{ $t('guildBank') }}
div.label(v-once) {{ $t('guildBank') }}
chat(
:label="$t('chat')",
:group="group",
@@ -100,6 +100,9 @@
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
padding: 1em;
text-align: center;
min-width: 80px;
max-width: 120px;
height: 76px;
.svg-icon.shield, .svg-icon.gem {
width: 28px;
@@ -115,7 +118,7 @@
font-weight: bold;
}
.member-list {
.label {
margin-top: .5em;
}
}
+18 -57
View File
@@ -48,14 +48,11 @@ div
.box.payment-providers
h3 Choose your payment method
.box.payment-button(@click='pay(PAYMENTS.STRIPE)')
div
.svg-icon.credit-card-icon(v-html="icons.group")
p.credit-card Credit Card
p Powered by Stripe
.box.payment-button(@click='pay(PAYMENTS.AMAZON)')
.svg-icon.amazon-pay-icon(v-html="icons.amazonpay")
.payments-column
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }}
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
.container.col-6.offset-3.create-option(v-if='!upgradingGroup._id')
.row
h1.col-12.text-center.purple-header Create your Group today!
@@ -99,13 +96,13 @@ div
.form-group
button.btn.btn-primary.btn-lg.btn-block(@click="createGroup()", :disabled="!newGroupIsReady") {{ $t('createGroupPlan') }}
.col-12(v-if='activePage === PAGES.PAY')
.payment-providers
.text-center
h3 Choose your payment method
.box.payment-button(@click='pay(PAYMENTS.STRIPE)')
p Credit Card
p Powered by Stripe
.box.payment-button(@click='pay(PAYMENTS.AMAZON)')
| Amazon Pay
.payments-column.mx-auto
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }}
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
</template>
<style lang="scss" scoped>
@@ -130,12 +127,6 @@ div
color: #fff;
}
.payment-button {
display: block;
margin: 0 auto;
margin-bottom: 1em;
}
.plus .svg-icon{
width: 24px;
}
@@ -143,26 +134,6 @@ div
.payment-providers {
width: 350px;
}
.credit-card {
font-size: 20px;
font-weight: bold;
margin-bottom: 0;
margin-top: .5em;
display: inline-block;
}
.credit-card-icon {
width: 25px;
display: inline-block;
margin-right: .5em;
}
.amazon-pay-icon {
width: 150px;
margin: 0 auto;
margin-top: .5em;
}
}
.header {
@@ -316,35 +287,25 @@ div
vertical-align: bottom;
}
}
.payment-button {
width: 200px;
height: 80px;
margin-bottom: .5em;
padding: .5em;
display: block;
}
.payment-button:hover {
cursor: pointer;
}
</style>
<script>
import paymentsMixin from '../../mixins/payments';
import { mapState } from 'client/libs/store';
import group from 'assets/svg/group.svg';
import amazonpay from 'assets/svg/amazonpay.svg';
import positiveIcon from 'assets/svg/positive.svg';
import creditCardIcon from 'assets/svg/credit-card-icon.svg';
import amazonButton from 'client/components/payments/amazonButton';
export default {
mixins: [paymentsMixin],
components: {
amazonButton,
},
data () {
return {
amazonPayments: {},
icons: Object.freeze({
group,
amazonpay,
creditCardIcon,
positiveIcon,
}),
PAGES: {
@@ -413,7 +374,7 @@ export default {
this.showStripe(paymentData);
} else if (this.paymentMethod === this.PAYMENTS.AMAZON) {
paymentData.type = 'subscription';
this.amazonPaymentsInit(paymentData);
return paymentData;
}
},
},
@@ -109,7 +109,7 @@ router-link.card-link(:to="{ name: 'guild', params: { groupId: guild._id } }")
.member-count {
position: relative;
top: -3.6em;
top: -3.7em;
font-size: 18px;
font-weight: bold;
line-height: 1.1;
@@ -3,6 +3,7 @@ div
inbox-modal
creator-intro
profileModal
report-flag-modal
send-gems-modal
b-navbar.topbar.navbar-inverse.static-top(toggleable="lg", type="dark", :class="navbarZIndexClass")
b-navbar-brand.brand
@@ -351,12 +352,15 @@ import profileModal from '../userMenu/profileModal';
import sendGemsModal from 'client/components/payments/sendGemsModal';
import userDropdown from './userDropdown';
import reportFlagModal from '../chat/reportFlagModal';
export default {
components: {
creatorIntro,
InboxModal,
notificationMenu,
profileModal,
reportFlagModal,
sendGemsModal,
userDropdown,
},
@@ -4,6 +4,7 @@
v-if="item != null",
:hide-header="true",
@change="onChange($event)"
@hide="fixDocBody()"
)
div.close
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close", @click="hideDialog()")
@@ -187,8 +188,16 @@
},
hideDialog () {
this.$root.$emit('bv::hide::modal', 'equipgear-modal');
},
fixDocBody () {
document.body.classList.remove('modal-open');
document.body.setAttribute('data-modal-open-count', document.body.getAttribute('data-modal-open-count') - 1);
if (document.body.getAttribute('data-modal-open-count') <= 1) {
document.body.removeAttribute('data-modal-open-count');
} else {
document.body.setAttribute('data-modal-open-count', document.body.getAttribute('data-modal-open-count') - 1);
}
document.body.removeAttribute('data-padding-right');
document.body.style.removeProperty('padding-right');
},
memberOverrideAvatarGear (gear) {
return {
@@ -82,7 +82,7 @@
)
div(
v-for="group in itemsGroups",
v-if="viewOptions[group.key].selected",
v-if="!anyFilterSelected || viewOptions[group.key].selected",
:key="group.key",
:class='group.key',
)
@@ -383,10 +383,13 @@ export default {
items () {
return this.groupBy === 'type' ? this.gearItemsByType : this.gearItemsByClass;
},
anyFilterSelected () {
return Object.values(this.viewOptions).some(g => g.selected);
},
itemsGroups () {
return map(this.groups, (label, group) => {
this.$set(this.viewOptions, group, {
selected: true,
selected: false,
open: false,
itemsInFirstPosition: [],
firstRender: true,
@@ -25,7 +25,7 @@
b-dropdown-item(@click="sortBy = 'AZ'", :active="sortBy === 'AZ'") {{ $t('AZ') }}
div(
v-for="group in groups",
v-if="group.selected",
v-if="!anyFilterSelected || group.selected",
:key="group.key",
)
h2.mb-3
@@ -225,7 +225,7 @@ const groups = [
return {
key: group,
quantity: 0,
selected: true,
selected: false,
classPrefix,
allowedItems,
};
@@ -340,6 +340,10 @@ export default {
return itemsByType;
},
anyFilterSelected () {
return this.groups.some(g => g.selected);
},
},
methods: {
userHasPet (potionKey, eggKey) {
@@ -465,8 +469,6 @@ export default {
let openedItem = result.data.data;
let text = this.content.gear.flat[openedItem.key].text();
this.drop(this.$t('messageDropMysteryItem', {dropText: text}), openedItem);
item.quantity--;
this.$forceUpdate();
} else {
this.$root.$emit('selectMembersModal::showItem', item);
}
@@ -69,7 +69,7 @@
span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}}
div(v-for="(petGroup, index) in petGroups",
v-if="viewOptions[petGroup.key].selected",
v-if="!anyFilterSelected || viewOptions[petGroup.key].selected",
:key="petGroup.key")
h4(v-if="viewOptions[petGroup.key].animalCount !== 0") {{ petGroup.label }}
@@ -92,7 +92,11 @@
@click="petClicked(item)"
)
template(slot="itemBadge", slot-scope="context")
starBadge(:selected="item.key === currentPet", :show="isOwned('pet', item)", @click="selectPet(item)")
starBadge(
:selected="context.item.key === currentPet",
:show="isOwned('pet', context.item)",
@click="selectPet(context.item)"
)
.btn.btn-flat.btn-show-more(@click="setShowMore(petGroup.key)", v-if='petGroup.key !== "specialPets"')
| {{ $_openedItemRows_isToggled(petGroup.key) ? $t('showLess') : $t('showMore') }}
@@ -103,7 +107,7 @@
span.badge.badge-pill.badge-default {{countOwnedAnimals(mountGroups[0], 'mount')}}
div(v-for="mountGroup in mountGroups",
v-if="viewOptions[mountGroup.key].selected",
v-if="!anyFilterSelected || viewOptions[mountGroup.key].selected",
:key="mountGroup.key")
h4(v-if="viewOptions[mountGroup.key].animalCount != 0") {{ mountGroup.label }}
@@ -469,7 +473,7 @@
petGroups.map((petGroup) => {
this.$set(this.viewOptions, petGroup.key, {
selected: true,
selected: false,
animalCount: 0,
});
});
@@ -514,7 +518,7 @@
mountGroups.map((mountGroup) => {
this.$set(this.viewOptions, mountGroup.key, {
selected: true,
selected: false,
animalCount: 0,
});
});
@@ -538,6 +542,9 @@
},
];
},
anyFilterSelected () {
return Object.values(this.viewOptions).some(g => g.selected);
},
},
methods: {
setShowMore (key) {
+27 -75
View File
@@ -23,21 +23,31 @@
span.mr-1(v-if="member.auth && member.auth.local && member.auth.local.username") @{{ member.auth.local.username }}
span.mr-1(v-if="member.auth && member.auth.local && member.auth.local.username")
span {{ characterLevel }}
.progress-container(v-b-tooltip.hover.bottom="$t('health')")
.svg-icon(v-html="icons.health")
.progress
.progress-bar.bg-health(:style="{width: `${percent(member.stats.hp, MAX_HEALTH)}%`}")
span.small-text {{member.stats.hp | statFloor}} / {{MAX_HEALTH}}
.progress-container(v-b-tooltip.hover.bottom="$t('experience')")
.svg-icon(v-html="icons.experience")
.progress
.progress-bar.bg-experience(:style="{width: `${percent(member.stats.exp, toNextLevel)}%`}")
span.small-text {{member.stats.exp | statFloor}} / {{toNextLevel}}
.progress-container(v-if="hasClass", v-b-tooltip.hover.bottom="$t('mana')")
.svg-icon(v-html="icons.mana")
.progress
.progress-bar.bg-mana(:style="{width: `${percent(member.stats.mp, maxMP)}%`}")
span.small-text {{member.stats.mp | statFloor}} / {{maxMP}}
stats-bar(
:icon="icons.health",
:value="member.stats.hp",
:maxValue="MAX_HEALTH",
:tooltip="$t('health')",
progressClass="bg-health",
:condensed="condensed"
)
stats-bar(
:icon="icons.experience",
:value="member.stats.exp",
:maxValue="toNextLevel",
:tooltip="$t('experience')",
progressClass="bg-experience",
:condensed="condensed"
)
stats-bar(
v-if="hasClass",
:icon="icons.mana",
:value="member.stats.mp",
:maxValue="maxMP",
:tooltip="$t('mana')",
progressClass="bg-mana",
:condensed="condensed"
)
</template>
<style lang="scss" scoped>
@@ -99,44 +109,6 @@
margin-bottom: .5em
}
.progress-container {
margin-left: 4px;
margin-bottom: .5em;
height: 24px;
}
.progress-container > span {
color: $header-color;
margin-left: 8px;
font-style: normal;
line-height: 1;
}
.progress-container > .svg-icon {
width: 24px;
height: 24px;
margin-right: 8px;
}
.progress-container > .progress {
min-width: 200px;
margin: 0px;
border-radius: 2px;
height: 12px;
background-color: $header-dark-background;
}
.progress-container > .progress > .progress-bar {
border-radius: 2px;
height: 12px;
min-width: 0px;
}
.progress-container .svg-icon, .progress-container .progress, .progress-container .small-text {
display: inline-block;
vertical-align: middle;
}
// Condensed version
.member-details.condensed.expanded {
background: $header-dark-background;
@@ -163,25 +135,6 @@
border-bottom-left-radius: 4px;
z-index: 9;
}
.progress-container > .svg-icon {
width: 19px;
height: 19px;
margin-top: -2px;
}
.progress-container > .progress {
width: 152px;
border-radius: 0px;
height: 10px;
margin-top: 2px;
background: $purple-100;
}
.progress-container > .progress > .progress-bar {
border-radius: 0px;
height: 10px;
}
}
</style>
@@ -190,6 +143,7 @@ import Avatar from './avatar';
import ClassBadge from './members/classBadge';
import { mapState } from 'client/libs/store';
import Profile from './userMenu/profile';
import StatsBar from './ui/statsbar';
import { toNextLevel } from '../../common/script/statHelpers';
import statsComputed from '../../common/script/libs/statsComputed';
@@ -205,9 +159,7 @@ export default {
Avatar,
Profile,
ClassBadge,
},
directives: {
// bTooltip,
StatsBar,
},
props: {
member: {
@@ -0,0 +1,114 @@
<template lang="pug">
.amazon-pay-button(:id="buttonId")
</template>
<script>
import axios from 'axios';
import { mapState } from 'client/libs/store';
import uuid from 'uuid';
import paymentsMixin from 'client/mixins/payments';
const AMAZON_PAYMENTS = process.env.AMAZON_PAYMENTS; // eslint-disable-line
export default {
mixins: [paymentsMixin],
data () {
return { // @TODO what needed here? can be moved to mixin?
amazonPayments: {
modal: null,
type: null,
gift: null,
loggedIn: false,
paymentSelected: false,
billingAgreementId: '',
recurringConsent: false,
orderReferenceId: null,
subscription: null,
coupon: null,
},
isAmazonSetup: false,
amazonButtonEnabled: false,
groupToCreate: null, // creating new group
group: null, // upgrading existing group
buttonId: null,
};
},
props: {
amazonData: Object,
amazonDisabled: {
type: Boolean,
default: false,
},
},
computed: {
...mapState({user: 'user.data'}),
...mapState(['isAmazonReady']),
amazonPaymentsCanCheckout () {
if (this.amazonPayments.type === 'single') {
return this.amazonPayments.paymentSelected === true;
} else if (this.amazonPayments.type === 'subscription') {
return this.amazonPayments.paymentSelected && this.amazonPayments.recurringConsent;
}
return false;
},
},
watch: {
amazonData () {
this.amazonPaymentsInit(this.amazonData);
},
},
beforeMount () {
this.buttonId = `AmazonPayButton-${uuid.v4()}`;
},
mounted () {
this.amazonPaymentsInit(this.amazonData);
if (this.isAmazonReady) return this.setupAmazon();
this.$store.watch(state => state.isAmazonReady, (isAmazonReady) => {
if (isAmazonReady) return this.setupAmazon();
});
},
methods: {
setupAmazon () {
if (this.isAmazonSetup) return false;
this.isAmazonSetup = true;
this.showButton();
},
showButton () {
window.OffAmazonPayments.Button( // eslint-disable-line new-cap
this.buttonId, // ID of the button
AMAZON_PAYMENTS.SELLER_ID,
{
type: 'PwA',
color: 'Gold',
size: 'large',
agreementType: 'BillingAgreement',
onSignIn: async (contract) => { // @TODO send to modal
if (this.amazonDisabled === true) return null;
// if (!this.checkGemAmount(this.amazonData)) return;
this.amazonPayments.billingAgreementId = contract.getAmazonBillingAgreementId();
this.$set(this.amazonPayments, 'loggedIn', true);
this.$root.$emit('habitica::pay-with-amazon', this.amazonPayments);
},
authorization: () => {
if (this.amazonDisabled === true) return null;
window.amazon.Login.authorize({
scope: 'payments:widget',
popup: true,
}, function amazonSuccess (response) {
if (response.error) return alert(response.error);
const url = '/amazon/verifyAccessToken';
axios.post(url, response).catch((e) => {
alert(e.message);
});
});
},
onError: this.amazonOnError, // @TODO port here
});
},
},
};
</script>
@@ -1,7 +1,6 @@
<template lang="pug">
b-modal#amazon-payment(title="Amazon", :hide-footer="true", size='md')
h2.text-center Continue with Amazon
#AmazonPayButton
#AmazonPayWallet(v-if="amazonPayments.loggedIn", style="width: 400px; height: 228px;")
template(v-if="amazonPayments.loggedIn && amazonPayments.type === 'subscription'")
br
@@ -19,7 +18,7 @@
margin-bottom: 12px;
}
#AmazonPayButton, #AmazonPayWallet, #AmazonPayRecurring {
#AmazonPayWallet, #AmazonPayRecurring {
margin: 0 auto;
}
@@ -54,7 +53,6 @@ export default {
subscription: null,
coupon: null,
},
OffAmazonPayments: {},
isAmazonSetup: false,
amazonButtonEnabled: false,
groupToCreate: null, // creating new group
@@ -74,12 +72,6 @@ export default {
},
},
mounted () {
if (this.isAmazonReady) return this.setupAmazon();
this.$store.watch(state => state.isAmazonReady, (isAmazonReady) => {
if (isAmazonReady) return this.setupAmazon();
});
this.$root.$on('habitica::pay-with-amazon', (amazonPaymentsData) => {
if (!amazonPaymentsData) return;
@@ -90,67 +82,30 @@ export default {
this.amazonPayments = Object.assign({}, amazonPayments, amazonPaymentsData);
this.$root.$emit('bv::show::modal', 'amazon-payment');
this.$nextTick(async () => {
if (this.amazonPayments.type === 'subscription') {
this.amazonInitWidgets();
} else {
let url = '/amazon/createOrderReferenceId';
let response = await axios.post(url, {
billingAgreementId: this.amazonPayments.billingAgreementId,
});
if (response.status <= 400) {
this.amazonPayments.orderReferenceId = response.data.data.orderReferenceId;
this.amazonInitWidgets();
} else {
alert(response.message);
}
}
});
});
},
destroyed () {
this.$root.$off('habitica::pay-with-amazon');
},
methods: {
setupAmazon () {
if (this.isAmazonSetup) return false;
this.isAmazonSetup = true;
this.OffAmazonPayments = window.OffAmazonPayments;
this.showButton();
},
showButton () {
// @TODO: prevent modal close form clicking outside
let amazonButton = this.OffAmazonPayments.Button( // eslint-disable-line
'AmazonPayButton',
AMAZON_PAYMENTS.SELLER_ID,
{
type: 'PwA',
color: 'Gold',
size: 'small',
agreementType: 'BillingAgreement',
onSignIn: async (contract) => {
this.amazonPayments.billingAgreementId = contract.getAmazonBillingAgreementId();
this.$set(this.amazonPayments, 'loggedIn', true);
if (this.amazonPayments.type === 'subscription') {
this.amazonInitWidgets();
} else {
let url = '/amazon/createOrderReferenceId';
let response = await axios.post(url, {
billingAgreementId: this.amazonPayments.billingAgreementId,
});
if (response.status <= 400) {
this.amazonPayments.orderReferenceId = response.data.data.orderReferenceId;
this.amazonInitWidgets();
return;
}
alert(response.message);
}
},
authorization: () => {
window.amazon.Login.authorize({
scope: 'payments:widget',
popup: true,
}, function amazonSuccess (response) {
if (response.error) return alert(response.error);
let url = '/amazon/verifyAccessToken';
axios.post(url, response)
.catch((e) => {
alert(e.message);
});
});
},
onError: this.amazonOnError,
});
},
amazonInitWidgets () {
let walletParams = {
sellerId: AMAZON_PAYMENTS.SELLER_ID, // @TODO: Import
@@ -167,15 +122,14 @@ export default {
walletParams.onReady = (billingAgreement) => {
this.amazonPayments.billingAgreementId = billingAgreement.getAmazonBillingAgreementId();
new this.OffAmazonPayments.Widgets.Consent({
new window.OffAmazonPayments.Widgets.Consent({
sellerId: AMAZON_PAYMENTS.SELLER_ID,
amazonBillingAgreementId: this.amazonPayments.billingAgreementId,
design: {
designMode: 'responsive',
},
onReady: (consent) => {
let getConsent = consent.getConsentStatus;
this.$set(this.amazonPayments, 'recurringConsent', getConsent ? Boolean(getConsent()) : false);
this.$set(this.amazonPayments, 'recurringConsent', consent.getConsentStatus ? Boolean(consent.getConsentStatus()) : false);
this.$set(this, 'amazonButtonEnabled', true);
},
onConsent: (consent) => {
@@ -189,7 +143,7 @@ export default {
walletParams.amazonOrderReferenceId = this.amazonPayments.orderReferenceId;
}
new this.OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet');
new window.OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet');
},
storePaymentStatusAndReload (url) {
let paymentType;
@@ -305,30 +259,6 @@ export default {
amazonOnPaymentSelect () {
this.$set(this.amazonPayments, 'paymentSelected', true);
},
amazonOnError (error) {
alert(error.getErrorMessage());
this.reset();
},
reset () {
// @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.amazonPayments.loggedIn = false;
// Gift
this.amazonPayments.gift = null;
this.amazonPayments.giftReceiver = null;
this.amazonPayments.billingAgreementId = null;
this.amazonPayments.orderReferenceId = null;
this.amazonPayments.paymentSelected = false;
this.amazonPayments.recurringConsent = false;
this.amazonPayments.subscription = null;
this.amazonPayments.coupon = null;
this.amazonPayments.groupToCreate = null;
this.amazonPayments.group = null;
},
},
};
</script>
@@ -44,16 +44,15 @@
button.btn.btn-primary(@click='gemAmount === 20 ? gemAmount = 0 : gemAmount = 20') {{gemAmount === 20 ? $t('selected') : '$5.00'}}
.row.text-center
h2.mx-auto.text-payment {{ $t('choosePaymentMethod') }}
.card-deck
.card.text-center.payment-method(@click='showStripe({})')
.card-body
.mx-auto(v-html='icons.creditCard', style='"height: 56px; width: 159px; margin-top: 1em;"')
.card.text-center.payment-method
a.card-body.paypal(@click="openPaypal(paypalCheckoutLink, 'gems')")
img(src='~assets/images/paypal.png')
.card.text-center.payment-method(@click="amazonPaymentsInit({type: 'single'})")
.card-body.amazon
img(src='~assets/images/amazon-payments.png')
.payments-column
button.purchase.btn.btn-primary.payment-button.payment-item(@click='showStripe({})')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }}
button.btn.payment-item.paypal-checkout.payment-button(@click="openPaypal(paypalCheckoutLink, 'gems')")
| &nbsp;
img(src='~assets/images/paypal-checkout.png', :alt="$t('paypal')")
| &nbsp;
amazon-button.payment-item(:amazon-data="{type: 'single'}")
.row.text-center
.svg-icon.mx-auto(v-html='icons.heart', style='"height: 24px; width: 24px;"')
.row.text-center.text-outtro
@@ -127,16 +126,16 @@
h2.mx-auto.text-payment(v-once) {{ $t('choosePaymentMethod') }}
.row.text-center
a.mx-auto(v-once) {{ $t('haveCouponCode') }}
.card-deck(v-if='subscriptionPlan')
.card.text-center.payment-method
.card-body(@click='showStripe({subscription: subscriptionPlan})')
.mx-auto(v-html='icons.creditCard', style='"height: 56px; width: 159px; margin-top: 1em;"')
.card.text-center.payment-method
a.card-body.paypal(@click="openPaypal(paypalSubscriptionLink, 'subscription')")
img(src='~assets/images/paypal.png')
.card.text-center.payment-method
.card-body.amazon(@click="amazonPaymentsInit({type: 'subscription', subscription: subscriptionPlan})")
img(src='~assets/images/amazon-payments.png')
.payments-column(v-if='subscriptionPlan')
button.purchase.btn.btn-primary.payment-button.payment-item(@click='showStripe({subscription: subscriptionPlan})')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }}
button.btn.payment-item.paypal-checkout.payment-button(@click="openPaypal(paypalSubscriptionLink, 'subscription')")
| &nbsp;
img(src='~assets/images/paypal-checkout.png', :alt="$t('paypal')")
| &nbsp;
amazon-button.payment-item(:amazon-data="{type: 'subscription', subscription: subscriptionPlan}")
.row.text-center
.svg-icon.mx-auto(v-html='icons.heart', style='"height: 24px; width: 24px;"')
.row.text-center.text-outtro
@@ -162,6 +161,11 @@
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.payments-column {
margin: 0 auto;
}
a.mx-auto {
color: #2995cd;
}
@@ -174,10 +178,6 @@
font-size: 16px;
}
.amazon {
padding-top: 1.8em;
}
.benefits {
font-size: 14px;
}
@@ -194,19 +194,6 @@
justify-content: center;
}
.card {
margin: 1em;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
}
.card.active {
border-color: #24cc8f;
button {
background-color: #24cc8f;
}
}
.divider {
width: 80%;
height: 1px;
@@ -272,18 +259,6 @@
cursor: pointer;
}
.payment-method {
background-color: #e1e0e3;
}
.payment-method:hover {
cursor: pointer;
}
.paypal {
padding-top: 1.3em;
}
.spacer {
height: 4em;
}
@@ -328,7 +303,6 @@
color: #4e4a57;
font-size: 24px;
margin: 1em;
opacity: 0.64;
}
</style>
@@ -339,7 +313,7 @@
import paymentsMixin from 'client/mixins/payments';
import checkIcon from 'assets/svg/check.svg';
import creditCard from 'assets/svg/credit-card.svg';
import creditCardIcon from 'assets/svg/credit-card-icon.svg';
import heart from 'assets/svg/health.svg';
import logo from 'assets/svg/habitica-logo.svg';
@@ -348,10 +322,13 @@
import fortyTwoGems from 'assets/svg/42-gems.svg';
import eightyFourGems from 'assets/svg/84-gems.svg';
import amazonButton from 'client/components/payments/amazonButton';
export default {
mixins: [paymentsMixin],
components: {
planGemLimits,
amazonButton,
},
computed: {
...mapState({user: 'user.data'}),
@@ -373,7 +350,7 @@
icons: Object.freeze({
logo,
check: checkIcon,
creditCard,
creditCardIcon,
fourGems,
heart,
twentyOneGems,
@@ -383,7 +360,6 @@
gemAmount: 0,
subscriptionPlan: '',
selectedPage: 'subscribe',
amazonPayments: {},
planGemLimits,
};
},
@@ -1,6 +1,6 @@
<template lang="pug">
b-modal#send-gems(:title="title", :hide-footer="true", size='lg', @hide='onHide()')
.modal-body(v-if='userReceivingGems')
b-modal#send-gems(:title="title", :hide-footer="true", size='md', @hide='onHide()')
div(v-if='userReceivingGems')
.panel.panel-default(
:class="gift.type === 'gems' ? 'panel-primary' : 'transparent'",
@click='gift.type = "gems"'
@@ -32,7 +32,7 @@ b-modal#send-gems(:title="title", :hide-footer="true", size='lg', @hide='onHide(
h3.panel-heading {{ $t('subscription') }}
.panel-body
.row
.col-md-4
.col-md-12
.form-group
.radio(v-for='block in subscriptionBlocks', v-if="block.target !== 'group' && block.canSubscribe === true")
label
@@ -48,11 +48,18 @@ b-modal#send-gems(:title="title", :hide-footer="true", size='lg', @hide='onHide(
@click="sendGift()",
:disabled="sendingInProgress"
) {{ $t("send") }}
template(v-else)
button.btn.btn-primary(@click='showStripe({gift, uuid: userReceivingGems._id, receiverName})') {{ $t('card') }}
button.btn.btn-warning(@click='openPaypalGift({gift: gift, giftedTo: userReceivingGems._id, receiverName})') PayPal
button.btn.btn-success(@click="amazonPaymentsInit({type: 'single', gift, giftedTo: userReceivingGems._id, receiverName})") Amazon Payments
button.btn.btn-secondary(@click='close()') {{$t('cancel')}}
.payments-column.mx-auto(v-else, :class="{'payments-disabled': !gift.subscription.key && gift.gems.amount < 1}")
button.purchase.btn.btn-primary.payment-button.payment-item(@click='showStripe({gift, uuid: userReceivingGems._id, receiverName})', :disabled="!gift.subscription.key && gift.gems.amount < 1")
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }}
button.btn.payment-item.paypal-checkout.payment-button(@click="openPaypalGift({gift: gift, giftedTo: userReceivingGems._id, receiverName})", :disabled="!gift.subscription.key && gift.gems.amount < 1")
| &nbsp;
img(src='~assets/images/paypal-checkout.png', :alt="$t('paypal')")
| &nbsp;
amazon-button.payment-item.mb-0(
:amazon-data="{type: 'single', gift, giftedTo: userReceivingGems._id, receiverName}",
:amazon-disabled="!gift.subscription.key && gift.gems.amount < 1",
)
</template>
<style lang="scss">
@@ -78,6 +85,12 @@ b-modal#send-gems(:title="title", :hide-footer="true", size='lg', @hide='onHide(
}
</style>
<style lang="scss" scoped>
input[type="radio"] {
margin-right: 4px;
}
</style>
<script>
import toArray from 'lodash/toArray';
import omitBy from 'lodash/omitBy';
@@ -86,12 +99,17 @@ import { mapState } from 'client/libs/store';
import planGemLimits from '../../../common/script/libs/planGemLimits';
import paymentsMixin from 'client/mixins/payments';
import notificationsMixin from 'client/mixins/notifications';
import amazonButton from 'client/components/payments/amazonButton';
import creditCardIcon from 'assets/svg/credit-card-icon.svg';
// @TODO: EMAILS.TECH_ASSISTANCE_EMAIL, load from config
const TECH_ASSISTANCE_EMAIL = 'admin@habitica.com';
export default {
mixins: [paymentsMixin, notificationsMixin],
components: {
amazonButton,
},
data () {
return {
planGemLimits,
@@ -110,6 +128,9 @@ export default {
},
sendingInProgress: false,
userReceivingGems: null,
icons: Object.freeze({
creditCardIcon,
}),
};
},
computed: {
@@ -33,6 +33,8 @@
span(v-html="$t(paymentData.newGroup ? 'groupPlanCreated' : 'groupPlanUpgraded', {groupName: paymentData.group.name})")
.details-block
span(v-html="$t('paymentSubBilling', {amount: groupPlanCost, months: paymentData.subscription.months})")
template(v-if="paymentData.paymentType === 'groupPlan' || paymentData.paymentType === 'subscription'")
span.small-text.auto-renew(v-once) {{ $t('paymentAutoRenew') }}
button.btn.btn-primary(@click='close()', v-once) {{$t('onwards')}}
</template>
@@ -116,6 +118,12 @@
}
}
}
.auto-renew {
margin-top: 16px;
color: $orange-10;
font-style: normal;
}
}
#payments-success-modal .modal-footer {
@@ -73,15 +73,15 @@
.subscribe-pay(v-if='!hasSubscription || hasCanceledSubscription')
h3 {{ $t('subscribeUsing') }}
.row.text-center
.col-md-4
button.purchase.btn.btn-primary(@click='showStripe({subscription:subscription.key, coupon:subscription.coupon})', :disabled='!subscription.key') {{ $t('card') }}
.col-md-4
a.purchase(@click="openPaypal(paypalPurchaseLink, 'subscription')", :disabled='!subscription.key')
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="payWithAmazon()")
img(src='https://payments.amazon.com/gp/cba/button', :alt="$t('amazonPayments')")
.payments-column
button.purchase.btn.btn-primary.payment-button.payment-item(@click='showStripe({subscription:subscription.key, coupon:subscription.coupon})', :disabled='!subscription.key')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }}
button.btn.payment-item.paypal-checkout.payment-button(@click="openPaypal(paypalPurchaseLink, 'subscription')", :disabled='!subscription.key')
| &nbsp;
img(src='~assets/images/paypal-checkout.png', :alt="$t('paypal')")
| &nbsp;
amazon-button.payment-item(:amazon-data="{type: 'subscription', subscription: this.subscription.key, coupon: this.subscription.coupon}")
.row
.col-6
h2(v-once) {{ $t('giftSubscription') }}
@@ -92,7 +92,7 @@
h4(v-once) {{ $t('giftSubscriptionText4') }}
</template>
<style scoped>
<style scoped lang="scss">
.badge.badge-success {
color: #fff;
}
@@ -115,8 +115,14 @@ import planGemLimits from '../../../common/script/libs/planGemLimits';
import paymentsMixin from '../../mixins/payments';
import notificationsMixin from '../../mixins/notifications';
import amazonButton from 'client/components/payments/amazonButton';
import creditCardIcon from 'assets/svg/credit-card-icon.svg';
export default {
mixins: [paymentsMixin, notificationsMixin],
components: {
amazonButton,
},
data () {
return {
loading: false,
@@ -137,6 +143,9 @@ export default {
PAYPAL: 'Paypal',
GIFT: 'Gift',
},
icons: Object.freeze({
creditCardIcon,
}),
};
},
computed: {
@@ -240,13 +249,6 @@ export default {
},
},
methods: {
payWithAmazon () {
this.amazonPaymentsInit({
type: 'subscription',
subscription: this.subscription.key,
coupon: this.subscription.coupon,
});
},
async applyCoupon (coupon) {
const response = await axios.post(`/api/v4/coupons/validate/${coupon}`);
@@ -21,7 +21,7 @@ layout-section(:title="$t('equipment')")
span(slot="item", slot-scope="ctx")
span.text {{ $t(ctx.item.id) }}
itemRows(
itemRows.equipment-rows(
:items="sortedGearItems",
:itemWidth=94,
:itemMargin=24,
@@ -169,6 +169,10 @@ layout-section(:title="$t('equipment')")
};
</script>
<style scoped>
<style lang="scss" scoped>
.equipment-rows {
/deep/ .item.item-empty {
background: white;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More