diff --git a/apps/frontend/src/pages/servers/index.vue b/apps/frontend/src/pages/servers/index.vue index 9030c0e01..9ccd98e89 100644 --- a/apps/frontend/src/pages/servers/index.vue +++ b/apps/frontend/src/pages/servers/index.vue @@ -520,27 +520,19 @@ @@ -639,7 +631,7 @@ import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue"; import ServerPlanSelector from "~/components/ui/servers/marketing/ServerPlanSelector.vue"; import OptionGroup from "~/components/ui/OptionGroup.vue"; -const billingPeriods = ref(["monthly", "yearly"]); +const billingPeriods = ref(["monthly", "quarterly"]); const billingPeriod = ref(billingPeriods.value.includes("quarterly") ? "quarterly" : "monthly"); const pyroProducts = products.filter((p) => p.metadata.type === "pyro"); diff --git a/packages/ui/src/components/billing/ModrinthServersPurchaseModal.vue b/packages/ui/src/components/billing/ModrinthServersPurchaseModal.vue index bdd3fc9ed..b988105f8 100644 --- a/packages/ui/src/components/billing/ModrinthServersPurchaseModal.vue +++ b/packages/ui/src/components/billing/ModrinthServersPurchaseModal.vue @@ -8,6 +8,7 @@ import { RightArrowIcon, XIcon, CheckCircleIcon, + SpinnerIcon, } from '@modrinth/assets' import type { CreatePaymentIntentRequest, @@ -27,6 +28,7 @@ import RegionSelector from './ServersPurchase1Region.vue' import PaymentMethodSelector from './ServersPurchase2PaymentMethod.vue' import ConfirmPurchase from './ServersPurchase3Review.vue' import { useStripe } from '../../composables/stripe' +import ModalLoadingIndicator from '../modal/ModalLoadingIndicator.vue' const { formatMessage } = useVIntl() @@ -49,11 +51,12 @@ const props = defineProps<{ initiatePayment: ( body: CreatePaymentIntentRequest | UpdatePaymentIntentRequest, ) => Promise + onError: (err: Error) => void }>() const modal = useTemplateRef>('modal') const selectedPlan = ref() -const selectedInterval = ref() +const selectedInterval = ref('quarterly') const loading = ref(false) const { @@ -72,21 +75,22 @@ const { reloadPaymentIntent, hasPaymentMethod, submitPayment, + completingPurchase, } = useStripe( props.publishableKey, props.customer, props.paymentMethods, - props.clientSecret, props.currency, selectedPlan, selectedInterval, props.initiatePayment, - console.error, + props.onError, ) const selectedRegion = ref() const customServer = ref(false) const acceptedEula = ref(false) +const firstTimeThru = ref(true) type Step = 'region' | 'payment' | 'review' @@ -111,9 +115,13 @@ const currentPing = computed(() => { const currentStep = ref() -const currentStepIndex = computed(() => steps.indexOf(currentStep.value)) -const previousStep = computed(() => steps[steps.indexOf(currentStep.value) - 1]) -const nextStep = computed(() => steps[steps.indexOf(currentStep.value) + 1]) +const currentStepIndex = computed(() => (currentStep.value ? steps.indexOf(currentStep.value) : -1)) +const previousStep = computed(() => + currentStep.value ? steps[steps.indexOf(currentStep.value) - 1] : undefined, +) +const nextStep = computed(() => + currentStep.value ? steps[steps.indexOf(currentStep.value) + 1] : undefined, +) const canProceed = computed(() => { switch (currentStep.value) { @@ -122,7 +130,7 @@ const canProceed = computed(() => { case 'payment': return selectedPaymentMethod.value || !loadingElements.value case 'review': - return acceptedEula.value && hasPaymentMethod.value + return acceptedEula.value && hasPaymentMethod.value && !completingPurchase.value default: return false } @@ -135,13 +143,14 @@ async function beforeProceed(step: string) { case 'payment': await initializeStripe() - if (primaryPaymentMethodId.value) { + if (primaryPaymentMethodId.value && firstTimeThru.value) { const paymentMethod = await props.paymentMethods.find( (x) => x.id === primaryPaymentMethodId.value, ) await selectPaymentMethod(paymentMethod) await setStep('review', true) - return true + firstTimeThru.value = false + return false } return true case 'review': @@ -166,13 +175,13 @@ async function afterProceed(step: string) { } } -async function setStep(step: Step, skipValidation = false) { +async function setStep(step: Step | undefined, skipValidation = false) { if (!step) { await submitPayment(props.returnUrl) return } - if (!canProceed.value || skipValidation) { + if (!skipValidation && !canProceed.value) { return } @@ -191,6 +200,7 @@ function begin(interval: ServerBillingInterval, plan?: ServerPlan) { customServer.value = !selectedPlan.value selectedPaymentMethod.value = undefined currentStep.value = steps[0] + firstTimeThru.value = true modal.value?.show() } @@ -206,7 +216,7 @@ defineExpose({ @@ -249,31 +259,48 @@ defineExpose({ v-else-if=" currentStep === 'review' && hasPaymentMethod && - selectedRegion && + currentRegion && selectedInterval && selectedPlan " - ref="currentStepRef" v-model:interval="selectedInterval" v-model:accepted-eula="acceptedEula" :currency="currency" :plan="selectedPlan" - :region="regions.find((x) => x.shortcode === selectedRegion)" + :region="currentRegion" :ping="currentPing" :loading="paymentMethodLoading" :selected-payment-method="selectedPaymentMethod || inputtedPaymentMethod" :tax="tax" :total="total" - :on-error="console.error" - @change-payment-method="setStep('payment')" + @change-payment-method="setStep('payment', true)" @reload-payment-intent="reloadPaymentIntent" - @error="console.error" />
Something went wrong
+
+
+ + Loading... + + +
+
+
+
+
+
- -
-
+