Skip to content

Commit 32183a2

Browse files
committed
feat: migrate servers manage page to api-client before page system
1 parent 0339a35 commit 32183a2

File tree

34 files changed

+500
-392
lines changed

34 files changed

+500
-392
lines changed

apps/frontend/nuxt.config.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ import Papa from 'papaparse'
1111
import { basename, relative, resolve } from 'pathe'
1212
import svgLoader from 'vite-svg-loader'
1313

14-
import {
15-
ServersOverviewPage,
16-
createComponentResolver,
17-
sharedRoutes,
18-
toNuxtPages,
19-
} from '@modrinth/ui/pages'
2014
import type { GeneratedState } from './src/composables/generated'
2115

2216
const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/'
@@ -309,17 +303,6 @@ export default defineNuxtConfig({
309303
children: [],
310304
}),
311305
)
312-
313-
// Shared routes
314-
const uiPackagePath = resolve(__dirname, '../../packages/ui')
315-
const componentResolver = createComponentResolver(uiPackagePath)
316-
317-
componentResolver.register(ServersOverviewPage, 'src/pages/servers/manage.vue')
318-
319-
const nuxtPages = toNuxtPages(sharedRoutes, (component) =>
320-
componentResolver.resolve(component),
321-
)
322-
routes.push(...nuxtPages)
323306
},
324307
async 'vintl:extendOptions'(opts) {
325308
opts.locales ??= []

apps/frontend/src/components/ui/servers/ServerListing.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@
112112
</template>
113113

114114
<script setup lang="ts">
115-
import { ChevronRightIcon, LockIcon, SparklesIcon } from '@modrinth/assets'
116115
import type { Archon } from '@modrinth/api-client'
116+
import { ChevronRightIcon, LockIcon, SparklesIcon } from '@modrinth/assets'
117117
import { Avatar, CopyCode, ServersSpecs } from '@modrinth/ui'
118118
import type { Project } from '@modrinth/utils'
119119
import dayjs from 'dayjs'

apps/frontend/src/components/ui/servers/ServersUpgradeModalWrapper.vue

Lines changed: 98 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<ModrinthServersPurchaseModal
3-
v-if="customer"
3+
v-if="customer && regionsData"
44
ref="purchaseModal"
55
:publishable-key="config.public.stripePublishableKey"
66
:initiate-payment="async (body) => await initiatePayment(body)"
@@ -11,7 +11,7 @@
1111
:currency="selectedCurrency"
1212
:return-url="`${config.public.siteUrl}/servers/manage`"
1313
:pings="regionPings"
14-
:regions="regions"
14+
:regions="regionsData"
1515
:refresh-payment-methods="fetchPaymentData"
1616
:fetch-stock="fetchStock"
1717
:plan-stage="true"
@@ -27,66 +27,80 @@
2727
</template>
2828

2929
<script setup lang="ts">
30-
import { injectNotificationManager, ModrinthServersPurchaseModal } from '@modrinth/ui'
31-
import type { ServerPlan } from '@modrinth/ui/src/utils/billing'
32-
import type { UserSubscription } from '@modrinth/utils'
33-
import { computed, onMounted, ref } from 'vue'
30+
import type { Archon, Labrinth } from '@modrinth/api-client'
31+
import {
32+
injectModrinthClient,
33+
injectNotificationManager,
34+
ModrinthServersPurchaseModal,
35+
} from '@modrinth/ui'
36+
import { useMutation, useQuery } from '@tanstack/vue-query'
37+
import { computed, ref, watch } from 'vue'
3438
35-
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
3639
import { products } from '~/generated/state.json'
3740
3841
const { addNotification } = injectNotificationManager()
42+
const { labrinth, archon } = injectModrinthClient()
3943
4044
const config = useRuntimeConfig()
4145
const purchaseModal = ref<InstanceType<typeof ModrinthServersPurchaseModal> | null>(null)
4246
const customer = ref<any>(null)
4347
const paymentMethods = ref<any[]>([])
4448
const selectedCurrency = ref<string>('USD')
45-
const regions = ref<any[]>([])
4649
const regionPings = ref<any[]>([])
4750
48-
const pyroProducts = (products as any[])
51+
const pyroProducts = (products as Labrinth.Billing.Internal.Product[])
4952
.filter((p) => p?.metadata?.type === 'pyro')
50-
.sort((a, b) => (a?.metadata?.ram ?? 0) - (b?.metadata?.ram ?? 0))
53+
.sort((a, b) => {
54+
const aRam = a?.metadata?.type === 'pyro' ? a.metadata.ram : 0
55+
const bRam = b?.metadata?.type === 'pyro' ? b.metadata.ram : 0
56+
return aRam - bRam
57+
})
5158
5259
function handleError(err: any) {
5360
console.error('Purchase modal error:', err)
5461
}
5562
56-
async function fetchPaymentData() {
57-
try {
58-
const [customerData, paymentMethodsData] = await Promise.all([
59-
useBaseFetch('billing/customer', { internal: true }),
60-
useBaseFetch('billing/payment_methods', { internal: true }),
61-
])
62-
customer.value = customerData as any
63-
paymentMethods.value = paymentMethodsData as any[]
64-
} catch (error) {
65-
console.error('Error fetching payment data:', error)
66-
}
67-
}
63+
const { data: customerData } = useQuery({
64+
queryKey: ['billing', 'customer'],
65+
queryFn: () => labrinth.billing_internal.getCustomer(),
66+
})
6867
69-
function fetchStock(region: any, request: any) {
70-
return useServersFetch(`stock?region=${region.shortcode}`, {
71-
method: 'POST',
72-
body: {
73-
...request,
74-
},
75-
bypassAuth: true,
76-
}).then((res: any) => res.available as number)
77-
}
68+
const { data: paymentMethodsData, refetch: refetchPaymentMethods } = useQuery({
69+
queryKey: ['billing', 'payment-methods'],
70+
queryFn: () => labrinth.billing_internal.getPaymentMethods(),
71+
})
72+
73+
const { data: regionsData } = useQuery({
74+
queryKey: ['servers', 'regions'],
75+
queryFn: () => archon.servers_v1.getRegions(),
76+
})
77+
78+
watch(customerData, (newCustomer) => {
79+
if (newCustomer) customer.value = newCustomer
80+
})
81+
82+
watch(paymentMethodsData, (newMethods) => {
83+
if (newMethods) paymentMethods.value = newMethods
84+
})
7885
79-
function pingRegions() {
80-
useServersFetch('regions', {
81-
method: 'GET',
82-
version: 1,
83-
bypassAuth: true,
84-
}).then((res: any) => {
85-
regions.value = res as any[]
86-
;(regions.value as any[]).forEach((region: any) => {
86+
watch(regionsData, (newRegions) => {
87+
if (newRegions) {
88+
newRegions.forEach((region) => {
8789
runPingTest(region)
8890
})
89-
})
91+
}
92+
})
93+
94+
async function fetchPaymentData() {
95+
await refetchPaymentMethods()
96+
}
97+
98+
async function fetchStock(
99+
region: Archon.Servers.v1.Region,
100+
request: Archon.Servers.v0.StockRequest,
101+
): Promise<number> {
102+
const result = await archon.servers_v0.checkStock(region.shortcode, request)
103+
return result.available
90104
}
91105
92106
const PING_COUNT = 20
@@ -141,24 +155,22 @@ function runPingTest(region: any, index = 1) {
141155
}
142156
}
143157
144-
const subscription = ref<UserSubscription | null>(null)
158+
const subscription = ref<Labrinth.Billing.Internal.UserSubscription | null>(null)
145159
// Dry run state
146160
const dryRunResponse = ref<{
147161
requires_payment: boolean
148162
required_payment_is_proration: boolean
149163
} | null>(null)
150-
const pendingDowngradeBody = ref<any | null>(null)
151-
const currentPlanFromSubscription = computed<ServerPlan | undefined>(() => {
164+
const pendingDowngradeBody = ref<Labrinth.Billing.Internal.EditSubscriptionRequest | null>(null)
165+
const currentPlanFromSubscription = computed<Labrinth.Billing.Internal.Product | undefined>(() => {
152166
return subscription.value
153-
? (pyroProducts.find(
154-
(p) =>
155-
p.prices.filter((price: { id: string }) => price.id === subscription.value?.price_id)
156-
.length > 0,
167+
? (pyroProducts.find((p) =>
168+
p.prices.some((price) => price.id === subscription.value?.price_id),
157169
) ?? undefined)
158170
: undefined
159171
})
160172
161-
const currentInterval = computed(() => {
173+
const currentInterval = computed<'monthly' | 'quarterly'>(() => {
162174
const interval = subscription.value?.interval
163175
164176
if (interval === 'monthly' || interval === 'quarterly') {
@@ -167,26 +179,45 @@ const currentInterval = computed(() => {
167179
return 'monthly'
168180
})
169181
170-
async function initiatePayment(body: any): Promise<any> {
182+
const editSubscriptionMutation = useMutation({
183+
mutationFn: async ({
184+
id,
185+
body,
186+
dry,
187+
}: {
188+
id: string
189+
body: Labrinth.Billing.Internal.EditSubscriptionRequest
190+
dry: boolean
191+
}) => {
192+
return await labrinth.billing_internal.editSubscription(id, body, dry)
193+
},
194+
})
195+
196+
async function initiatePayment(
197+
body: Labrinth.Billing.Internal.InitiatePaymentRequest,
198+
): Promise<Labrinth.Billing.Internal.EditSubscriptionResponse | null> {
171199
if (subscription.value) {
172-
const transformedBody = {
173-
interval: body.charge?.interval,
200+
const transformedBody: Labrinth.Billing.Internal.EditSubscriptionRequest = {
201+
interval: body.charge.type === 'new' ? body.charge.interval : undefined,
174202
payment_method: body.type === 'confirmation_token' ? body.token : body.id,
175-
product: body.charge?.product_id,
203+
product: body.charge.type === 'new' ? body.charge.product_id : undefined,
176204
region: body.metadata?.server_region,
177205
}
178206
179207
try {
180-
const dry = await useBaseFetch(`billing/subscription/${subscription.value.id}?dry=true`, {
181-
internal: true,
182-
method: 'PATCH',
208+
const dry = await editSubscriptionMutation.mutateAsync({
209+
id: subscription.value.id,
183210
body: transformedBody,
211+
dry: true,
184212
})
185213
186-
if (dry && typeof dry === 'object' && 'requires_payment' in dry) {
187-
dryRunResponse.value = dry as any
214+
if (dry && typeof dry === 'object' && 'payment_intent_id' in dry) {
215+
dryRunResponse.value = {
216+
requires_payment: !!dry.payment_intent_id,
217+
required_payment_is_proration: true,
218+
}
188219
pendingDowngradeBody.value = transformedBody
189-
if (dry.requires_payment) {
220+
if (dry.payment_intent_id) {
190221
return await finalizeImmediate(transformedBody)
191222
} else {
192223
return null
@@ -209,14 +240,16 @@ async function initiatePayment(body: any): Promise<any> {
209240
}
210241
}
211242
212-
async function finalizeImmediate(body: any) {
213-
const result = await useBaseFetch(`billing/subscription/${subscription.value?.id}`, {
214-
internal: true,
215-
method: 'PATCH',
243+
async function finalizeImmediate(body: Labrinth.Billing.Internal.EditSubscriptionRequest) {
244+
if (!subscription.value) return null
245+
246+
const result = await editSubscriptionMutation.mutateAsync({
247+
id: subscription.value.id,
216248
body,
249+
dry: false,
217250
})
218251
219-
return result
252+
return result ?? null
220253
}
221254
222255
async function finalizeDowngrade() {
@@ -243,11 +276,9 @@ async function finalizeDowngrade() {
243276
244277
async function open(id?: string) {
245278
if (id) {
246-
const subscriptions = (await useBaseFetch(`billing/subscriptions`, {
247-
internal: true,
248-
})) as any[]
279+
const subscriptions = await labrinth.billing_internal.getSubscriptions()
249280
for (const sub of subscriptions) {
250-
if (sub?.metadata?.id === id) {
281+
if (sub?.metadata?.type === 'pyro' && sub.metadata.id === id) {
251282
subscription.value = sub
252283
break
253284
}
@@ -262,9 +293,4 @@ async function open(id?: string) {
262293
defineExpose({
263294
open,
264295
})
265-
266-
onMounted(() => {
267-
fetchPaymentData()
268-
pingRegions()
269-
})
270296
</script>

apps/frontend/src/components/ui/servers/marketing/MedalServerListing.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@
127127
</template>
128128

129129
<script setup lang="ts">
130-
import { ChevronRightIcon, LockIcon, RocketIcon, SparklesIcon } from '@modrinth/assets'
131130
import type { Archon } from '@modrinth/api-client'
131+
import { ChevronRightIcon, LockIcon, RocketIcon, SparklesIcon } from '@modrinth/assets'
132132
import { AutoLink, Avatar, ButtonStyled, CopyCode } from '@modrinth/ui'
133133
import type { Project } from '@modrinth/utils'
134134
import dayjs from 'dayjs'

apps/frontend/src/error.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,12 @@
5252

5353
<script setup>
5454
import { SadRinthbot } from '@modrinth/assets'
55-
import { NotificationPanel, provideNotificationManager } from '@modrinth/ui'
55+
import { NotificationPanel, provideModrinthClient, provideNotificationManager } from '@modrinth/ui'
5656
import { defineMessage, useVIntl } from '@vintl/vintl'
5757
import { IntlFormatted } from '@vintl/vintl/components'
5858
5959
import Logo404 from '~/assets/images/404.svg'
6060
61-
import { provideModrinthClient } from '@modrinth/ui'
6261
import ModrinthLoadingIndicator from './components/ui/modrinth-loading-indicator.ts'
6362
import { createModrinthClient } from './helpers/api.ts'
6463
import { FrontendNotificationManager } from './providers/frontend-notifications.ts'

apps/frontend/src/helpers/api.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import {
1+
import type {
22
AbstractFeature,
3+
type AuthConfig,
34
AuthFeature,
45
CircuitBreakerFeature,
56
NuxtCircuitBreakerStorage,
7+
type NuxtClientConfig,
68
NuxtModrinthClient,
79
VerboseLoggingFeature,
8-
type AuthConfig,
9-
type NuxtClientConfig,
1010
} from '@modrinth/api-client'
1111

1212
export function createModrinthClient(

apps/frontend/src/pages/servers/manage/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
</div>
5858
</div>
5959

60-
<Transition name="fade" mode="out-in" v-else>
60+
<Transition v-else name="fade" mode="out-in">
6161
<div v-if="isLoading && !serverResponse" key="loading" class="flex flex-col gap-4 py-8">
6262
<div class="mb-4 text-center">
6363
<LoaderCircleIcon class="mx-auto size-8 animate-spin text-contrast" />

apps/frontend/src/plugins/tanstack.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// https://tanstack.com/query/v5/docs/framework/vue/examples/nuxt3
22
import type { DehydratedState, VueQueryPluginOptions } from '@tanstack/vue-query'
3-
import { QueryClient, VueQueryPlugin, dehydrate, hydrate } from '@tanstack/vue-query'
3+
import { dehydrate, hydrate, QueryClient, VueQueryPlugin } from '@tanstack/vue-query'
44

55
import { defineNuxtPlugin, useState } from '#imports'
66

packages/api-client/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ export {
1212
export { type BackoffStrategy, type RetryConfig, RetryFeature } from './features/retry'
1313
export { type VerboseLoggingConfig, VerboseLoggingFeature } from './features/verbose-logging'
1414
export type { InferredClientModules } from './modules'
15-
export * from './modules/types'
16-
export type { Archon } from './modules/archon/servers/types'
1715
export type { Labrinth } from './modules/labrinth/billing/types'
16+
export * from './modules/types'
1817
export { GenericModrinthClient } from './platform/generic'
1918
export type { NuxtClientConfig } from './platform/nuxt'
2019
export { NuxtCircuitBreakerStorage, NuxtModrinthClient } from './platform/nuxt'
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
export * from './servers/v0'
21
export * from './servers/types'
2+
export * from './servers/v0'
3+
export * from './servers/v1'

0 commit comments

Comments
 (0)