feat: frontend explicit imports + error page fix (#4184)

* feat: frontend explicit imports

* fix: error handling

* fix: dashboard missing import

* fix: error page issues

* fix: exclude RouterView

* feat: fix lint issues

* fix: lint issues

* fix: import issues

* add getVersionLink

* make articles.json use tabs on generation so it doesn't have to be reformatted

* fix: lint issues

---------

Signed-off-by: Cal H. <hendersoncal117@gmail.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Cal H.
2025-08-17 12:15:49 +01:00
committed by GitHub
parent 74d2d85cb5
commit 3e735b99eb
54 changed files with 1295 additions and 1020 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -82,6 +82,7 @@
<script setup>
import { CheckIcon, XIcon } from '@modrinth/assets'
import { Avatar, Button, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import { useAuth } from '@/composables/auth.js'
import { useScopes } from '@/composables/auth/scopes.ts'

View File

@@ -141,6 +141,7 @@ import {
SSOSteamIcon,
} from '@modrinth/assets'
import { commonMessages, injectNotificationManager } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import HCaptcha from '@/components/ui/HCaptcha.vue'

View File

@@ -146,6 +146,7 @@ import {
UserIcon,
} from '@modrinth/assets'
import { Checkbox, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import HCaptcha from '@/components/ui/HCaptcha.vue'

View File

@@ -50,6 +50,7 @@
<script setup>
import { RightArrowIcon, WavingRinthbot } from '@modrinth/assets'
import { Checkbox, commonMessages } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
const route = useRoute()

View File

@@ -407,6 +407,7 @@ import {
useRelativeTime,
} from '@modrinth/ui'
import { isAdmin } from '@modrinth/utils'
import { IntlFormatted } from '@vintl/vintl/components'
import UpToDate from 'assets/images/illustrations/up_to_date.svg'
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'

View File

@@ -292,6 +292,7 @@ import {
} from '@modrinth/ui'
import type { Project, Report, User, Version } from '@modrinth/utils'
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
import { IntlFormatted } from '@vintl/vintl/components'
import { useImageUpload } from '~/composables/image-upload.ts'

View File

@@ -116,7 +116,7 @@
}"
>
<div class="flex w-full min-w-0 select-none flex-col items-center gap-6 pt-4 sm:flex-row">
<UiServersServerIcon :image="serverData.image" class="drop-shadow-lg sm:drop-shadow-none" />
<ServerIcon :image="serverData.image" class="drop-shadow-lg sm:drop-shadow-none" />
<div
class="flex min-w-0 flex-1 flex-col-reverse items-center gap-2 sm:flex-col sm:items-start"
>
@@ -137,7 +137,7 @@
data-pyro-server-action-buttons
class="server-action-buttons-anim flex w-fit flex-shrink-0"
>
<UiServersPanelServerActionButton
<PanelServerActionButton
v-if="!serverData.flows?.intro"
class="flex-shrink-0"
:is-online="isServerRunning"
@@ -158,7 +158,7 @@
>
<SettingsIcon /> Configuring server...
</div>
<UiServersServerInfoLabels
<ServerInfoLabels
v-else
:server-data="serverData"
:show-game-label="showGameLabel"
@@ -175,7 +175,7 @@
v-if="serverData?.status === 'installing'"
class="w-50 h-50 flex items-center justify-center gap-2 text-center text-lg font-bold"
>
<LazyUiServersPanelSpinner class="size-10 animate-spin" /> Setting up your server...
<PanelSpinner class="size-10 animate-spin" /> Setting up your server...
</div>
<div v-else>
<h2 class="my-4 text-xl font-extrabold">
@@ -196,7 +196,7 @@
data-pyro-navigation
class="isolate flex w-full select-none flex-col justify-between gap-4 overflow-auto md:flex-row md:items-center"
>
<UiNavTabs :links="navLinks" />
<NavTabs :links="navLinks" />
</div>
<div data-pyro-mount class="h-full w-full flex-1">
@@ -304,7 +304,7 @@
data-pyro-server-ws-reconnecting
class="mb-4 flex w-full flex-row items-center gap-4 rounded-2xl bg-bg-orange p-4 text-sm text-contrast"
>
<UiServersPanelSpinner />
<PanelSpinner />
Hang on, we're reconnecting to your server.
</div>
@@ -313,13 +313,13 @@
data-pyro-server-installing
class="mb-4 flex w-full flex-row items-center gap-4 rounded-2xl bg-bg-blue p-4 text-sm text-contrast"
>
<UiServersServerIcon :image="serverData.image" class="!h-10 !w-10" />
<ServerIcon :image="serverData.image" class="!h-10 !w-10" />
<div class="flex flex-col gap-1">
<span class="text-lg font-bold"> We're preparing your server! </span>
<div class="flex flex-row items-center gap-2">
<UiServersPanelSpinner class="!h-3 !w-3" />
<LazyUiServersInstallingTicker />
<PanelSpinner class="!h-3 !w-3" />
<InstallingTicker />
</div>
</div>
</div>
@@ -382,7 +382,13 @@ import DOMPurify from 'dompurify'
import { computed, onMounted, onUnmounted, type Reactive, ref } from 'vue'
import { reloadNuxtApp } from '#app'
import NavTabs from '~/components/ui/NavTabs.vue'
import PanelErrorIcon from '~/components/ui/servers/icons/PanelErrorIcon.vue'
import InstallingTicker from '~/components/ui/servers/InstallingTicker.vue'
import PanelServerActionButton from '~/components/ui/servers/PanelServerActionButton.vue'
import PanelSpinner from '~/components/ui/servers/PanelSpinner.vue'
import ServerIcon from '~/components/ui/servers/ServerIcon.vue'
import ServerInfoLabels from '~/components/ui/servers/ServerInfoLabels.vue'
import ServerInstallation from '~/components/ui/servers/ServerInstallation.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { useModrinthServers } from '~/composables/servers/modrinth-servers.ts'

View File

@@ -1,5 +1,5 @@
<template>
<UiServersContentVersionEditModal
<ContentVersionEditModal
v-if="!invalidModal"
ref="versionEditModal"
:type="type"
@@ -59,7 +59,7 @@
/>
</div>
<ButtonStyled>
<UiServersTeleportOverflowMenu
<TeleportOverflowMenu
position="bottom"
direction="left"
:aria-label="`Filter ${type}s`"
@@ -77,8 +77,8 @@
<template #all> All {{ type.toLocaleLowerCase() }}s </template>
<template #enabled> Only enabled </template>
<template #disabled> Only disabled </template>
</UiServersTeleportOverflowMenu>
</ButtonStyled>
</TeleportOverflowMenu></ButtonStyled
>
</div>
<div v-if="hasMods" class="flex w-full items-center gap-2 sm:w-fit">
<ButtonStyled>
@@ -202,7 +202,7 @@
@click="showVersionModal(mod)"
>
<template v-if="mod.changing">
<UiServersIconsLoadingIcon class="animate-spin" />
<LoadingIcon class="animate-spin" />
</template>
<template v-else>
<EditIcon />
@@ -212,13 +212,13 @@
<!-- Dropdown for mobile -->
<div class="mr-2 flex items-center sm:hidden">
<UiServersIconsLoadingIcon
<LoadingIcon
v-if="mod.changing"
class="mr-2 h-5 w-5 animate-spin"
style="color: var(--color-base)"
/>
<ButtonStyled v-else circular type="transparent">
<UiServersTeleportOverflowMenu
<TeleportOverflowMenu
:options="[
{
id: 'edit',
@@ -240,8 +240,8 @@
<TrashIcon class="h-5 w-5" />
<span>Delete</span>
</template>
</UiServersTeleportOverflowMenu>
</ButtonStyled>
</TeleportOverflowMenu></ButtonStyled
>
</div>
<input
@@ -312,7 +312,7 @@
</div>
</div>
<div v-else class="mt-4 flex h-full flex-col items-center justify-center gap-4 text-center">
<UiServersIconsLoaderIcon loader="Vanilla" class="size-24" />
<LoaderIcon loader="Vanilla" class="size-24" />
<p class="m-0 pt-3 font-bold text-contrast">Your server is running Vanilla Minecraft</p>
<p class="m-0">
Add content to your server by installing a modpack or choosing a different platform that
@@ -359,8 +359,11 @@ import { Avatar, ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import type { Mod } from '@modrinth/utils'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import ContentVersionEditModal from '~/components/ui/servers/ContentVersionEditModal.vue'
import FilesUploadDragAndDrop from '~/components/ui/servers/FilesUploadDragAndDrop.vue'
import FilesUploadDropdown from '~/components/ui/servers/FilesUploadDropdown.vue'
import LoaderIcon from '~/components/ui/servers/icons/LoaderIcon.vue'
import LoadingIcon from '~/components/ui/servers/icons/LoadingIcon.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { acceptFileFromProjectType } from '~/helpers/fileUtils.js'

View File

@@ -1,31 +1,19 @@
<template>
<div data-pyro-file-manager-root class="contents">
<LazyUiServersFilesCreateItemModal
ref="createItemModal"
:type="newItemType"
@create="handleCreateNewItem"
/>
<FilesCreateItemModal ref="createItemModal" :type="newItemType" @create="handleCreateNewItem" />
<FilesUploadZipUrlModal ref="uploadZipModal" :server="server" />
<FilesUploadConflictModal ref="uploadConflictModal" @proceed="extractItem" />
<LazyUiServersFilesRenameItemModal
ref="renameItemModal"
:item="selectedItem"
@rename="handleRenameItem"
/>
<FilesRenameItemModal ref="renameItemModal" :item="selectedItem" @rename="handleRenameItem" />
<LazyUiServersFilesMoveItemModal
<FilesMoveItemModal
ref="moveItemModal"
:item="selectedItem"
:current-path="currentPath"
@move="handleMoveItem"
/>
<LazyUiServersFilesDeleteItemModal
ref="deleteItemModal"
:item="selectedItem"
@delete="handleDeleteItem"
/>
<FilesDeleteItemModal ref="deleteItemModal" :item="selectedItem" @delete="handleDeleteItem" />
<FilesUploadDragAndDrop
class="relative flex w-full flex-col rounded-2xl border border-solid border-bg-raised"
@@ -33,7 +21,7 @@
>
<div ref="mainContent" class="relative isolate flex w-full flex-col">
<div v-if="!isEditing" class="contents">
<UiServersFilesBrowseNavbar
<FilesBrowseNavbar
:breadcrumb-segments="breadcrumbSegments"
:search-query="searchQuery"
:current-filter="viewFilter"
@@ -46,11 +34,7 @@
@filter="handleFilter"
@update:search-query="searchQuery = $event"
/>
<UiServersFilesLabelBar
:sort-field="sortMethod"
:sort-desc="sortDesc"
@sort="handleSort"
/>
<FilesLabelBar :sort-field="sortMethod" :sort-desc="sortDesc" @sort="handleSort" />
<div
v-for="op in ops"
:key="`fs-op-${op.op}-${op.src}`"
@@ -172,7 +156,7 @@
@upload-complete="refreshList()"
/>
</div>
<UiServersFilesEditingNavbar
<FilesEditingNavbar
v-else
:file-name="editingFile?.name"
:is-image="isEditingImage"
@@ -211,10 +195,10 @@
class="ace_editor ace_hidpi ace-one-dark ace_dark rounded-b-lg"
@init="onInit"
/>
<UiServersFilesImageViewer v-else :image-blob="imagePreview" />
<FilesImageViewer v-else :image-blob="imagePreview" />
</div>
<div v-else-if="items.length > 0" class="h-full w-full overflow-hidden rounded-b-2xl">
<UiServersFileVirtualList
<FileVirtualList
:items="filteredItems"
@extract="handleExtractItem"
@delete="showDeleteModal"
@@ -239,7 +223,7 @@
</div>
</div>
<LazyUiServersFileManagerError
<FileManagerError
v-else-if="loadError"
title="Unable to load files"
message="The folder may not exist."
@@ -259,7 +243,7 @@
</div>
</FilesUploadDragAndDrop>
<UiServersFilesContextMenu
<FilesContextMenu
ref="contextMenu"
:item="contextMenuInfo.item"
:x="contextMenuInfo.x"
@@ -289,10 +273,21 @@ import { formatBytes, ModrinthServersFetchError } from '@modrinth/utils'
import { useInfiniteScroll } from '@vueuse/core'
import { computed } from 'vue'
import FileManagerError from '~/components/ui/servers/FileManagerError.vue'
import FilesBrowseNavbar from '~/components/ui/servers/FilesBrowseNavbar.vue'
import FilesContextMenu from '~/components/ui/servers/FilesContextMenu.vue'
import FilesCreateItemModal from '~/components/ui/servers/FilesCreateItemModal.vue'
import FilesDeleteItemModal from '~/components/ui/servers/FilesDeleteItemModal.vue'
import FilesEditingNavbar from '~/components/ui/servers/FilesEditingNavbar.vue'
import FilesImageViewer from '~/components/ui/servers/FilesImageViewer.vue'
import FilesLabelBar from '~/components/ui/servers/FilesLabelBar.vue'
import FilesMoveItemModal from '~/components/ui/servers/FilesMoveItemModal.vue'
import FilesRenameItemModal from '~/components/ui/servers/FilesRenameItemModal.vue'
import FilesUploadConflictModal from '~/components/ui/servers/FilesUploadConflictModal.vue'
import FilesUploadDragAndDrop from '~/components/ui/servers/FilesUploadDragAndDrop.vue'
import FilesUploadDropdown from '~/components/ui/servers/FilesUploadDropdown.vue'
import FilesUploadZipUrlModal from '~/components/ui/servers/FilesUploadZipUrlModal.vue'
import FileVirtualList from '~/components/ui/servers/FileVirtualList.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { handleServersError } from '~/composables/servers/modrinth-servers.ts'

View File

@@ -75,7 +75,7 @@
</div>
<div class="flex flex-col-reverse gap-6 md:flex-col">
<UiServersServerStats
<ServerStats
:data="isConnected && !isWsAuthIncorrect ? stats : undefined"
:loading="!isConnected || isWsAuthIncorrect"
/>
@@ -87,17 +87,11 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<h2 class="m-0 text-3xl font-extrabold text-contrast">Console</h2>
<UiServersPanelServerStatus
v-if="isConnected && !isWsAuthIncorrect"
:state="serverPowerState"
/>
<PanelServerStatus v-if="isConnected && !isWsAuthIncorrect" :state="serverPowerState" />
</div>
</div>
<UiServersPanelTerminal
:full-screen="fullScreen"
:loading="!isConnected || isWsAuthIncorrect"
>
<PanelTerminal :full-screen="fullScreen" :loading="!isConnected || isWsAuthIncorrect">
<div class="relative w-full px-4 pt-4">
<ul
v-if="suggestions.length && isConnected && !isWsAuthIncorrect"
@@ -169,7 +163,7 @@
/>
</div>
</div>
</UiServersPanelTerminal>
</PanelTerminal>
</div>
</div>
@@ -191,6 +185,9 @@ import { IssuesIcon, TerminalSquareIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import type { ServerState, Stats } from '@modrinth/utils'
import PanelServerStatus from '~/components/ui/servers/PanelServerStatus.vue'
import PanelTerminal from '~/components/ui/servers/PanelTerminal.vue'
import ServerStats from '~/components/ui/servers/ServerStats.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
type ServerProps = {

View File

@@ -1,5 +1,5 @@
<template>
<UiServersServerSidebar
<ServerSidebar
:route="route"
:nav-links="navLinks"
:server="server"
@@ -20,6 +20,7 @@ import {
} from '@modrinth/assets'
import { isAdmin as isUserAdmin, type User } from '@modrinth/utils'
import ServerSidebar from '~/components/ui/servers/ServerSidebar.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import type { BackupInProgressReason } from '~/pages/servers/manage/[id].vue'

View File

@@ -88,7 +88,7 @@
>
<EditIcon class="h-8 w-8 text-contrast" />
</div>
<UiServersServerIcon :image="icon" />
<ServerIcon :image="icon" />
</div>
<ButtonStyled>
<button v-tooltip="'Synchronize icon with installed modpack'" @click="resetIcon">
@@ -101,7 +101,7 @@
</div>
</div>
<div v-else />
<UiServersSaveBanner
<SaveBanner
:is-visible="!!hasUnsavedChanges && !!isValidServerName"
:server="props.server"
:is-updating="isUpdating"
@@ -116,6 +116,8 @@ import { EditIcon, TransferIcon } from '@modrinth/assets'
import { injectNotificationManager } from '@modrinth/ui'
import ButtonStyled from '@modrinth/ui/src/components/base/ButtonStyled.vue'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import ServerIcon from '~/components/ui/servers/ServerIcon.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -251,7 +251,7 @@
</div>
</div>
</div>
<UiServersSaveBanner
<SaveBanner
:is-visible="!!hasUnsavedChanges && !!isValidSubdomain"
:server="props.server"
:is-updating="isUpdating"
@@ -282,6 +282,7 @@ import {
} from '@modrinth/ui'
import { computed, nextTick, ref } from 'vue'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -31,7 +31,7 @@
</div>
</div>
</div>
<UiServersSaveBanner
<SaveBanner
:is-visible="hasUnsavedChanges"
:server="props.server"
:is-updating="false"
@@ -45,6 +45,7 @@
import { injectNotificationManager } from '@modrinth/ui'
import { useStorage } from '@vueuse/core'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -131,7 +131,7 @@
</p>
</div>
<UiServersSaveBanner
<SaveBanner
:is-visible="hasUnsavedChanges"
:server="props.server"
:is-updating="isUpdating"
@@ -144,10 +144,11 @@
<script setup lang="ts">
import { EyeIcon, IssuesIcon, SearchIcon } from '@modrinth/assets'
import { injectNotificationManager } from '@modrinth/ui'
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import Fuse from 'fuse.js'
import { computed, inject, ref, watch } from 'vue'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -101,7 +101,7 @@
</div>
</div>
</div>
<UiServersSaveBanner
<SaveBanner
:is-visible="!!hasUnsavedChanges"
:server="props.server"
:is-updating="isUpdating"
@@ -115,6 +115,7 @@
import { IssuesIcon, UpdatedIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -53,7 +53,7 @@
</div>
</div>
<LazyUiServersServerManageEmptyState
<ServerManageEmptyState
v-else-if="serverList.length === 0 && !isPollingForNewServers && !hasError"
/>
@@ -93,12 +93,8 @@
v-if="filteredData.length > 0 || isPollingForNewServers"
class="m-0 flex flex-col gap-4 p-0"
>
<UiServersServerListing
v-for="server in filteredData"
:key="server.server_id"
v-bind="server"
/>
<LazyUiServersServerListingSkeleton v-if="isPollingForNewServers" />
<ServerListing v-for="server in filteredData" :key="server.server_id" v-bind="server" />
<ServerListingSkeleton v-if="isPollingForNewServers" />
</ul>
<div v-else class="flex h-full items-center justify-center">
<p class="text-contrast">No servers found.</p>
@@ -115,6 +111,9 @@ import Fuse from 'fuse.js'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { reloadNuxtApp } from '#app'
import ServerListing from '~/components/ui/servers/ServerListing.vue'
import ServerListingSkeleton from '~/components/ui/servers/ServerListingSkeleton.vue'
import ServerManageEmptyState from '~/components/ui/servers/ServerManageEmptyState.vue'
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
definePageMeta({

View File

@@ -205,12 +205,9 @@
>
<div class="flex flex-col justify-between gap-4">
<div class="flex flex-col gap-4">
<LazyUiServersModrinthServersIcon class="flex h-8 w-fit" />
<ModrinthServersIcon class="flex h-8 w-fit" />
<div class="flex flex-col gap-2">
<UiServersServerListing
v-if="subscription.serverInfo"
v-bind="subscription.serverInfo"
/>
<ServerListing v-if="subscription.serverInfo" v-bind="subscription.serverInfo" />
<div v-else class="w-fit">
<p>
A linked server couldn't be found for this subscription. There are a few possible
@@ -588,6 +585,8 @@ import {
import { calculateSavings, formatPrice, getCurrency } from '@modrinth/utils'
import { computed, ref } from 'vue'
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
import ServerListing from '~/components/ui/servers/ServerListing.vue'
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
import { products } from '~/generated/state.json'

View File

@@ -207,6 +207,8 @@
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
import { Button, injectNotificationManager, ThemeSelector } from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { IntlFormatted } from '@vintl/vintl/components'
import MessageBanner from '~/components/ui/MessageBanner.vue'
import type { DisplayLocation } from '~/plugins/cosmetics'

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { IssuesIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
import { commonSettingsMessages } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import Fuse from 'fuse.js/dist/fuse.basic'
import { isModifierKeyDown } from '~/helpers/events.ts'

View File

@@ -212,6 +212,7 @@ import {
injectNotificationManager,
useRelativeTime,
} from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import Modal from '~/components/ui/Modal.vue'
import {

View File

@@ -92,6 +92,7 @@
<script setup>
import { SaveIcon, TrashIcon, UndoIcon, UploadIcon, UserIcon, XIcon } from '@modrinth/assets'
import { Avatar, Button, commonMessages, FileInput, injectNotificationManager } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()

View File

@@ -366,6 +366,7 @@ import {
OverflowMenu,
useRelativeTime,
} from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import TenMClubBadge from '~/assets/images/badges/10m-club.svg?component'
import AlphaTesterBadge from '~/assets/images/badges/alpha-tester.svg?component'