feat: finish server card layout

This commit is contained in:
Calum H. 2025-08-05 12:42:53 +01:00
parent 0b3cbbd37a
commit e010e617c1
3 changed files with 126 additions and 54 deletions

View File

@ -1,63 +1,92 @@
<template>
<NuxtLink
class="contents"
:to="status === 'suspended' ? '' : `/servers/manage/${props.server_id}`"
>
<div>
<div
class="medal-promotion flex flex-row items-center overflow-x-hidden rounded-2xl p-4 shadow-xl transition-transform duration-100"
:class="status === 'suspended' ? '!rounded-b-none border-b-0 opacity-75' : 'active:scale-95'"
:class="status === 'suspended' ? '!rounded-b-none border-b-0 opacity-75' : ''"
data-pyro-server-listing
:data-pyro-server-listing-id="server_id"
>
<div class="overlay"></div>
<MedalPromoBackground class="background-pattern" />
<MedalServerIcon
v-if="status !== 'suspended'"
class="z-10 size-16 rounded-xl bg-bg text-orange"
/>
<div
v-else
class="bg-bg-secondary z-10 flex size-16 items-center justify-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg shadow-sm"
<NuxtLink
:to="status === 'suspended' ? '' : `/servers/manage/${props.server_id}`"
class="z-10 flex flex-grow flex-row items-center overflow-x-hidden"
:class="status !== 'suspended' && 'active:scale-95'"
>
<LockIcon class="size-12 text-secondary" />
</div>
<div class="z-10 ml-4 flex flex-col gap-2.5">
<div class="flex flex-row items-center gap-2">
<h2 class="m-0 text-xl font-bold text-contrast">{{ name }}</h2>
<ChevronRightIcon />
<span>{{ timeLeftCountdown }}</span>
</div>
<div
v-if="projectData?.title"
class="m-0 flex flex-row items-center gap-2 text-sm font-medium text-secondary"
>
<Avatar
:src="iconUrl"
no-shadow
style="min-height: 20px; min-width: 20px; height: 20px; width: 20px"
alt="Server Icon"
/>
Using {{ projectData?.title || "Unknown" }}
</div>
<div
v-if="isConfiguring"
class="flex min-w-0 items-center gap-2 truncate text-sm font-semibold text-brand"
>
<SparklesIcon class="size-5 shrink-0" /> New server
</div>
<UiServersServerInfoLabels
v-else
:server-data="{ game, mc_version, loader, loader_version, net }"
:show-game-label="showGameLabel"
:show-loader-label="showLoaderLabel"
:linked="false"
class="pointer-events-none flex w-full flex-row flex-wrap items-center gap-4 text-secondary *:hidden sm:flex-row sm:*:flex"
<MedalServerIcon
v-if="status !== 'suspended'"
class="z-10 size-16 shrink-0 rounded-xl bg-bg text-orange"
/>
<div
v-else
class="bg-bg-secondary z-10 flex size-16 shrink-0 items-center justify-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg shadow-sm"
>
<LockIcon class="size-12 text-secondary" />
</div>
<div class="z-10 ml-4 flex min-w-0 flex-col gap-2.5">
<div class="flex flex-row items-center gap-2">
<h2 class="m-0 truncate text-xl font-bold text-contrast">{{ name }}</h2>
<ChevronRightIcon />
<span class="truncate">
<span class="text-medal-orange">
{{ timeLeftCountdown.days }}
</span>
days
<span class="text-medal-orange">
{{ timeLeftCountdown.hours }}
</span>
hours
<span class="text-medal-orange">
{{ timeLeftCountdown.minutes }}
</span>
minutes
<span class="text-medal-orange">
{{ timeLeftCountdown.seconds }}
</span>
seconds remaining...
</span>
</div>
<div
v-if="projectData?.title"
class="m-0 flex flex-row items-center gap-2 text-sm font-medium text-secondary"
>
<Avatar
:src="iconUrl"
no-shadow
style="min-height: 20px; min-width: 20px; height: 20px; width: 20px"
alt="Server Icon"
/>
Using {{ projectData?.title || "Unknown" }}
</div>
<div
v-if="isConfiguring"
class="flex min-w-0 items-center gap-2 truncate text-sm font-semibold text-brand"
>
<SparklesIcon class="size-5 shrink-0" /> New server
</div>
<UiServersServerInfoLabels
v-else
:server-data="{ game, mc_version, loader, loader_version, net }"
:show-game-label="showGameLabel"
:show-loader-label="showLoaderLabel"
:linked="false"
class="pointer-events-none flex w-full flex-row flex-wrap items-center gap-4 text-secondary *:hidden sm:flex-row sm:*:flex"
/>
</div>
</NuxtLink>
<div class="z-10 ml-auto mr-6">
<ButtonStyled color="orange" type="outlined" size="large">
<button class="my-auto" @click="handleUpgrade"><RocketIcon /> Upgrade</button>
</ButtonStyled>
</div>
</div>
<div
v-if="status === 'suspended' && suspension_reason === 'upgrading'"
class="relative -mt-4 flex w-full flex-row items-center gap-2 rounded-b-2xl border-[1px] border-t-0 border-solid border-bg-blue bg-bg-blue p-4 text-sm font-bold text-contrast"
@ -96,17 +125,21 @@
</div>
<CopyCode :text="`${props.server_id}`" class="ml-auto" />
</div>
</NuxtLink>
</div>
</template>
<script setup lang="ts">
import { ChevronRightIcon, LockIcon, SparklesIcon } from "@modrinth/assets";
import { ChevronRightIcon, LockIcon, SparklesIcon, RocketIcon } from "@modrinth/assets";
import type { Project, Server } from "@modrinth/utils";
import { Avatar, CopyCode } from "@modrinth/ui";
import { Avatar, CopyCode, ButtonStyled } from "@modrinth/ui";
import dayjs from "dayjs";
import dayjsDuration from "dayjs/plugin/duration";
import MedalServerIcon from "~/assets/images/servers/medal_server_icon.svg?component";
import MedalPromoBackground from "~/assets/images/illustrations/medal_promo_background.svg?component";
// eslint-disable-next-line import/no-named-as-default-member
dayjs.extend(dayjsDuration);
const props = defineProps<Partial<Server>>();
const showGameLabel = computed(() => !!props.game);
@ -130,10 +163,41 @@ const iconUrl = computed(() => projectData.value?.icon_url || undefined);
const isConfiguring = computed(() => props.flows?.intro);
const expiryDate = dayjs().add(5, "day");
const timeLeftCountdown = computed(() => {
const timeLeftCountdown = ref({ days: 0, hours: 0, minutes: 0, seconds: 0 });
function handleUpgrade(event: Event) {
event.stopPropagation();
// TODO: Upgrade logic.
}
function updateCountdown() {
const now = dayjs();
const diff = expiryDate.diff(now, "day");
return diff > 0 ? `${diff} day${diff > 1 ? "s" : ""} left` : "Expired";
const diff = expiryDate.diff(now);
if (diff <= 0) {
timeLeftCountdown.value = { days: 0, hours: 0, minutes: 0, seconds: 0 };
return;
}
const duration = dayjs.duration(diff);
timeLeftCountdown.value = {
days: duration.days(),
hours: duration.hours(),
minutes: duration.minutes(),
seconds: duration.seconds(),
};
}
updateCountdown();
const intervalId = ref<NodeJS.Timeout | null>(null);
onMounted(() => {
intervalId.value = setInterval(updateCountdown, 1000);
});
onUnmounted(() => {
if (intervalId.value) clearInterval(intervalId.value);
});
</script>
@ -169,4 +233,9 @@ const timeLeftCountdown = computed(() => {
border-radius: inherit;
color: var(--medal-promotion-text-orange);
}
.text-medal-orange {
color: var(--medal-promotion-text-orange);
font-weight: bold;
}
</style>

View File

@ -139,6 +139,7 @@ import _ReplyIcon from './icons/reply.svg?component'
import _ReportIcon from './icons/report.svg?component'
import _RestoreIcon from './icons/restore.svg?component'
import _RightArrowIcon from './icons/right-arrow.svg?component'
import _RocketIcon from './icons/rocket.svg?component'
import _RotateClockwiseIcon from './icons/rotate-clockwise.svg?component'
import _RotateCounterClockwiseIcon from './icons/rotate-counter-clockwise.svg?component'
import _RssIcon from './icons/rss.svg?component'
@ -333,6 +334,7 @@ export const ReplyIcon = _ReplyIcon
export const ReportIcon = _ReportIcon
export const RestoreIcon = _RestoreIcon
export const RightArrowIcon = _RightArrowIcon
export const RocketIcon = _RocketIcon
export const RotateClockwiseIcon = _RotateClockwiseIcon
export const RotateCounterClockwiseIcon = _RotateCounterClockwiseIcon
export const RssIcon = _RssIcon

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rocket-icon lucide-rocket"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>

After

Width:  |  Height:  |  Size: 544 B