feat(event): 10th Birthday Bash
with @CuriousMagpie and @phillipthelen
88
migrations/archive/2023/20230123_habit_birthday.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230123_habit_birthday';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const inc = { 'balance': 5 };
|
||||
const set = {};
|
||||
const push = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.armor_special_birthday2022 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2023'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2021 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2022'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2020 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2021'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2019 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2020'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2019'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2018'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2017'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2016'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2015'] = true;
|
||||
} else {
|
||||
set['items.gear.owned.armor_special_birthday'] = true;
|
||||
}
|
||||
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 1!',
|
||||
text: 'Enjoy your new Birthday Robe and 20 Gems on us!',
|
||||
destination: 'equipment',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
69
migrations/archive/2023/20230127_habit_birthday_day5.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230127_habit_birthday_day5';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const set = {};
|
||||
const push = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
set['items.gear.owned.back_special_anniversary'] = true;
|
||||
set['items.gear.owned.body_special_anniversary'] = true;
|
||||
set['items.gear.owned.eyewear_special_anniversary'] = true;
|
||||
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 5!',
|
||||
text: 'Come celebrate by wearing your new Habitica Hero Cape, Collar, and Mask!',
|
||||
destination: 'equipment',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
79
migrations/archive/2023/20230201_habit_birthday_day10.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230201_habit_birthday_day10';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
'purchased.background.birthday_bash': true,
|
||||
};
|
||||
const push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 10!',
|
||||
text: 'Join in for the end of our birthday celebrations with 10th Birthday background, Cake, and achievement!',
|
||||
destination: 'backgrounds',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
const inc = {
|
||||
'items.food.Cake_Skeleton': 1,
|
||||
'items.food.Cake_Base': 1,
|
||||
'items.food.Cake_CottonCandyBlue': 1,
|
||||
'items.food.Cake_CottonCandyPink': 1,
|
||||
'items.food.Cake_Shade': 1,
|
||||
'items.food.Cake_White': 1,
|
||||
'items.food.Cake_Golden': 1,
|
||||
'items.food.Cake_Zombie': 1,
|
||||
'items.food.Cake_Desert': 1,
|
||||
'items.food.Cake_Red': 1,
|
||||
'achievements.habitBirthdays': 1,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push, $inc: inc }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -12,7 +12,7 @@ const { i18n } = common;
|
||||
describe('Apple Payments', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
|
||||
describe('verifyGemPurchase', () => {
|
||||
describe('verifyPurchase', () => {
|
||||
let sku; let user; let token; let receipt; let
|
||||
headers;
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let
|
||||
@@ -54,7 +54,7 @@ describe('Apple Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -66,7 +66,7 @@ describe('Apple Payments', () => {
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData').returns([]);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -76,7 +76,7 @@ describe('Apple Payments', () => {
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -95,7 +95,7 @@ describe('Apple Payments', () => {
|
||||
transactionId: token,
|
||||
}]);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -138,7 +138,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await applePayments.verifyGemPurchase({ user, receipt, headers });
|
||||
await applePayments.verifyPurchase({ user, receipt, headers });
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -173,7 +173,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
const gift = { uuid: receivingUser._id };
|
||||
await applePayments.verifyGemPurchase({
|
||||
await applePayments.verifyPurchase({
|
||||
user, gift, receipt, headers,
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const { i18n } = common;
|
||||
describe('Google Payments', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
|
||||
describe('verifyGemPurchase', () => {
|
||||
describe('verifyPurchase', () => {
|
||||
let sku; let user; let token; let receipt; let signature; let
|
||||
headers; const gemsBlock = common.content.gems['21gems'];
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
|
||||
@@ -48,7 +48,7 @@ describe('Google Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -61,7 +61,7 @@ describe('Google Payments', () => {
|
||||
it('should throw an error if productId is invalid', async () => {
|
||||
receipt = `{"token": "${token}", "productId": "invalid"}`;
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -74,7 +74,7 @@ describe('Google Payments', () => {
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -88,7 +88,7 @@ describe('Google Payments', () => {
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await googlePayments.verifyGemPurchase({
|
||||
await googlePayments.verifyPurchase({
|
||||
user, receipt, signature, headers,
|
||||
});
|
||||
|
||||
@@ -120,7 +120,7 @@ describe('Google Payments', () => {
|
||||
await receivingUser.save();
|
||||
|
||||
const gift = { uuid: receivingUser._id };
|
||||
await googlePayments.verifyGemPurchase({
|
||||
await googlePayments.verifyPurchase({
|
||||
user, gift, receipt, signature, headers,
|
||||
});
|
||||
|
||||
|
||||
40
test/api/unit/libs/payments/skuItem.test.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
canBuySkuItem,
|
||||
} from '../../../../../website/server/libs/payments/skuItem';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
|
||||
describe('payments/skuItems', () => {
|
||||
let user;
|
||||
let clock;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
clock = null;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (clock !== null) clock.restore();
|
||||
});
|
||||
|
||||
describe('#canBuySkuItem', () => {
|
||||
it('returns true for random sku', () => {
|
||||
expect(canBuySkuItem('something', user)).to.be.true;
|
||||
});
|
||||
|
||||
describe('#gryphatrice', () => {
|
||||
const sku = 'com.habitrpg.android.habitica.iap.pets.gryphatrice-jubilant';
|
||||
it('returns true during birthday week', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-01-29'));
|
||||
expect(canBuySkuItem(sku, user)).to.be.true;
|
||||
});
|
||||
it('returns false outside of birthday week', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-01-20'));
|
||||
expect(canBuySkuItem(sku, user)).to.be.false;
|
||||
});
|
||||
it('returns false if user already owns it', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-02-01'));
|
||||
user.items.pets['Gryphatrice-Jubilant'] = 5;
|
||||
expect(canBuySkuItem(sku, user)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,11 +21,11 @@ describe('payments : apple #verify', () => {
|
||||
let verifyStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').resolves({});
|
||||
verifyStub = sinon.stub(applePayments, 'verifyPurchase').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
applePayments.verifyGemPurchase.restore();
|
||||
applePayments.verifyPurchase.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
|
||||
@@ -21,11 +21,11 @@ describe('payments : google #verify', () => {
|
||||
let verifyStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').resolves({});
|
||||
verifyStub = sinon.stub(googlePayments, 'verifyPurchase').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
googlePayments.verifyGemPurchase.restore();
|
||||
googlePayments.verifyPurchase.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<sub-canceled-modal v-if="isUserLoaded" />
|
||||
<bug-report-modal v-if="isUserLoaded" />
|
||||
<bug-report-success-modal v-if="isUserLoaded" />
|
||||
<birthday-modal />
|
||||
<snackbars />
|
||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||
<template v-else>
|
||||
@@ -42,6 +43,7 @@
|
||||
<damage-paused-banner />
|
||||
<gems-promo-banner />
|
||||
<gift-promo-banner />
|
||||
<birthday-banner />
|
||||
<notifications-display />
|
||||
<app-menu />
|
||||
<div
|
||||
@@ -153,11 +155,13 @@
|
||||
import axios from 'axios';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
|
||||
import birthdayModal from '@/components/news/birthdayModal';
|
||||
import AppMenu from './components/header/menu';
|
||||
import AppHeader from './components/header/index';
|
||||
import DamagePausedBanner from './components/header/banners/damagePaused';
|
||||
import GemsPromoBanner from './components/header/banners/gemsPromo';
|
||||
import GiftPromoBanner from './components/header/banners/giftPromo';
|
||||
import BirthdayBanner from './components/header/banners/birthdayBanner';
|
||||
import AppFooter from './components/appFooter';
|
||||
import notificationsDisplay from './components/notifications';
|
||||
import snackbars from './components/snackbars/notifications';
|
||||
@@ -191,9 +195,11 @@ export default {
|
||||
AppMenu,
|
||||
AppHeader,
|
||||
AppFooter,
|
||||
birthdayModal,
|
||||
DamagePausedBanner,
|
||||
GemsPromoBanner,
|
||||
GiftPromoBanner,
|
||||
BirthdayBanner,
|
||||
notificationsDisplay,
|
||||
snackbars,
|
||||
BuyModal,
|
||||
|
||||
@@ -156,6 +156,12 @@
|
||||
height: 99px;
|
||||
}
|
||||
|
||||
.Pet-Gryphatrice-Jubilant {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Gryphatrice-Jubilant.gif") no-repeat;
|
||||
width: 81px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.Mount_Head_Gryphon-Gryphatrice, .Mount_Body_Gryphon-Gryphatrice {
|
||||
width: 135px;
|
||||
height: 135px;
|
||||
|
||||
@@ -665,6 +665,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_birthday_bash {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_birthday_bash.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_birthday_party {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_birthday_party.png');
|
||||
width: 141px;
|
||||
@@ -2296,6 +2301,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_birthday_bash {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_birthday_bash.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_birthday_party {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_birthday_party.png');
|
||||
width: 68px;
|
||||
@@ -2835,11 +2845,6 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_habitversary_bash {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_habitversary_bash.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_halflings_house {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_halflings_house.png');
|
||||
width: 68px;
|
||||
@@ -22830,6 +22835,16 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.back_special_anniversary {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_special_anniversary.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.body_special_anniversary {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/body_special_anniversary.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_birthday {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_birthday.png');
|
||||
width: 90px;
|
||||
@@ -22875,6 +22890,16 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_birthday2023 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_birthday2023.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.eyewear_special_anniversary {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/eyewear_special_anniversary.png');
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_armor_special_birthday {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_birthday.png');
|
||||
width: 68px;
|
||||
@@ -22920,6 +22945,26 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_birthday2023 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_birthday2023.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_back_special_anniversary {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_back_special_anniversary.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_body_special_anniversary {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_body_special_anniversary.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_eyewear_special_anniversary {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_special_anniversary.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.slim_armor_special_birthday {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_birthday.png');
|
||||
width: 90px;
|
||||
@@ -22965,6 +23010,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_birthday2023 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_birthday2023.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_fall2015Healer {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_fall2015Healer.png');
|
||||
width: 93px;
|
||||
@@ -34823,6 +34873,11 @@
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.notif_head_special_nye {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_head_special_nye.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_01 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_inventory_present_01.png');
|
||||
width: 28px;
|
||||
|
||||
BIN
website/client/src/assets/images/10-birthday.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 29 KiB |
BIN
website/client/src/assets/images/fancy-divider.png
Normal file
|
After Width: | Height: | Size: 332 B |
BIN
website/client/src/assets/images/glitter.png
Normal file
|
After Width: | Height: | Size: 358 B |
BIN
website/client/src/assets/images/habitica-hero-goober.webp
Normal file
|
After Width: | Height: | Size: 850 B |
BIN
website/client/src/assets/images/robes.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
61
website/client/src/assets/svg/10th-birthday-linear.svg
Normal file
@@ -0,0 +1,61 @@
|
||||
<svg width="199" height="24" viewBox="0 0 199 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#c19w6aye5a)" fill="#fff">
|
||||
<path d="M56.47 18.83V6.003L56 3.662l.47-1.405h8.942c1.773 0 3.193.344 4.26 1.03 1.066.687 1.6 1.733 1.6 3.137 0 .765-.142 1.397-.424 1.896a4.175 4.175 0 0 1-1.035 1.24 4.14 4.14 0 0 1 1.505.703c.471.327.855.772 1.154 1.334.297.546.447 1.225.447 2.036 0 1.639-.487 2.918-1.46 3.839-.956.905-2.502 1.358-4.635 1.358H56.471zm5.177-10.136h2.777c.533 0 .918-.078 1.153-.234.235-.171.353-.429.353-.772 0-.359-.173-.609-.518-.75-.345-.155-.753-.233-1.223-.233h-2.542v1.99zm0 5.688h4c.628 0 1.067-.093 1.318-.28a.974.974 0 0 0 .377-.796c0-.75-.486-1.124-1.46-1.124h-4.235v2.2zM75.515 18.83V6.003l-.236-2.341.236-1.405h5.2V18.83h-5.2zM84 18.83V6.026l-.471-2.364.47-1.405h8.472c1.27 0 2.36.203 3.27.61.91.405 1.608 1.06 2.095 1.965.486.89.73 2.068.73 3.535 0 1.357-.228 2.473-.683 3.347a4.552 4.552 0 0 1-2 1.99l.4.327 1.623 2.2 1.341 1.194v1.405h-6.235l-2.588-4.284h-1.248v.515l.236 2.34-.236 1.429H84zm5.176-8.66h1.365c.55 0 1.02-.024 1.412-.071.408-.047.722-.187.941-.421.22-.25.33-.648.33-1.194 0-.578-.118-.991-.353-1.24-.236-.25-.565-.399-.989-.446a9.614 9.614 0 0 0-1.435-.093h-1.506l.235 3.464zM104.666 18.83V6.728l-4.706.234V2.257h14.707v4.705l-4.824-.234v8.357l.259 2.34-.259 1.405h-5.177zM116.785 18.83V6.026l-.235-2.34.235-1.429h5.177v6.344h4.918V2.257h5.177v8.802l.235 1.615v6.156h-5.412v-5.946h-4.918v1.639l.235 4.307h-5.412zM135.588 18.83V6.026l-.471-2.34.471-1.429h7.977c1.114 0 2.188.11 3.223.328 1.051.203 1.985.593 2.801 1.17.831.578 1.49 1.405 1.976 2.482.486 1.076.73 2.473.73 4.19 0 1.716-.251 3.128-.753 4.236-.487 1.092-1.146 1.943-1.977 2.552a7.477 7.477 0 0 1-2.8 1.264 14.463 14.463 0 0 1-3.2.35h-7.977zm5.224-4.448h1.788c.926 0 1.702-.101 2.33-.304a2.498 2.498 0 0 0 1.458-1.147c.33-.577.495-1.412.495-2.504 0-1.108-.173-1.92-.518-2.435-.33-.53-.816-.874-1.459-1.03-.628-.171-1.396-.257-2.306-.257h-1.788v7.677zM153.013 18.83l1.13-3.956V11.9l1.741-.702 3.294-8.918h7.083l4.024 10.486 1.812 3.324v2.739h-5.106l-1.177-3.488h-6.377l-1.012 3.488h-5.412zm7.977-7.584h3.53l-1.553-4.658h-.494l-1.483 4.658zM176.04 18.83v-6.788l-5.835-8.38V2.257h5.906l2.353 4.822h.47l2.33-4.822h5.883v1.405l-6.001 8.52.141 2.364v4.284h-5.247zM191.923 12.72l-2.07-8.847L192.676 2l2.8 1.896-2.141 8.824h-1.412zm.518 7.28-3.059-3.043 3.059-3.043 3.059 3.043L192.441 20z"/>
|
||||
</g>
|
||||
<g filter="url(#s1alkvv8kb)">
|
||||
<path d="M5.87 18.825V7.601H3V3.17l8.228-.937.239 1.406-.24 2.344v12.841H5.87z" fill="url(#xidihnl5xc)"/>
|
||||
<path d="M21.258 19.06a9.043 9.043 0 0 1-2.87-.446 6.484 6.484 0 0 1-2.369-1.453c-.67-.671-1.195-1.546-1.578-2.624-.383-1.094-.574-2.43-.574-4.007 0-1.562.191-2.883.574-3.96.382-1.094.909-1.977 1.578-2.648a6.092 6.092 0 0 1 2.368-1.453A8.63 8.63 0 0 1 21.257 2c1.356 0 2.584.281 3.684.844 1.116.562 2.001 1.468 2.655 2.718.67 1.234 1.004 2.89 1.004 4.968s-.335 3.741-1.004 4.991c-.654 1.25-1.539 2.156-2.655 2.718-1.1.547-2.328.82-3.683.82zm0-5.039c.701 0 1.187-.25 1.459-.75.27-.5.406-1.413.406-2.741 0-1.313-.136-2.219-.407-2.719-.27-.515-.757-.773-1.459-.773-.685 0-1.18.258-1.483.773-.287.516-.43 1.422-.43 2.719 0 1.312.143 2.226.43 2.742.303.5.798.75 1.483.75z" fill="url(#9hqzmmkygd)"/>
|
||||
<path d="M32.721 12.014V4.745l-2.87.14V2.06h8.97v2.826l-2.943-.141v5.02l.158 1.405-.158.844h-3.157z" fill="url(#bzq8gpt5ve)"/>
|
||||
<path d="M40.543 12.014v-7.69l-.144-1.407.144-.857H43.7v3.81h3V2.06h3.156v5.286l.144.97v3.698h-3.3V8.443h-3v.984l.143 2.587h-3.3z" fill="url(#4t6arxwa4f)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="xidihnl5xc" x1="3" y1="2" x2="29.822" y2="35.308" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6133B4"/>
|
||||
<stop offset="1" stop-color="#4F2A93"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="9hqzmmkygd" x1="3" y1="2" x2="29.822" y2="35.308" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6133B4"/>
|
||||
<stop offset="1" stop-color="#4F2A93"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="bzq8gpt5ve" x1="3" y1="2" x2="29.822" y2="35.308" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6133B4"/>
|
||||
<stop offset="1" stop-color="#4F2A93"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="4t6arxwa4f" x1="3" y1="2" x2="29.822" y2="35.308" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6133B4"/>
|
||||
<stop offset="1" stop-color="#4F2A93"/>
|
||||
</linearGradient>
|
||||
<filter id="c19w6aye5a" x="53" y="0" width="145.5" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix values="0 0 0 0 0.101961 0 0 0 0 0.0941176 0 0 0 0 0.113725 0 0 0 0.12 0"/>
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_45_799"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix values="0 0 0 0 0.101961 0 0 0 0 0.0941176 0 0 0 0 0.113725 0 0 0 0.24 0"/>
|
||||
<feBlend in2="effect1_dropShadow_45_799" result="effect2_dropShadow_45_799"/>
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow_45_799" result="shape"/>
|
||||
</filter>
|
||||
<filter id="s1alkvv8kb" x="0" y="0" width="53" height="23.059" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix values="0 0 0 0 0.101961 0 0 0 0 0.0941176 0 0 0 0 0.113725 0 0 0 0.12 0"/>
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_45_799"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix values="0 0 0 0 0.101961 0 0 0 0 0.0941176 0 0 0 0 0.113725 0 0 0 0.24 0"/>
|
||||
<feBlend in2="effect1_dropShadow_45_799" result="effect2_dropShadow_45_799"/>
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow_45_799" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
22
website/client/src/assets/svg/birthday-gems.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg width="58" height="48" viewBox="0 0 58 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m16.853 4.36 7.959-1.453-2.71 7.556-2.708 7.557-5.25-6.103-5.25-6.103 7.959-1.453z" fill="#5DDEAB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.771 1.454 40.731 0l-2.71 7.556-2.709 7.556-5.25-6.102-5.25-6.103 7.96-1.453z" fill="#5DDEAB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m43.272 13.659-7.96 1.453 2.71-7.556L40.73 0l5.25 6.103 5.25 6.102-7.96 1.454z" fill="#38C38D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m27.353 16.566-7.96 1.453 2.71-7.556 2.709-7.556 5.25 6.103 5.25 6.102-7.96 1.454zM11.434 19.473l-7.959 1.453 2.71-7.556 2.708-7.556 5.25 6.103 5.25 6.102-7.959 1.454z" fill="#B0F1D7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m3.475 20.926 28.05 18.662L19.394 18.02 3.475 20.926z" fill="#38C38D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.249 12.202 31.525 39.588l3.805-24.48 15.919-2.906z" fill="#B0F1D7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m19.394 18.02 12.131 21.568 3.787-24.476-15.918 2.907z" fill="#5DDEAB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m51.904 26.44-3.832-.897 1.132 3.736 1.132 3.737 2.7-2.84 2.7-2.84-3.832-.896zM44.24 24.647l-3.832-.897 1.132 3.736 1.132 3.736 2.7-2.84 2.7-2.839-3.832-.896z" fill="#87E3E1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m38.84 30.326 3.832.896-1.132-3.736-1.132-3.736-2.7 2.84-2.7 2.839 3.832.897zM46.504 32.12l3.832.896-1.132-3.736-1.132-3.737-2.7 2.84-2.7 2.84 3.832.896z" fill="#C0FBFA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.168 33.912 58 34.81l-1.132-3.736-1.133-3.736-2.7 2.84-2.7 2.839 3.833.896z" fill="#5EC5C2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m58 34.81-14.084 8.395 6.42-10.19L58 34.81z" fill="#C0FBFA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m35 29.427 8.916 13.779-1.252-11.986L35 29.427z" fill="#5EC5C2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m50.336 33.016-6.42 10.19-1.244-11.984 7.664 1.794z" fill="#87E3E1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m16.877 22.666-5.078 1.971 4.262 3.372 4.262 3.37.816-5.341.816-5.343-5.078 1.971zM6.721 26.609l-5.078 1.97 4.262 3.372 4.262 3.371.816-5.342.816-5.343-5.078 1.972z" fill="#7BE3CF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m5.09 37.294 5.077-1.972-4.262-3.371-4.261-3.371-.817 5.342-.816 5.343 5.078-1.971z" fill="#C5F3EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m15.245 33.351 5.078-1.971-4.262-3.371-4.262-3.371-.816 5.342-.816 5.342 5.078-1.97z" fill="#C5F3EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m25.4 29.41 5.078-1.972-4.262-3.371-4.261-3.372-.816 5.343-.816 5.342 5.078-1.97z" fill="#41C7AF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.478 27.438 21.117 48l-.794-16.62 10.155-3.942z" fill="#C5F3EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m0 39.269 21.117 8.73-10.961-12.672L0 39.269z" fill="#41C7AF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.323 31.38 21.117 48l-10.95-12.678 10.156-3.942z" fill="#7BE3CF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
22
website/client/src/assets/svg/confetti.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg width="518" height="152" viewBox="0 0 518 152" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.48 65.487v5.042h-1.772v-5.042h1.772zm1.621 6.671h5.013v1.782h-5.013v-1.782zm-10.027 0h5.013v1.782h-5.013v-1.782zm8.406 3.412v5.041h-1.772V75.57h1.772z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".5"/>
|
||||
<path opacity=".92" fill-rule="evenodd" clip-rule="evenodd" d="m9.504 29.894 2.707-4.715 1.658.962-2.707 4.715-1.658-.962zm2.066-7.12-4.689-2.722.958-1.667 4.688 2.723-.957 1.667zm9.378 5.445-4.689-2.722.957-1.667 4.69 2.722-.958 1.667zm-6.03-7.755 2.707-4.715 1.658.962-2.707 4.715-1.658-.962z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m60.85 11.508.708-3.662 1.288.251-.708 3.662-1.288-.252zm-.24-5.076-3.642-.712.25-1.295 3.642.712-.25 1.295zm7.283 1.423-3.642-.711.25-1.295 3.642.712-.25 1.294zm-5.627-3.671.708-3.662 1.287.251-.708 3.662-1.287-.251z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".81"/>
|
||||
<path opacity=".76" fill-rule="evenodd" clip-rule="evenodd" d="m107.034 22.162.493 5.675-1.995.175-.494-5.674 1.996-.176zm2.477 7.349 5.643-.497.175 2.007-5.644.496-.174-2.006zm-11.287.993 5.644-.497.174 2.007-5.643.496-.175-2.006zm9.797 3.008.494 5.675-1.996.175-.493-5.675 1.995-.175z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m16.191 92.492-5.006 1.636-.575-1.78 5.006-1.636.575 1.78zm-6.098 3.792 1.626 5.034-1.77.578-1.626-5.034 1.77-.578zM6.839 86.215l1.627 5.035-1.77.578-1.627-5.034 1.77-.579zm-.66 9.549-5.006 1.635-.576-1.78 5.007-1.635.575 1.78z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".91"/>
|
||||
<path opacity=".92" fill-rule="evenodd" clip-rule="evenodd" d="m35.176 59.176 5.102-1.97.692 1.814-5.101 1.97-.693-1.814zm6.118-4.264-1.958-5.13 1.803-.696 1.958 5.13-1.803.696zm3.916 10.26-1.958-5.13 1.804-.696 1.958 5.13-1.804.696zm.17-9.935 5.1-1.969.693 1.814-5.101 1.969-.693-1.814z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m95.733 86.583-4.383 2.649-.931-1.559 4.383-2.648.93 1.558zm-4.949 4.93 2.634 4.407-1.55.936-2.633-4.407 1.55-.937zm-5.267-8.816 2.633 4.408-1.55.936-2.633-4.408 1.55-.936zm1.45 9.183-4.384 2.648-.93-1.558 4.382-2.648.931 1.558z" fill="#36205D" style="mix-blend-mode:multiply"/>
|
||||
<path opacity=".98" fill-rule="evenodd" clip-rule="evenodd" d="m24.804 132.406-2.1-3.015 1.06-.746 2.1 3.014-1.06.747zm-3.747-3.307-2.998 2.111-.742-1.066 2.998-2.111.742 1.066zm5.996-4.222-2.998 2.111-.742-1.066 2.998-2.11.742 1.065zm-6.447 1.5-2.1-3.015 1.06-.746 2.1 3.014-1.06.747z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m60.65 142.295-1.594 3.144-1.105-.567 1.593-3.144 1.105.567zm-1.098 4.678 3.126 1.602-.563 1.112-3.127-1.602.564-1.112zm-6.254-3.204 3.127 1.602-.563 1.112-3.127-1.603.563-1.111zm4.165 4.814-1.593 3.144-1.106-.566 1.593-3.144 1.106.566z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".82"/>
|
||||
<path opacity=".71" fill-rule="evenodd" clip-rule="evenodd" d="m110.507 140.233 2.321-4.582 1.611.826-2.321 4.581-1.611-.825zm1.599-6.817-4.556-2.335.821-1.62 4.556 2.335-.821 1.62zm9.112 4.669-4.556-2.335.821-1.62 4.556 2.335-.821 1.62zm-6.068-7.015 2.321-4.582 1.611.825-2.321 4.582-1.611-.825z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M373.52 65.487v5.042h1.772v-5.042h-1.772zm-1.621 6.671h-5.013v1.782h5.013v-1.782zm10.027 0h-5.013v1.782h5.013v-1.782zm-8.406 3.412v5.041h1.772V75.57h-1.772z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".5"/>
|
||||
<path opacity=".92" fill-rule="evenodd" clip-rule="evenodd" d="m508.496 29.894-2.707-4.715-1.658.962 2.707 4.715 1.658-.962zm-2.066-7.12 4.689-2.722-.958-1.667-4.689 2.723.958 1.667zm-9.378 5.445 4.689-2.722-.957-1.667-4.689 2.722.957 1.667zm6.03-7.755-2.707-4.715-1.658.962 2.707 4.715 1.658-.962z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m457.15 11.508-.708-3.662-1.287.251.707 3.662 1.288-.252zm.24-5.076 3.642-.712-.25-1.295-3.642.712.25 1.295zm-7.283 1.423 3.642-.711-.251-1.295-3.641.712.25 1.294zm5.627-3.671-.708-3.662-1.287.251.708 3.662 1.287-.251z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".81"/>
|
||||
<path opacity=".76" fill-rule="evenodd" clip-rule="evenodd" d="m410.966 22.162-.493 5.675 1.995.175.494-5.674-1.996-.176zm-2.477 7.349-5.643-.497-.175 2.007 5.644.496.174-2.006zm11.287.993-5.644-.497-.174 2.007 5.643.496.175-2.006zm-9.797 3.008-.494 5.675 1.996.175.493-5.675-1.995-.175z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m501.809 92.492 5.006 1.636.575-1.78-5.006-1.636-.575 1.78zm6.098 3.792-1.626 5.034 1.77.578 1.626-5.034-1.77-.578zm3.254-10.069-1.627 5.035 1.77.578 1.627-5.034-1.77-.579zm.66 9.549 5.006 1.635.576-1.78-5.007-1.635-.575 1.78z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".91"/>
|
||||
<path opacity=".92" fill-rule="evenodd" clip-rule="evenodd" d="m482.824 59.176-5.102-1.97-.692 1.814 5.101 1.97.693-1.814zm-6.118-4.264 1.958-5.13-1.803-.696-1.958 5.13 1.803.696zm-3.916 10.26 1.958-5.13-1.804-.696-1.958 5.13 1.804.696zm-.169-9.935-5.102-1.969-.692 1.814 5.101 1.969.693-1.814z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m422.267 86.583 4.383 2.649.932-1.559-4.384-2.648-.931 1.558zm4.949 4.93-2.634 4.407 1.55.936 2.634-4.407-1.55-.937zm5.267-8.816-2.633 4.408 1.549.936 2.634-4.408-1.55-.936zm-1.449 9.183 4.383 2.648.931-1.558-4.383-2.648-.931 1.558z" fill="#36205D" style="mix-blend-mode:multiply"/>
|
||||
<path opacity=".98" fill-rule="evenodd" clip-rule="evenodd" d="m493.196 132.406 2.099-3.015-1.06-.746-2.099 3.014 1.06.747zm3.747-3.307 2.998 2.111.742-1.066-2.998-2.111-.742 1.066zm-5.996-4.222 2.998 2.111.742-1.066-2.998-2.11-.742 1.065zm6.447 1.5 2.1-3.015-1.06-.746-2.099 3.014 1.059.747z" fill="#fff"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m457.351 142.295 1.593 3.144 1.105-.567-1.593-3.144-1.105.567zm1.097 4.678-3.126 1.602.563 1.112 3.127-1.602-.564-1.112zm6.254-3.204-3.127 1.602.563 1.112 3.127-1.603-.563-1.111zm-4.165 4.814 1.593 3.144 1.106-.566-1.593-3.144-1.106.566z" fill="#36205D" style="mix-blend-mode:multiply" opacity=".82"/>
|
||||
<path opacity=".71" fill-rule="evenodd" clip-rule="evenodd" d="m407.493 140.233-2.321-4.582-1.611.826 2.321 4.581 1.611-.825zm-1.599-6.817 4.556-2.335-.821-1.62-4.556 2.335.821 1.62zm-9.112 4.669 4.556-2.335-.821-1.62-4.556 2.335.821 1.62zm6.068-7.015-2.321-4.582-1.611.825 2.321 4.582 1.611-.825z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
3
website/client/src/assets/svg/cross.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.26512 0L4.84341 3.57829L3.57829 4.84341L0 1.26512L1.26512 0ZM7.15659 3.57829L10.7349 5.33207e-08L12 1.26512L8.42171 4.84341L7.15659 3.57829ZM5.33207e-08 10.7349L3.57829 7.15659L4.84341 8.42171L1.26512 12L5.33207e-08 10.7349ZM8.42171 7.15659L12 10.7349L10.7349 12L7.15659 8.42171L8.42171 7.15659Z" fill="#FFB445"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 469 B |
4
website/client/src/assets/svg/divider.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="138" height="12" viewBox="0 0 138 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m127.265 0 3.578 3.578-1.265 1.265L126 1.265 127.265 0zm5.892 3.578L136.735 0 138 1.265l-3.578 3.578-1.265-1.265zM126 10.735l3.578-3.578 1.265 1.265L127.265 12 126 10.735zm8.422-3.578L138 10.735 136.735 12l-3.578-3.578 1.265-1.265z" fill="#FFB445"/>
|
||||
<path d="M114.445 4.555 112.5 1l-1.945 3.555L107.914 6h-3.828l-1.349-.737L101.5 3l-1.237 2.263L98.914 6H0v1h98.914l1.349.737L101.5 10l1.237-2.263L104.086 7h3.828l2.641 1.445L112.5 12l1.945-3.555L118 6.5l-3.555-1.945z" fill="#36205D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 647 B |
37
website/client/src/assets/svg/gifts-birthday.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<svg width="85" height="32" viewBox="0 0 85 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m4.93 12.255 2.466-.63-1.983-1.597-.63-2.468-1.595 1.986-2.465.63 1.983 1.597.63 2.468 1.595-1.986zM80.034 7.698l2.465-.63-1.983-1.597-.63-2.468-1.594 1.985-2.466.631 1.983 1.596.63 2.469 1.595-1.986zM42.27 7.427l2.929.487-1.368-2.638.487-2.932-2.635 1.37-2.928-.488 1.367 2.638-.486 2.932 2.634-1.37zM78.215 26.355l2.694 2.064.033-3.396 2.063-2.697-3.393-.034-2.694-2.065-.033 3.397-2.062 2.697 3.392.034zM38.321 28.092l2.092.348-.977-1.885.347-2.094-1.881.978-2.092-.348.977 1.884-.348 2.095 1.882-.978zM12.17 30.035l.916 1.915.981-1.882 1.913-.916-1.88-.982-.915-1.916-.981 1.882-1.913.917 1.88.982z" fill="#fff" fill-opacity=".5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m24.878 12.01 6.73-1.805 2.524 9.433-6.73 1.806-2.524-9.433z" fill="#F9F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m18.148 13.816 6.73-1.805 2.524 9.433-6.73 1.805-2.524-9.433z" fill="#E1E0E3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m23.532 12.372 1.346-.361 2.524 9.433-1.346.36-2.524-9.432z" fill="#6133B4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m24.878 12.01 1.346-.36 2.524 9.433-1.346.36-2.524-9.432z" fill="#9A62FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m25.696 20.457 1.345-.36.361 1.347-1.346.36-.36-1.347zM23.532 12.372l1.346-.361.36 1.347-1.346.361-.36-1.347z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m18.148 13.816 5.384-1.444.36 1.348-5.383 1.444-.36-1.348zM20.312 21.902l5.384-1.445.36 1.348-5.383 1.444-.36-1.347zM26.224 11.65l5.383-1.445.36 1.348-5.383 1.444-.36-1.347zM28.387 19.735l5.384-1.444.36 1.347-5.383 1.445-.36-1.348z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m27.041 20.096 1.346-.36.361 1.347-1.346.36-.36-1.347zM24.878 12.01l1.346-.36.36 1.347-1.346.361-.36-1.347z" fill="#6133B4"/>
|
||||
<path clip-rule="evenodd" d="M24.735 4.954c-.335-1.183-1.148-2.301-2.285-2.51-1.138-.21-1.923.616-1.7 1.476.221.86 1 1.122 3.498 2.183.71.302.823.034.487-1.149z" stroke="#6133B4" stroke-width="1.5"/>
|
||||
<path clip-rule="evenodd" d="M27.66 5.365c.648-1.044 1.737-1.895 2.888-1.782 1.151.112 1.678 1.123 1.228 1.889-.45.765-1.27.802-3.964 1.133-.765.094-.8-.195-.152-1.24z" stroke="#9A62FF" stroke-width="1.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.319 4.294c-2.24-.315-1.259 2.44-.36 2.566.898.126 2.6-2.25.36-2.566z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m26.016 6.454 8.279 1.165-.582 4.145-8.279-1.165.582-4.145z" fill="#F9F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m17.737 5.29 8.279 1.164-.582 4.145-8.279-1.165.582-4.145z" fill="#E1E0E3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m23.256 6.066 5.52.777-.582 4.144-5.52-.776.582-4.145z" fill="#9A62FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m23.256 6.066 2.76.388-.582 4.145-2.76-.388.582-4.145z" fill="#6133B4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m26.016 6.454 2.76.389-.195 1.381-2.76-.388.195-1.382z" fill="#6133B4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m23.256 6.066 2.76.388-.194 1.382-2.76-.388.194-1.382z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m17.349 8.053 5.52.776-.195 1.382-5.519-.777.194-1.381zM28.388 9.606l5.519.776-.194 1.382-5.52-.777.195-1.381z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path clip-rule="evenodd" d="M56.55 9.16c-.624-1.413-1.83-2.662-3.282-2.724-1.452-.062-2.285 1.104-1.858 2.135.426 1.031 1.441 1.22 4.734 2.104.935.25 1.03-.102.406-1.515z" stroke="#6133B4" stroke-width="1.5"/>
|
||||
<path clip-rule="evenodd" d="M60.26 9.16c.624-1.413 1.83-2.662 3.283-2.724 1.451-.062 2.284 1.104 1.857 2.135-.426 1.031-1.44 1.22-4.734 2.104-.935.25-1.03-.102-.406-1.515z" stroke="#9A62FF" stroke-width="1.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.405 8.061c-2.842 0-1.14 3.256 0 3.256s2.842-3.256 0-3.256z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.405 10.802H68.91v5.259H58.405v-5.259z" fill="#F9F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.901 10.802h10.504v5.259H47.901v-5.259z" fill="#E1E0E3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.904 10.802h7.002v5.259h-7.002v-5.259z" fill="#9A62FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.904 10.802h3.501v5.259h-3.501v-5.259zM58.405 10.802h3.501v1.753h-3.5v-1.753z" fill="#6133B4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.904 10.802h3.501v1.753h-3.501v-1.753z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.405 16.06h8.753v12.27h-8.753V16.06z" fill="#F9F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.652 16.06h8.753v12.27h-8.753V16.06z" fill="#E1E0E3"/>
|
||||
<path fill="#6133B4" d="M56.654 16.061h1.751v12.27h-1.751z"/>
|
||||
<path fill="#9A62FF" d="M58.405 16.061h1.751v12.27h-1.751z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M56.654 26.578h1.751v1.753h-1.75v-1.753zM56.654 16.06h1.751v1.754h-1.75V16.06z" fill="#4F2A93"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.652 16.06h7.002v1.754h-7.002V16.06z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.901 14.308h7.003v1.753H47.9v-1.753zM49.652 26.578h7.002v1.753h-7.002v-1.753zM60.156 16.06h7.002v1.754h-7.002V16.06z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M61.906 14.308h7.003v1.753h-7.003v-1.753zM60.156 26.578h7.002v1.753h-7.002v-1.753z" fill="#BDA8FF" fill-opacity=".3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.405 26.578h1.75v1.753h-1.75v-1.753zM58.405 16.06h1.75v1.754h-1.75V16.06z" fill="#6133B4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path fill="url(#sxizdfpdya)" d="M0 0h68v68H0z"/>
|
||||
<defs>
|
||||
<pattern id="sxizdfpdya" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#pomapjzcdb" transform="scale(.0147)"/>
|
||||
</pattern>
|
||||
<image id="pomapjzcdb" width="68" height="68" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAABECAYAAAA4E5OyAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5gwJFx8lKwafmAAABjRJREFUeNrtm39MVWUYx78IV5BfF3Qg8kNppNIoyhKWZmUBlpRNpC3Uac7EUUGrscxM+AOHia1foKmDHOHItkbhLGnpzGVBimtlozmVTeByCRDhwgXll/THc17de3zvuedernDd3uefc895f5/zuc/3ed57DyBNmjRpzpuH+sLvR8fGAODR0nS+YmAgAMCQNQMAMJKwkys//+tUAMDQDTrPzNgIACj58iAAoLH5LADgx/Kzwols/zyba3+jny9n7WNmJ3LXffzoONUHTrVPyvDg7sEUyQRvXrYKrvX2AgCmK2RUxL9OT35oDzWs38bVf3XlNe78sfindE3gam/TpCw87fwPwuuSEL2EXMg6DgBY/HW6+E4OdQAA4lZ4CYnotVgBAEvShuiIRwAAXxV/y9Xbve+jSSHj2SfIl1RLQpwkRO1LmDEimJV+Qyqy+709wvZx/3wBAFhk3QQAONlYqKoxpGuijLDfvnduodmnTwIAUrM2ataThDhKSEp9FH2o38/5iopCuuO1/drqceAy+YiSAPI5m+rE9UvLcjTnweKfJWk7nVtppb5qkhB7hLAI01Y8wdSjyzLM9aCOJ9qrI+jDZTqkNlwBAByLixZOpMtk4uIeZrVrqkgVhtYL4x9m830iXXJDJCH2CGGqwXKB4gKxevw9eBgA0HDwIgAg94UdAICZL1JSkd5H5w2VVB4ZHg8A6IkkEoKMREKPhVTM/AHFPdNLxHFP6TnKdTIX8vOp7a8AABxWcpnVwZ9pLvjSlRYAwNzoKEmIS1TGlnqc2H1aWO5n8qSIFGsAAKdazVx53pR5pDog1SkMuF8z7rnD1yi+a4bRICy39vUBAPwDAhSVe5uy3Scp283+iUjE8ymSEJcQolaPVxZkkldPJRKiYmZqtl+3iy9vaWwk1agcJTUKJ18S9TI5gSu59ASjP07R9F0PW1YLV1BlzSMfhh1SZSaEkHe25ZMK1LQBANpa2wEAyTELxjUw64dZFOYJs21bxkjxcdAL1qzdCgBYXrlLEuJSlWFPdGlmuOIL6Lz/L4pITeZmIicnnH/SVV5cOfM9rJ9TpUyF5rl0YZXtebpISTr6viTEpXHIgy9NAwD4+voCAA4da+YIamn05OqbzKNc+bqMhwAAAwMDCiHudUMkIc4Swr7zakvf7698omNoaKiKqA6uXG+/kpB7jRCmBsEhscoVyikWrpoFAPAYJZ8wcqvjQQBAeKgRADDmST7n3HdXlRoGpV+KXFcVxWiO391F+zCY7dxC9baXhOglxPIL/UhqNlOEunKfNwAgInKYU5kLJ9Sbqjds9Ej15iYZOJVhVlfM9k3oERqf8ZE+xK0JYZElix96/iQ1iFRtXcYm+3E+5OaUafwdv3md8yEWi0WoMrcjVjKjiyNXSYirCWE5hzqrPVPA5ybZR4xc+d6VlzlfkKbakj20voMrj00f4fZNWI4k4xB3J4R9p5dmQjM3OX7AU3Of4+cy7XI/Ve7Dxk3eEiMJuSciVZbd3jZSjfkgdVi2KUyzvb1ydf+Tnf1KQvQSYisLTcyYodlh8ub7uPjDXnt1xGpr3M7OrnEtVG97SYg9QrYVPQcAWDywXZXdQslF9HXMIlZbpDBrrTOofAhlv7W+bwjr118sI082SL/wfZpfJ33IhBJiDAxWvtx0iFg0zMcR9bwqtGFM1cOAQ0NGLBLXvzUPdRbe2y1Vxi1UpiFsr/LpE+76rIQR7jzI3yD0Hepsl1mPdVilMrbGhSY51zt7JSFuEameKZjOZbfrKnxdOoGqLCuX/U62SUIcJaS8ZQN3fmItX74hqpwj6M1q/h9BJataOQLKW14TD9Sib8JMZT7MrZOEuAUhDhO0AuMiwJb919k0LjIu/UH7MMuXSUIcsjveuYt7IH7MHSdq7FkOAAgJpm3/t/Lpf6v23rlj/7Pt7DYJ28t37hwlJDHh8TEA6LcOCJ8QM3an7e5DKE/mDrUIquHO/fzF8Y2X6WnhdTb+lqJsISHs/R1b47P2R/7NkYQ4pDJhIXPoCXp3c/sOIR7OvW1gi6QRPyJimnegMLtlqhI+JwgAYG7qEZL37uatuubB+rltVqkyExKH3G1TkzHefhixhkBJiDRp0qTddfsfGCIUXNZsU4gAAAAASUVORK5CYII="/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
1
website/client/src/assets/svg/new-close.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect width="16" height="16" fill="none"/><g id="b"><g id="c"><g id="d"><polygon id="e" points="12.2 2 14 3.8 9.8 8 14 12.2 12.2 14 8 9.8 3.8 14 2 12.2 6.2 8 2 3.8 3.8 2 8 6.2 12.2 2"/></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 308 B |
5
website/client/src/assets/svg/stripe.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="48" height="20" viewBox="0 0 48 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48 10.334c0-3.418-1.653-6.115-4.813-6.115-3.174 0-5.094 2.697-5.094 6.088 0 4.019 2.267 6.048 5.52 6.048 1.587 0 2.787-.36 3.694-.868v-2.67c-.907.454-1.947.735-3.267.735-1.293 0-2.44-.454-2.587-2.03h6.52c0-.173.027-.868.027-1.188zm-6.587-1.268c0-1.51.92-2.137 1.76-2.137.813 0 1.68.628 1.68 2.136h-3.44zM32.947 4.22c-1.307 0-2.147.613-2.614 1.04l-.173-.827h-2.933V20l3.333-.707.013-3.779c.48.347 1.187.841 2.36.841 2.387 0 4.56-1.922 4.56-6.155-.013-3.871-2.213-5.98-4.546-5.98zm-.8 9.198c-.787 0-1.254-.28-1.574-.627l-.013-4.954c.347-.387.827-.654 1.587-.654 1.213 0 2.053 1.362 2.053 3.11 0 1.79-.827 3.125-2.053 3.125zM22.64 3.431l3.346-.72V0L22.64.708V3.43z" fill="#635BFF"/>
|
||||
<path fill="#635BFF" d="M22.64 4.446h3.347v11.682H22.64z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m19.053 5.434-.213-.988h-2.88v11.682h3.333V8.211c.787-1.028 2.12-.841 2.534-.694V4.446c-.427-.16-1.987-.454-2.774.988zM12.387 1.549l-3.254.694-.013 10.694c0 1.976 1.48 3.431 3.453 3.431 1.094 0 1.894-.2 2.334-.44v-2.71c-.427.173-2.534.787-2.534-1.189V7.29h2.534V4.447h-2.534l.014-2.897zM3.373 7.837c0-.52.427-.72 1.134-.72 1.013 0 2.293.306 3.306.854V4.833a8.783 8.783 0 0 0-3.306-.614C1.8 4.22 0 5.634 0 7.997c0 3.685 5.067 3.098 5.067 4.687 0 .614-.534.814-1.28.814-1.107 0-2.52-.454-3.64-1.068v3.178a9.233 9.233 0 0 0 3.64.76c2.773 0 4.68-1.375 4.68-3.764-.014-3.98-5.094-3.271-5.094-4.767z" fill="#635BFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -198,52 +198,79 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!filterBackgrounds"
|
||||
class="row text-center title-row"
|
||||
>
|
||||
<strong>{{ backgroundShopSets[1].text }}</strong>
|
||||
</div>
|
||||
<div
|
||||
v-if="!filterBackgrounds"
|
||||
class="row title-row"
|
||||
v-if="!filterBackgrounds && user.purchased.background.birthday_bash"
|
||||
>
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[1].items"
|
||||
:key="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="!user.purchased.background[bg.key]
|
||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||
class="row text-center title-row"
|
||||
>
|
||||
<strong>{{ backgroundShopSets[2].text }}</strong>
|
||||
</div>
|
||||
<div
|
||||
class="row title-row"
|
||||
>
|
||||
<div
|
||||
class="background"
|
||||
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
||||
></div>
|
||||
<i
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="glyphicon glyphicon-lock"
|
||||
></i>
|
||||
<div
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="purchase-background single d-flex align-items-center justify-content-center"
|
||||
v-for="bg in backgroundShopSets[2].items"
|
||||
:key="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
class="svg-icon hourglass"
|
||||
v-html="icons.hourglass"
|
||||
class="background"
|
||||
:class="`background_${bg.key}`"
|
||||
></div>
|
||||
<span class="price">1</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="badge-top"
|
||||
@click.stop.prevent="togglePinned(bg)"
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!filterBackgrounds">
|
||||
<div
|
||||
class="row text-center title-row"
|
||||
>
|
||||
<strong>{{ backgroundShopSets[1].text }}</strong>
|
||||
</div>
|
||||
<div
|
||||
class="row title-row"
|
||||
>
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[1].items"
|
||||
:key="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="!user.purchased.background[bg.key]
|
||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isBackgroundPinned(bg)"
|
||||
/>
|
||||
</span>
|
||||
<div
|
||||
class="background"
|
||||
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
||||
></div>
|
||||
<i
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="glyphicon glyphicon-lock"
|
||||
></i>
|
||||
<div
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="purchase-background single d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<div
|
||||
class="svg-icon hourglass"
|
||||
v-html="icons.hourglass"
|
||||
></div>
|
||||
<span class="price">1</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="badge-top"
|
||||
@click.stop.prevent="togglePinned(bg)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isBackgroundPinned(bg)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<sub-menu
|
||||
|
||||
119
website/client/src/components/header/banners/birthdayBanner.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<base-banner
|
||||
banner-id="birthday-banner"
|
||||
class="birthday-banner"
|
||||
:show="showBirthdayBanner"
|
||||
height="3rem"
|
||||
:can-close="false"
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
:aria-label="$t('celebrateBirthday')"
|
||||
class="content d-flex justify-content-around align-items-center ml-auto mr-auto"
|
||||
@click="showBirthdayModal"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gifts left-gift"
|
||||
v-html="icons.giftsBirthday"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-ten-birthday"
|
||||
v-html="icons.tenBirthday"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="announce-text"
|
||||
v-html="$t('celebrateBirthday')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gifts right-gift"
|
||||
v-html="icons.giftsBirthday"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</base-banner>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.announce-text {
|
||||
color: $purple-50;
|
||||
}
|
||||
|
||||
.birthday-banner {
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 8px;
|
||||
background-image: linear-gradient(90deg,
|
||||
rgba(255,190,93,0) 0%,
|
||||
rgba(255,190,93,1) 25%,
|
||||
rgba(255,190,93,1) 75%,
|
||||
rgba(255,190,93,0) 100%),
|
||||
url('~@/assets/images/glitter.png');
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.left-gift {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.right-gift {
|
||||
margin: auto auto auto 8px;
|
||||
filter: flipH;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.svg-gifts {
|
||||
width: 85px;
|
||||
}
|
||||
|
||||
.svg-ten-birthday {
|
||||
width: 192.5px;
|
||||
margin-left: 8px;
|
||||
margin-right: 8.5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import { mapState } from '@/libs/store';
|
||||
import BaseBanner from './base';
|
||||
|
||||
import giftsBirthday from '@/assets/svg/gifts-birthday.svg';
|
||||
import tenBirthday from '@/assets/svg/10th-birthday-linear.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseBanner,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
giftsBirthday,
|
||||
tenBirthday,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
showBirthdayBanner () {
|
||||
return Boolean(find(this.currentEventList, event => Boolean(event.event === 'birthday10')));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showBirthdayModal () {
|
||||
this.$root.$emit('bv::show::modal', 'birthday-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<base-notification
|
||||
:can-remove="canRemove"
|
||||
:has-icon="true"
|
||||
:notification="notification"
|
||||
:read-after-click="true"
|
||||
@click="action"
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
>
|
||||
<strong> {{ notification.data.title }} </strong>
|
||||
<span> {{ notification.data.text }} </span>
|
||||
</div>
|
||||
<div
|
||||
slot="icon"
|
||||
class="mt-3"
|
||||
:class="notification.data.icon"
|
||||
></div>
|
||||
</base-notification>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseNotification from './base';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseNotification,
|
||||
},
|
||||
props: {
|
||||
notification: {
|
||||
type: Object,
|
||||
default (data) {
|
||||
return data;
|
||||
},
|
||||
},
|
||||
canRemove: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
action () {
|
||||
if (!this.notification || !this.notification.data) {
|
||||
return;
|
||||
}
|
||||
if (this.notification.data.destination === 'backgrounds') {
|
||||
this.$store.state.avatarEditorOptions.editingUser = true;
|
||||
this.$store.state.avatarEditorOptions.startingPage = 'backgrounds';
|
||||
this.$store.state.avatarEditorOptions.subpage = '2023';
|
||||
this.$root.$emit('bv::show::modal', 'avatar-modal');
|
||||
} else {
|
||||
this.$router.push({ name: this.notification.data.destination || 'items' });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -123,23 +123,24 @@ import successImage from '@/assets/svg/success.svg';
|
||||
import starBadge from '@/assets/svg/star-badge.svg';
|
||||
|
||||
// Notifications
|
||||
import NEW_STUFF from './notifications/newStuff';
|
||||
import GROUP_TASK_NEEDS_WORK from './notifications/groupTaskNeedsWork';
|
||||
import GUILD_INVITATION from './notifications/guildInvitation';
|
||||
import PARTY_INVITATION from './notifications/partyInvitation';
|
||||
import CARD_RECEIVED from './notifications/cardReceived';
|
||||
import CHALLENGE_INVITATION from './notifications/challengeInvitation';
|
||||
import QUEST_INVITATION from './notifications/questInvitation';
|
||||
import GIFT_ONE_GET_ONE from './notifications/g1g1';
|
||||
import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned';
|
||||
import GROUP_TASK_CLAIMED from './notifications/groupTaskClaimed';
|
||||
import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints';
|
||||
import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems';
|
||||
import CARD_RECEIVED from './notifications/cardReceived';
|
||||
import NEW_INBOX_MESSAGE from './notifications/newPrivateMessage';
|
||||
import GROUP_TASK_NEEDS_WORK from './notifications/groupTaskNeedsWork';
|
||||
import GUILD_INVITATION from './notifications/guildInvitation';
|
||||
import ITEM_RECEIVED from './notifications/itemReceived';
|
||||
import NEW_CHAT_MESSAGE from './notifications/newChatMessage';
|
||||
import WORLD_BOSS from './notifications/worldBoss';
|
||||
import VERIFY_USERNAME from './notifications/verifyUsername';
|
||||
import NEW_INBOX_MESSAGE from './notifications/newPrivateMessage';
|
||||
import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems';
|
||||
import NEW_STUFF from './notifications/newStuff';
|
||||
import ONBOARDING_COMPLETE from './notifications/onboardingComplete';
|
||||
import GIFT_ONE_GET_ONE from './notifications/g1g1';
|
||||
import PARTY_INVITATION from './notifications/partyInvitation';
|
||||
import QUEST_INVITATION from './notifications/questInvitation';
|
||||
import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints';
|
||||
import VERIFY_USERNAME from './notifications/verifyUsername';
|
||||
import WORLD_BOSS from './notifications/worldBoss';
|
||||
import OnboardingGuide from './onboardingGuide';
|
||||
|
||||
export default {
|
||||
@@ -147,24 +148,25 @@ export default {
|
||||
MenuDropdown,
|
||||
MessageCount,
|
||||
// One component for each type
|
||||
NEW_STUFF,
|
||||
GROUP_TASK_NEEDS_WORK,
|
||||
GUILD_INVITATION,
|
||||
PARTY_INVITATION,
|
||||
CARD_RECEIVED,
|
||||
CHALLENGE_INVITATION,
|
||||
QUEST_INVITATION,
|
||||
GIFT_ONE_GET_ONE,
|
||||
GROUP_TASK_ASSIGNED,
|
||||
GROUP_TASK_CLAIMED,
|
||||
UNALLOCATED_STATS_POINTS,
|
||||
NEW_MYSTERY_ITEMS,
|
||||
CARD_RECEIVED,
|
||||
NEW_INBOX_MESSAGE,
|
||||
GROUP_TASK_NEEDS_WORK,
|
||||
GUILD_INVITATION,
|
||||
ITEM_RECEIVED,
|
||||
NEW_CHAT_MESSAGE,
|
||||
WorldBoss: WORLD_BOSS,
|
||||
VERIFY_USERNAME,
|
||||
OnboardingGuide,
|
||||
NEW_INBOX_MESSAGE,
|
||||
NEW_MYSTERY_ITEMS,
|
||||
NEW_STUFF,
|
||||
ONBOARDING_COMPLETE,
|
||||
GIFT_ONE_GET_ONE,
|
||||
PARTY_INVITATION,
|
||||
QUEST_INVITATION,
|
||||
UNALLOCATED_STATS_POINTS,
|
||||
VERIFY_USERNAME,
|
||||
WorldBoss: WORLD_BOSS,
|
||||
OnboardingGuide,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -185,6 +187,7 @@ export default {
|
||||
// NOTE: Those not listed here won't be shown in the notification panel!
|
||||
handledNotifications: [
|
||||
'NEW_STUFF',
|
||||
'ITEM_RECEIVED',
|
||||
'GIFT_ONE_GET_ONE',
|
||||
'GROUP_TASK_NEEDS_WORK',
|
||||
'GUILD_INVITATION',
|
||||
|
||||
877
website/client/src/components/news/birthdayModal.vue
Normal file
@@ -0,0 +1,877 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="birthday-modal"
|
||||
:hide-header="true"
|
||||
:hide-footer="true"
|
||||
>
|
||||
<div class="modal-content">
|
||||
<div
|
||||
class="modal-close"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon svg-close"
|
||||
v-html="icons.close"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="svg-confetti svg-icon"
|
||||
v-html="icons.confetti"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<img
|
||||
src="~@/assets/images/10-birthday.png"
|
||||
class="ten-birthday"
|
||||
>
|
||||
</div>
|
||||
<div class="limited-wrapper">
|
||||
<div
|
||||
class="svg-gifts svg-icon"
|
||||
v-html="icons.gifts"
|
||||
>
|
||||
</div>
|
||||
<div class="limited-event">
|
||||
{{ $t('limitedEvent') }}
|
||||
</div>
|
||||
<div class="dates">
|
||||
{{ $t('limitedDates') }}
|
||||
</div>
|
||||
<div
|
||||
class="svg-gifts-flip svg-icon"
|
||||
v-html="icons.gifts"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="celebrate d-flex justify-content-center">
|
||||
{{ $t('celebrateAnniversary') }}
|
||||
</div>
|
||||
<h2 class="d-flex justify-content-center">
|
||||
<span
|
||||
class="left-divider"
|
||||
v-html="icons.divider"
|
||||
></span>
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
{{ $t('jubilantGryphatrice') }}
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="right-divider"
|
||||
></span>
|
||||
</h2>
|
||||
<!-- gryphatrice info -->
|
||||
<div class="d-flex">
|
||||
<div class="jubilant-gryphatrice d-flex mr-auto">
|
||||
<img
|
||||
src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Gryphatrice-Jubilant-Large.gif"
|
||||
width="156px"
|
||||
height="144px"
|
||||
alt="a pink, purple, and green gryphatrice pet winks at you adorably"
|
||||
>
|
||||
</div>
|
||||
<div class="align-items-center">
|
||||
<div class="limited-edition mr-auto">
|
||||
{{ $t('limitedEdition') }}
|
||||
</div>
|
||||
<div class="gryphatrice-text">
|
||||
{{ $t('anniversaryGryphatriceText') }}
|
||||
</div>
|
||||
<div
|
||||
class="gryphatrice-price"
|
||||
v-html="$t('anniversaryGryphatricePrice')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- beginning of payments -->
|
||||
<!-- buy with money OR gems -->
|
||||
<div
|
||||
v-if="!ownGryphatrice && !gryphBought"
|
||||
>
|
||||
<div
|
||||
v-if="selectedPage !== 'payment-buttons'"
|
||||
id="initial-buttons"
|
||||
class="d-flex justify-content-center"
|
||||
>
|
||||
<button
|
||||
class="btn btn-secondary buy-now-left"
|
||||
:class="{active: selectedPage === 'payment-buttons'}"
|
||||
@click="selectedPage = 'payment-buttons'"
|
||||
>
|
||||
{{ $t('buyNowMoneyButton') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary buy-now-right"
|
||||
@click="buyGryphatriceGems()"
|
||||
>
|
||||
{{ $t('buyNowGemsButton') }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- buy with money -->
|
||||
<div
|
||||
v-else-if="selectedPage === 'payment-buttons'"
|
||||
id="payment-buttons"
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<button
|
||||
class="btn btn-secondary d-flex stripe"
|
||||
@click="redirectToStripe({ sku: 'price_0MPZ6iZCD0RifGXlLah2furv' })"
|
||||
>
|
||||
<span
|
||||
class="svg-stripe"
|
||||
v-html="icons.stripe"
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary d-flex paypal"
|
||||
@click="openPaypal({
|
||||
url: paypalCheckoutLink, type: 'sku', sku: 'Pet-Gryphatrice-Jubilant'
|
||||
})"
|
||||
>
|
||||
<span
|
||||
class="svg-paypal"
|
||||
v-html="icons.paypal"
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
<amazon-button
|
||||
:disabled="disabled"
|
||||
:amazon-data="amazonData"
|
||||
class="btn btn-secondary d-flex amazon"
|
||||
v-html="icons.amazon"
|
||||
/>
|
||||
<div
|
||||
class="pay-with-gems"
|
||||
@click="selectedPage = 'initial-buttons'"
|
||||
>
|
||||
{{ $t('wantToPayWithGemsText') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Own the gryphatrice -->
|
||||
<div
|
||||
v-else
|
||||
class="d-flex"
|
||||
>
|
||||
<button
|
||||
class="own-gryphatrice-button"
|
||||
@click="closeAndRedirect('/inventory/stable')"
|
||||
v-html="$t('ownJubilantGryphatrice')"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<!-- end of payments -->
|
||||
<h2 class="d-flex justify-content-center">
|
||||
<span
|
||||
class="left-divider"
|
||||
v-html="icons.divider"
|
||||
></span>
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
{{ $t('plentyOfPotions') }}
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="right-divider"
|
||||
></span>
|
||||
</h2>
|
||||
<div class="plenty-of-potions d-flex">
|
||||
{{ $t('plentyOfPotionsText') }}
|
||||
</div>
|
||||
<div class="potions">
|
||||
<div class="pot-1">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Porcelain.png">
|
||||
</div>
|
||||
<div class="pot-2">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Vampire.png">
|
||||
</div>
|
||||
<div class="pot-3">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Aquatic.png">
|
||||
</div>
|
||||
<div class="pot-4">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_StainedGlass.png">
|
||||
</div>
|
||||
<div class="pot-5">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Celestial.png">
|
||||
</div>
|
||||
<div class="pot-6">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Glow.png">
|
||||
</div>
|
||||
<div class="pot-7">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_AutumnLeaf.png">
|
||||
</div>
|
||||
<div class="pot-8">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_SandSculpture.png">
|
||||
</div>
|
||||
<div class="pot-9">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Peppermint.png">
|
||||
</div>
|
||||
<div class="pot-10">
|
||||
<img src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Shimmer.png">
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-secondary d-flex justify-content-center visit-the-market"
|
||||
@click="closeAndRedirect('/shops/market')"
|
||||
>
|
||||
{{ $t('visitTheMarketButton') }}
|
||||
</button>
|
||||
<h2 class="d-flex justify-content-center">
|
||||
<span
|
||||
class="left-divider"
|
||||
v-html="icons.divider"
|
||||
></span>
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
{{ $t('fourForFree') }}
|
||||
<span
|
||||
class="svg-cross"
|
||||
v-html="icons.cross"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="right-divider"
|
||||
></span>
|
||||
</h2>
|
||||
<div class="four-for-free">
|
||||
{{ $t('fourForFreeText') }}
|
||||
</div>
|
||||
<div class="four-grid d-flex justify-content-center">
|
||||
<div class="day-one-a">
|
||||
<div class="day-text">
|
||||
{{ $t('dayOne') }}
|
||||
</div>
|
||||
<div class="gift d-flex justify-content-center align-items-middle">
|
||||
<img
|
||||
src="~@/assets/images/robes.webp"
|
||||
class="m-auto"
|
||||
width="40px"
|
||||
height="66px"
|
||||
>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ $t('partyRobes') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="day-one-b">
|
||||
<div class="day-text">
|
||||
{{ $t('dayOne') }}
|
||||
</div>
|
||||
<div class="gift d-flex justify-content-center align-items-middle">
|
||||
<div
|
||||
class="svg-gem svg-icon m-auto"
|
||||
v-html="icons.birthdayGems"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ $t('twentyGems') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="day-five">
|
||||
<div class="day-text">
|
||||
{{ $t('dayFive') }}
|
||||
</div>
|
||||
<div class="gift d-flex justify-content-center align-items-middle">
|
||||
<img
|
||||
src="~@/assets/images/habitica-hero-goober.webp"
|
||||
class="m-auto"
|
||||
><!-- Birthday Set -->
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ $t('birthdaySet') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="day-ten">
|
||||
<div class="day-text">
|
||||
{{ $t('dayTen') }}
|
||||
</div>
|
||||
<div class="gift d-flex justify-content-center align-items-middle">
|
||||
<div
|
||||
class="svg-background svg-icon m-auto"
|
||||
v-html="icons.birthdayBackground"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ $t('background') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-bottom">
|
||||
<div class="limitations d-flex justify-content-center">
|
||||
{{ $t('limitations') }}
|
||||
</div>
|
||||
<div class="fine-print">
|
||||
{{ $t('anniversaryLimitations') }}
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#birthday-modal {
|
||||
.modal-body {
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
.modal-content {
|
||||
border-radius: 14px;
|
||||
border: 0px;
|
||||
}
|
||||
.modal-footer {
|
||||
border-radius: 14px;
|
||||
border: 0px;
|
||||
}
|
||||
.amazon {
|
||||
margin-bottom: 16px;
|
||||
|
||||
svg {
|
||||
width: 84px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.amazonpay-button-inner-image {
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/mixins.scss';
|
||||
|
||||
#birthday-modal {
|
||||
h2 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
color: $white;
|
||||
column-gap: 0.5rem;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.modal-body{
|
||||
box-shadow: 0 14px 28px 0 rgba(26, 24, 29, 0.24), 0 10px 10px 0 rgba(26, 24, 29, 0.28);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 566px;
|
||||
padding: 32px 24px 24px;
|
||||
background: linear-gradient(158deg,#6133b4,#4f2a93);
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
.modal-bottom {
|
||||
width: 566px;
|
||||
background-color: $purple-50;
|
||||
color: $purple-500;
|
||||
line-height: 1.33;
|
||||
border-top: 0px;
|
||||
padding: 16px 40px 28px 40px;
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
}
|
||||
.limitations {
|
||||
color: $white;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
margin-top: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
.fine-print {
|
||||
font-size: 0.75rem;
|
||||
color: $purple-500;
|
||||
line-height: 1.33;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ten-birthday {
|
||||
position: relative;
|
||||
width: 268px;
|
||||
height: 244px;
|
||||
margin: 0 125px 16px;
|
||||
}
|
||||
|
||||
.limited-event {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
letter-spacing: 2.4px;
|
||||
margin-top: -8px;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.dates {
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.celebrate {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
margin: 16px 16px 24px 16px;
|
||||
text-align: center;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.jubilant-gryphatrice {
|
||||
height: 176px;
|
||||
width: 204px;
|
||||
border-radius: 12px;
|
||||
background-color: $purple-50;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24px;
|
||||
margin-left: 4px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.limited-wrapper {
|
||||
margin-top: -36px;
|
||||
margin-bottom: -36px;
|
||||
}
|
||||
|
||||
.limited-edition, .gryphatrice-text, .gryphatrice-price {
|
||||
max-width: 274px;
|
||||
}
|
||||
|
||||
.limited-edition {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
line-height:1.33;
|
||||
letter-spacing:2.4px;
|
||||
padding-top: 18px;
|
||||
margin-left: 24px;
|
||||
margin-bottom: 8px;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.gryphatrice-text, .gryphatrice-price {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
margin-left: 24px;
|
||||
margin-right: 4px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.gryphatrice-price {
|
||||
padding-top: 16px;
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
.buy-now-left {
|
||||
width: 243px;
|
||||
margin: 24px 8px 24px 0px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
|
||||
.buy-now-right {
|
||||
width: 243px;
|
||||
margin: 24px 0px 24px 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
|
||||
.stripe {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.paypal {
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.stripe, .paypal, .amazon {
|
||||
width: 506px;
|
||||
height: 32px;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
border-radius: 4px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pay-with-gems {
|
||||
color: $white;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pay-with-gems:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.own-gryphatrice-button {
|
||||
width: 506px;
|
||||
height: 32px;
|
||||
margin: 24px 4px;
|
||||
border-radius: 4px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: $green-100;
|
||||
background-color: $green-100;
|
||||
color: $green-1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plenty-of-potions {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
margin: 0 8px 24px;
|
||||
text-align: center;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.potions {
|
||||
display: grid;
|
||||
grid-template-columns: 5;
|
||||
grid-template-rows: 2;
|
||||
gap: 24px 24px;
|
||||
justify-content: center;
|
||||
|
||||
.pot-1, .pot-2, .pot-3, .pot-4, .pot-5,
|
||||
.pot-6, .pot-7, .pot-8, .pot-9, .pot-10 {
|
||||
height: 68px;
|
||||
width: 68px;
|
||||
border-radius: 8px;
|
||||
background-color: $purple-50;
|
||||
}
|
||||
|
||||
.pot-1 {
|
||||
grid-column: 1 / 1;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-2 {
|
||||
grid-column: 2 / 2;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-3 {
|
||||
grid-column: 3 / 3;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-4 {
|
||||
grid-column: 4 / 4;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-5 {
|
||||
grid-column: 5 / 5;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
.pot-6 {
|
||||
grid-column: 1 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
.pot-7 {
|
||||
grid-column: 2 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
.pot-8 {
|
||||
grid-column: 3 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
.pot-9 {
|
||||
grid-column: 4 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
.pot-10 {
|
||||
grid-column: 5 / 5;
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
|
||||
}
|
||||
.visit-the-market {
|
||||
height: 32px;
|
||||
margin: 24px 4px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.four-for-free {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
margin: 0 36px 24px;
|
||||
text-align: center;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.four-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 4;
|
||||
grid-template-rows: 1;
|
||||
gap: 24px;
|
||||
}
|
||||
.day-one-a, .day-one-b, .day-five, .day-ten {
|
||||
height: 140px;
|
||||
width: 100px;
|
||||
border-radius: 8px;
|
||||
background-color: $purple-50;
|
||||
}
|
||||
|
||||
.day-one-a {
|
||||
grid-column: 1 / 1;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
.day-one-b {
|
||||
grid-column: 2 / 2;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
.day-five {
|
||||
grid-column: 3 / 3;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
.day-ten {
|
||||
grid-column: 4 / 4;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
|
||||
.day-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
letter-spacing: 2.4px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
padding: 4px 0px;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.gift {
|
||||
height: 80px;
|
||||
width: 84px;
|
||||
margin: 0 8px 32px;
|
||||
background-color: $purple-100;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
padding: 8px 0px;
|
||||
margin-top: -32px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
// SVG CSS
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
.svg-close {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
fill: $purple-50;
|
||||
|
||||
& svg path {
|
||||
fill: $purple-50 !important;;
|
||||
}
|
||||
& :hover {
|
||||
fill: $purple-50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.svg-confetti {
|
||||
position: absolute;
|
||||
height: 152px;
|
||||
width: 518px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.svg-gifts, .svg-gifts-flip {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
width: 85px;
|
||||
}
|
||||
|
||||
.svg-gifts {
|
||||
margin-left: 70px;
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
.svg-gifts-flip {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
left: 366px;
|
||||
bottom: 34px;
|
||||
}
|
||||
|
||||
.left-divider, .right-divider {
|
||||
background-image: url('~@/assets/images/fancy-divider.png');
|
||||
background-position: right center;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-flex;
|
||||
flex-grow: 2;
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
|
||||
.right-divider {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.svg-cross {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
color: $yellow-50;
|
||||
}
|
||||
|
||||
.svg-gem {
|
||||
height: 48px;
|
||||
width: 58px;
|
||||
}
|
||||
|
||||
.svg-background {
|
||||
height: 68px;
|
||||
width: 68px;
|
||||
}
|
||||
|
||||
.svg-stripe {
|
||||
height: 20px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.svg-paypal {
|
||||
height: 16px;
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
// to check if user owns JG or not
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
// Purchase functionality
|
||||
import buy from '@/mixins/buy';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import payments from '@/mixins/payments';
|
||||
import content from '@/../../common/script/content/index';
|
||||
import amazonButton from '@/components/payments/buttons/amazon';
|
||||
|
||||
// import images
|
||||
import close from '@/assets/svg/new-close.svg';
|
||||
import confetti from '@/assets/svg/confetti.svg';
|
||||
import gifts from '@/assets/svg/gifts-birthday.svg';
|
||||
import cross from '@/assets/svg/cross.svg';
|
||||
import stripe from '@/assets/svg/stripe.svg';
|
||||
import paypal from '@/assets/svg/paypal-logo.svg';
|
||||
import amazon from '@/assets/svg/amazonpay.svg';
|
||||
import birthdayGems from '@/assets/svg/birthday-gems.svg';
|
||||
import birthdayBackground from '@/assets/svg/icon-background-birthday.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
amazonButton,
|
||||
},
|
||||
mixins: [buy, notifications, payments],
|
||||
data () {
|
||||
return {
|
||||
amazonData: {
|
||||
type: 'single',
|
||||
sku: 'Pet-Gryphatrice-Jubilant',
|
||||
},
|
||||
icons: Object.freeze({
|
||||
close,
|
||||
confetti,
|
||||
gifts,
|
||||
cross,
|
||||
stripe,
|
||||
paypal,
|
||||
amazon,
|
||||
birthdayGems,
|
||||
birthdayBackground,
|
||||
}),
|
||||
selectedPage: 'initial-buttons',
|
||||
gryphBought: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
ownGryphatrice () {
|
||||
return Boolean(this.user && this.user.items.pets['Gryphatrice-Jubilant']);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hide () {
|
||||
this.$root.$emit('bv::hide::modal', 'birthday-modal');
|
||||
},
|
||||
buyGryphatriceGems () {
|
||||
const gryphatrice = content.petInfo['Gryphatrice-Jubilant'];
|
||||
if (this.user.balance * 4 < gryphatrice.value) {
|
||||
this.$root.$emit('bv::show::modal', 'buy-gems');
|
||||
return this.hide();
|
||||
}
|
||||
if (!this.confirmPurchase(gryphatrice.currency, gryphatrice.value)) {
|
||||
return null;
|
||||
}
|
||||
this.makeGenericPurchase(gryphatrice);
|
||||
this.gryphBought = true;
|
||||
return this.purchased(gryphatrice.text());
|
||||
},
|
||||
closeAndRedirect (route) {
|
||||
const routeTerminator = route.split('/')[route.split('/').length - 1];
|
||||
if (this.$router.history.current.name !== routeTerminator) {
|
||||
this.$router.push(route);
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'birthday-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -78,6 +78,7 @@ export default {
|
||||
orderReferenceId: null,
|
||||
subscription: null,
|
||||
coupon: null,
|
||||
sku: null,
|
||||
},
|
||||
isAmazonSetup: false,
|
||||
amazonButtonEnabled: false,
|
||||
@@ -174,7 +175,10 @@ export default {
|
||||
storePaymentStatusAndReload (url) {
|
||||
let paymentType;
|
||||
|
||||
if (this.amazonPayments.type === 'single' && !this.amazonPayments.gift) paymentType = 'gems';
|
||||
if (this.amazonPayments.type === 'single') {
|
||||
if (this.amazonPayments.sku) paymentType = 'sku';
|
||||
else if (!this.amazonPayments.gift) paymentType = 'gems';
|
||||
}
|
||||
if (this.amazonPayments.type === 'subscription') paymentType = 'subscription';
|
||||
if (this.amazonPayments.groupId || this.amazonPayments.groupToCreate) paymentType = 'groupPlan';
|
||||
if (this.amazonPayments.type === 'single' && this.amazonPayments.gift && this.amazonPayments.giftReceiver) {
|
||||
@@ -223,6 +227,7 @@ export default {
|
||||
const data = {
|
||||
orderReferenceId: this.amazonPayments.orderReferenceId,
|
||||
gift: this.amazonPayments.gift,
|
||||
sku: this.amazonPayments.sku,
|
||||
};
|
||||
|
||||
if (this.amazonPayments.gemsBlock) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="payments-success-modal"
|
||||
:hide-footer="isNewGroup || isGems || isSubscription"
|
||||
:modal-class="isNewGroup || isGems || isSubscription
|
||||
:hide-footer="isNewGroup || isGems || isSubscription || ownsJubilantGryphatrice"
|
||||
:modal-class="isNewGroup || isGems || isSubscription || ownsJubilantGryphatrice
|
||||
? ['modal-hidden-footer'] : []"
|
||||
>
|
||||
<!-- HEADER -->
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="check-container d-flex align-items-center justify-content-center">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon check"
|
||||
class="svg-icon svg-check"
|
||||
v-html="icons.check"
|
||||
></div>
|
||||
</div>
|
||||
@@ -107,6 +107,35 @@
|
||||
class="small-text auto-renew"
|
||||
>{{ $t('paymentAutoRenew') }}</span>
|
||||
</template>
|
||||
<!-- if you buy the Jubilant Gryphatrice during 10th birthday -->
|
||||
<template
|
||||
v-if="ownsJubilantGryphatrice"
|
||||
>
|
||||
<div class="words">
|
||||
<p class="jub-success">
|
||||
<span
|
||||
v-once
|
||||
v-html="$t('jubilantSuccess')"
|
||||
>
|
||||
</span>
|
||||
</p>
|
||||
<p class="jub-success">
|
||||
<span
|
||||
v-once
|
||||
v-html="$t('stableVisit')"
|
||||
>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="gryph-bg">
|
||||
<img
|
||||
src="https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Gryphatrice-Jubilant-Large.gif"
|
||||
alt="a pink, purple, and green gryphatrice pet winks at you adorably"
|
||||
width="78px"
|
||||
height="72px"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<!-- buttons for subscriptions / new Group / buy Gems for self -->
|
||||
<button
|
||||
v-if="isNewGroup || isGems || isSubscription"
|
||||
@@ -116,6 +145,14 @@
|
||||
>
|
||||
{{ $t('onwards') }}
|
||||
</button>
|
||||
<!-- buttons for Jubilant Gryphatrice purchase during 10th birthday -->
|
||||
<button
|
||||
v-if="ownsJubilantGryphatrice"
|
||||
class="btn btn-primary mx-auto btn-jub"
|
||||
@click="closeAndRedirect()"
|
||||
>
|
||||
{{ $t('takeMeToStable') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- FOOTER -->
|
||||
@@ -232,9 +269,8 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.check {
|
||||
width: 35.1px;
|
||||
height: 28px;
|
||||
.svg-check {
|
||||
width: 45px;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
@@ -293,6 +329,34 @@
|
||||
.group-billing-date {
|
||||
width: 269px;
|
||||
}
|
||||
|
||||
.words {
|
||||
margin-bottom: 16px;
|
||||
justify-content: center;
|
||||
font-size: 0.875rem;
|
||||
color: $gray-50;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
.jub-success {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.gryph-bg {
|
||||
width: 110px;
|
||||
height: 104px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
background-color: $gray-700;
|
||||
}
|
||||
.btn-jub {
|
||||
margin-bottom: 8px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
}
|
||||
.modal-footer {
|
||||
background: $gray-700;
|
||||
@@ -430,6 +494,9 @@ export default {
|
||||
isNewGroup () {
|
||||
return this.paymentData.paymentType === 'groupPlan' && this.paymentData.newGroup;
|
||||
},
|
||||
ownsJubilantGryphatrice () {
|
||||
return this.paymentData.paymentType === 'sku'; // will need to be revised when there are other discrete skus in system
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:payment-success', data => {
|
||||
@@ -458,6 +525,12 @@ export default {
|
||||
this.sendingInProgress = false;
|
||||
this.$root.$emit('bv::hide::modal', 'payments-success-modal');
|
||||
},
|
||||
closeAndRedirect () {
|
||||
if (this.$router.history.current.name !== 'stable') {
|
||||
this.$router.push('/inventory/stable');
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
submit () {
|
||||
if (this.paymentData.group && !this.paymentData.newGroup) {
|
||||
Analytics.track({
|
||||
|
||||
@@ -26,6 +26,7 @@ export default {
|
||||
'Fox-Veteran',
|
||||
'JackOLantern-Glow',
|
||||
'Gryphon-Gryphatrice',
|
||||
'Gryphatrice-Jubilant',
|
||||
'JackOLantern-RoyalPurple',
|
||||
];
|
||||
const BASE_PETS = [
|
||||
|
||||
@@ -9,7 +9,6 @@ import { CONSTANTS, setLocalSetting } from '@/libs/userlocalManager';
|
||||
|
||||
const { STRIPE_PUB_KEY } = process.env;
|
||||
|
||||
// const habiticaUrl = `${window.location.protocol}//${window.location.host}`;
|
||||
let stripeInstance = null;
|
||||
|
||||
export default {
|
||||
@@ -70,6 +69,7 @@ export default {
|
||||
type,
|
||||
giftData,
|
||||
gemsBlock,
|
||||
sku,
|
||||
} = data;
|
||||
let { url } = data;
|
||||
|
||||
@@ -93,6 +93,11 @@ export default {
|
||||
url += `?gemsBlock=${gemsBlock.key}`;
|
||||
}
|
||||
|
||||
if (type === 'sku') {
|
||||
appState.sku = sku;
|
||||
url += `?sku=${sku}`;
|
||||
}
|
||||
|
||||
setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState));
|
||||
window.open(url, '_blank');
|
||||
|
||||
@@ -129,6 +134,7 @@ export default {
|
||||
if (data.group || data.groupToCreate) paymentType = 'groupPlan';
|
||||
if (data.gift && data.gift.type === 'gems') paymentType = 'gift-gems';
|
||||
if (data.gift && data.gift.type === 'subscription') paymentType = 'gift-subscription';
|
||||
if (data.sku) paymentType = 'sku';
|
||||
|
||||
let url = '/stripe/checkout-session';
|
||||
const postData = {};
|
||||
@@ -148,6 +154,7 @@ export default {
|
||||
if (data.coupon) postData.coupon = data.coupon;
|
||||
if (data.groupId) postData.groupId = data.groupId;
|
||||
if (data.demographics) postData.demographics = data.demographics;
|
||||
if (data.sku) postData.sku = data.sku;
|
||||
|
||||
const response = await axios.post(url, postData);
|
||||
|
||||
@@ -250,6 +257,7 @@ export default {
|
||||
|
||||
if (data.type === 'single') {
|
||||
this.amazonPayments.gemsBlock = data.gemsBlock;
|
||||
this.amazonPayments.sku = data.sku;
|
||||
}
|
||||
|
||||
if (data.gift) {
|
||||
|
||||
@@ -857,5 +857,9 @@
|
||||
"backgroundSteamworksText": "Steamworks",
|
||||
"backgroundSteamworksNotes": "Build mighty contraptions of vapor and steel in a Steamworks.",
|
||||
"backgroundClocktowerText": "Clock Tower",
|
||||
"backgroundClocktowerNotes": "Situate your secret lair behind the face of a Clock Tower."
|
||||
"backgroundClocktowerNotes": "Situate your secret lair behind the face of a Clock Tower.",
|
||||
|
||||
"eventBackgrounds": "Event Backgrounds",
|
||||
"backgroundBirthdayBashText": "Birthday Bash",
|
||||
"backgroundBirthdayBashNotes": "Habitica's having a birthday party, and everyone's invited!"
|
||||
}
|
||||
|
||||
@@ -801,7 +801,8 @@
|
||||
"armorSpecialBirthday2021Notes": "Happy Birthday, Habitica! Wear these Extravagant Party Robes to celebrate this wonderful day. Confers no benefit.",
|
||||
"armorSpecialBirthday2022Text": "Preposterous Party Robes",
|
||||
"armorSpecialBirthday2022Notes": "Happy Birthday, Habitica! Wear these Proposterous Party Robes to celebrate this wonderful day. Confers no benefit.",
|
||||
|
||||
"armorSpecialBirthday2023Text": "Fabulous Party Robes",
|
||||
"armorSpecialBirthday2023Notes": "Happy Birthday, Habitica! Wear these Fabulous Party Robes to celebrate this wonderful day. Confers no benefit.",
|
||||
|
||||
"armorSpecialGaymerxText": "Rainbow Warrior Armor",
|
||||
"armorSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special armor is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
|
||||
@@ -2680,6 +2681,9 @@
|
||||
"backSpecialTurkeyTailGildedNotes": "Plumage fit for a parade! Confers no benefit.",
|
||||
"backSpecialNamingDay2020Text": "Royal Purple Gryphon Tail",
|
||||
"backSpecialNamingDay2020Notes": "Happy Naming Day! Swish this fiery, pixely tail about as you celebrate Habitica. Confers no benefit.",
|
||||
"backSpecialAnniversaryText": "Habitica Hero Cape",
|
||||
"backSpecialAnniversaryNotes": "Let this proud cape fly in the wind and tell everyone that you're a Habitica Hero. Confers no benefit. Special Edition 10th Birthday Bash Item.",
|
||||
|
||||
"backBearTailText": "Bear Tail",
|
||||
"backBearTailNotes": "This tail makes you look like a brave bear! Confers no benefit.",
|
||||
"backCactusTailText": "Cactus Tail",
|
||||
@@ -2711,6 +2715,8 @@
|
||||
"bodySpecialTakeThisNotes": "These pauldrons were earned by participating in a sponsored Challenge made by Take This. Congratulations! Increases all Stats by <%= attrs %>.",
|
||||
"bodySpecialAetherAmuletText": "Aether Amulet",
|
||||
"bodySpecialAetherAmuletNotes": "This amulet has a mysterious history. Increases Constitution and Strength by <%= attrs %> each.",
|
||||
"bodySpecialAnniversaryText": "Habitica Hero Collar",
|
||||
"bodySpecialAnniversaryNotes": "Perfectly complement your royal purple ensemble! Confers no benefit. Special Edition 10th Birthday Bash Item.",
|
||||
|
||||
"bodySpecialSummerMageText": "Shining Capelet",
|
||||
"bodySpecialSummerMageNotes": "Neither salt water nor fresh water can tarnish this metallic capelet. Confers no benefit. Limited Edition 2014 Summer Gear.",
|
||||
@@ -2913,6 +2919,8 @@
|
||||
"eyewearSpecialAetherMaskNotes": "This mask has a mysterious history. Increases Intelligence by <%= int %>.",
|
||||
"eyewearSpecialKS2019Text": "Mythic Gryphon Visor",
|
||||
"eyewearSpecialKS2019Notes": "Bold as a gryphon's... hmm, gryphons don't have visors. It reminds you to... oh, who are we kidding, it just looks cool! Confers no benefit.",
|
||||
"eyewearSpecialAnniversaryText": "Habitica Hero Mask",
|
||||
"eyewearSpecialAnniversaryNotes": "Look through the eyes of a Habitica Hero - you! Confers no benefit. Special Edition 10th Birthday Bash Item.",
|
||||
|
||||
"eyewearSpecialSummerRogueText": "Roguish Eyepatch",
|
||||
"eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"dateEndOctober": "October 31",
|
||||
"dateEndNovember": "November 30",
|
||||
"dateEndDecember": "December 31",
|
||||
"dateStartFebruary": "February 1",
|
||||
"januaryYYYY": "January <%= year %>",
|
||||
"februaryYYYY": "February <%= year %>",
|
||||
"marchYYYY": "March <%= year %>",
|
||||
@@ -236,5 +237,33 @@
|
||||
"g1g1Limitations": "This is a limited time event that starts on December 15th at 8:00 AM ET (13:00 UTC) and will end January 8th at 11:59 PM ET (January 9th 04:59 UTC). This promotion only applies when you gift to another Habitican. If you or your gift recipient already have a subscription, the gifted subscription will add months of credit that will only be used after the current subscription is canceled or expires.",
|
||||
"noLongerAvailable": "This item is no longer available.",
|
||||
"gemSaleHow": "Between <%= eventStartMonth %> <%= eventStartOrdinal %> and <%= eventEndOrdinal %>, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!",
|
||||
"gemSaleLimitations": "This promotion only applies during the limited time event. This event starts on <%= eventStartMonth %> <%= eventStartOrdinal %> at 8:00 AM EDT (12:00 UTC) and will end <%= eventStartMonth %> <%= eventEndOrdinal %> at 8:00 PM EDT (00:00 UTC). The promo offer is only available when buying Gems for yourself."
|
||||
}
|
||||
"gemSaleLimitations": "This promotion only applies during the limited time event. This event starts on <%= eventStartMonth %> <%= eventStartOrdinal %> at 8:00 AM EDT (12:00 UTC) and will end <%= eventStartMonth %> <%= eventEndOrdinal %> at 8:00 PM EDT (00:00 UTC). The promo offer is only available when buying Gems for yourself.",
|
||||
"anniversaryLimitations": "This is a limited time event that starts on January 23rd at 8:00 AM ET (13:00 UTC) and will end February 1st at 11:59 PM ET (04:59 UTC). The Limited Edition Jubilant Gryphatrice and ten Magic Hatching Potions will be available to buy during this time. The other Gifts listed in the Four for Free section will be automatically delivered to all accounts that were active in the 30 days prior to day the gift is sent. Accounts created after the gifts are sent will not be able to claim them.",
|
||||
"limitedEvent": "Limited Event",
|
||||
"limitedDates": "January 23rd to February 1st",
|
||||
"celebrateAnniversary": "Celebrate Habitica's 10th Birthday with gifts and exclusive items below!",
|
||||
"celebrateBirthday": "Celebrate Habitica's 10th Birthday with gifts and exclusive items!",
|
||||
"jubilantGryphatrice": "Animated Jubilant Gryphatrice Pet",
|
||||
"limitedEdition": "Limited Edition",
|
||||
"anniversaryGryphatriceText": "The rare Jubilant Gryphatrice joins the birthday celebrations! Don't miss your chance to own this exclusive animated Pet.",
|
||||
"anniversaryGryphatricePrice": "Own it today for <strong>$9.99</strong> or <strong>60 gems</strong>",
|
||||
"buyNowMoneyButton": "Buy Now for $9.99",
|
||||
"buyNowGemsButton": "Buy Now for 60 Gems",
|
||||
"wantToPayWithGemsText": "Want to pay with Gems?",
|
||||
"wantToPayWithMoneyText": "Want to pay with Stripe, Paypal, or Amazon?",
|
||||
"ownJubilantGryphatrice": "<strong>You own the Jubilant Gryphatrice!</strong> Visit the Stable to equip!",
|
||||
"jubilantSuccess": "You've successfully purchased the <strong>Jubilant Gryphatrice!</strong>",
|
||||
"stableVisit": "Visit the Stable to equip!",
|
||||
"takeMeToStable": "Take me to the Stable",
|
||||
"plentyOfPotions": "Plenty of Potions",
|
||||
"plentyOfPotionsText": "We're bringing back 10 of the community's favorite Magic Hatching potions. Head over to The Market to fill out your collection!",
|
||||
"visitTheMarketButton": "Visit the Market",
|
||||
"fourForFree": "Four for Free",
|
||||
"fourForFreeText": "To keep the party going, we'll be giving away Party Robes, 20 Gems, and a limited edition birthday Background and item set that includes a Cape, Pauldrons, and an Eyemask.",
|
||||
"dayOne": "Day 1",
|
||||
"dayFive": "Day 5",
|
||||
"dayTen": "Day 10",
|
||||
"partyRobes": "Party Robes",
|
||||
"twentyGems": "20 Gems",
|
||||
"birthdaySet": "Birthday Set"
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
"royalPurpleJackalope": "Royal Purple Jackalope",
|
||||
"invisibleAether": "Invisible Aether",
|
||||
"gryphatrice": "Gryphatrice",
|
||||
"jubilantGryphatrice": "Jubilant Gryphatrice",
|
||||
"potion": "<%= potionType %> Potion",
|
||||
"egg": "<%= eggType %> Egg",
|
||||
"eggs": "Eggs",
|
||||
|
||||
@@ -540,6 +540,11 @@ const backgrounds = {
|
||||
snowy_temple: { },
|
||||
winter_lake_with_swans: { },
|
||||
},
|
||||
eventBackgrounds: {
|
||||
birthday_bash: {
|
||||
price: 0,
|
||||
},
|
||||
},
|
||||
timeTravelBackgrounds: {
|
||||
airship: {
|
||||
price: 1,
|
||||
@@ -583,7 +588,9 @@ forOwn(backgrounds, (backgroundsInSet, set) => {
|
||||
forOwn(backgroundsInSet, (background, bgKey) => {
|
||||
background.key = bgKey;
|
||||
background.set = set;
|
||||
background.price = background.price || 7;
|
||||
if (background.price !== 0) {
|
||||
background.price = background.price || 7;
|
||||
}
|
||||
background.text = background.text || t(`background${upperFirst(camelCase(bgKey))}Text`);
|
||||
background.notes = background.notes || t(`background${upperFirst(camelCase(bgKey))}Notes`);
|
||||
|
||||
|
||||
@@ -10,11 +10,15 @@ const gemsPromo = {
|
||||
|
||||
export const EVENTS = {
|
||||
noEvent: {
|
||||
start: '2023-01-31T20:00-05:00',
|
||||
start: '2023-02-01T23:59-05:00',
|
||||
end: '2023-02-14T08:00-05:00',
|
||||
season: 'normal',
|
||||
npcImageSuffix: '',
|
||||
},
|
||||
birthday10: {
|
||||
start: '2023-01-23T08:00-05:00',
|
||||
end: '2023-02-01T23:59-05:00',
|
||||
},
|
||||
winter2023: {
|
||||
start: '2022-12-20T08:00-05:00',
|
||||
end: '2023-01-31T23:59-05:00',
|
||||
|
||||
@@ -798,6 +798,12 @@ const armor = {
|
||||
winter2023Healer: {
|
||||
set: 'winter2023CardinalHealerSet',
|
||||
},
|
||||
birthday2023: {
|
||||
text: t('armorSpecialBirthday2023Text'),
|
||||
notes: t('armorSpecialBirthday2023Notes'),
|
||||
value: 0,
|
||||
canOwn: ownsItem('armor_special_birthday2023'),
|
||||
},
|
||||
};
|
||||
|
||||
const armorStats = {
|
||||
@@ -923,6 +929,12 @@ const back = {
|
||||
value: 0,
|
||||
canOwn: ownsItem('back_special_namingDay2020'),
|
||||
},
|
||||
anniversary: {
|
||||
text: t('backSpecialAnniversaryText'),
|
||||
notes: t('backSpecialAnniversaryNotes'),
|
||||
value: 0,
|
||||
canOwn: ownsItem('back_special_anniversary'),
|
||||
},
|
||||
};
|
||||
|
||||
const body = {
|
||||
@@ -992,6 +1004,12 @@ const body = {
|
||||
value: 0,
|
||||
canOwn: ownsItem('body_special_namingDay2018'),
|
||||
},
|
||||
anniversary: {
|
||||
text: t('bodySpecialAnniversaryText'),
|
||||
notes: t('bodySpecialAnniversaryNotes'),
|
||||
value: 0,
|
||||
canOwn: ownsItem('body_special_anniversary'),
|
||||
},
|
||||
};
|
||||
|
||||
const eyewear = {
|
||||
@@ -1140,6 +1158,12 @@ const eyewear = {
|
||||
value: 0,
|
||||
canOwn: ownsItem('eyewear_special_ks2019'),
|
||||
},
|
||||
anniversary: {
|
||||
text: t('eyewearSpecialAnniversaryText'),
|
||||
notes: t('eyewearSpecialAnniversaryNotes'),
|
||||
value: 0,
|
||||
canOwn: ownsItem('eyewear_special_anniversary'),
|
||||
},
|
||||
};
|
||||
|
||||
const head = {
|
||||
|
||||
@@ -70,13 +70,13 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionShimmer'),
|
||||
limited: true,
|
||||
event: EVENTS.spring2022,
|
||||
event: EVENTS.birthday10,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndMarch'),
|
||||
previousDate: t('marchYYYY', { year: 2020 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('marchYYYY', { year: 2022 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBefore(EVENTS.spring2022.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
Fairy: {
|
||||
@@ -109,13 +109,13 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionAquatic'),
|
||||
limited: true,
|
||||
event: EVENTS.summer2022,
|
||||
event: EVENTS.birthday10,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndJuly'),
|
||||
previousDate: t('augustYYYY', { year: 2020 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('julyYYYY', { year: 2022 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.summer2022.start, EVENTS.summer2022.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
Ember: {
|
||||
@@ -188,12 +188,12 @@ const premium = {
|
||||
text: t('hatchingPotionPeppermint'),
|
||||
limited: true,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndJanuary'),
|
||||
previousDate: t('januaryYYYY', { year: 2018 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('januaryYYYY', { year: 2022 }),
|
||||
}),
|
||||
event: EVENTS.winter2022,
|
||||
event: EVENTS.birthday10,
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.winter2022.start, EVENTS.winter2022.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
StarryNight: {
|
||||
@@ -239,13 +239,13 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionGlow'),
|
||||
limited: true,
|
||||
event: EVENTS.fall2021,
|
||||
event: EVENTS.birthday10,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndOctober'),
|
||||
previousDate: t('septemberYYYY', { year: 2019 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('octoberYYYY', { year: 2021 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBefore(EVENTS.fall2021.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
Frost: {
|
||||
@@ -286,13 +286,13 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionCelestial'),
|
||||
limited: true,
|
||||
event: EVENTS.spring2022,
|
||||
event: EVENTS.birthday10,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndMarch'),
|
||||
previousDate: t('marchYYYY', { year: 2020 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('marchYYYY', { year: 2022 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBefore(EVENTS.spring2022.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
Sunshine: {
|
||||
@@ -399,13 +399,13 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionSandSculpture'),
|
||||
limited: true,
|
||||
event: EVENTS.summer2021,
|
||||
event: EVENTS.birthday10,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndJuly'),
|
||||
previousDate: t('juneYYYY', { year: 2020 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('julyYYYY', { year: 2021 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.summer2021.start, EVENTS.summer2021.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
Windup: {
|
||||
@@ -426,26 +426,26 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionVampire'),
|
||||
limited: true,
|
||||
event: EVENTS.fall2022,
|
||||
event: EVENTS.birthday10,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndOctober'),
|
||||
previousDate: t('octoberYYYY', { year: 2021 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('octoberYYYY', { year: 2022 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.fall2022.start, EVENTS.fall2022.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
AutumnLeaf: {
|
||||
value: 2,
|
||||
text: t('hatchingPotionAutumnLeaf'),
|
||||
limited: true,
|
||||
event: EVENTS.potions202111,
|
||||
event: EVENTS.birthday10,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndNovember'),
|
||||
previousDate: t('novemberYYYY', { year: 2020 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('novemberYYYY', { year: 2021 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBefore(EVENTS.potions202111.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
BlackPearl: {
|
||||
@@ -460,12 +460,12 @@ const premium = {
|
||||
text: t('hatchingPotionStainedGlass'),
|
||||
limited: true,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndJanuary'),
|
||||
previousDate: t('januaryYYYY', { year: 2021 }),
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('januaryYYYY', { year: 2022 }),
|
||||
}),
|
||||
event: EVENTS.winter2022,
|
||||
event: EVENTS.birthday10,
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.winter2022.start, EVENTS.winter2022.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
PolkaDot: {
|
||||
@@ -532,12 +532,13 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionPorcelain'),
|
||||
limited: true,
|
||||
event: EVENTS.potions202208,
|
||||
_addlNotes: t('premiumPotionAddlNotes', {
|
||||
date: t('dateEndAugust'),
|
||||
event: EVENTS.birthday10,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateStartFebruary'),
|
||||
previousDate: t('augustYYYY', { year: 2022 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.potions202208.start, EVENTS.potions202208.end);
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import each from 'lodash/each';
|
||||
import moment from 'moment';
|
||||
import { EVENTS } from './constants/events';
|
||||
import {
|
||||
drops as dropEggs,
|
||||
quests as questEggs,
|
||||
@@ -118,6 +120,9 @@ const canFindSpecial = {
|
||||
'Jackalope-RoyalPurple': true, // subscription
|
||||
'Wolf-Cerberus': false, // Pet once granted to backers
|
||||
'Gryphon-Gryphatrice': false, // Pet once granted to kickstarter
|
||||
|
||||
// Birthday Pet
|
||||
'Gryphatrice-Jubilant': false,
|
||||
},
|
||||
mounts: {
|
||||
// Thanksgiving pet ladder
|
||||
@@ -174,6 +179,7 @@ const specialPets = {
|
||||
'Fox-Veteran': 'veteranFox',
|
||||
'JackOLantern-Glow': 'glowJackolantern',
|
||||
'Gryphon-Gryphatrice': 'gryphatrice',
|
||||
'Gryphatrice-Jubilant': 'jubilantGryphatrice',
|
||||
'JackOLantern-RoyalPurple': 'royalPurpleJackolantern',
|
||||
};
|
||||
|
||||
@@ -207,6 +213,16 @@ each(specialPets, (translationString, key) => {
|
||||
};
|
||||
});
|
||||
|
||||
Object.assign(petInfo['Gryphatrice-Jubilant'], {
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
currency: 'gems',
|
||||
event: 'birthday10',
|
||||
value: 60,
|
||||
purchaseType: 'pets',
|
||||
});
|
||||
|
||||
each(specialMounts, (translationString, key) => {
|
||||
mountInfo[key] = {
|
||||
key,
|
||||
|
||||
@@ -7,6 +7,7 @@ export default {
|
||||
missingTypeParam: '"req.params.type" is required.',
|
||||
missingKeyParam: '"req.params.key" is required.',
|
||||
itemNotFound: 'Item "<%= key %>" not found.',
|
||||
petNotFound: 'Pet "<%= key %>" not found.',
|
||||
questNotFound: 'Quest "<%= key %>" not found.',
|
||||
spellNotFound: 'Skill "<%= spellId %>" not found.',
|
||||
invalidQuantity: 'Quantity to purchase must be a positive whole number.',
|
||||
|
||||
@@ -13,6 +13,7 @@ import hourglassPurchase from './hourglassPurchase';
|
||||
import errorMessage from '../../libs/errorMessage';
|
||||
import { BuyGemOperation } from './buyGem';
|
||||
import { BuyQuestWithGemOperation } from './buyQuestGem';
|
||||
import { BuyPetWithGemOperation } from './buyPetGem';
|
||||
import { BuyHourglassMountOperation } from './buyMount';
|
||||
|
||||
// @TODO: remove the req option style. Dependency on express structure is an anti-pattern
|
||||
@@ -86,7 +87,12 @@ export default async function buy (
|
||||
break;
|
||||
}
|
||||
case 'pets':
|
||||
buyRes = hourglassPurchase(user, req, analytics);
|
||||
if (key === 'Gryphatrice-Jubilant') {
|
||||
const buyOp = new BuyPetWithGemOperation(user, req, analytics);
|
||||
buyRes = await buyOp.purchase();
|
||||
} else {
|
||||
buyRes = hourglassPurchase(user, req, analytics);
|
||||
}
|
||||
break;
|
||||
case 'quest': {
|
||||
const buyOp = new BuyQuestWithGoldOperation(user, req, analytics);
|
||||
|
||||
61
website/common/script/ops/buy/buyPetGem.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import get from 'lodash/get';
|
||||
import {
|
||||
BadRequest,
|
||||
NotFound,
|
||||
} from '../../libs/errors';
|
||||
import content from '../../content/index';
|
||||
|
||||
import errorMessage from '../../libs/errorMessage';
|
||||
import { AbstractGemItemOperation } from './abstractBuyOperation';
|
||||
|
||||
export class BuyPetWithGemOperation extends AbstractGemItemOperation { // eslint-disable-line import/prefer-default-export, max-len
|
||||
multiplePurchaseAllowed () { // eslint-disable-line class-methods-use-this
|
||||
return false;
|
||||
}
|
||||
|
||||
getItemKey () {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
getItemValue (item) { // eslint-disable-line class-methods-use-this
|
||||
return item.value / 4;
|
||||
}
|
||||
|
||||
getItemType () { // eslint-disable-line class-methods-use-this
|
||||
return 'pet';
|
||||
}
|
||||
|
||||
extractAndValidateParams (user, req) {
|
||||
this.key = get(req, 'params.key');
|
||||
const { key } = this;
|
||||
if (!key) throw new BadRequest(errorMessage('missingKeyParam'));
|
||||
|
||||
const item = content.petInfo[key];
|
||||
|
||||
if (!item) throw new NotFound(errorMessage('petNotFound', { key }));
|
||||
|
||||
this.canUserPurchase(user, item);
|
||||
}
|
||||
|
||||
canUserPurchase (user, item) {
|
||||
if (item && user.items.pets[item.key]) {
|
||||
throw new BadRequest(this.i18n('petsAlreadyOwned'));
|
||||
}
|
||||
|
||||
super.canUserPurchase(user, item);
|
||||
}
|
||||
|
||||
async executeChanges (user, item, req) {
|
||||
user.items.pets[item.key] = 5;
|
||||
if (user.markModified) user.markModified('items.pets');
|
||||
|
||||
await this.subtractCurrency(user, item);
|
||||
|
||||
return [
|
||||
user.items.pets,
|
||||
this.i18n('messageBought', {
|
||||
itemText: item.text(req.language),
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -251,9 +251,10 @@ export default async function unlock (user, req = {}, analytics) {
|
||||
return invalidSet(req);
|
||||
}
|
||||
|
||||
cost = getIndividualItemPrice(setType, item, req);
|
||||
|
||||
unlockedAlready = alreadyUnlocked(user, setType, firstPath);
|
||||
if (!unlockedAlready) {
|
||||
cost = getIndividualItemPrice(setType, item, req);
|
||||
}
|
||||
|
||||
// Since only an item is being unlocked here,
|
||||
// remove all the other items from the set
|
||||
|
||||
@@ -975,7 +975,7 @@ api.disableClasses = {
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiParam (Path) {String="gems","eggs","hatchingPotions","premiumHatchingPotions"
|
||||
,"food","quests","gear"} type Type of item to purchase.
|
||||
,"food","quests","gear","pets"} type Type of item to purchase.
|
||||
* @apiParam (Path) {String} key Item's key (use "gem" for purchasing gems)
|
||||
*
|
||||
* @apiParam (Body) {Integer} [quantity=1] Count of items to buy.
|
||||
|
||||
@@ -75,12 +75,14 @@ api.checkout = {
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
const { orderReferenceId, gift, gemsBlock } = req.body;
|
||||
const {
|
||||
orderReferenceId, gift, gemsBlock, sku,
|
||||
} = req.body;
|
||||
|
||||
if (!orderReferenceId) throw new BadRequest('Missing req.body.orderReferenceId');
|
||||
|
||||
await amzLib.checkout({
|
||||
gemsBlock, gift, user, orderReferenceId, headers: req.headers,
|
||||
gemsBlock, gift, sku, user, orderReferenceId, headers: req.headers,
|
||||
});
|
||||
|
||||
res.respond(200);
|
||||
|
||||
@@ -23,7 +23,7 @@ api.iapAndroidVerify = {
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
if (!req.body.transaction) throw new BadRequest(res.t('missingReceipt'));
|
||||
const googleRes = await googlePayments.verifyGemPurchase({
|
||||
const googleRes = await googlePayments.verifyPurchase({
|
||||
user: res.locals.user,
|
||||
receipt: req.body.transaction.receipt,
|
||||
signature: req.body.transaction.signature,
|
||||
@@ -120,7 +120,7 @@ api.iapiOSVerify = {
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
if (!req.body.transaction) throw new BadRequest(res.t('missingReceipt'));
|
||||
const appleRes = await applePayments.verifyGemPurchase({
|
||||
const appleRes = await applePayments.verifyPurchase({
|
||||
user: res.locals.user,
|
||||
receipt: req.body.transaction.receipt,
|
||||
gift: req.body.gift,
|
||||
|
||||
@@ -27,10 +27,13 @@ api.checkout = {
|
||||
const gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
||||
req.session.gift = req.query.gift;
|
||||
|
||||
const { gemsBlock } = req.query;
|
||||
const { gemsBlock, sku } = req.query;
|
||||
req.session.gemsBlock = gemsBlock;
|
||||
req.session.sku = sku;
|
||||
|
||||
const link = await paypalPayments.checkout({ gift, gemsBlock, user: res.locals.user });
|
||||
const link = await paypalPayments.checkout({
|
||||
gift, gemsBlock, sku, user: res.locals.user,
|
||||
});
|
||||
|
||||
if (req.query.noRedirect) {
|
||||
res.respond(200);
|
||||
@@ -56,14 +59,15 @@ api.checkoutSuccess = {
|
||||
const { user } = res.locals;
|
||||
const gift = req.session.gift ? JSON.parse(req.session.gift) : undefined;
|
||||
delete req.session.gift;
|
||||
const { gemsBlock } = req.session;
|
||||
const { gemsBlock, sku } = req.session;
|
||||
delete req.session.gemsBlock;
|
||||
delete req.session.sku;
|
||||
|
||||
if (!paymentId) throw new BadRequest(apiError('missingPaymentId'));
|
||||
if (!customerId) throw new BadRequest(apiError('missingCustomerId'));
|
||||
|
||||
await paypalPayments.checkoutSuccess({
|
||||
user, gemsBlock, gift, paymentId, customerId, headers: req.headers,
|
||||
user, gemsBlock, gift, paymentId, customerId, headers: req.headers, sku,
|
||||
});
|
||||
|
||||
if (req.query.noRedirect) {
|
||||
|
||||
@@ -27,13 +27,13 @@ api.createCheckoutSession = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
const {
|
||||
gift, sub: subKey, gemsBlock, coupon, groupId,
|
||||
gift, sub: subKey, gemsBlock, coupon, groupId, sku,
|
||||
} = req.body;
|
||||
|
||||
const sub = subKey ? shared.content.subscriptionBlocks[subKey] : false;
|
||||
|
||||
const session = await stripePayments.createCheckoutSession({
|
||||
user, gemsBlock, gift, sub, groupId, coupon,
|
||||
user, gemsBlock, gift, sub, groupId, coupon, sku,
|
||||
});
|
||||
|
||||
res.respond(200, {
|
||||
|
||||
@@ -46,6 +46,7 @@ api.constants = {
|
||||
GIFT_TYPE_SUBSCRIPTION: 'subscription',
|
||||
|
||||
METHOD_BUY_GEMS: 'buyGems',
|
||||
METHOD_BUY_SKU_ITEM: 'buySkuItem',
|
||||
METHOD_CREATE_SUBSCRIPTION: 'createSubscription',
|
||||
PAYMENT_METHOD: 'Amazon Payments',
|
||||
PAYMENT_METHOD_GIFT: 'Amazon Payments (Gift)',
|
||||
@@ -110,7 +111,7 @@ api.authorize = function authorize (inputSet) {
|
||||
*/
|
||||
api.checkout = async function checkout (options = {}) {
|
||||
const {
|
||||
gift, user, orderReferenceId, headers, gemsBlock: gemsBlockKey,
|
||||
gift, user, orderReferenceId, headers, gemsBlock: gemsBlockKey, sku,
|
||||
} = options;
|
||||
let amount;
|
||||
let gemsBlock;
|
||||
@@ -127,6 +128,12 @@ api.checkout = async function checkout (options = {}) {
|
||||
} else if (gift.type === this.constants.GIFT_TYPE_SUBSCRIPTION) {
|
||||
amount = common.content.subscriptionBlocks[gift.subscription.key].price;
|
||||
}
|
||||
} else if (sku) {
|
||||
if (sku === 'Pet-Gryphatrice-Jubilant') {
|
||||
amount = 9.99;
|
||||
} else {
|
||||
throw new NotFound('SKU not found.');
|
||||
}
|
||||
} else {
|
||||
gemsBlock = getGemsBlock(gemsBlockKey);
|
||||
amount = gemsBlock.price / 100;
|
||||
@@ -171,12 +178,16 @@ api.checkout = async function checkout (options = {}) {
|
||||
|
||||
// execute payment
|
||||
let method = this.constants.METHOD_BUY_GEMS;
|
||||
if (sku) {
|
||||
method = this.constants.METHOD_BUY_SKU_ITEM;
|
||||
}
|
||||
|
||||
const data = {
|
||||
user,
|
||||
paymentMethod: this.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
gemsBlock,
|
||||
sku,
|
||||
};
|
||||
|
||||
if (gift) {
|
||||
|
||||
@@ -2,13 +2,14 @@ import moment from 'moment';
|
||||
import shared from '../../../common';
|
||||
import iap from '../inAppPurchases';
|
||||
import payments from './payments';
|
||||
import { getGemsBlock, validateGiftMessage } from './gems';
|
||||
import { validateGiftMessage } from './gems';
|
||||
import {
|
||||
NotAuthorized,
|
||||
BadRequest,
|
||||
} from '../errors';
|
||||
import { model as IapPurchaseReceipt } from '../../models/iapPurchaseReceipt';
|
||||
import { model as User } from '../../models/user';
|
||||
import { buySkuItem } from './skuItem';
|
||||
|
||||
const api = {};
|
||||
|
||||
@@ -22,7 +23,7 @@ api.constants = {
|
||||
RESPONSE_NO_ITEM_PURCHASED: 'NO_ITEM_PURCHASED',
|
||||
};
|
||||
|
||||
api.verifyGemPurchase = async function verifyGemPurchase (options) {
|
||||
api.verifyPurchase = async function verifyPurchase (options) {
|
||||
const {
|
||||
gift, user, receipt, headers,
|
||||
} = options;
|
||||
@@ -44,7 +45,6 @@ api.verifyGemPurchase = async function verifyGemPurchase (options) {
|
||||
if (purchaseDataList.length === 0) {
|
||||
throw new NotAuthorized(api.constants.RESPONSE_NO_ITEM_PURCHASED);
|
||||
}
|
||||
let correctReceipt = false;
|
||||
|
||||
// Purchasing one item at a time (processing of await(s) below is sequential not parallel)
|
||||
for (const purchaseData of purchaseDataList) {
|
||||
@@ -62,46 +62,16 @@ api.verifyGemPurchase = async function verifyGemPurchase (options) {
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let gemsBlockKey;
|
||||
switch (purchaseData.productId) { // eslint-disable-line default-case
|
||||
case 'com.habitrpg.ios.Habitica.4gems':
|
||||
gemsBlockKey = '4gems';
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.20gems':
|
||||
case 'com.habitrpg.ios.Habitica.21gems':
|
||||
gemsBlockKey = '21gems';
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.42gems':
|
||||
gemsBlockKey = '42gems';
|
||||
break;
|
||||
case 'com.habitrpg.ios.Habitica.84gems':
|
||||
gemsBlockKey = '84gems';
|
||||
break;
|
||||
}
|
||||
if (!gemsBlockKey) throw new NotAuthorized(api.constants.RESPONSE_INVALID_ITEM);
|
||||
const gemsBlock = getGemsBlock(gemsBlockKey);
|
||||
|
||||
if (gift) {
|
||||
gift.type = 'gems';
|
||||
if (!gift.gems) gift.gems = {};
|
||||
gift.gems.amount = shared.content.gems[gemsBlock.key].gems;
|
||||
}
|
||||
|
||||
if (gemsBlock) {
|
||||
correctReceipt = true;
|
||||
await payments.buyGems({ // eslint-disable-line no-await-in-loop
|
||||
user,
|
||||
gift,
|
||||
paymentMethod: api.constants.PAYMENT_METHOD_APPLE,
|
||||
gemsBlock,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
await buySkuItem({ // eslint-disable-line no-await-in-loop
|
||||
user,
|
||||
gift,
|
||||
paymentMethod: api.constants.PAYMENT_METHOD_APPLE,
|
||||
sku: purchaseData.productId,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!correctReceipt) throw new NotAuthorized(api.constants.RESPONSE_INVALID_ITEM);
|
||||
|
||||
return appleRes;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
} from '../errors';
|
||||
import { model as IapPurchaseReceipt } from '../../models/iapPurchaseReceipt';
|
||||
import { model as User } from '../../models/user';
|
||||
import { getGemsBlock, validateGiftMessage } from './gems';
|
||||
import { validateGiftMessage } from './gems';
|
||||
import { buySkuItem } from './skuItem';
|
||||
|
||||
const api = {};
|
||||
|
||||
@@ -21,7 +22,7 @@ api.constants = {
|
||||
RESPONSE_STILL_VALID: 'SUBSCRIPTION_STILL_VALID',
|
||||
};
|
||||
|
||||
api.verifyGemPurchase = async function verifyGemPurchase (options) {
|
||||
api.verifyPurchase = async function verifyPurchase (options) {
|
||||
const {
|
||||
gift, user, receipt, signature, headers,
|
||||
} = options;
|
||||
@@ -61,39 +62,11 @@ api.verifyGemPurchase = async function verifyGemPurchase (options) {
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let gemsBlockKey;
|
||||
|
||||
switch (receiptObj.productId) { // eslint-disable-line default-case
|
||||
case 'com.habitrpg.android.habitica.iap.4gems':
|
||||
gemsBlockKey = '4gems';
|
||||
break;
|
||||
case 'com.habitrpg.android.habitica.iap.20gems':
|
||||
case 'com.habitrpg.android.habitica.iap.21gems':
|
||||
gemsBlockKey = '21gems';
|
||||
break;
|
||||
case 'com.habitrpg.android.habitica.iap.42gems':
|
||||
gemsBlockKey = '42gems';
|
||||
break;
|
||||
case 'com.habitrpg.android.habitica.iap.84gems':
|
||||
gemsBlockKey = '84gems';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!gemsBlockKey) throw new NotAuthorized(this.constants.RESPONSE_INVALID_ITEM);
|
||||
|
||||
const gemsBlock = getGemsBlock(gemsBlockKey);
|
||||
|
||||
if (gift) {
|
||||
gift.type = 'gems';
|
||||
if (!gift.gems) gift.gems = {};
|
||||
gift.gems.amount = shared.content.gems[gemsBlock.key].gems;
|
||||
}
|
||||
|
||||
await payments.buyGems({
|
||||
await buySkuItem({ // eslint-disable-line no-await-in-loop
|
||||
user,
|
||||
gift,
|
||||
paymentMethod: this.constants.PAYMENT_METHOD_GOOGLE,
|
||||
gemsBlock,
|
||||
paymentMethod: api.constants.PAYMENT_METHOD_GOOGLE,
|
||||
sku: googleRes.productId,
|
||||
headers,
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ import { // eslint-disable-line import/no-cycle
|
||||
import { // eslint-disable-line import/no-cycle
|
||||
buyGems,
|
||||
} from './gems';
|
||||
import { // eslint-disable-line import/no-cycle
|
||||
buySkuItem,
|
||||
} from './skuItem';
|
||||
import { paymentConstants } from './constants';
|
||||
|
||||
const api = {};
|
||||
@@ -31,4 +34,6 @@ api.cancelSubscription = cancelSubscription;
|
||||
|
||||
api.buyGems = buyGems;
|
||||
|
||||
api.buySkuItem = buySkuItem;
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -77,7 +77,9 @@ api.paypalBillingAgreementCancel = util
|
||||
api.ipnVerifyAsync = util.promisify(paypalIpn.verify.bind(paypalIpn));
|
||||
|
||||
api.checkout = async function checkout (options = {}) {
|
||||
const { gift, user, gemsBlock: gemsBlockKey } = options;
|
||||
const {
|
||||
gift, gemsBlock: gemsBlockKey, sku, user,
|
||||
} = options;
|
||||
|
||||
let amount;
|
||||
let gemsBlock;
|
||||
@@ -99,12 +101,17 @@ api.checkout = async function checkout (options = {}) {
|
||||
amount = Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2);
|
||||
description = 'mo. Habitica Subscription (Gift)';
|
||||
}
|
||||
} else if (sku) {
|
||||
if (sku === 'Pet-Gryphatrice-Jubilant') {
|
||||
description = 'Jubilant Gryphatrice';
|
||||
amount = 9.99;
|
||||
}
|
||||
} else {
|
||||
gemsBlock = getGemsBlock(gemsBlockKey);
|
||||
amount = gemsBlock.price / 100;
|
||||
}
|
||||
|
||||
if (!gift || gift.type === 'gems') {
|
||||
if (gemsBlock || (gift && gift.type === 'gems')) {
|
||||
const receiver = gift ? gift.member : user;
|
||||
const receiverCanGetGems = await receiver.canGetGems();
|
||||
if (!receiverCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', receiver.preferences.language));
|
||||
@@ -146,10 +153,10 @@ api.checkout = async function checkout (options = {}) {
|
||||
|
||||
api.checkoutSuccess = async function checkoutSuccess (options = {}) {
|
||||
const {
|
||||
user, gift, gemsBlock: gemsBlockKey, paymentId, customerId,
|
||||
user, gift, gemsBlock: gemsBlockKey, paymentId, customerId, sku,
|
||||
} = options;
|
||||
|
||||
let method = 'buyGems';
|
||||
let method = sku ? 'buySkuItem' : 'buyGems';
|
||||
const data = {
|
||||
user,
|
||||
customerId,
|
||||
@@ -164,6 +171,8 @@ api.checkoutSuccess = async function checkoutSuccess (options = {}) {
|
||||
|
||||
data.paymentMethod = 'PayPal (Gift)';
|
||||
data.gift = gift;
|
||||
} else if (sku) {
|
||||
data.sku = sku;
|
||||
} else {
|
||||
data.gemsBlock = getGemsBlock(gemsBlockKey);
|
||||
}
|
||||
|
||||
108
website/server/libs/payments/skuItem.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import moment from 'moment';
|
||||
import {
|
||||
BadRequest,
|
||||
} from '../errors';
|
||||
import shared from '../../../common';
|
||||
import { getAnalyticsServiceByEnvironment } from '../analyticsService';
|
||||
import { getGemsBlock, buyGems } from './gems'; // eslint-disable-line import/no-cycle
|
||||
|
||||
const analytics = getAnalyticsServiceByEnvironment();
|
||||
|
||||
const RESPONSE_INVALID_ITEM = 'INVALID_ITEM_PURCHASED';
|
||||
|
||||
const EVENTS = {
|
||||
birthday10: {
|
||||
start: '2023-01-23T08:00-05:00',
|
||||
end: '2023-02-01T23:59-05:00',
|
||||
},
|
||||
};
|
||||
|
||||
function canBuyGryphatrice (user) {
|
||||
if (!moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end)) return false;
|
||||
if (user.items.pets['Gryphatrice-Jubilant']) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function buyGryphatrice (data) {
|
||||
// Double check it's available
|
||||
if (!canBuyGryphatrice(data.user)) throw new BadRequest();
|
||||
const key = 'Gryphatrice-Jubilant';
|
||||
data.user.items.pets[key] = 5;
|
||||
data.user.purchased.txnCount += 1;
|
||||
|
||||
analytics.trackPurchase({
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Gryphatrice',
|
||||
sku: `${data.paymentMethod.toLowerCase()}-checkout`,
|
||||
purchaseType: 'checkout',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: Boolean(data.gift),
|
||||
purchaseValue: 10,
|
||||
headers: data.headers,
|
||||
firstPurchase: data.user.purchased.txnCount === 1,
|
||||
});
|
||||
if (data.user.markModified) data.user.markModified('items.pets');
|
||||
await data.user.save();
|
||||
}
|
||||
|
||||
export function canBuySkuItem (sku, user) {
|
||||
switch (sku) {
|
||||
case 'com.habitrpg.android.habitica.iap.pets.gryphatrice_jubilant':
|
||||
case 'com.habitrpg.ios.Habitica.pets.gryphatrice_jubilant':
|
||||
return canBuyGryphatrice(user);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export async function buySkuItem (data) {
|
||||
let gemsBlockKey;
|
||||
|
||||
switch (data.sku) { // eslint-disable-line default-case
|
||||
case 'com.habitrpg.android.habitica.iap.4gems':
|
||||
case 'com.habitrpg.ios.Habitica.4gems':
|
||||
gemsBlockKey = '4gems';
|
||||
break;
|
||||
case 'com.habitrpg.android.habitica.iap.20gems':
|
||||
case 'com.habitrpg.android.habitica.iap.21gems':
|
||||
case 'com.habitrpg.ios.Habitica.20gems':
|
||||
case 'com.habitrpg.ios.Habitica.21gems':
|
||||
gemsBlockKey = '21gems';
|
||||
break;
|
||||
case 'com.habitrpg.android.habitica.iap.42gems':
|
||||
case 'com.habitrpg.ios.Habitica.42gems':
|
||||
gemsBlockKey = '42gems';
|
||||
break;
|
||||
case 'com.habitrpg.android.habitica.iap.84gems':
|
||||
case 'com.habitrpg.ios.Habitica.84gems':
|
||||
gemsBlockKey = '84gems';
|
||||
break;
|
||||
case 'com.habitrpg.android.habitica.iap.pets.gryphatrice_jubilant':
|
||||
case 'com.habitrpg.ios.Habitica.pets.Gryphatrice_Jubilant':
|
||||
case 'Pet-Gryphatrice-Jubilant':
|
||||
case 'price_0MPZ6iZCD0RifGXlLah2furv':
|
||||
buyGryphatrice(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gemsBlockKey) {
|
||||
const gemsBlock = getGemsBlock(gemsBlockKey);
|
||||
|
||||
if (data.gift) {
|
||||
data.gift.type = 'gems';
|
||||
if (!data.gift.gems) data.gift.gems = {};
|
||||
data.gift.gems.amount = shared.content.gems[gemsBlock.key].gems;
|
||||
}
|
||||
|
||||
await buyGems({
|
||||
user: data.user,
|
||||
gift: data.gift,
|
||||
paymentMethod: data.paymentMethod,
|
||||
gemsBlock,
|
||||
headers: data.headers,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw new BadRequest(RESPONSE_INVALID_ITEM);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import shared from '../../../../common';
|
||||
import { getOneTimePaymentInfo } from './oneTimePayments'; // eslint-disable-line import/no-cycle
|
||||
import { checkSubData } from './subscriptions'; // eslint-disable-line import/no-cycle
|
||||
import { validateGiftMessage } from '../gems'; // eslint-disable-line import/no-cycle
|
||||
import { buySkuItem } from '../skuItem'; // eslint-disable-line import/no-cycle
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
@@ -24,6 +25,7 @@ export async function createCheckoutSession (options, stripeInc) {
|
||||
sub,
|
||||
groupId,
|
||||
coupon,
|
||||
sku,
|
||||
} = options;
|
||||
|
||||
// @TODO: We need to mock this, but curently we don't have correct
|
||||
@@ -37,6 +39,8 @@ export async function createCheckoutSession (options, stripeInc) {
|
||||
validateGiftMessage(gift, user);
|
||||
} else if (sub) {
|
||||
type = 'subscription';
|
||||
} else if (sku) {
|
||||
type = 'sku';
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
@@ -71,6 +75,12 @@ export async function createCheckoutSession (options, stripeInc) {
|
||||
price: sub.key,
|
||||
quantity,
|
||||
}];
|
||||
} else if (type === 'sku') {
|
||||
metadata.sku = sku;
|
||||
lineItems = [{
|
||||
price: sku,
|
||||
quantity: 1,
|
||||
}];
|
||||
} else {
|
||||
const {
|
||||
amount,
|
||||
|
||||
@@ -22,6 +22,20 @@ function getGiftAmount (gift) {
|
||||
return `${(gift.gems.amount / 4) * 100}`;
|
||||
}
|
||||
|
||||
export async function applySku (session) {
|
||||
const { metadata } = session;
|
||||
const { userId, sku } = metadata;
|
||||
const user = await User.findById(metadata.userId).exec();
|
||||
if (!user) throw new NotFound(shared.i18n.t('userWithIDNotFound', { userId }));
|
||||
if (sku === 'price_0MPZ6iZCD0RifGXlLah2furv') {
|
||||
await payments.buySkuItem({
|
||||
sku, user, paymentMethod: stripeConstants.PAYMENT_METHOD,
|
||||
});
|
||||
} else {
|
||||
throw new NotFound('SKU not found.');
|
||||
}
|
||||
}
|
||||
|
||||
export async function getOneTimePaymentInfo (gemsBlockKey, gift, user) {
|
||||
let receiver = user;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { // eslint-disable-line import/no-cycle
|
||||
basicFields as basicGroupFields,
|
||||
} from '../../../models/group';
|
||||
import shared from '../../../../common';
|
||||
import { applyGemPayment } from './oneTimePayments'; // eslint-disable-line import/no-cycle
|
||||
import { applyGemPayment, applySku } from './oneTimePayments'; // eslint-disable-line import/no-cycle
|
||||
import { applySubscription, handlePaymentMethodChange } from './subscriptions'; // eslint-disable-line import/no-cycle
|
||||
|
||||
const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET');
|
||||
@@ -69,10 +69,12 @@ export async function handleWebhooks (options, stripeInc) {
|
||||
|
||||
if (metadata.type === 'edit-card-group' || metadata.type === 'edit-card-user') {
|
||||
await handlePaymentMethodChange(session);
|
||||
} else if (metadata.type !== 'subscription') {
|
||||
await applyGemPayment(session);
|
||||
} else {
|
||||
} else if (metadata.type === 'subscription') {
|
||||
await applySubscription(session);
|
||||
} else if (metadata.type === 'sku') {
|
||||
await applySku(session);
|
||||
} else {
|
||||
await applyGemPayment(session);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -31,6 +31,7 @@ const NOTIFICATION_TYPES = [
|
||||
'SCORED_TASK',
|
||||
'UNALLOCATED_STATS_POINTS',
|
||||
'WON_CHALLENGE',
|
||||
'ITEM_RECEIVED', // notify user when they've got goodies via migration
|
||||
// achievement notifications
|
||||
'ACHIEVEMENT', // generic achievement notification, details inside `notification.data`
|
||||
'CHALLENGE_JOINED_ACHIEVEMENT',
|
||||
|
||||