Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7d4ce86
fix type errors
tdgao Mar 17, 2026
86dad83
fix some stylesheets not imported for storybook
tdgao Mar 17, 2026
4238185
add server listing stories
tdgao Mar 17, 2026
957e43a
add fix for frontend stylesheet imports
tdgao Mar 18, 2026
6f81061
remove props.
tdgao Mar 18, 2026
b7452dd
convert copy code to use tailwind
tdgao Mar 18, 2026
166a47c
update server listing component styles
tdgao Mar 18, 2026
83df48a
update server info label styles
tdgao Mar 18, 2026
2386b4d
start status/player count info label, more style updates and fixes
tdgao Mar 18, 2026
313ff30
Merge branch 'main' into truman/new-server-card-states
tdgao Mar 18, 2026
d37c1bd
add new server card buttons
tdgao Mar 18, 2026
0a05061
hook up server cards and implement updated styles
tdgao Mar 20, 2026
672757c
hook up on download button
tdgao Mar 20, 2026
83202a3
fix tauri throwing error when api returns 204 No Content
tdgao Mar 20, 2026
345929e
hook up purchase server modal in app
tdgao Mar 20, 2026
fb1445c
fix upgrading state loading icon
tdgao Mar 23, 2026
504ba7f
pnpm prepr
tdgao Mar 23, 2026
d01e9d7
filter out servers past 30 days after cancellation
tdgao Mar 23, 2026
1999e79
do not apply opacity on lock or spiner icons
tdgao Mar 23, 2026
eb82562
fix disabled server icon background
tdgao Mar 23, 2026
ef6837d
update pending change stage
tdgao Mar 23, 2026
8d61bc7
handle known suspension states
tdgao Mar 23, 2026
0abdefd
refactor: reduce code duplication for server listing
tdgao Mar 23, 2026
324da50
update disabled state text color
tdgao Mar 23, 2026
1771009
fix loading icon color
tdgao Mar 24, 2026
bc2fa1e
clean up copy
tdgao Mar 24, 2026
2ab69ea
fix disabled opacity for server card
tdgao Mar 24, 2026
d2ec943
update server listing files kept to be countdown
tdgao Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@
},
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
}
},
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore"
}
14 changes: 7 additions & 7 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -970,13 +970,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<NavButton v-if="themeStore.featureFlags.worlds_tab" v-tooltip.right="'Worlds'" to="/worlds">
<WorldIcon />
</NavButton>
<NavButton
v-if="themeStore.featureFlags.servers_in_app"
v-tooltip.right="'Servers'"
to="/hosting/manage"
>
<ServerIcon />
</NavButton>
<NavButton
v-tooltip.right="'Discover content'"
to="/browse/modpack"
Expand All @@ -1000,6 +993,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
>
<LibraryIcon />
</NavButton>
<NavButton
v-if="themeStore.featureFlags.servers_in_app"
v-tooltip.right="'Servers'"
to="/hosting/manage"
>
<ServerIcon />
</NavButton>
<div class="h-px w-6 mx-auto my-2 bg-surface-5"></div>
<suspense>
<QuickInstanceSwitcher />
Expand Down
13 changes: 8 additions & 5 deletions apps/app-frontend/src/assets/stylesheets/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,8 @@ body {
}

a {
color: var(--color-link);
color: inherit;
text-decoration: none;

&:hover {
text-decoration: none;
}
}

.badge {
Expand Down Expand Up @@ -174,4 +170,11 @@ img {
}
}

button,
input[type='button'] {
cursor: pointer;
border: none;
outline: 2px solid transparent;
}

@import '@modrinth/assets/omorphia.scss';
7 changes: 7 additions & 0 deletions apps/app-frontend/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// src/config.ts
export const config = {
siteUrl: import.meta.env.VITE_SITE_URL,
stripePublishableKey:
import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY ||
'pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b',
}
27 changes: 27 additions & 0 deletions apps/app-frontend/src/pages/Servers.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { injectModrinthClient, ServersManagePageIndex } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import { computed } from 'vue'

import { config } from '../config'

const stripePublishableKey = (config.stripePublishableKey as string) || ''

const client = injectModrinthClient()

const { data: products } = useQuery({
queryKey: ['billing', 'products'],
queryFn: () => client.labrinth.billing_internal.getProducts(),
})

const resolvedProducts = computed<Labrinth.Billing.Internal.Product[]>(() => products.value ?? [])
</script>

<template>
<ServersManagePageIndex
:stripe-publishable-key="stripePublishableKey"
:site-url="'https://modrinth.com'"
:products="resolvedProducts"
/>
</template>
3 changes: 2 additions & 1 deletion apps/app-frontend/src/pages/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Browse from './Browse.vue'
import Index from './Index.vue'
import Servers from './Servers.vue'
import Skins from './Skins.vue'
import Worlds from './Worlds.vue'

export { Browse, Index, Skins, Worlds }
export { Browse, Index, Servers, Skins, Worlds }
3 changes: 1 addition & 2 deletions apps/app-frontend/src/routes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ServersManagePageIndex } from '@modrinth/ui'
import { createRouter, createWebHistory } from 'vue-router'

import * as Pages from '@/pages'
Expand Down Expand Up @@ -31,7 +30,7 @@ export default new createRouter({
{
path: '/hosting/manage/',
name: 'Servers',
component: ServersManagePageIndex,
component: Pages.Servers,
meta: {
breadcrumb: [{ name: 'Servers' }],
},
Expand Down
2 changes: 2 additions & 0 deletions apps/app-frontend/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

"strict": true,

"types": ["vite/client"],

"paths": {
"@/*": ["./src/*"]
}
Expand Down
6 changes: 3 additions & 3 deletions apps/app/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@
"capabilities": ["ads", "core", "plugins"],
"csp": {
"default-src": "'self' customprotocol: asset:",
"connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://api.mclo.gs http://textures.minecraft.net https://textures.minecraft.net 'self' data: blob:",
"connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://posthog.modrinth.com https://*.sentry.io https://api.mclo.gs http://textures.minecraft.net https://textures.minecraft.net https://js.stripe.com https://*.stripe.com wss://*.stripe.com wss://*.nodes.modrinth.com 'self' data: blob:",
"font-src": ["https://cdn-raw.modrinth.com/fonts/"],
"img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost http://textures.minecraft.net blob: data:",
"style-src": "'unsafe-inline' 'self'",
"script-src": "https://*.posthog.com https://tally.so/widgets/embed.js 'self'",
"frame-src": "https://www.youtube.com https://www.youtube-nocookie.com https://discord.com https://tally.so/popup/ 'self'",
"script-src": "https://*.posthog.com https://posthog.modrinth.com https://js.stripe.com https://tally.so/widgets/embed.js 'self'",
"frame-src": "https://www.youtube.com https://www.youtube-nocookie.com https://discord.com https://tally.so/popup/ https://js.stripe.com https://hooks.stripe.com 'self'",
"media-src": "https://*.githubusercontent.com"
}
}
Expand Down
6 changes: 3 additions & 3 deletions apps/frontend/src/assets/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,9 @@ kbd {
font-size: 0.85em !important;
}

@import '~/assets/styles/layout.scss';
@import '~/assets/styles/utils.scss';
@import '~/assets/styles/components.scss';
@import './layout.scss';
@import './utils.scss';
@import './components.scss';

// OMORPHIA FIXES
.card {
Expand Down
5 changes: 3 additions & 2 deletions apps/frontend/src/pages/hosting/manage/[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
:show-loader-label="showLoaderLabel"
:uptime-seconds="uptimeSeconds"
:linked="true"
class="server-action-buttons-anim flex min-w-0 flex-col flex-wrap items-center gap-4 text-secondary *:hidden sm:flex-row sm:*:flex"
class="server-action-buttons-anim flex min-w-0 flex-col flex-wrap items-center gap-2 text-primary *:hidden sm:flex-row sm:*:flex"
/>
</div>
</div>
Expand Down Expand Up @@ -1135,7 +1135,8 @@ const handleInstallationResult = async (data: Archon.Websocket.v0.WSInstallation
}

const updateStats = (currentStats: Stats['current']) => {
isConnected.value = true
if (!isMounted.value) return
if (!isConnected.value) isConnected.value = true
stats.value = {
current: currentStats,
past: { ...stats.value.current },
Expand Down
84 changes: 76 additions & 8 deletions apps/frontend/src/pages/settings/billing/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@
v-if="subscription.serverInfo"
v-bind="subscription.serverInfo"
:pending-change="getPendingChange(subscription)"
:cancellation-date="getCancellationDate(subscription)"
:on-download-backup="getLatestBackupDownload(subscription.serverInfo)"
/>
<div v-else class="w-fit">
<p>
Expand Down Expand Up @@ -764,6 +766,11 @@ const { data: serversData } = useQuery({
queryFn: () => client.archon.servers_v0.list(),
})

const { data: serverFullList } = useQuery({
queryKey: ['servers', 'v1'],
queryFn: () => client.archon.servers_v1.list(),
})

const midasProduct = ref(products.find((x) => x.metadata?.type === 'midas'))
const midasSubscription = computed(() =>
subscriptions.value?.find(
Expand Down Expand Up @@ -793,13 +800,27 @@ const pyroSubscriptions = computed(() => {
const pyroSubs = subscriptions.value?.filter((s) => s?.metadata?.type === 'pyro') || []
const servers = serversData.value?.servers || []

return pyroSubs.map((subscription) => {
const server = servers.find((s) => s.server_id === subscription.metadata.id)
return {
...subscription,
serverInfo: server,
}
})
return pyroSubs
.map((subscription) => {
const server = servers.find((s) => s.server_id === subscription.metadata.id)
return {
...subscription,
serverInfo: server,
}
})
.filter((subscription) => {
// files expire 30 days after cancellation
const cancellationDate = getCancellationDate(subscription)
if (
!cancellationDate ||
subscription.serverInfo?.status !== 'suspended' ||
subscription.serverInfo?.suspension_reason !== 'cancelled'
)
return true
const cancellation = new Date(cancellationDate)
const thirtyDaysLater = new Date(cancellation.getTime() + 30 * 24 * 60 * 60 * 1000)
return new Date() <= thirtyDaysLater
})
})

const midasPurchaseModal = ref()
Expand Down Expand Up @@ -912,13 +933,20 @@ const getProductFromPriceId = (priceId) => {
return productsData.value.find((p) => p.prices?.some((x) => x.id === priceId))
}

const getPyroCharge = (subscription) => {
function getPyroCharge(subscription) {
if (!subscription || !charges.value) return null
return charges.value.find(
(charge) => charge.subscription_id === subscription.id && charge.status !== 'succeeded',
)
}

function getCancellationDate(subscription) {
const charge = getPyroCharge(subscription)
if (!charge) return null
if (charge.status === 'cancelled') return charge.due
return null
}

const getProductSize = (product) => {
if (!product || !product.metadata) return 'Unknown'
const ramSize = product.metadata.ram
Expand Down Expand Up @@ -982,6 +1010,46 @@ const resubscribePyro = async (subscriptionId, wasSuspended) => {
}
}

function getLatestBackupDownload(serverInfo) {
const serverFull = serverFullList.value?.find((s) => s.id === serverInfo.server_id)
if (!serverFull) return null

const activeWorld = serverFull.worlds.find((w) => w.is_active) ?? serverFull.worlds[0]
if (!activeWorld?.backups?.length) return null

const latestBackup = activeWorld.backups
.filter((b) => b.status === 'done')
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0]
if (!latestBackup) return null

return async () => {
try {
const server = await client.archon.servers_v0.get(serverInfo.server_id)
const kyrosUrl = server.node?.instance
const jwt = server.node?.token
if (!kyrosUrl || !jwt) {
addNotification({
title: 'Download unavailable',
text: 'Server connection info is not available. Please contact support.',
type: 'error',
})
return
}

window.open(
`https://${kyrosUrl}/modrinth/v0/backups/${latestBackup.id}/download?auth=${jwt}`,
'_blank',
)
} catch {
addNotification({
title: 'Download failed',
text: 'An error occurred while trying to download the backup.',
type: 'error',
})
}
}
}

const refresh = async () => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ['billing'] }),
Expand Down
7 changes: 5 additions & 2 deletions packages/api-client/src/platform/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,11 @@ export class TauriModrinthClient extends XHRUploadClient {
throw error
}

const data = await response.json()
return data as T
const text = await response.text()
if (!text) {
return undefined as T
}
return JSON.parse(text) as T
} catch (error) {
throw this.normalizeError(error)
}
Expand Down
Loading
Loading