You've already forked AstralRinth
forked from didirus/AstralRinth
Initial servers upgrades frontend (#3219)
* Initial servers upgrades frontend * Fix error when purchasing non-custom servers * fix backend * Fix comment --------- Signed-off-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
@@ -29,6 +29,7 @@ async function copyText() {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.code {
|
||||
color: var(--color-text);
|
||||
display: inline-flex;
|
||||
grid-gap: 0.5rem;
|
||||
font-family: var(--mono-font);
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
<NewModal ref="purchaseModal">
|
||||
<template #title>
|
||||
<span class="text-contrast text-xl font-extrabold">
|
||||
<template v-if="product.metadata.type === 'midas'">Subscribe to Modrinth Plus!</template>
|
||||
<template v-else-if="product.metadata.type === 'pyro'"
|
||||
>Subscribe to Modrinth Servers!</template
|
||||
>
|
||||
<template v-if="productType === 'midas'">Subscribe to Modrinth+!</template>
|
||||
<template v-else-if="productType === 'pyro'">
|
||||
<template v-if="existingSubscription"> Upgrade server plan </template>
|
||||
<template v-else> Subscribe to Modrinth Servers! </template>
|
||||
</template>
|
||||
<template v-else>Purchase product</template>
|
||||
</span>
|
||||
</template>
|
||||
<div class="flex items-center gap-1 pb-4">
|
||||
<template v-if="product.metadata.type === 'pyro' && !projectId">
|
||||
<template v-if="productType === 'pyro' && !projectId">
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary': purchaseModalStep !== 0,
|
||||
@@ -24,24 +25,20 @@
|
||||
</template>
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 1 : 0),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 1 : 0),
|
||||
'text-secondary': purchaseModalStep !== (productType === 'pyro' && !projectId ? 1 : 0),
|
||||
'font-bold': purchaseModalStep === (productType === 'pyro' && !projectId ? 1 : 0),
|
||||
}"
|
||||
>
|
||||
{{ product.metadata.type === 'pyro' ? 'Billing' : 'Plan' }}
|
||||
{{ productType === 'pyro' ? 'Billing' : 'Plan' }}
|
||||
<span class="hidden sm:inline">{{
|
||||
product.metadata.type === 'pyro' ? 'interval' : 'selection'
|
||||
productType === 'pyro' ? 'interval' : 'selection'
|
||||
}}</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="h-5 w-5 text-secondary" />
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 2 : 1),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 2 : 1),
|
||||
'text-secondary': purchaseModalStep !== (productType === 'pyro' && !projectId ? 2 : 1),
|
||||
'font-bold': purchaseModalStep === (productType === 'pyro' && !projectId ? 2 : 1),
|
||||
}"
|
||||
>
|
||||
Payment
|
||||
@@ -49,20 +46,18 @@
|
||||
<ChevronRightIcon class="h-5 w-5 text-secondary" />
|
||||
<span
|
||||
:class="{
|
||||
'text-secondary':
|
||||
purchaseModalStep !== (product.metadata.type === 'pyro' && !projectId ? 3 : 2),
|
||||
'font-bold':
|
||||
purchaseModalStep === (product.metadata.type === 'pyro' && !projectId ? 3 : 2),
|
||||
'text-secondary': purchaseModalStep !== (productType === 'pyro' && !projectId ? 3 : 2),
|
||||
'font-bold': purchaseModalStep === (productType === 'pyro' && !projectId ? 3 : 2),
|
||||
}"
|
||||
>
|
||||
Review
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="product.metadata.type === 'pyro' && !projectId && purchaseModalStep === 0"
|
||||
v-if="productType === 'pyro' && !projectId && purchaseModalStep === 0"
|
||||
class="md:w-[600px] flex flex-col gap-4"
|
||||
>
|
||||
<div>
|
||||
<div v-if="!existingSubscription">
|
||||
<p class="my-2 text-lg font-bold">Configure your server</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
<input v-model="serverName" placeholder="Server name" class="input" maxlength="48" />
|
||||
@@ -105,13 +100,20 @@
|
||||
</div>
|
||||
<div v-if="customServer">
|
||||
<div class="flex gap-2 items-center">
|
||||
<p class="my-2 text-lg font-bold">Configure your RAM</p>
|
||||
<p class="my-2 text-lg font-bold">
|
||||
<template v-if="existingSubscription">Upgrade your RAM</template>
|
||||
<template v-else>Configure your RAM</template>
|
||||
</p>
|
||||
<IssuesIcon
|
||||
v-if="customServerConfig.ramInGb < 4"
|
||||
v-tooltip="'This might not be enough resources for your Minecraft server.'"
|
||||
class="h-6 w-6 text-orange"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="existingPlan" class="mt-1 mb-2 text-secondary">
|
||||
Your current plan has <strong>{{ existingPlan.metadata.ram / 1024 }} GB RAM</strong> and
|
||||
<strong>{{ existingPlan.metadata.cpu }} vCPUs</strong>.
|
||||
</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex w-full gap-2 items-center">
|
||||
<Slider
|
||||
@@ -125,7 +127,7 @@
|
||||
<div class="font-semibold text-nowrap"></div>
|
||||
</div>
|
||||
<div
|
||||
v-if="customMatchingProduct && !customOutOfStock"
|
||||
v-if="customMatchingProduct && (existingPlan || !customOutOfStock)"
|
||||
class="flex sm:flex-row flex-col gap-4 w-full"
|
||||
>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
@@ -166,6 +168,16 @@
|
||||
>
|
||||
<div>
|
||||
<p class="my-2 text-lg font-bold">Choose billing interval</p>
|
||||
<div v-if="existingPlan" class="flex flex-col gap-3 mb-4 text-secondary">
|
||||
<p class="m-0">
|
||||
The prices below reflect the new <strong>renewal cost</strong> of your upgraded
|
||||
subscription.
|
||||
</p>
|
||||
<p class="m-0">
|
||||
Today, you will be charged a prorated amount for the remainder of your current billing
|
||||
cycle.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
v-for="([interval, rawPrice], index) in Object.entries(price.prices.intervals)"
|
||||
@@ -228,7 +240,10 @@
|
||||
v-if="purchaseModalStep === (mutatedProduct.metadata.type === 'pyro' && !projectId ? 3 : 2)"
|
||||
class="md:w-[650px]"
|
||||
>
|
||||
<div v-if="mutatedProduct.metadata.type === 'pyro'" class="r-4 rounded-xl bg-bg p-4 mb-4">
|
||||
<div
|
||||
v-if="mutatedProduct.metadata.type === 'pyro' && !existingSubscription"
|
||||
class="r-4 rounded-xl bg-bg p-4 mb-4"
|
||||
>
|
||||
<p class="my-2 text-lg font-bold text-primary">Server details</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<img
|
||||
@@ -248,12 +263,19 @@
|
||||
<div class="r-4 rounded-xl bg-bg p-4">
|
||||
<p class="my-2 text-lg font-bold text-primary">Purchase details</p>
|
||||
<div class="mb-2 flex justify-between">
|
||||
<span class="text-secondary"
|
||||
>{{ mutatedProduct.metadata.type === 'midas' ? 'Modrinth+' : 'Modrinth Servers' }}
|
||||
{{ selectedPlan }}</span
|
||||
>
|
||||
<span class="text-secondary text-end">
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }} /
|
||||
<span class="text-secondary">
|
||||
{{ mutatedProduct.metadata.type === 'midas' ? 'Modrinth+' : 'Modrinth Servers' }}
|
||||
{{
|
||||
existingPlan
|
||||
? `(${dayjs(renewalDate).diff(dayjs(), 'days')} days prorated)`
|
||||
: selectedPlan
|
||||
}}
|
||||
</span>
|
||||
<span v-if="existingPlan" class="text-secondary text-end">
|
||||
{{ formatPrice(locale, total - tax, price.currency_code) }}
|
||||
</span>
|
||||
<span v-else class="text-secondary text-end">
|
||||
{{ formatPrice(locale, total - tax, price.currency_code) }} /
|
||||
{{ selectedPlan }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -266,7 +288,7 @@
|
||||
<div class="mt-4 flex justify-between border-0 border-t border-solid border-code-bg pt-4">
|
||||
<span class="text-lg font-bold">Today's total</span>
|
||||
<span class="text-lg font-extrabold text-primary text-end">
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||
{{ formatPrice(locale, total, price.currency_code) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,7 +385,8 @@
|
||||
<br />
|
||||
You'll be charged
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }} /
|
||||
{{ selectedPlan }} plus applicable taxes starting today, until you cancel.
|
||||
{{ selectedPlan }} plus applicable taxes starting
|
||||
{{ existingPlan ? dayjs(renewalDate).format('MMMM D, YYYY') : 'today' }}, until you cancel.
|
||||
<br />
|
||||
You can cancel anytime from your settings page.
|
||||
</p>
|
||||
@@ -389,12 +412,19 @@
|
||||
:disabled="
|
||||
paymentLoading ||
|
||||
(mutatedProduct.metadata.type === 'pyro' && !projectId && !serverName) ||
|
||||
customAllowedToContinue
|
||||
customNotAllowedToContinue ||
|
||||
upgradeNotAllowedToContinue
|
||||
"
|
||||
@click="nextStep"
|
||||
>
|
||||
<RightArrowIcon />
|
||||
{{ mutatedProduct.metadata.type === 'pyro' && !projectId ? 'Next' : 'Select' }}
|
||||
<template v-if="customServer && customLoading">
|
||||
<SpinnerIcon class="animate-spin" />
|
||||
Checking availability...
|
||||
</template>
|
||||
<template v-else>
|
||||
<RightArrowIcon />
|
||||
{{ mutatedProduct.metadata.type === 'pyro' && !projectId ? 'Next' : 'Select' }}
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
<template
|
||||
@@ -468,6 +498,7 @@
|
||||
import { ref, computed, nextTick, reactive, watch } from 'vue'
|
||||
import NewModal from '../modal/NewModal.vue'
|
||||
import {
|
||||
SpinnerIcon,
|
||||
CardIcon,
|
||||
CheckCircleIcon,
|
||||
ChevronRightIcon,
|
||||
@@ -487,6 +518,7 @@ import { useVIntl, defineMessages } from '@vintl/vintl'
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
import Checkbox from '../base/Checkbox.vue'
|
||||
import Slider from '../base/Slider.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import Admonition from '../base/Admonition.vue'
|
||||
|
||||
const { locale, formatMessage } = useVIntl()
|
||||
@@ -562,8 +594,25 @@ const props = defineProps({
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
existingSubscription: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
existingPlan: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
renewalDate: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const productType = computed(() => (props.customServer ? 'pyro' : props.product.metadata.type))
|
||||
|
||||
const messages = defineMessages({
|
||||
paymentMethodCardDisplay: {
|
||||
id: 'omorphia.component.purchase_modal.payment_method_card_display',
|
||||
@@ -645,7 +694,7 @@ const total = ref()
|
||||
|
||||
const serverName = ref(props.serverName || '')
|
||||
const serverLoader = ref('Vanilla')
|
||||
const eulaAccepted = ref(false)
|
||||
const eulaAccepted = ref(!!props.existingSubscription)
|
||||
|
||||
const mutatedProduct = ref({ ...props.product })
|
||||
const customMinRam = ref(0)
|
||||
@@ -653,11 +702,15 @@ const customMaxRam = ref(0)
|
||||
const customMatchingProduct = ref()
|
||||
const customOutOfStock = ref(false)
|
||||
const customLoading = ref(true)
|
||||
const customAllowedToContinue = computed(
|
||||
const customNotAllowedToContinue = computed(
|
||||
() =>
|
||||
props.customServer &&
|
||||
!props.existingSubscription &&
|
||||
(!customMatchingProduct.value || customLoading.value || customOutOfStock.value),
|
||||
)
|
||||
const upgradeNotAllowedToContinue = computed(
|
||||
() => props.existingSubscription && (!hasUpgradeCapacityForConfig.value || customLoading.value),
|
||||
)
|
||||
|
||||
const customServerConfig = reactive({
|
||||
ramInGb: 4,
|
||||
@@ -670,7 +723,9 @@ const updateCustomServerProduct = () => {
|
||||
(product) => product.metadata.ram === customServerConfig.ram,
|
||||
)
|
||||
|
||||
if (customMatchingProduct.value) mutatedProduct.value = { ...customMatchingProduct.value }
|
||||
if (customMatchingProduct.value) {
|
||||
mutatedProduct.value = { ...customMatchingProduct.value }
|
||||
}
|
||||
}
|
||||
|
||||
let updateCustomServerStockTimeout = null
|
||||
@@ -688,19 +743,25 @@ const updateCustomServerStock = async () => {
|
||||
} else {
|
||||
customOutOfStock.value = false
|
||||
}
|
||||
customLoading.value = false
|
||||
} else {
|
||||
} else if (!props.existingServer) {
|
||||
console.error('No fetchCapacityStatuses function provided.')
|
||||
customOutOfStock.value = true
|
||||
}
|
||||
customLoading.value = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
if (props.customServer) {
|
||||
function updateRamValues() {
|
||||
const ramValues = props.product.map((product) => product.metadata.ram / 1024)
|
||||
customMinRam.value = Math.min(...ramValues)
|
||||
customMaxRam.value = Math.max(...ramValues)
|
||||
|
||||
customServerConfig.ramInGb = customMinRam.value
|
||||
}
|
||||
|
||||
if (props.customServer) {
|
||||
updateRamValues()
|
||||
|
||||
const updateProductAndStock = () => {
|
||||
updateCustomServerProduct()
|
||||
updateCustomServerStock()
|
||||
@@ -880,16 +941,25 @@ async function refreshPayment(confirmationId, paymentMethodId) {
|
||||
id: paymentMethodId,
|
||||
}
|
||||
|
||||
const result = await props.sendBillingRequest({
|
||||
charge: {
|
||||
type: 'new',
|
||||
product_id: mutatedProduct.value.id,
|
||||
interval: selectedPlan.value,
|
||||
},
|
||||
existing_payment_intent: paymentIntentId.value,
|
||||
metadata: metadata.value,
|
||||
...base,
|
||||
})
|
||||
const result = await props.sendBillingRequest(
|
||||
props.existingSubscription
|
||||
? {
|
||||
interval: selectedPlan.value,
|
||||
cancelled: false,
|
||||
product: mutatedProduct.value.id,
|
||||
payment_method: paymentMethodId,
|
||||
}
|
||||
: {
|
||||
charge: {
|
||||
type: 'new',
|
||||
product_id: mutatedProduct.value.id,
|
||||
interval: selectedPlan.value,
|
||||
},
|
||||
existing_payment_intent: paymentIntentId.value,
|
||||
metadata: metadata.value,
|
||||
...base,
|
||||
},
|
||||
)
|
||||
|
||||
if (!paymentIntentId.value) {
|
||||
paymentIntentId.value = result.payment_intent_id
|
||||
@@ -903,10 +973,14 @@ async function refreshPayment(confirmationId, paymentMethodId) {
|
||||
|
||||
if (confirmationId) {
|
||||
confirmationToken.value = confirmationId
|
||||
inputtedPaymentMethod.value = result.payment_method
|
||||
if (result.payment_method) {
|
||||
inputtedPaymentMethod.value = result.payment_method
|
||||
}
|
||||
}
|
||||
|
||||
selectedPaymentMethod.value = result.payment_method
|
||||
if (result.payment_method) {
|
||||
selectedPaymentMethod.value = result.payment_method
|
||||
}
|
||||
} catch (err) {
|
||||
props.onError(err)
|
||||
}
|
||||
@@ -928,11 +1002,20 @@ async function submitPayment() {
|
||||
paymentLoading.value = false
|
||||
}
|
||||
|
||||
const hasUpgradeCapacityForConfig = computed(() => {
|
||||
// TODO: Check for upgrade capacity here when Pyro provides route
|
||||
return props.existingPlan
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
if (props.customServer) {
|
||||
updateRamValues()
|
||||
}
|
||||
|
||||
stripe = Stripe(props.publishableKey)
|
||||
|
||||
selectedPlan.value = 'yearly'
|
||||
selectedPlan.value = props.existingSubscription ? props.existingSubscription.interval : 'yearly'
|
||||
serverName.value = props.serverName || ''
|
||||
serverLoader.value = 'Vanilla'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user