mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 21:57:22 +01:00
444 lines
14 KiB
Vue
444 lines
14 KiB
Vue
<template>
|
|
<div class="standard-page pt-0 px-0">
|
|
<div class="banner-g1g1 mx-n3 d-flex justify-content-center">
|
|
<div
|
|
class="svg-icon svg-gifts my-auto mr-5 ml-auto"
|
|
v-html="icons.giftsVertical">
|
|
</div>
|
|
<div class="my-auto text-center">
|
|
<strong> {{ $t('g1g1Announcement') }} </strong>
|
|
<div class="mt-1"> {{ $t('g1g1Details') }} </div>
|
|
</div>
|
|
<div
|
|
class="svg-icon svg-gifts gifts-right my-auto ml-5 mr-auto"
|
|
v-html="icons.giftsVertical">
|
|
</div>
|
|
</div>
|
|
<h1>{{ $t('subscription') }}</h1>
|
|
<div class="row">
|
|
<div class="col-6">
|
|
<h2>{{ $t('benefits') }}</h2>
|
|
<ul>
|
|
<li>
|
|
<span
|
|
class="hint"
|
|
:popover="$t('buyGemsGoldText', {gemCostTranslation})"
|
|
popover-trigger="mouseenter"
|
|
popover-placement="right"
|
|
>{{ $t('buyGemsGold') }}</span>
|
|
<span
|
|
v-if="subscription.key !== 'basic_earned'"
|
|
class="badge badge-success"
|
|
>{{ $t('buyGemsGoldCap', buyGemsGoldCap) }}</span>
|
|
</li>
|
|
<li>
|
|
<span
|
|
class="hint"
|
|
:popover="$t('retainHistoryText')"
|
|
popover-trigger="mouseenter"
|
|
popover-placement="right"
|
|
>{{ $t('retainHistory') }}</span>
|
|
</li>
|
|
<li>
|
|
<span
|
|
class="hint"
|
|
:popover="$t('doubleDropsText')"
|
|
popover-trigger="mouseenter"
|
|
popover-placement="right"
|
|
>{{ $t('doubleDrops') }}</span>
|
|
</li>
|
|
<li>
|
|
<span
|
|
class="hint"
|
|
:popover="$t('mysteryItemText')"
|
|
popover-trigger="mouseenter"
|
|
popover-placement="right"
|
|
>{{ $t('mysteryItem') }}</span>
|
|
<div v-if="subscription.key !== 'basic_earned'">
|
|
<div class="badge badge-success">
|
|
{{ $t('mysticHourglass', mysticHourglass) }}
|
|
</div>
|
|
<div class="small muted">
|
|
{{ $t('mysticHourglassText') }}
|
|
</div>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<span
|
|
class="hint"
|
|
:popover="$t('exclusiveJackalopePetText')"
|
|
popover-trigger="mouseenter"
|
|
popover-placement="right"
|
|
>{{ $t('exclusiveJackalopePet') }}</span>
|
|
</li>
|
|
<li>
|
|
<span
|
|
class="hint"
|
|
:popover="$t('supportDevsText')"
|
|
popover-trigger="mouseenter"
|
|
popover-placement="right"
|
|
>{{ $t('supportDevs') }}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-6">
|
|
<h2>Plan</h2>
|
|
<table
|
|
v-if="hasSubscription"
|
|
class="table alert alert-info"
|
|
>
|
|
<tr v-if="hasCanceledSubscription">
|
|
<td class="alert alert-warning">
|
|
<span class="noninteractive-button btn-danger">{{ $t('canceledSubscription') }}</span>
|
|
<i class="glyphicon glyphicon-time"></i>
|
|
{{ $t('subCanceled') }}
|
|
<strong>{{ dateTerminated }}</strong>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="!hasCanceledSubscription">
|
|
<td>
|
|
<h4>{{ $t('subscribed') }}</h4>
|
|
<p v-if="hasPlan && !hasGroupPlan">
|
|
{{ $t('purchasedPlanId', purchasedPlanIdInfo) }}
|
|
</p>
|
|
<p v-if="hasGroupPlan">
|
|
{{ $t('youHaveGroupPlan') }}
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="user.purchased.plan.extraMonths">
|
|
<td>
|
|
<span class="glyphicon glyphicon-credit-card"></span>
|
|
{{ $t('purchasedPlanExtraMonths', purchasedPlanExtraMonthsDetails) }}
|
|
</td>
|
|
</tr>
|
|
<tr v-if="hasConsecutiveSubscription">
|
|
<td>
|
|
<span class="glyphicon glyphicon-forward"></span>
|
|
{{ $t('consecutiveSubscription') }}
|
|
<ul class="list-unstyled">
|
|
<li>{{ $t('consecutiveMonths') }} {{ user.purchased.plan.consecutive.count + user.purchased.plan.consecutive.offset }}</li> <!-- eslint-disable-line max-len -->
|
|
<li>{{ $t('gemCapExtra') }} {{ user.purchased.plan.consecutive.gemCapExtra }}</li>
|
|
<li>{{ $t('mysticHourglasses') }} {{ user.purchased.plan.consecutive.trinkets }}</li> <!-- eslint-disable-line max-len -->
|
|
</ul>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<div v-if="!hasSubscription || hasCanceledSubscription">
|
|
<h4 v-if="hasCanceledSubscription">
|
|
{{ $t("resubscribe") }}
|
|
</h4>
|
|
<div class="form-group reduce-top-margin">
|
|
<!-- eslint-disable vue/no-use-v-if-with-v-for -->
|
|
<div
|
|
v-for="block in subscriptionBlocksOrdered"
|
|
v-if="block.target !== 'group' && block.canSubscribe === true"
|
|
:key="block.key"
|
|
class="radio"
|
|
>
|
|
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
|
|
<label>
|
|
<input
|
|
v-model="subscription.key"
|
|
type="radio"
|
|
name="subRadio"
|
|
:value="block.key"
|
|
>
|
|
<span v-if="block.original">
|
|
<span class="label label-success line-through">${{ block.original }}</span>
|
|
{{ $t('subscriptionRateText', {price: block.price, months: block.months}) }}
|
|
</span>
|
|
<span
|
|
v-if="!block.original"
|
|
>{{ $t('subscriptionRateText', {price: block.price, months: block.months}) }}</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-inline">
|
|
<div class="form-group">
|
|
<input
|
|
v-model="subscription.coupon"
|
|
class="form-control"
|
|
type="text"
|
|
:placeholder="$t('couponPlaceholder')"
|
|
>
|
|
</div>
|
|
<div class="form-group">
|
|
<button
|
|
class="btn btn-primary"
|
|
type="button"
|
|
@click="applyCoupon(subscription.coupon)"
|
|
>
|
|
{{ $t("apply") }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-if="hasSubscription">
|
|
<div
|
|
v-if="canEditCardDetails"
|
|
class="btn btn-primary"
|
|
@click="showStripeEdit()"
|
|
>
|
|
{{ $t('subUpdateCard') }}
|
|
</div>
|
|
<div
|
|
v-if="canCancelSubscription && !loading"
|
|
class="btn btn-sm btn-danger"
|
|
@click="cancelSubscriptionConfirm()"
|
|
>
|
|
{{ $t('cancelSub') }}
|
|
</div>
|
|
<small
|
|
v-if="!canCancelSubscription && !hasCanceledSubscription"
|
|
v-html="getCancelSubInfo()"
|
|
></small>
|
|
</div>
|
|
<div
|
|
v-if="!hasSubscription || hasCanceledSubscription"
|
|
class="subscribe-pay"
|
|
>
|
|
<h3>{{ $t('subscribeUsing') }}</h3>
|
|
<div class="payments-column">
|
|
<button
|
|
class="purchase btn btn-primary payment-button payment-item"
|
|
:disabled="!subscription.key"
|
|
@click="showStripe({subscription:subscription.key, coupon:subscription.coupon})"
|
|
>
|
|
<div
|
|
class="svg-icon credit-card-icon"
|
|
v-html="icons.creditCardIcon"
|
|
></div>
|
|
{{ $t('card') }}
|
|
</button>
|
|
<button
|
|
class="btn payment-item paypal-checkout payment-button"
|
|
:disabled="!subscription.key"
|
|
@click="openPaypal(paypalPurchaseLink, 'subscription')"
|
|
>
|
|
|
|
<img
|
|
src="~@/assets/images/paypal-checkout.png"
|
|
:alt="$t('paypal')"
|
|
>
|
|
</button>
|
|
<amazon-button
|
|
class="payment-item"
|
|
:amazon-data="{
|
|
type: 'subscription', subscription: subscription.key, coupon: subscription.coupon}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-6">
|
|
<h2 v-once>
|
|
{{ $t('giftSubscription') }}
|
|
</h2>
|
|
<ol>
|
|
<li v-once>
|
|
{{ $t('giftSubscriptionText1') }}
|
|
</li>
|
|
<li v-once>
|
|
{{ $t('giftSubscriptionText2') }}
|
|
</li>
|
|
<li v-once>
|
|
{{ $t('giftSubscriptionText3') }}
|
|
</li>
|
|
</ol>
|
|
<h4 v-once>
|
|
{{ $t('giftSubscriptionText4') }}
|
|
</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
@import '~@/assets/scss/colors.scss';
|
|
|
|
.badge.badge-success {
|
|
color: $white;
|
|
}
|
|
|
|
.banner-g1g1 {
|
|
height: 5.75rem;
|
|
background-color: $teal-50;
|
|
color: $white;
|
|
}
|
|
|
|
.gifts-right {
|
|
filter: flipH;
|
|
transform: scaleX(-1);
|
|
}
|
|
|
|
.subscribe-pay {
|
|
margin-top: 1em;
|
|
}
|
|
|
|
.svg-gifts {
|
|
width: 55px;
|
|
height: 65px;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
import axios from 'axios';
|
|
import filter from 'lodash/filter';
|
|
import sortBy from 'lodash/sortBy';
|
|
import min from 'lodash/min';
|
|
import { mapState } from '@/libs/store';
|
|
|
|
import subscriptionBlocks from '@/../../common/script/content/subscriptionBlocks';
|
|
import planGemLimits from '@/../../common/script/libs/planGemLimits';
|
|
import paymentsMixin from '../../mixins/payments';
|
|
import notificationsMixin from '../../mixins/notifications';
|
|
|
|
import amazonButton from '@/components/payments/amazonButton';
|
|
import creditCardIcon from '@/assets/svg/credit-card-icon.svg';
|
|
import giftsVertical from '@/assets/svg/gifts-vertical.svg';
|
|
|
|
export default {
|
|
components: {
|
|
amazonButton,
|
|
},
|
|
mixins: [paymentsMixin, notificationsMixin],
|
|
data () {
|
|
return {
|
|
loading: false,
|
|
gemCostTranslation: {
|
|
gemCost: planGemLimits.convRate,
|
|
gemLimit: planGemLimits.convRate,
|
|
},
|
|
subscription: {
|
|
key: 'basic_earned',
|
|
},
|
|
// @TODO: Remove the need for this or move it to mixin
|
|
amazonPayments: {},
|
|
paymentMethods: {
|
|
AMAZON_PAYMENTS: 'Amazon Payments',
|
|
STRIPE: 'Stripe',
|
|
GOOGLE: 'Google',
|
|
APPLE: 'Apple',
|
|
PAYPAL: 'Paypal',
|
|
GIFT: 'Gift',
|
|
},
|
|
icons: Object.freeze({
|
|
creditCardIcon,
|
|
giftsVertical,
|
|
}),
|
|
};
|
|
},
|
|
computed: {
|
|
...mapState({ user: 'user.data', credentials: 'credentials' }),
|
|
subscriptionBlocksOrdered () {
|
|
const subscriptions = filter(subscriptionBlocks, o => o.discount !== true);
|
|
|
|
return sortBy(subscriptions, [o => o.months]);
|
|
},
|
|
purchasedPlanIdInfo () {
|
|
if (!this.subscriptionBlocks[this.user.purchased.plan.planId]) {
|
|
// @TODO: find which subs are in the common
|
|
// console.log(this.subscriptionBlocks
|
|
// [this.user.purchased.plan.planId]); // eslint-disable-line
|
|
return {
|
|
price: 0,
|
|
months: 0,
|
|
plan: '',
|
|
};
|
|
}
|
|
|
|
return {
|
|
price: this.subscriptionBlocks[this.user.purchased.plan.planId].price,
|
|
months: this.subscriptionBlocks[this.user.purchased.plan.planId].months,
|
|
plan: this.user.purchased.plan.paymentMethod,
|
|
};
|
|
},
|
|
subscriptionBlocks () {
|
|
return subscriptionBlocks;
|
|
},
|
|
canEditCardDetails () {
|
|
return Boolean(
|
|
!this.hasCanceledSubscription
|
|
&& this.user.purchased.plan.paymentMethod === this.paymentMethods.STRIPE,
|
|
);
|
|
},
|
|
hasSubscription () {
|
|
return Boolean(this.user.purchased.plan.customerId);
|
|
},
|
|
hasCanceledSubscription () {
|
|
return (
|
|
this.hasSubscription
|
|
&& Boolean(this.user.purchased.plan.dateTerminated)
|
|
);
|
|
},
|
|
hasPlan () {
|
|
return Boolean(this.user.purchased.plan.planId);
|
|
},
|
|
hasGroupPlan () {
|
|
return this.user.purchased.plan.customerId === 'group-plan';
|
|
},
|
|
hasConsecutiveSubscription () {
|
|
return Boolean(this.user.purchased.plan.consecutive.count)
|
|
|| Boolean(this.user.purchased.plan.consecutive.offset);
|
|
},
|
|
purchasedPlanExtraMonthsDetails () {
|
|
return {
|
|
months: parseFloat(this.user.purchased.plan.extraMonths).toFixed(2),
|
|
};
|
|
},
|
|
buyGemsGoldCap () {
|
|
return {
|
|
amount: min(this.gemGoldCap),
|
|
};
|
|
},
|
|
gemGoldCap () {
|
|
const baseCap = 25;
|
|
const gemCapIncrement = 5;
|
|
const capIncrementThreshold = 3;
|
|
const { gemCapExtra } = this.user.purchased.plan.consecutive;
|
|
const blocks = subscriptionBlocks[this.subscription.key].months / capIncrementThreshold;
|
|
const flooredBlocks = Math.floor(blocks);
|
|
|
|
const userTotalDropCap = baseCap + gemCapExtra + flooredBlocks * gemCapIncrement;
|
|
const maxDropCap = 50;
|
|
|
|
return [userTotalDropCap, maxDropCap];
|
|
},
|
|
numberOfMysticHourglasses () {
|
|
const numberOfHourglasses = subscriptionBlocks[this.subscription.key].months / 3;
|
|
return Math.floor(numberOfHourglasses);
|
|
},
|
|
mysticHourglass () {
|
|
return {
|
|
amount: this.numberOfMysticHourglasses,
|
|
};
|
|
},
|
|
canCancelSubscription () {
|
|
return (
|
|
this.user.purchased.plan.paymentMethod !== this.paymentMethods.GOOGLE
|
|
&& this.user.purchased.plan.paymentMethod !== this.paymentMethods.APPLE
|
|
&& !this.hasCanceledSubscription
|
|
&& !this.hasGroupPlan
|
|
);
|
|
},
|
|
},
|
|
methods: {
|
|
async applyCoupon (coupon) {
|
|
const response = await axios.post(`/api/v4/coupons/validate/${coupon}`);
|
|
|
|
if (!response.data.data.valid) return;
|
|
|
|
this.text('Coupon applied!');
|
|
this.subscription.key = 'google_6mo';
|
|
},
|
|
getCancelSubInfo () {
|
|
let payMethod = this.user.purchased.plan.paymentMethod || '';
|
|
if (payMethod === 'Group Plan') payMethod = 'GroupPlan';
|
|
return this.$t(`cancelSubInfo${payMethod}`);
|
|
},
|
|
},
|
|
};
|
|
</script>
|