feat: medal promotion on servers page (#4117)

* feat: medal promotion on servers page

* feat: medal server card

* fix: styling changes

* fix: colors for dark mode only

* fix: light mode medal promotion

* feat: finish server card layout

* feat: countdown on server panel

* fix: lint

* feat: use same gradient as promo

* fix: scale for medal bg

* fix: border around server icon

* feat: medal subscr expiry date stuff

* feat: progress on plans within the modal

* feat: finalize plan modal stage

* fix: unused scss

* feat: remove buttons from cards

* feat: upgrade button opens modal on server panel

* feat: billing endpoint

* fix: lint issues

* fix: lint issues

* fix: lint issues

* feat: better handling of downgrades + existing plan checks

* feat: update medal url

* feat: proration visual in modal

* feat: standardize upgrade modal into ServersUpgradeModalWrapper

* feat: replace upgrade PurchaseModal with ServersUpgradeModalWrapper

* feat: allow server region

* fix: lint

* fix: lint

* fix: medal frontend completion

* fix: lint issues

* feat: ad

* fix: hover tooltip + orange new server sparkle

* feat: ad

* fix: lint issues new eslint

* feat: match ad

* feat: support for ?dry=true

* fix: lint isuses

* fix: lint issues

* fix: TeleportDropdownMenu imports

* fix: hash nav issues

* feat: clarify confirm changes btn

* fix: lint issues

* fix: "Using new payment method"

* fix: lint

* fix: re-add -mt-2

---------

Signed-off-by: Cal H. <hendersoncal117@gmail.com>
This commit is contained in:
Cal H.
2025-08-18 18:59:19 +01:00
committed by GitHub
parent 9af1391e0e
commit 14eac461be
34 changed files with 2476 additions and 285 deletions

View File

@@ -35,7 +35,7 @@ export const useStripe = (
project: Ref<string | undefined>,
initiatePayment: (
body: CreatePaymentIntentRequest | UpdatePaymentIntentRequest,
) => Promise<CreatePaymentIntentResponse | UpdatePaymentIntentResponse>,
) => Promise<CreatePaymentIntentResponse | UpdatePaymentIntentResponse | null>,
onError: (err: Error) => void,
) => {
const stripe = ref<StripeJs | null>(null)
@@ -55,17 +55,22 @@ export const useStripe = (
const inputtedPaymentMethod = ref<Stripe.PaymentMethod>()
const clientSecret = ref<string>()
const completingPurchase = ref<boolean>(false)
const noPaymentRequired = ref<boolean>(false)
async function initialize() {
stripe.value = await loadStripe(publishableKey)
}
function createIntent(body: CreatePaymentIntentRequest): Promise<CreatePaymentIntentResponse> {
return initiatePayment(body) as Promise<CreatePaymentIntentResponse>
function createIntent(
body: CreatePaymentIntentRequest,
): Promise<CreatePaymentIntentResponse | null> {
return initiatePayment(body) as Promise<CreatePaymentIntentResponse | null>
}
function updateIntent(body: UpdatePaymentIntentRequest): Promise<UpdatePaymentIntentResponse> {
return initiatePayment(body) as Promise<UpdatePaymentIntentResponse>
function updateIntent(
body: UpdatePaymentIntentRequest,
): Promise<UpdatePaymentIntentResponse | null> {
return initiatePayment(body) as Promise<UpdatePaymentIntentResponse | null>
}
const planPrices = computed(() => {
@@ -222,7 +227,7 @@ export const useStripe = (
interval: interval.value,
}
let result: BasePaymentIntentResponse
let result: BasePaymentIntentResponse | null = null
const metadata: CreatePaymentIntentRequest['metadata'] = {
type: 'pyro',
@@ -241,26 +246,34 @@ export const useStripe = (
existing_payment_intent: paymentIntentId.value,
metadata,
})
console.log(`Updated payment intent: ${interval.value} for ${result.total}`)
if (result) console.log(`Updated payment intent: ${interval.value} for ${result.total}`)
} else {
;({
payment_intent_id: paymentIntentId.value,
client_secret: clientSecret.value,
...result
} = await createIntent({
const created = await createIntent({
...requestType,
charge,
metadata: metadata,
}))
console.log(`Created payment intent: ${interval.value} for ${result.total}`)
})
if (created) {
paymentIntentId.value = created.payment_intent_id
clientSecret.value = created.client_secret
result = created
console.log(`Created payment intent: ${interval.value} for ${created.total}`)
}
}
tax.value = result.tax
total.value = result.total
if (!result) {
tax.value = 0
total.value = 0
noPaymentRequired.value = true
} else {
tax.value = result.tax
total.value = result.total
noPaymentRequired.value = false
}
if (confirmation) {
confirmationToken.value = id
if (result.payment_method) {
if (result && result.payment_method) {
inputtedPaymentMethod.value = result.payment_method
}
}
@@ -346,6 +359,10 @@ export const useStripe = (
const loadingElements = computed(() => elementsLoaded.value < 2)
async function submitPayment(returnUrl: string) {
if (noPaymentRequired.value) {
completingPurchase.value = false
return true
}
completingPurchase.value = true
const secert = clientSecret.value
@@ -387,7 +404,9 @@ export const useStripe = (
}
}
const hasPaymentMethod = computed(() => selectedPaymentMethod.value || confirmationToken.value)
const hasPaymentMethod = computed(
() => selectedPaymentMethod.value || confirmationToken.value || noPaymentRequired.value,
)
return {
initializeStripe: initialize,
@@ -406,5 +425,6 @@ export const useStripe = (
total,
submitPayment,
completingPurchase,
noPaymentRequired,
}
}