You've already forked AstralRinth
forked from didirus/AstralRinth
feat(servers): full custom plan capacity checks & more (#2911)
* feat(servers): improve plan button logic * feat(servers): custom plan capacity checks * feat(servers): custom plan dynamic ram values * feat(servers): add custom plan selector back * fix(servers): final fixes
This commit is contained in:
@@ -22,6 +22,8 @@
|
|||||||
:payment-methods="paymentMethods"
|
:payment-methods="paymentMethods"
|
||||||
:return-url="`${config.public.siteUrl}/servers/manage`"
|
:return-url="`${config.public.siteUrl}/servers/manage`"
|
||||||
:server-name="`${auth?.user?.username}'s server`"
|
:server-name="`${auth?.user?.username}'s server`"
|
||||||
|
:fetch-capacity-statuses="fetchCapacityStatuses"
|
||||||
|
:out-of-stock-url="outOfStockUrl"
|
||||||
@hidden="handleModalHidden"
|
@hidden="handleModalHidden"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -523,29 +525,34 @@
|
|||||||
$12<span class="text-sm font-normal text-secondary">/month</span>
|
$12<span class="text-sm font-normal text-secondary">/month</span>
|
||||||
</h2>
|
</h2>
|
||||||
<ButtonStyled color="blue" size="large">
|
<ButtonStyled color="blue" size="large">
|
||||||
<button
|
|
||||||
v-if="!isSmallAtCapacity"
|
|
||||||
class="!bg-highlight-blue !font-medium !text-blue"
|
|
||||||
@click="selectProduct(pyroPlanProducts[0])"
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
<RightArrowIcon class="!min-h-4 !min-w-4 !text-blue" />
|
|
||||||
</button>
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-else
|
v-if="loggedOut"
|
||||||
:to="loggedOut ? redirectUrl : 'https://support.modrinth.com'"
|
:to="loginUrl"
|
||||||
:target="loggedOut ? '_self' : '_blank'"
|
target="_self"
|
||||||
class="!bg-highlight-blue !font-medium !text-blue"
|
class="!bg-highlight-blue !font-medium !text-blue"
|
||||||
>
|
>
|
||||||
<template v-if="loggedOut">
|
Login
|
||||||
Login
|
<UserIcon class="!min-h-4 !min-w-4 !text-blue" />
|
||||||
<UserIcon class="!min-h-4 !min-w-4 !text-blue" />
|
</NuxtLink>
|
||||||
</template>
|
<template v-else>
|
||||||
<template v-else>
|
<NuxtLink
|
||||||
|
v-if="isSmallAtCapacity"
|
||||||
|
:to="outOfStockUrl"
|
||||||
|
target="_blank"
|
||||||
|
class="!bg-highlight-blue !font-medium !text-blue"
|
||||||
|
>
|
||||||
Out of Stock
|
Out of Stock
|
||||||
<ExternalIcon class="!min-h-4 !min-w-4 !text-blue" />
|
<ExternalIcon class="!min-h-4 !min-w-4 !text-blue" />
|
||||||
</template>
|
</NuxtLink>
|
||||||
</NuxtLink>
|
<button
|
||||||
|
v-else
|
||||||
|
class="!bg-highlight-blue !font-medium !text-blue"
|
||||||
|
@click="selectProduct(pyroPlanProducts[0])"
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
<RightArrowIcon class="!min-h-4 !min-w-4 !text-blue" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@@ -581,29 +588,34 @@
|
|||||||
$18<span class="text-sm font-normal text-secondary">/month</span>
|
$18<span class="text-sm font-normal text-secondary">/month</span>
|
||||||
</h2>
|
</h2>
|
||||||
<ButtonStyled color="brand" size="large">
|
<ButtonStyled color="brand" size="large">
|
||||||
<button
|
|
||||||
v-if="!isMediumAtCapacity"
|
|
||||||
class="shadow-xl"
|
|
||||||
@click="selectProduct(pyroPlanProducts[1])"
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
<RightArrowIcon class="!min-h-4 !min-w-4" />
|
|
||||||
</button>
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-else
|
v-if="loggedOut"
|
||||||
:to="loggedOut ? redirectUrl : 'https://support.modrinth.com'"
|
:to="loginUrl"
|
||||||
:target="loggedOut ? '_self' : '_blank'"
|
target="_self"
|
||||||
class="!bg-highlight-green !font-medium !text-green"
|
class="!bg-highlight-green !font-medium !text-green"
|
||||||
>
|
>
|
||||||
<template v-if="loggedOut">
|
Login
|
||||||
Login
|
<UserIcon class="!min-h-4 !min-w-4 !text-green" />
|
||||||
<UserIcon class="!min-h-4 !min-w-4 !text-green" />
|
</NuxtLink>
|
||||||
</template>
|
<template v-else>
|
||||||
<template v-else>
|
<NuxtLink
|
||||||
|
v-if="isMediumAtCapacity"
|
||||||
|
:to="outOfStockUrl"
|
||||||
|
target="_blank"
|
||||||
|
class="!bg-highlight-green !font-medium !text-green"
|
||||||
|
>
|
||||||
Out of Stock
|
Out of Stock
|
||||||
<ExternalIcon class="!min-h-4 !min-w-4 !text-green" />
|
<ExternalIcon class="!min-h-4 !min-w-4 !text-green" />
|
||||||
</template>
|
</NuxtLink>
|
||||||
</NuxtLink>
|
<button
|
||||||
|
v-else
|
||||||
|
class="!bg-highlight-green !font-medium !text-green"
|
||||||
|
@click="selectProduct(pyroPlanProducts[1])"
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
<RightArrowIcon class="!min-h-4 !min-w-4 !text-green" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@@ -628,34 +640,39 @@
|
|||||||
$24<span class="text-sm font-normal text-secondary">/month</span>
|
$24<span class="text-sm font-normal text-secondary">/month</span>
|
||||||
</h2>
|
</h2>
|
||||||
<ButtonStyled color="purple" size="large">
|
<ButtonStyled color="purple" size="large">
|
||||||
<button
|
|
||||||
v-if="!isLargeAtCapacity"
|
|
||||||
class="!bg-highlight-purple !font-medium !text-purple"
|
|
||||||
@click="selectProduct(pyroPlanProducts[2])"
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
<RightArrowIcon class="!min-h-4 !min-w-4 !text-purple" />
|
|
||||||
</button>
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-else
|
v-if="loggedOut"
|
||||||
:to="loggedOut ? redirectUrl : 'https://support.modrinth.com'"
|
:to="loginUrl"
|
||||||
:target="loggedOut ? '_self' : '_blank'"
|
target="_self"
|
||||||
class="!bg-highlight-purple !font-medium !text-purple"
|
class="!bg-highlight-purple !font-medium !text-purple"
|
||||||
>
|
>
|
||||||
<template v-if="loggedOut">
|
Login
|
||||||
Login
|
<UserIcon class="!min-h-4 !min-w-4 !text-purple" />
|
||||||
<UserIcon class="!min-h-4 !min-w-4 !text-purple" />
|
</NuxtLink>
|
||||||
</template>
|
<template v-else>
|
||||||
<template v-else>
|
<NuxtLink
|
||||||
|
v-if="isLargeAtCapacity"
|
||||||
|
:to="outOfStockUrl"
|
||||||
|
target="_blank"
|
||||||
|
class="!bg-highlight-purple !font-medium !text-purple"
|
||||||
|
>
|
||||||
Out of Stock
|
Out of Stock
|
||||||
<ExternalIcon class="!min-h-4 !min-w-4 !text-purple" />
|
<ExternalIcon class="!min-h-4 !min-w-4 !text-purple" />
|
||||||
</template>
|
</NuxtLink>
|
||||||
</NuxtLink>
|
<button
|
||||||
|
v-else
|
||||||
|
class="!bg-highlight-purple !font-medium !text-purple"
|
||||||
|
@click="selectProduct(pyroPlanProducts[2])"
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
<RightArrowIcon class="!min-h-4 !min-w-4 !text-purple" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- <div
|
<div
|
||||||
class="flex w-full flex-col items-start justify-between gap-4 rounded-2xl bg-bg p-8 text-left md:flex-row md:gap-0"
|
class="flex w-full flex-col items-start justify-between gap-4 rounded-2xl bg-bg p-8 text-left md:flex-row md:gap-0"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
@@ -668,33 +685,18 @@
|
|||||||
|
|
||||||
<div class="flex w-full flex-col-reverse gap-2 md:w-auto md:flex-col md:items-center">
|
<div class="flex w-full flex-col-reverse gap-2 md:w-auto md:flex-col md:items-center">
|
||||||
<ButtonStyled color="standard" size="large">
|
<ButtonStyled color="standard" size="large">
|
||||||
<button
|
<NuxtLink v-if="loggedOut" :to="loginUrl" target="_self" class="w-full md:w-fit">
|
||||||
v-if="!isLargeAtCapacity"
|
Login
|
||||||
class="w-full md:w-fit"
|
<UserIcon class="!min-h-4 !min-w-4" />
|
||||||
@click="selectProduct(pyroProducts, true)"
|
</NuxtLink>
|
||||||
>
|
<button v-else class="w-full md:w-fit" @click="selectProduct(pyroProducts, true)">
|
||||||
Build your own
|
Build your own
|
||||||
<RightArrowIcon class="!min-h-4 !min-w-4" />
|
<RightArrowIcon class="!min-h-4 !min-w-4" />
|
||||||
</button>
|
</button>
|
||||||
<NuxtLink
|
|
||||||
v-else
|
|
||||||
:to="loggedOut ? redirectUrl : 'https://support.modrinth.com'"
|
|
||||||
:target="loggedOut ? '_self' : '_blank'"
|
|
||||||
class="w-full md:w-fit"
|
|
||||||
>
|
|
||||||
<template v-if="loggedOut">
|
|
||||||
Login
|
|
||||||
<UserIcon class="!min-h-4 !min-w-4" />
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
Out of Stock
|
|
||||||
<ExternalIcon class="!min-h-4 !min-w-4" />
|
|
||||||
</template>
|
|
||||||
</NuxtLink>
|
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<p class="m-0 text-sm">Starting at $3/GB RAM</p>
|
<p class="m-0 text-sm">Starting at $3/GB RAM</p>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -770,7 +772,8 @@ const deletingSpeed = 25;
|
|||||||
const pauseTime = 2000;
|
const pauseTime = 2000;
|
||||||
|
|
||||||
const loggedOut = computed(() => !auth.value.user);
|
const loggedOut = computed(() => !auth.value.user);
|
||||||
const redirectUrl = `/auth/sign-in?redirect=${encodeURIComponent("/servers#plan")}`;
|
const loginUrl = `/auth/sign-in?redirect=${encodeURIComponent("/servers#plan")}`;
|
||||||
|
const outOfStockUrl = "https://support.modrinth.com";
|
||||||
|
|
||||||
const { data: hasServers } = await useAsyncData("ServerListCountCheck", async () => {
|
const { data: hasServers } = await useAsyncData("ServerListCountCheck", async () => {
|
||||||
try {
|
try {
|
||||||
@@ -782,37 +785,47 @@ const { data: hasServers } = await useAsyncData("ServerListCountCheck", async ()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: capacityStatuses, refresh: refreshCapacity } = await useAsyncData(
|
async function fetchCapacityStatuses(customProduct = null) {
|
||||||
"ServerCapacityAll",
|
try {
|
||||||
async () => {
|
const productsToCheck = customProduct?.metadata ? [customProduct] : pyroPlanProducts;
|
||||||
try {
|
const capacityChecks = productsToCheck.map((product) =>
|
||||||
const capacityChecks = pyroPlanProducts.map((product) =>
|
usePyroFetch("capacity", {
|
||||||
usePyroFetch("capacity", {
|
method: "POST",
|
||||||
method: "POST",
|
body: {
|
||||||
body: {
|
cpu: product.metadata.cpu,
|
||||||
cpu: product.metadata.cpu,
|
memory_mb: product.metadata.ram,
|
||||||
memory_mb: product.metadata.ram,
|
swap_mb: product.metadata.swap,
|
||||||
swap_mb: product.metadata.swap,
|
storage_mb: product.metadata.storage,
|
||||||
storage_mb: product.metadata.storage,
|
},
|
||||||
},
|
}),
|
||||||
}),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const results = await Promise.all(capacityChecks);
|
const results = await Promise.all(capacityChecks);
|
||||||
|
if (customProduct?.metadata) {
|
||||||
|
return {
|
||||||
|
custom: results[0],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
small: results[0],
|
small: results[0],
|
||||||
medium: results[1],
|
medium: results[1],
|
||||||
large: results[2],
|
large: results[2],
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
console.error("Error checking server capacities:", error);
|
|
||||||
return {
|
|
||||||
small: { available: 0 },
|
|
||||||
medium: { available: 0 },
|
|
||||||
large: { available: 0 },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
} catch (error) {
|
||||||
|
console.error("Error checking server capacities:", error);
|
||||||
|
return {
|
||||||
|
custom: { available: 0 },
|
||||||
|
small: { available: 0 },
|
||||||
|
medium: { available: 0 },
|
||||||
|
large: { available: 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: capacityStatuses, refresh: refreshCapacity } = await useAsyncData(
|
||||||
|
"ServerCapacityAll",
|
||||||
|
fetchCapacityStatuses,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSmallAtCapacity = computed(() => capacityStatuses.value?.small?.available === 0);
|
const isSmallAtCapacity = computed(() => capacityStatuses.value?.small?.available === 0);
|
||||||
@@ -931,7 +944,7 @@ const selectProduct = async (product, custom) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!auth.value.user) {
|
if (!auth.value.user) {
|
||||||
data.$router.push(redirectUrl);
|
data.$router.push(loginUrl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,14 +109,17 @@
|
|||||||
<Slider
|
<Slider
|
||||||
v-model="customServerConfig.ramInGb"
|
v-model="customServerConfig.ramInGb"
|
||||||
class="fix-slider"
|
class="fix-slider"
|
||||||
:min="2"
|
:min="customMinRam"
|
||||||
:max="12"
|
:max="customMaxRam"
|
||||||
:step="2"
|
:step="2"
|
||||||
unit="GB"
|
unit="GB"
|
||||||
/>
|
/>
|
||||||
<div class="font-semibold text-nowrap"></div>
|
<div class="font-semibold text-nowrap"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customMatchingProduct" class="flex sm:flex-row flex-col gap-4 w-full">
|
<div
|
||||||
|
v-if="customMatchingProduct && !customOutOfStock"
|
||||||
|
class="flex sm:flex-row flex-col gap-4 w-full"
|
||||||
|
>
|
||||||
<div class="flex flex-col w-full gap-2">
|
<div class="flex flex-col w-full gap-2">
|
||||||
<div class="font-semibold">vCPUs</div>
|
<div class="font-semibold">vCPUs</div>
|
||||||
<input v-model="mutatedProduct.metadata.cpu" disabled class="input" />
|
<input v-model="mutatedProduct.metadata.cpu" disabled class="input" />
|
||||||
@@ -134,7 +137,15 @@
|
|||||||
<div class="flex flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
<InfoIcon class="hidden flex-none h-8 w-8 text-blue sm:block" />
|
<InfoIcon class="hidden flex-none h-8 w-8 text-blue sm:block" />
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div v-if="customOutOfStock && customMatchingProduct" class="flex flex-col gap-2">
|
||||||
|
<div class="font-semibold">This plan is currently out of stock</div>
|
||||||
|
<div class="font-normal">
|
||||||
|
We are currently
|
||||||
|
<a :href="outOfStockUrl" class="underline" target="_blank">out of capacity</a>
|
||||||
|
for your selected RAM amount. Please try again later, or try a different amount.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-col gap-2">
|
||||||
<div class="font-semibold">We can't seem to find your selected plan</div>
|
<div class="font-semibold">We can't seem to find your selected plan</div>
|
||||||
<div class="font-normal">
|
<div class="font-normal">
|
||||||
We are currently unable to find a server for your selected RAM amount. Please
|
We are currently unable to find a server for your selected RAM amount. Please
|
||||||
@@ -383,7 +394,7 @@
|
|||||||
:disabled="
|
:disabled="
|
||||||
paymentLoading ||
|
paymentLoading ||
|
||||||
(mutatedProduct.metadata.type === 'pyro' && !projectId && !serverName) ||
|
(mutatedProduct.metadata.type === 'pyro' && !projectId && !serverName) ||
|
||||||
(customServer && !customMatchingProduct)
|
customAllowedToContinue
|
||||||
"
|
"
|
||||||
@click="nextStep"
|
@click="nextStep"
|
||||||
>
|
>
|
||||||
@@ -545,6 +556,16 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
fetchCapacityStatuses: {
|
||||||
|
type: Function,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
outOfStockUrl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@@ -631,7 +652,16 @@ const serverLoader = ref('Vanilla')
|
|||||||
const eulaAccepted = ref(false)
|
const eulaAccepted = ref(false)
|
||||||
|
|
||||||
const mutatedProduct = ref({ ...props.product })
|
const mutatedProduct = ref({ ...props.product })
|
||||||
|
const customMinRam = ref(0)
|
||||||
|
const customMaxRam = ref(0)
|
||||||
const customMatchingProduct = ref()
|
const customMatchingProduct = ref()
|
||||||
|
const customOutOfStock = ref(false)
|
||||||
|
const customLoading = ref(true)
|
||||||
|
const customAllowedToContinue = computed(
|
||||||
|
() =>
|
||||||
|
props.customServer &&
|
||||||
|
(!customMatchingProduct.value || customLoading.value || customOutOfStock.value),
|
||||||
|
)
|
||||||
|
|
||||||
const customServerConfig = reactive({
|
const customServerConfig = reactive({
|
||||||
ramInGb: 4,
|
ramInGb: 4,
|
||||||
@@ -640,23 +670,48 @@ const customServerConfig = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const updateCustomServerProduct = () => {
|
const updateCustomServerProduct = () => {
|
||||||
if (props.customServer) {
|
customMatchingProduct.value = props.product.find(
|
||||||
customMatchingProduct.value = props.product.find(
|
(product) => product.metadata.ram === customServerConfig.ram,
|
||||||
(product) => product.metadata.ram === customServerConfig.ram,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if (customMatchingProduct.value) mutatedProduct.value = { ...customMatchingProduct.value }
|
if (customMatchingProduct.value) mutatedProduct.value = { ...customMatchingProduct.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
let updateCustomServerStockTimeout = null
|
||||||
|
const updateCustomServerStock = async () => {
|
||||||
|
if (updateCustomServerStockTimeout) {
|
||||||
|
clearTimeout(updateCustomServerStockTimeout)
|
||||||
|
customLoading.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCustomServerStockTimeout = setTimeout(async () => {
|
||||||
|
if (props.fetchCapacityStatuses) {
|
||||||
|
const capacityStatus = await props.fetchCapacityStatuses(mutatedProduct.value)
|
||||||
|
if (capacityStatus.custom?.available === 0) {
|
||||||
|
customOutOfStock.value = true
|
||||||
|
} else {
|
||||||
|
customOutOfStock.value = false
|
||||||
|
}
|
||||||
|
customLoading.value = false
|
||||||
|
} else {
|
||||||
|
console.error('No fetchCapacityStatuses function provided.')
|
||||||
|
customOutOfStock.value = true
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.customServer) {
|
if (props.customServer) {
|
||||||
updateCustomServerProduct()
|
const ramValues = props.product.map((product) => product.metadata.ram / 1024)
|
||||||
watch(
|
customMinRam.value = Math.min(...ramValues)
|
||||||
() => customServerConfig.ram,
|
customMaxRam.value = Math.max(...ramValues)
|
||||||
() => {
|
|
||||||
updateCustomServerProduct()
|
const updateProductAndStock = () => {
|
||||||
},
|
updateCustomServerProduct()
|
||||||
)
|
updateCustomServerStock()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProductAndStock()
|
||||||
|
watch(() => customServerConfig.ram, updateProductAndStock)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedPaymentMethod = ref()
|
const selectedPaymentMethod = ref()
|
||||||
|
|||||||
Reference in New Issue
Block a user