Skip to content

Commit

Permalink
Merge pull request #437 from gobitfly/BIDS-3120/ImprovePricingLogic
Browse files Browse the repository at this point in the history
Bids 3120/improve pricing logic
  • Loading branch information
D13ce authored Jun 11, 2024
2 parents 7996f9f + 92d6fe4 commit 1751b21
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 27 deletions.
196 changes: 173 additions & 23 deletions frontend/components/pricing/PremiumAddonBox.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
<script lang="ts" setup>
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faInfoCircle } from '@fortawesome/pro-regular-svg-icons'
import { faInfoCircle, faMinus, faPlus } from '@fortawesome/pro-regular-svg-icons'
import { type ExtraDashboardValidatorsPremiumAddon, ProductCategoryPremiumAddon } from '~/types/api/user'
import { formatPremiumProductPrice } from '~/utils/format'
import { Target } from '~/types/links'
const { t: $t } = useI18n()
const { user, isLoggedIn } = useUserStore()
const { stripeCustomerPortal, stripePurchase, isStripeDisabled } = useStripe()
interface Props {
addon: ExtraDashboardValidatorsPremiumAddon,
isYearly: boolean
isYearly: boolean,
maximumValidatorLimit?: number
}
const props = defineProps<Props>()
const quantityForPurchase = ref(1)
const prices = computed(() => {
const mainPrice = props.isYearly ? props.addon.price_per_year_eur / 12 : props.addon.price_per_month_eur
Expand All @@ -31,37 +35,72 @@ const prices = computed(() => {
}
})
const text = computed(() => {
const boxText = computed(() => {
return {
validatorCount: $t('pricing.addons.validator_amount', { amount: formatNumber(props.addon.extra_dashboard_validators) }),
perValidator: $t('pricing.per_validator', { amount: prices.value.perValidator })
}
})
const addonSubscription = computed(() => {
return user.value?.subscriptions?.find(sub => sub.product_category === ProductCategoryPremiumAddon)
const addonSubscriptionCount = computed(() => {
return user.value?.subscriptions?.filter(sub => sub.product_category === ProductCategoryPremiumAddon && (sub.product_id === props.addon.product_id_monthly || sub.product_id === props.addon.product_id_yearly)).length || 0
})
async function buttonCallback () {
if (isStripeDisabled.value) {
return
const addonButton = computed(() => {
let text = $t('pricing.get_started')
if (isLoggedIn.value) {
text = addonSubscriptionCount.value > 0 ? $t('pricing.addons.button.manage_addon') : $t('pricing.addons.button.select_addon')
}
if (isLoggedIn.value) {
if (addonSubscription.value) {
await stripeCustomerPortal()
async function callback () {
if (isStripeDisabled.value) {
return
}
if (isLoggedIn.value) {
if (addonSubscriptionCount.value > 0) {
await stripeCustomerPortal()
} else {
await stripePurchase(props.isYearly ? props.addon.stripe_price_id_yearly : props.addon.stripe_price_id_monthly, quantityForPurchase.value)
}
} else {
await stripePurchase(props.isYearly ? props.addon.stripe_price_id_yearly : props.addon.stripe_price_id_monthly, 1)
await navigateTo('/login')
}
} else {
await navigateTo('/register')
}
}
const addonButton = computed(() => {
return {
text: addonSubscription.value ? $t('pricing.addons.button.manage_addon') : $t('pricing.addons.button.select_addon'),
disabled: isStripeDisabled.value
text,
disabled: isStripeDisabled.value,
callback
}
})
const maximumQuantity = computed(() => {
return Math.ceil(((props.maximumValidatorLimit || 10000) - (user.value?.premium_perks.validators_per_dashboard || 0)) / props.addon.extra_dashboard_validators)
})
const limitReached = computed(() => {
return quantityForPurchase.value >= maximumQuantity.value
})
const purchaseQuantityButtons = computed(() => {
return {
minus: {
disabled: quantityForPurchase.value <= 1,
callback: () => {
if (quantityForPurchase.value > 1) {
quantityForPurchase.value--
}
}
},
plus: {
disabled: limitReached.value,
callback: () => {
if (quantityForPurchase.value < maximumQuantity.value) {
quantityForPurchase.value++
}
}
}
}
})
Expand All @@ -71,7 +110,7 @@ const addonButton = computed(() => {
<div class="box-container">
<div class="summary-container">
<div class="validator-count">
{{ text.validatorCount }}
{{ boxText.validatorCount }}
<div class="subtext">
{{ $t('pricing.addons.per_dashboard') }}
<BcTooltip position="top" :fit-content="true">
Expand All @@ -84,7 +123,7 @@ const addonButton = computed(() => {
</BcTooltip>
</div>
<div class="per-validator">
{{ text.perValidator }}
{{ boxText.perValidator }}
</div>
</div>
</div>
Expand Down Expand Up @@ -123,7 +162,44 @@ const addonButton = computed(() => {
</template>
</BcTooltip>
</div>
<Button :label="addonButton.text" :disabled="addonButton.disabled" class="select-button" @click="buttonCallback" />
<div class="quantity-row">
<div v-if="addonSubscriptionCount" class="quantity-label">
{{ $t('pricing.addons.currently_active', { amount: addonSubscriptionCount }) }}
</div>
<div v-else class="quantity-setter">
<Button
class="p-button-icon-only"
:disabled="purchaseQuantityButtons.minus.disabled"
@click="purchaseQuantityButtons.minus.callback"
>
<FontAwesomeIcon :icon="faMinus" />
</Button>
<InputNumber
v-model="quantityForPurchase"
class="quantity-input"
input-id="integeronly"
:min="1"
:max="maximumQuantity"
/>
<Button
class="p-button-icon-only"
:disabled="purchaseQuantityButtons.plus.disabled"
@click="purchaseQuantityButtons.plus.callback"
>
<FontAwesomeIcon :icon="faPlus" />
</Button>
</div>
</div>
<div class="limit-reached-row">
<div v-if="limitReached">
{{ tOf($t, 'pricing.addons.contact_support', 0) }}
<BcLink to="https://dsc.gg/beaconchain " :target="Target.External" class="link">
{{ tOf($t, 'pricing.addons.contact_support', 1) }}
</BcLink>
{{ tOf($t, 'pricing.addons.contact_support', 2) }}
</div>
</div>
<Button :label="addonButton.text" :disabled="addonButton.disabled" class="select-button" @click="addonButton.callback" />
</div>
</div>
</template>
Expand Down Expand Up @@ -208,7 +284,48 @@ const addonButton = computed(() => {
border-radius: 15px;
background: var(--subcontainer-background);
font-size: 15px;
margin-bottom: 56px;
margin-bottom: 24px;
}
.quantity-row {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 30px;
.quantity-label {
font-size: 17px;
}
.quantity-setter {
height: 100%;
display: flex;
justify-content: center;
gap: 15px;
.quantity-input {
width: 45px;
> :first-child {
width: 100%;
text-align: center;
}
}
> * {
height: 100%;
}
}
margin-bottom: 20px;
}
.limit-reached-row {
height: 16px;
font-size: 13px;
margin-bottom: 20px;
}
.select-button {
Expand Down Expand Up @@ -264,7 +381,40 @@ const addonButton = computed(() => {
height: 21px;
gap: 4px;
font-size: 10px;
margin-bottom: 39px;
margin-bottom: 17px;
}
.quantity-row {
height: 20px;
.quantity-label {
font-size: 12px;
}
.quantity-setter {
gap: 8px;
.quantity-input {
width: 35px;
> :first-child {
font-size: 12px;
}
}
> .p-button {
width: 20px;
}
}
margin-bottom: 10px;
}
.limit-reached-row {
height: 10px;
font-size: 8px;
margin-bottom: 20px;
}
.select-button {
Expand Down
1 change: 1 addition & 0 deletions frontend/components/pricing/PremiumAddons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defineProps<Props>()
:key="addon.product_id_yearly"
:addon="addon"
:is-yearly="isYearly"
:maximum-validator-limit="products?.validators_per_dashboard_limit"
/>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/pricing/PremiumProductBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async function buttonCallback () {
await stripePurchase(props.isYearly ? props.product.stripe_price_id_yearly : props.product.stripe_price_id_monthly, 1)
}
} else {
await navigateTo('/register')
await navigateTo('/login')
}
}
Expand All @@ -96,7 +96,7 @@ const planButton = computed(() => {
}
}
} else {
text = $t('pricing.sign_up')
text = $t('pricing.get_started')
}
return { text, isDowngrade, disabled: isStripeDisabled.value || undefined }
Expand Down
6 changes: 4 additions & 2 deletions frontend/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -610,17 +610,17 @@
"premium": "beaconcha.in Premium",
"API_keys": "API Keys",
"subtitle": "Monitoring without limits on web and mobile.",
"per_month": "per month",
"monthly": "Monthly",
"yearly": "Yearly",
"save_up_to": "SAVE UP TO {percentage}%",
"excluding_vat": "* All prices are excluding VAT",
"per_month": "per month",
"amount_per_year": "{amount} yearly",
"savings": "You save {amount} a year",
"savings_tooltip": "Compared to paying monthly. The full monthly price is {monthly}. The monthly price within the yearly subscription is {monthly_yearly}.",
"per_validator": "{amount} per validator",
"pectra_tooltip": "After Pectra hardfork: {effectiveBalance} ETH maximum effective balance",
"sign_up": "Sign Up",
"get_started": "Get started now",
"premium_product": {
"popular": "Popular!",
"validator_dashboards": "{amount} Validator Dashboard | {amount} Validator Dashboards",
Expand Down Expand Up @@ -674,6 +674,8 @@
"subtitle": "Go even further with beaconcha.in Premium custom Add-Ons.",
"validator_amount": "+{amount} Validators",
"per_dashboard": "per Dashboard",
"currently_active": "Currently active: {amount}",
"contact_support": ["Contact", "support", "for a higher limit"],
"button": {
"select_addon": "Select Add-On",
"manage_addon": "Manage Add-On"
Expand Down

0 comments on commit 1751b21

Please sign in to comment.