Skip to content

Commit eeed4e5

Browse files
authored
Credit subscriptions (#4575)
* Implement subscription crediting * chore: query cache, clippy, fmt * Improve code, improve query for next open charge * chore: query cache, clippy, fmt * Move server ID copy button up * Node + region crediting * Make it less ugly * chore: query cache, clippy, fmt * Bugfixes * Fix lint * Adjust migration * Adjust migration * Remove billing change * Move DEFAULT_CREDIT_EMAIL_MESSAGE to utils.ts * Lint * Merge * bump clickhouse, disable validation * tombi fmt * Update cargo lock
1 parent 79502a1 commit eeed4e5

22 files changed

+1052
-8
lines changed

apps/frontend/src/composables/servers/servers-fetch.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface ServersFetchOptions {
66
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
77
contentType?: string
88
body?: Record<string, any>
9-
version?: number
9+
version?: number | 'internal'
1010
override?: {
1111
url?: string
1212
token?: string
@@ -82,7 +82,9 @@ export async function useServersFetch<T>(
8282
? `https://${newOverrideUrl}/${path.replace(/^\//, '')}`
8383
: version === 0
8484
? `${base}/modrinth/v${version}/${path.replace(/^\//, '')}`
85-
: `${base}/v${version}/${path.replace(/^\//, '')}`
85+
: version === 'internal'
86+
? `${base}/_internal/${path.replace(/^\//, '')}`
87+
: `${base}/modrinth/v${version}/${path.replace(/^\//, '')}`
8688

8789
const headers: Record<string, string> = {
8890
'User-Agent': 'Modrinth/1.0 (https://modrinth.com)',

apps/frontend/src/layouts/default.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,12 @@
461461
link: '/admin/servers/notices',
462462
shown: isAdmin(auth.user),
463463
},
464+
{
465+
id: 'servers-nodes',
466+
color: 'primary',
467+
link: '/admin/servers/nodes',
468+
shown: isAdmin(auth.user),
469+
},
464470
]"
465471
>
466472
<ModrinthIcon aria-hidden="true" />
@@ -480,6 +486,7 @@
480486
<template #servers-notices>
481487
<IssuesIcon aria-hidden="true" /> {{ formatMessage(messages.manageServerNotices) }}
482488
</template>
489+
<template #servers-nodes> <ServerIcon aria-hidden="true" /> Server Nodes </template>
483490
</OverflowMenu>
484491
</ButtonStyled>
485492
<ButtonStyled type="transparent">

apps/frontend/src/pages/admin/billing/[id].vue

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,41 @@
9797
</div>
9898
</div>
9999
</NewModal>
100+
<NewModal ref="creditModal">
101+
<template #title>
102+
<span class="text-lg font-extrabold text-contrast">Credit subscription</span>
103+
</template>
104+
<div class="flex flex-col gap-3">
105+
<div class="flex flex-col gap-2">
106+
<label for="days" class="flex flex-col gap-1">
107+
<span class="text-lg font-semibold text-contrast">Days to credit</span>
108+
<span>Enter the number of days to add to the next due date.</span>
109+
</label>
110+
<input id="days" v-model.number="creditDays" type="number" min="1" autocomplete="off" />
111+
</div>
112+
<div class="flex flex-col gap-2">
113+
<label for="sendEmail" class="flex flex-col gap-1">
114+
<span class="text-lg font-semibold text-contrast">Send email to user</span>
115+
<span>Notify the user about the credited days.</span>
116+
</label>
117+
<Toggle id="sendEmail" v-model="creditSendEmail" />
118+
</div>
119+
<div class="flex gap-2">
120+
<ButtonStyled color="brand">
121+
<button :disabled="crediting" @click="applyCredit">
122+
<CheckIcon aria-hidden="true" />
123+
Apply credit
124+
</button>
125+
</ButtonStyled>
126+
<ButtonStyled>
127+
<button @click="creditModal.hide()">
128+
<XIcon aria-hidden="true" />
129+
Cancel
130+
</button>
131+
</ButtonStyled>
132+
</div>
133+
</div>
134+
</NewModal>
100135
<div class="page experimental-styles-within">
101136
<div
102137
class="mb-4 flex items-center justify-between border-0 border-b border-solid border-divider pb-4"
@@ -140,6 +175,7 @@
140175
</div>
141176
</div>
142177
<div v-if="subscription.metadata?.id" class="flex flex-col items-end gap-2">
178+
<CopyCode :text="subscription.metadata.id" />
143179
<ButtonStyled
144180
v-if="
145181
subscription.metadata?.type === 'pyro' || subscription.metadata?.type === 'medal'
@@ -153,7 +189,12 @@
153189
<ServerIcon /> Server panel <ExternalIcon class="h-4 w-4" />
154190
</nuxt-link>
155191
</ButtonStyled>
156-
<CopyCode :text="subscription.metadata.id" />
192+
<ButtonStyled>
193+
<button @click="showCreditModal(subscription)">
194+
<CurrencyIcon />
195+
Credit
196+
</button>
197+
</ButtonStyled>
157198
</div>
158199
</div>
159200
<div class="flex flex-col gap-2">
@@ -292,6 +333,7 @@ import {
292333
useRelativeTime,
293334
} from '@modrinth/ui'
294335
import { formatCategory, formatPrice } from '@modrinth/utils'
336+
import { DEFAULT_CREDIT_EMAIL_MESSAGE } from '@modrinth/utils/utils.ts'
295337
import dayjs from 'dayjs'
296338
297339
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
@@ -370,6 +412,11 @@ const modifying = ref(false)
370412
const modifyModal = ref()
371413
const cancel = ref(false)
372414
415+
const crediting = ref(false)
416+
const creditModal = ref()
417+
const creditDays = ref(7)
418+
const creditSendEmail = ref(true)
419+
373420
function showRefundModal(charge) {
374421
selectedCharge.value = charge
375422
refundType.value = 'full'
@@ -385,6 +432,44 @@ function showModifyModal(charge, subscription) {
385432
modifyModal.value.show()
386433
}
387434
435+
function showCreditModal(subscription) {
436+
selectedSubscription.value = subscription
437+
creditDays.value = 1
438+
creditSendEmail.value = true
439+
creditModal.value.show()
440+
}
441+
442+
async function applyCredit() {
443+
crediting.value = true
444+
try {
445+
const daysParsed = Math.max(1, Math.floor(Number(creditDays.value) || 1))
446+
await useBaseFetch('billing/credit', {
447+
method: 'POST',
448+
body: JSON.stringify({
449+
subscription_ids: [selectedSubscription.value.id],
450+
days: daysParsed,
451+
send_email: creditSendEmail.value,
452+
message: DEFAULT_CREDIT_EMAIL_MESSAGE,
453+
}),
454+
internal: true,
455+
})
456+
addNotification({
457+
title: 'Credit applied',
458+
text: 'The subscription due date has been updated.',
459+
type: 'success',
460+
})
461+
await refreshCharges()
462+
creditModal.value.hide()
463+
} catch (err) {
464+
addNotification({
465+
title: 'Error applying credit',
466+
text: err.data?.description ?? String(err),
467+
type: 'error',
468+
})
469+
}
470+
crediting.value = false
471+
}
472+
388473
async function refundCharge() {
389474
refunding.value = true
390475
try {

0 commit comments

Comments
 (0)