implement proper provisioning state for resubscribe

This commit is contained in:
tdgao
2026-03-26 21:12:18 -06:00
parent 557eed9759
commit 340c62eb35
4 changed files with 96 additions and 21 deletions
@@ -80,7 +80,7 @@
<div v-if="noticeType" class="server-listing-notice">
<div v-if="noticeType === 'provisioning'" class="flex gap-2">
Please wait while we set up your server. This should only take a minute.
Please wait while we set up your server. This can take up to 10 minutes.
</div>
<div v-else-if="noticeType === 'upgrading'" class="flex gap-2">
Your server's hardware is currently being upgraded and will be back online shortly.
@@ -234,6 +234,7 @@ type ServerListingProps = {
current?: number
max?: number
}
isProvisioning?: boolean
cancellationDate?: string | Date | null
onResubscribe?: (() => void) | null
onDownloadBackup?: (() => void) | null
@@ -244,11 +245,10 @@ const props = defineProps<ServerListingProps>()
const { kyros, labrinth } = injectModrinthClient()
const isConfiguring = computed(() => props.flows?.intro)
const isProvisioning = computed(() => props.status === 'installing' && !isConfiguring.value)
const isUpgrading = computed(
() => props.status === 'suspended' && props.suspension_reason === 'upgrading',
)
const isDisabled = computed(() => props.status === 'suspended' || isProvisioning.value)
const isDisabled = computed(() => props.status === 'suspended' || props.isProvisioning)
const isSetToCancel = computed(() => !!props.cancellationDate && props.status !== 'suspended')
const filesRemainingDays = computed(() => {
if (!props.cancellationDate) return 0
@@ -260,7 +260,7 @@ const filesRemainingDays = computed(() => {
const isFilesExpired = computed(() => filesRemainingDays.value <= 0)
const hasIconOverlay = computed(
() => isProvisioning.value || isUpgrading.value || props.status === 'suspended',
() => props.isProvisioning || isUpgrading.value || props.status === 'suspended',
)
type NoticeType =
@@ -273,7 +273,7 @@ type NoticeType =
| 'setToCancel'
const noticeType = computed<NoticeType | null>(() => {
if (isProvisioning.value) return 'provisioning'
if (props.isProvisioning) return 'provisioning'
if (props.status === 'suspended') {
switch (props.suspension_reason) {
case 'upgrading':
@@ -181,6 +181,7 @@
:key="server.server_id"
v-bind="server"
:cancellation-date="serverBillingMap.get(server.server_id)?.cancellationDate"
:is-provisioning="serverBillingMap.get(server.server_id)?.isProvisioning"
:on-resubscribe="serverBillingMap.get(server.server_id)?.onResubscribe"
:on-download-backup="serverBillingMap.get(server.server_id)?.onDownloadBackup"
/>
@@ -208,6 +209,7 @@ import {
} from '@modrinth/ui'
import type { ModrinthServersFetchError } from '@modrinth/utils'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { useIntervalFn } from '@vueuse/core'
import dayjs from 'dayjs'
import Fuse from 'fuse.js'
import type Stripe from 'stripe'
@@ -539,6 +541,41 @@ const { data: charges } = useQuery({
queryFn: () => client.labrinth.billing_internal.getPayments(),
})
const CHARGE_POLL_INTERVAL_MS = 20_000
const hasProvisioningSubscription = computed(() => {
if (!subscriptions.value || !charges.value) return false
return subscriptions.value
.filter((s) => s?.metadata?.type === 'pyro')
.some((sub) => {
if (sub.status !== 'unprovisioned') return false
const charge = charges.value?.find((c) => c.subscription_id === sub.id)
return charge?.status === 'processing' || charge?.status === 'open'
})
})
const { pause: pauseChargePoll, resume: resumeChargePoll } = useIntervalFn(
() => {
queryClient.invalidateQueries({ queryKey: ['billing', 'payments'] })
queryClient.invalidateQueries({ queryKey: ['billing', 'subscriptions'] })
queryClient.invalidateQueries({ queryKey: ['servers'] })
},
CHARGE_POLL_INTERVAL_MS,
{ immediate: false },
)
watch(
hasProvisioningSubscription,
(isProvisioning) => {
if (isProvisioning) {
resumeChargePoll()
} else {
pauseChargePoll()
}
},
{ immediate: true },
)
const { data: serverFullList } = useQuery({
queryKey: ['servers', 'v1'],
queryFn: () => client.archon.servers_v1.list(),
@@ -546,6 +583,7 @@ const { data: serverFullList } = useQuery({
type ServerBillingInfo = {
cancellationDate?: string | null
isProvisioning?: boolean
onResubscribe?: () => void
onDownloadBackup?: (() => void) | null
}
@@ -598,7 +636,10 @@ function getLatestBackupDownload(serverId: string): (() => void) | null {
function getProductFromPriceId(priceId: string | null | undefined) {
if (!priceId) return null
return pyroProducts.value.find((product) => product.prices.some((price) => price.id === priceId)) ?? null
return (
pyroProducts.value.find((product) => product.prices.some((price) => price.id === priceId)) ??
null
)
}
function getPlanName(product: Labrinth.Billing.Internal.Product | null): string {
@@ -673,7 +714,8 @@ function openResubscribeModal(
resubscribeModal.value?.show({
subscriptionId: subscription.id,
wasSuspended: !!charge?.due && dayjs(charge.due).isBefore(dayjs()),
serverName: serverList.value.find((server) => server.server_id === serverId)?.name ?? 'this server',
serverName:
serverList.value.find((server) => server.server_id === serverId)?.name ?? 'this server',
planName: getPlanName(product),
ramGb: getRamGb(product),
storageGb: getStorageGb(product),
@@ -697,7 +739,7 @@ async function handleResubscribeConfirm({ subscriptionId, wasSuspended }: Resubs
if (wasSuspended) {
addNotification({
title: 'Resubscription request submitted',
text: 'If the server is currently suspended, it may take up to 10 minutes for another charge attempt to be made.',
text: 'If the server is currently cancelled, it may take up to 10 minutes for another charge attempt to be made.',
type: 'success',
})
} else {
@@ -729,7 +771,11 @@ const serverBillingMap = computed(() => {
(c) => c.subscription_id === sub.id && c.status !== 'succeeded',
)
const info: ServerBillingInfo = {}
const info: ServerBillingInfo = {
isProvisioning:
sub.status === 'unprovisioned' &&
(charge?.status === 'processing' || charge?.status === 'open'),
}
info.onDownloadBackup = getLatestBackupDownload(serverId)