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,32 +1,53 @@
<template> <template>
<NuxtLink <div>
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="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
:data-pyro-server-listing-id="server_id" :data-pyro-server-listing-id="server_id"
> >
<div class="overlay"></div> <div class="overlay"></div>
<MedalPromoBackground class="background-pattern" /> <MedalPromoBackground class="background-pattern" />
<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'"
>
<MedalServerIcon <MedalServerIcon
v-if="status !== 'suspended'" v-if="status !== 'suspended'"
class="z-10 size-16 rounded-xl bg-bg text-orange" class="z-10 size-16 shrink-0 rounded-xl bg-bg text-orange"
/> />
<div <div
v-else 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" 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" /> <LockIcon class="size-12 text-secondary" />
</div> </div>
<div class="z-10 ml-4 flex flex-col gap-2.5">
<div class="z-10 ml-4 flex min-w-0 flex-col gap-2.5">
<div class="flex flex-row items-center gap-2"> <div class="flex flex-row items-center gap-2">
<h2 class="m-0 text-xl font-bold text-contrast">{{ name }}</h2> <h2 class="m-0 truncate text-xl font-bold text-contrast">{{ name }}</h2>
<ChevronRightIcon /> <ChevronRightIcon />
<span>{{ timeLeftCountdown }}</span> <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>
<div <div
@ -57,7 +78,15 @@
class="pointer-events-none flex w-full flex-row flex-wrap items-center gap-4 text-secondary *:hidden sm:flex-row sm:*:flex" class="pointer-events-none flex w-full flex-row flex-wrap items-center gap-4 text-secondary *:hidden sm:flex-row sm:*:flex"
/> />
</div> </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>
<div <div
v-if="status === 'suspended' && suspension_reason === 'upgrading'" 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" 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> </div>
<CopyCode :text="`${props.server_id}`" class="ml-auto" /> <CopyCode :text="`${props.server_id}`" class="ml-auto" />
</div> </div>
</NuxtLink> </div>
</template> </template>
<script setup lang="ts"> <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 type { Project, Server } from "@modrinth/utils";
import { Avatar, CopyCode } from "@modrinth/ui"; import { Avatar, CopyCode, ButtonStyled } from "@modrinth/ui";
import dayjs from "dayjs"; import dayjs from "dayjs";
import dayjsDuration from "dayjs/plugin/duration";
import MedalServerIcon from "~/assets/images/servers/medal_server_icon.svg?component"; import MedalServerIcon from "~/assets/images/servers/medal_server_icon.svg?component";
import MedalPromoBackground from "~/assets/images/illustrations/medal_promo_background.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 props = defineProps<Partial<Server>>();
const showGameLabel = computed(() => !!props.game); const showGameLabel = computed(() => !!props.game);
@ -130,10 +163,41 @@ const iconUrl = computed(() => projectData.value?.icon_url || undefined);
const isConfiguring = computed(() => props.flows?.intro); const isConfiguring = computed(() => props.flows?.intro);
const expiryDate = dayjs().add(5, "day"); 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 now = dayjs();
const diff = expiryDate.diff(now, "day"); const diff = expiryDate.diff(now);
return diff > 0 ? `${diff} day${diff > 1 ? "s" : ""} left` : "Expired";
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> </script>
@ -169,4 +233,9 @@ const timeLeftCountdown = computed(() => {
border-radius: inherit; border-radius: inherit;
color: var(--medal-promotion-text-orange); color: var(--medal-promotion-text-orange);
} }
.text-medal-orange {
color: var(--medal-promotion-text-orange);
font-weight: bold;
}
</style> </style>

View File

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