You've already forked AstralRinth
Merge tag 'v0.14.7' into beta
v0.14.7
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="relative overflow-clip rounded-xl bg-bg px-4 py-3">
|
||||
<div
|
||||
class="absolute bottom-0 left-0 top-0 w-1"
|
||||
:class="
|
||||
charge.type === 'refund' ? 'bg-purple' : (chargeStatuses[charge.status]?.color ?? 'bg-blue')
|
||||
"
|
||||
/>
|
||||
<div class="grid w-full grid-cols-[1fr_auto] items-center gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span>
|
||||
<span class="font-bold text-contrast">
|
||||
<template v-if="charge.status === 'succeeded'"> Succeeded </template>
|
||||
<template v-else-if="charge.status === 'failed'"> Failed </template>
|
||||
<template v-else-if="charge.status === 'cancelled'"> Cancelled </template>
|
||||
<template v-else-if="charge.status === 'processing'"> Processing </template>
|
||||
<template v-else-if="charge.status === 'open'"> Upcoming </template>
|
||||
<template v-else-if="charge.status === 'expiring'"> Expiring </template>
|
||||
<template v-else> {{ charge.status }} </template>
|
||||
</span>
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
<span>
|
||||
<template v-if="charge.type === 'refund'"> Refund </template>
|
||||
<template v-else-if="charge.type === 'subscription'">
|
||||
<template v-if="charge.status === 'cancelled'"> Subscription </template>
|
||||
<template v-else-if="isLatestCharge"> Started subscription </template>
|
||||
<template v-else> Subscription renewal </template>
|
||||
</template>
|
||||
<template v-else-if="charge.type === 'one-time'"> One-time charge </template>
|
||||
<template v-else-if="charge.type === 'proration'"> Proration charge </template>
|
||||
<template v-else> {{ charge.status }} </template>
|
||||
</span>
|
||||
<template v-if="charge.status !== 'cancelled'">
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
{{ formatPrice(charge.amount, charge.currency_code) }}
|
||||
</template>
|
||||
</span>
|
||||
<span
|
||||
v-if="productMetadata && productMetadata.type === 'pyro'"
|
||||
class="flex items-center gap-1 text-sm text-secondary"
|
||||
>
|
||||
<span class="font-bold">Product:</span>
|
||||
<span v-if="productMetadata.ram">{{ productMetadata.ram / 1024 }}GB RAM</span>
|
||||
<span v-else>Unknown RAM</span>
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
<span v-if="productMetadata.cpu">{{ productMetadata.cpu }} vCPU</span>
|
||||
<span v-else>Unknown CPU</span>
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
<span v-if="productMetadata.storage">{{ productMetadata.storage / 1024 }}GB Storage</span>
|
||||
<span v-else>Unknown Storage</span>
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
<span v-if="productMetadata.swap">{{ productMetadata.swap }}MB Swap</span>
|
||||
<span v-else>Unknown Swap</span>
|
||||
</span>
|
||||
<span class="text-sm text-secondary">
|
||||
<span
|
||||
v-if="charge.status === 'cancelled' && dayjs(charge.due).isBefore(dayjs())"
|
||||
class="font-bold"
|
||||
>
|
||||
Ended:
|
||||
</span>
|
||||
<span v-else-if="charge.status === 'cancelled'" class="font-bold">Ends:</span>
|
||||
<span v-else-if="charge.type === 'refund'" class="font-bold">Issued:</span>
|
||||
<span v-else class="font-bold">Due:</span>
|
||||
{{ formatDateTime(charge.due) }}
|
||||
<span class="text-secondary">({{ formatRelativeTime(charge.due) }}) </span>
|
||||
</span>
|
||||
<span v-if="charge.last_attempt != null" class="text-sm text-secondary">
|
||||
<span v-if="charge.status === 'failed'" class="font-bold">Last attempt:</span>
|
||||
<span v-else class="font-bold">Charged:</span>
|
||||
{{ formatDateTime(charge.last_attempt) }}
|
||||
<span class="text-secondary">({{ formatRelativeTime(charge.last_attempt) }}) </span>
|
||||
</span>
|
||||
<div class="flex w-full items-center gap-1 text-xs text-secondary">
|
||||
{{ charge.status }}
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
{{ charge.type }}
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
{{ formatPrice(charge.amount, charge.currency_code) }}
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
|
||||
{{ formatDateTimeShort(charge.due) }}
|
||||
<template v-if="charge.subscription_interval">
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
{{ charge.subscription_interval }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled v-if="isRefunded">
|
||||
<div class="button-like disabled"><CheckIcon /> Charge refunded</div>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else-if="charge.status === 'succeeded' && charge.type !== 'refund'"
|
||||
color="red"
|
||||
color-fill="text"
|
||||
>
|
||||
<button @click="emit('refund', charge)">
|
||||
<CurrencyIcon />
|
||||
Refund options
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else-if="charge.status === 'failed' || charge.status === 'open'"
|
||||
color="red"
|
||||
color-fill="text"
|
||||
>
|
||||
<button @click="emit('modify', charge, subscription)">
|
||||
<CurrencyIcon />
|
||||
Modify charge
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { CheckIcon, CurrencyIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, useFormatDateTime, useFormatPrice, useRelativeTime } from '@modrinth/ui'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { products } from '~/generated/state.json'
|
||||
|
||||
const props = defineProps<{
|
||||
charge: Labrinth.Billing.Internal.Charge
|
||||
subscription: Labrinth.Billing.Internal.UserSubscription
|
||||
allCharges: Labrinth.Billing.Internal.Charge[]
|
||||
chargeIndex: number
|
||||
chargeCount: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
refund: [charge: Labrinth.Billing.Internal.Charge]
|
||||
modify: [
|
||||
charge: Labrinth.Billing.Internal.Charge,
|
||||
subscription: Labrinth.Billing.Internal.UserSubscription,
|
||||
]
|
||||
}>()
|
||||
|
||||
const formatPrice = useFormatPrice()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
const formatDateTimeShort = useFormatDateTime({
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
})
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
const isLatestCharge = computed(() => props.chargeIndex === props.chargeCount - 1)
|
||||
|
||||
const isRefunded = computed(() =>
|
||||
props.allCharges.some(
|
||||
(charge) => charge.type === 'refund' && charge.parent_charge_id === props.charge.id,
|
||||
),
|
||||
)
|
||||
|
||||
const productMetadata = computed(
|
||||
() =>
|
||||
products.find((product) => product.prices.some((price) => price.id === props.charge.price_id))
|
||||
?.metadata,
|
||||
)
|
||||
|
||||
const chargeStatuses = {
|
||||
open: {
|
||||
color: 'bg-blue',
|
||||
},
|
||||
processing: {
|
||||
color: 'bg-orange',
|
||||
},
|
||||
succeeded: {
|
||||
color: 'bg-green',
|
||||
},
|
||||
failed: {
|
||||
color: 'bg-red',
|
||||
},
|
||||
cancelled: {
|
||||
color: 'bg-red',
|
||||
},
|
||||
expiring: {
|
||||
color: 'bg-orange',
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -132,23 +132,39 @@ export function createLoaderParsers(
|
||||
),
|
||||
}
|
||||
},
|
||||
// Fabric
|
||||
// Fabric (or Babric for mc version beta 1.7.3)
|
||||
'fabric.mod.json': (file: string): InferredVersionInfo => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
|
||||
const detectedGameVersions = metadata.depends
|
||||
const mcDependency = metadata.depends?.minecraft
|
||||
const mcDependencies = Array.isArray(mcDependency) ? mcDependency : [mcDependency]
|
||||
|
||||
let detectedGameVersions = metadata.depends
|
||||
? getGameVersionsMatchingSemverRange(metadata.depends.minecraft, simplifiedGameVersions)
|
||||
: []
|
||||
const loaders: string[] = []
|
||||
|
||||
// Detect Beta 1.7.3 -> Babric
|
||||
const hasBabricVersion = mcDependencies.some(
|
||||
(version: string | undefined) => version?.includes('1.0.0-beta.7.3'), // this is fabric's normalized mc version format
|
||||
)
|
||||
|
||||
// Detect 1.3-1.13 -> legacy-fabric
|
||||
const hasLegacyVersions = detectedGameVersions.some((version) => {
|
||||
const match = version.match(/^1\.(\d+)/)
|
||||
return match && parseInt(match[1]) >= 3 && parseInt(match[1]) <= 13
|
||||
})
|
||||
|
||||
if (hasLegacyVersions) loaders.push('legacy-fabric')
|
||||
else loaders.push('fabric')
|
||||
if (hasBabricVersion) {
|
||||
loaders.push('babric')
|
||||
detectedGameVersions = gameVersions
|
||||
.filter((version) => version.version === 'b1.7.3')
|
||||
.map((version) => version.version)
|
||||
} else if (hasLegacyVersions) {
|
||||
loaders.push('legacy-fabric')
|
||||
} else {
|
||||
loaders.push('fabric')
|
||||
}
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
|
||||
@@ -50,15 +50,15 @@ export function getGameVersionsMatchingMavenRange(
|
||||
ranges.push(range)
|
||||
}
|
||||
|
||||
const LESS_THAN_EQUAL = /^\(,(.*)]$/
|
||||
const LESS_THAN = /^\(,(.*)\)$/
|
||||
const EQUAL = /^\[(.*)]$/
|
||||
const GREATER_THAN_EQUAL = /^\[(.*),\)$/
|
||||
const GREATER_THAN = /^\((.*),\)$/
|
||||
const BETWEEN = /^\((.*),(.*)\)$/
|
||||
const BETWEEN_EQUAL = /^\[(.*),(.*)]$/
|
||||
const BETWEEN_LESS_THAN_EQUAL = /^\((.*),(.*)]$/
|
||||
const BETWEEN_GREATER_THAN_EQUAL = /^\[(.*),(.*)\)$/
|
||||
const LESS_THAN_EQUAL = /^\(,([^,]*)]$/
|
||||
const LESS_THAN = /^\(,([^,]*)\)$/
|
||||
const EQUAL = /^\[([^,]*)]$/
|
||||
const GREATER_THAN_EQUAL = /^\[([^,]*),\)$/
|
||||
const GREATER_THAN = /^\(([^,]*),\)$/
|
||||
const BETWEEN = /^\(([^,]*),([^,]*)\)$/
|
||||
const BETWEEN_EQUAL = /^\[([^,]*),([^,]*)]$/
|
||||
const BETWEEN_LESS_THAN_EQUAL = /^\(([^,]*),([^,]*)]$/
|
||||
const BETWEEN_GREATER_THAN_EQUAL = /^\[([^,]*),([^,]*)\)$/
|
||||
|
||||
const semverRanges = []
|
||||
|
||||
|
||||
+13
-140
@@ -198,115 +198,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div
|
||||
<AdminBillingChargeCard
|
||||
v-for="(charge, index) in subscription.charges"
|
||||
:key="charge.id"
|
||||
class="relative overflow-clip rounded-xl bg-bg px-4 py-3"
|
||||
>
|
||||
<div
|
||||
class="absolute bottom-0 left-0 top-0 w-1"
|
||||
:class="
|
||||
charge.type === 'refund'
|
||||
? 'bg-purple'
|
||||
: (chargeStatuses[charge.status]?.color ?? 'bg-blue')
|
||||
"
|
||||
/>
|
||||
<div class="grid w-full grid-cols-[1fr_auto] items-center gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span>
|
||||
<span class="font-bold text-contrast">
|
||||
<template v-if="charge.status === 'succeeded'"> Succeeded </template>
|
||||
<template v-else-if="charge.status === 'failed'"> Failed </template>
|
||||
<template v-else-if="charge.status === 'cancelled'"> Cancelled </template>
|
||||
<template v-else-if="charge.status === 'processing'"> Processing </template>
|
||||
<template v-else-if="charge.status === 'open'"> Upcoming </template>
|
||||
<template v-else-if="charge.status === 'expiring'"> Expiring </template>
|
||||
<template v-else> {{ charge.status }} </template>
|
||||
</span>
|
||||
⋅
|
||||
<span>
|
||||
<template v-if="charge.type === 'refund'"> Refund </template>
|
||||
<template v-else-if="charge.type === 'subscription'">
|
||||
<template v-if="charge.status === 'cancelled'"> Subscription </template>
|
||||
<template v-else-if="index === subscription.charges.length - 1">
|
||||
Started subscription
|
||||
</template>
|
||||
<template v-else> Subscription renewal </template>
|
||||
</template>
|
||||
<template v-else-if="charge.type === 'one-time'"> One-time charge </template>
|
||||
<template v-else-if="charge.type === 'proration'"> Proration charge </template>
|
||||
<template v-else> {{ charge.status }} </template>
|
||||
</span>
|
||||
<template v-if="charge.status !== 'cancelled'">
|
||||
⋅
|
||||
{{ formatPrice(charge.amount, charge.currency_code) }}
|
||||
</template>
|
||||
</span>
|
||||
<span class="text-sm text-secondary">
|
||||
<span
|
||||
v-if="charge.status === 'cancelled' && $dayjs(charge.due).isBefore($dayjs())"
|
||||
class="font-bold"
|
||||
>
|
||||
Ended:
|
||||
</span>
|
||||
<span v-else-if="charge.status === 'cancelled'" class="font-bold">Ends:</span>
|
||||
<span v-else-if="charge.type === 'refund'" class="font-bold">Issued:</span>
|
||||
<span v-else class="font-bold">Due:</span>
|
||||
{{ formatDateTime(charge.due) }}
|
||||
<span class="text-secondary">({{ formatRelativeTime(charge.due) }}) </span>
|
||||
</span>
|
||||
<span v-if="charge.last_attempt != null" class="text-sm text-secondary">
|
||||
<span v-if="charge.status === 'failed'" class="font-bold">Last attempt:</span>
|
||||
<span v-else class="font-bold">Charged:</span>
|
||||
{{ formatDateTime(charge.last_attempt) }}
|
||||
<span class="text-secondary"
|
||||
>({{ formatRelativeTime(charge.last_attempt) }})
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex w-full items-center gap-1 text-xs text-secondary">
|
||||
{{ charge.status }}
|
||||
⋅
|
||||
{{ charge.type }}
|
||||
⋅
|
||||
{{ formatPrice(charge.amount, charge.currency_code) }}
|
||||
⋅
|
||||
{{ formatDateTimeShort(charge.due) }}
|
||||
<template v-if="charge.subscription_interval">
|
||||
⋅ {{ charge.subscription_interval }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled
|
||||
v-if="
|
||||
charges.some((x) => x.type === 'refund' && x.parent_charge_id === charge.id)
|
||||
"
|
||||
>
|
||||
<div class="button-like disabled"><CheckIcon /> Charge refunded</div>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else-if="charge.status === 'succeeded' && charge.type !== 'refund'"
|
||||
color="red"
|
||||
color-fill="text"
|
||||
>
|
||||
<button @click="showRefundModal(charge)">
|
||||
<CurrencyIcon />
|
||||
Refund options
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else-if="charge.status === 'failed' || charge.status === 'open'"
|
||||
color="red"
|
||||
color-fill="text"
|
||||
>
|
||||
<button @click="showModifyModal(charge, subscription)">
|
||||
<CurrencyIcon />
|
||||
Modify charge
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:charge="charge"
|
||||
:subscription="subscription"
|
||||
:all-charges="charges"
|
||||
:charge-index="index"
|
||||
:charge-count="subscription.charges.length"
|
||||
@refund="showRefundModal"
|
||||
@modify="showModifyModal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -334,7 +236,6 @@ import {
|
||||
StyledInput,
|
||||
Toggle,
|
||||
useFormatDateTime,
|
||||
useFormatPrice,
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
@@ -344,21 +245,14 @@ import { useQuery } from '@tanstack/vue-query'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import ModrinthServersIcon from '~/components/brand/ModrinthServersIcon.vue'
|
||||
import AdminBillingChargeCard from '~/components/ui/admin/AdminBillingChargeCard.vue'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { labrinth } = injectModrinthClient()
|
||||
const formatPrice = useFormatPrice()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
const formatDateTimeShort = useFormatDateTime({
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
})
|
||||
|
||||
const vintl = useVIntl()
|
||||
|
||||
@@ -372,15 +266,15 @@ const messages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const chargeId = useRouteId('charge')
|
||||
const userId = useRouteId('user')
|
||||
|
||||
const {
|
||||
data: user,
|
||||
error: userError,
|
||||
suspense: userSuspense,
|
||||
} = useQuery({
|
||||
queryKey: ['user', chargeId],
|
||||
queryFn: () => labrinth.users_v2.get(chargeId),
|
||||
queryKey: ['user', userId],
|
||||
queryFn: () => labrinth.users_v2.get(userId),
|
||||
})
|
||||
|
||||
onServerPrefetch(userSuspense)
|
||||
@@ -533,27 +427,6 @@ async function modifyCharge() {
|
||||
}
|
||||
modifying.value = false
|
||||
}
|
||||
|
||||
const chargeStatuses = {
|
||||
open: {
|
||||
color: 'bg-blue',
|
||||
},
|
||||
processing: {
|
||||
color: 'bg-orange',
|
||||
},
|
||||
succeeded: {
|
||||
color: 'bg-green',
|
||||
},
|
||||
failed: {
|
||||
color: 'bg-red',
|
||||
},
|
||||
cancelled: {
|
||||
color: 'bg-red',
|
||||
},
|
||||
expiring: {
|
||||
color: 'bg-orange',
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.page {
|
||||
@@ -1,7 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { GitGraphIcon, RssIcon } from '@modrinth/assets'
|
||||
import { articles as rawArticles } from '@modrinth/blog'
|
||||
import { Avatar, ButtonStyled, injectModrinthClient, useFormatDateTime } from '@modrinth/ui'
|
||||
import {
|
||||
ArticleBody,
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
injectModrinthClient,
|
||||
useFormatDateTime,
|
||||
} from '@modrinth/ui'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted } from 'vue'
|
||||
@@ -169,7 +175,7 @@ onMounted(() => {
|
||||
class="aspect-video w-full rounded-xl border-[1px] border-solid border-button-border object-cover sm:rounded-2xl"
|
||||
:alt="article.title"
|
||||
/>
|
||||
<div class="markdown-body" v-html="article.html" />
|
||||
<ArticleBody :html="article.html" />
|
||||
<h3
|
||||
class="mb-0 mt-4 border-0 border-t-[1px] border-solid border-divider pt-4 text-base font-extrabold sm:text-lg"
|
||||
>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
@@ -1,5 +1,12 @@
|
||||
{
|
||||
"articles": [
|
||||
{
|
||||
"title": "Modrinth joins Spark Universe",
|
||||
"summary": "The next chapter. What it means and why we think it’s right for Modrinth.",
|
||||
"thumbnail": "https://modrinth.com/news/article/joining-spark-universe/thumbnail.webp",
|
||||
"date": "2026-06-15T14:00:00.000Z",
|
||||
"link": "https://modrinth.com/news/article/joining-spark-universe"
|
||||
},
|
||||
{
|
||||
"title": "Manage servers together",
|
||||
"summary": "Add other users to your server, assign roles, and track what’s changed.",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user