Merge commit '6d810a421af0b160ce49bf44e4266fd0cf5fba07' into feature-clean

This commit is contained in:
2025-02-12 23:07:46 +03:00
18 changed files with 1201 additions and 147 deletions

View File

@@ -0,0 +1,16 @@
<template>
<div class="page experimental-styles-within">
<h1 class="m-0 text-3xl font-extrabold">Changelog</h1>
<p class="my-3">Keep up-to-date on what's new with Modrinth.</p>
<NuxtPage />
</div>
</template>
<style lang="scss" scoped>
.page {
padding: 1rem;
margin-left: auto;
margin-right: auto;
max-width: 56rem;
}
</style>

View File

@@ -0,0 +1,79 @@
<script setup lang="ts">
import { getChangelog } from "@modrinth/utils";
import { ChangelogEntry } from "@modrinth/ui";
import { ChevronLeftIcon } from "@modrinth/assets";
const route = useRoute();
const changelogEntry = computed(() =>
route.params.date
? getChangelog().find((x) => {
if (x.product === route.params.product) {
console.log("Found matching product!");
if (x.version && x.version === route.params.date) {
console.log("Found matching version!");
return x;
} else if (x.date.unix() === Number(route.params.date as string)) {
console.log("Found matching date!");
return x;
}
}
return undefined;
})
: undefined,
);
const isFirst = computed(() => changelogEntry.value?.date === getChangelog()[0].date);
if (!changelogEntry.value) {
createError({ statusCode: 404, statusMessage: "Version not found" });
}
</script>
<template>
<div v-if="changelogEntry">
<nuxt-link
:to="`/news/changelog?filter=${changelogEntry.product}`"
class="mb-4 mt-4 flex w-fit items-center gap-2 text-link"
>
<ChevronLeftIcon /> View full changelog
</nuxt-link>
<div class="relative flex flex-col gap-4 pb-6">
<div class="absolute flex h-full w-4 justify-center">
<div class="timeline-indicator" :class="{ first: isFirst }" />
</div>
<ChangelogEntry :entry="changelogEntry" :first="isFirst" show-type class="relative z-[1]" />
</div>
</div>
</template>
<style lang="scss" scoped>
.timeline-indicator {
background-image: linear-gradient(
to bottom,
var(--color-raised-bg) 66%,
rgba(255, 255, 255, 0) 0%
);
background-size: 100% 30px;
background-repeat: repeat-y;
height: calc(100% + 2rem);
width: 4px;
margin-top: -2rem;
mask-image: linear-gradient(
to bottom,
transparent 0%,
black 8rem,
black calc(100% - 8rem),
transparent 100%
);
&.first {
margin-top: 1rem;
mask-image: linear-gradient(black calc(100% - 15rem), transparent 100%);
}
}
</style>

View File

@@ -0,0 +1,86 @@
<script setup lang="ts">
import { type Product, getChangelog } from "@modrinth/utils";
import { ChangelogEntry } from "@modrinth/ui";
import NavTabs from "~/components/ui/NavTabs.vue";
const route = useRoute();
const filter = ref<Product | undefined>(undefined);
const allChangelogEntries = ref(getChangelog());
function updateFilter() {
if (route.query.filter) {
filter.value = route.query.filter as Product;
} else {
filter.value = undefined;
}
}
updateFilter();
watch(
() => route.query,
() => updateFilter(),
);
const changelogEntries = computed(() =>
allChangelogEntries.value.filter((x) => !filter.value || x.product === filter.value),
);
</script>
<template>
<NavTabs
:links="[
{
label: 'All',
href: '',
},
{
label: 'Website',
href: 'web',
},
{
label: 'Servers',
href: 'servers',
},
{
label: 'App',
href: 'app',
},
]"
query="filter"
class="mb-4"
/>
<div class="relative flex flex-col gap-4 pb-6">
<div class="absolute flex h-full w-4 justify-center">
<div class="timeline-indicator" />
</div>
<ChangelogEntry
v-for="(entry, index) in changelogEntries"
:key="entry.date"
:entry="entry"
:first="index === 0"
:show-type="filter === undefined"
has-link
class="relative z-[1]"
/>
</div>
</template>
<style lang="scss" scoped>
.timeline-indicator {
background-image: linear-gradient(
to bottom,
var(--color-raised-bg) 66%,
rgba(255, 255, 255, 0) 0%
);
background-size: 100% 30px;
background-repeat: repeat-y;
margin-top: 1rem;
height: calc(100% - 1rem);
width: 4px;
mask-image: linear-gradient(to bottom, black calc(100% - 15rem), transparent 100%);
}
</style>

View File

@@ -63,7 +63,20 @@
<h2 class="m-0 text-lg font-extrabold">{{ formatMessage(messages.formNotFor) }}</h2>
<div class="text-md flex items-center gap-2 font-semibold text-contrast">
<XCircleIcon class="h-8 w-8 shrink-0 text-brand-red" />
<span>{{ formatMessage(messages.bugReports) }}</span>
<div class="flex flex-col">
<span>{{ formatMessage(messages.bugReports) }}</span>
<span v-if="itemIssueTracker" class="text-sm font-medium text-secondary">
<IntlFormatted :message-id="messages.bugReportsDescription">
<template #issues-link="{ children }">
<a class="text-link" :href="itemIssueTracker" target="_blank">
<component :is="() => children" />
<ExternalIcon aria-hidden="true" class="mb-1 ml-1 h-2.5 w-2.5" />
</a>
</template>
</IntlFormatted>
</span>
</div>
</div>
<div class="text-md flex items-center gap-2 font-semibold text-contrast">
<XCircleIcon class="h-8 w-8 shrink-0 text-brand-red" />
@@ -238,6 +251,7 @@ import {
AutoLink,
} from "@modrinth/ui";
import {
ExternalIcon,
LeftArrowIcon,
RightArrowIcon,
CheckIcon,
@@ -289,6 +303,7 @@ const itemIcon = ref<string | Component | undefined>();
const itemName = ref<string | undefined>();
const itemLink = ref<string | undefined>();
const itemId = ref<string | undefined>();
const itemIssueTracker = ref<string | undefined>();
const reports = ref<Report[]>([]);
const existingReport = computed(() =>
@@ -319,6 +334,7 @@ async function fetchItem() {
itemName.value = undefined;
itemLink.value = undefined;
itemId.value = undefined;
itemIssueTracker.value = undefined;
try {
if (reportItem.value === "project") {
const project = (await useBaseFetch(`project/${reportItemID.value}`)) as Project;
@@ -328,6 +344,7 @@ async function fetchItem() {
itemName.value = project.title;
itemLink.value = `/project/${project.id}`;
itemId.value = project.id;
itemIssueTracker.value = project.issues_url;
} else if (reportItem.value === "version") {
const version = (await useBaseFetch(`version/${reportItemID.value}`)) as Version;
currentVersion.value = version;
@@ -540,6 +557,10 @@ const messages = defineMessages({
id: "report.not-for.bug-reports",
defaultMessage: "Bug reports",
},
bugReportsDescription: {
id: "report.not-for.bug-reports.description",
defaultMessage: "You can report bugs to their <issues-link>issue tracker</issues-link>.",
},
dmcaTakedown: {
id: "report.not-for.dmca",
defaultMessage: "DMCA takedowns",
@@ -586,7 +607,7 @@ const messages = defineMessages({
<style scoped lang="scss">
.page {
padding: 0.5rem;
padding: 1rem;
margin-left: auto;
margin-right: auto;
max-width: 56rem;

View File

@@ -494,6 +494,97 @@
</div>
</section>
<section
class="relative mt-24 flex flex-col bg-[radial-gradient(65%_50%_at_50%_-10%,var(--color-brand-highlight)_0%,var(--color-accent-contrast)_100%)] px-3 pt-24 md:mt-48 md:pt-48"
>
<div class="faded-brand-line absolute left-0 top-0 h-[1px] w-full"></div>
<div class="mx-auto flex w-full max-w-7xl flex-col gap-8">
<div class="grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<div
class="relative w-fit rounded-full bg-highlight-green px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
>
Server Locations
</div>
<h1 class="relative m-0 max-w-2xl text-4xl leading-[120%] md:text-7xl">
Coast-to-Coast Coverage
</h1>
</div>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<div class="flex items-center gap-3">
<div class="grid size-8 place-content-center rounded-full bg-highlight-green">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-brand"
>
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" />
<circle cx="12" cy="10" r="3" />
</svg>
</div>
<h2 class="relative m-0 text-xl font-medium leading-[155%] md:text-2xl">
US Coverage
</h2>
</div>
<p
class="relative m-0 max-w-xl text-base font-normal leading-[155%] text-secondary md:text-[18px]"
>
With strategically placed servers in New York, Los Angeles, Seattle, and Miami, we
ensure low latency connections for players across North America. Each location is
equipped with high-performance hardware and DDoS protection.
</p>
</div>
<div class="flex flex-col gap-4">
<div class="flex items-center gap-3">
<div class="grid size-8 place-content-center rounded-full bg-highlight-blue">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-blue"
>
<path d="M12 2a10 10 0 1 0 10 10" />
<path d="M18 13a6 6 0 0 0-6-6" />
<path d="M13 2.05a10 10 0 0 1 2 2" />
<path d="M19.5 8.5a10 10 0 0 1 2 2" />
</svg>
</div>
<h2 class="relative m-0 text-xl font-medium leading-[155%] md:text-2xl">
Global Expansion
</h2>
</div>
<p
class="relative m-0 max-w-xl text-base font-normal leading-[155%] text-secondary md:text-[18px]"
>
We're expanding to Europe and Asia-Pacific regions soon, bringing Modrinth's
seamless hosting experience worldwide. Join our Discord to stay updated on new
region launches.
</p>
</div>
</div>
</div>
<Globe />
</div>
</div>
</section>
<section
id="plan"
class="relative mt-24 flex flex-col bg-[radial-gradient(65%_50%_at_50%_-10%,var(--color-brand-highlight)_0%,var(--color-accent-contrast)_100%)] px-3 pt-24 md:mt-48 md:pt-48"
@@ -511,147 +602,180 @@
? "We are currently at capacity. Please try again later."
: "There's a plan for everyone! Choose the one that fits your needs."
}}
<span class="font-bold">
Servers are currently US only, in New York, Los Angeles, Seattle, and Miami. More
regions coming soon!
</span>
</h2>
<ul class="m-0 flex w-full flex-col gap-8 p-0 lg:flex-row">
<li class="flex w-full flex-col gap-4 rounded-2xl bg-bg p-8 text-left lg:w-1/3">
<div class="flex flex-row items-center justify-between">
<h1 class="m-0">Small</h1>
<div
class="grid size-8 place-content-center rounded-full bg-highlight-blue text-xs font-bold text-blue"
>
S
<ul class="m-0 mt-8 flex w-full flex-col gap-8 p-0 lg:flex-row">
<li class="relative flex w-full flex-col justify-between pt-12 lg:w-1/3">
<div
v-if="isSmallLowStock"
class="absolute left-0 right-0 top-[-2px] rounded-t-2xl bg-yellow-500/20 p-4 text-center font-bold"
>
Only {{ capacityStatuses?.small?.available }} left in stock!
</div>
<div
class="flex w-full flex-col justify-between gap-4 rounded-2xl bg-bg p-8 text-left"
:class="{ '!rounded-t-none': isSmallLowStock }"
>
<div class="flex flex-col gap-4">
<div class="flex flex-row items-center justify-between">
<h1 class="m-0">Small</h1>
<div
class="grid size-8 place-content-center rounded-full bg-highlight-blue text-xs font-bold text-blue"
>
S
</div>
</div>
<p class="m-0">
Perfect for vanilla multiplayer, small friend groups, SMPs, and light modding.
</p>
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
<p class="m-0">4 GB RAM</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">4 vCPUs</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">32 GB Storage</p>
</div>
<h2 class="m-0 text-3xl text-contrast">
$12<span class="text-sm font-normal text-secondary">/month</span>
</h2>
</div>
<ButtonStyled color="blue" size="large">
<NuxtLink
v-if="!loggedOut && isSmallAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="flex items-center gap-2 !bg-highlight-blue !font-medium !text-blue"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-blue" />
</NuxtLink>
<button
v-else
class="!bg-highlight-blue !font-medium !text-blue"
@click="selectProduct('small')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-blue" />
</button>
</ButtonStyled>
</div>
<p class="m-0">
Perfect for vanilla multiplayer, small friend groups, SMPs, and light modding.
</p>
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
<p class="m-0">4 GB RAM</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">4 vCPUs</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">32 GB Storage</p>
</div>
<h2 class="m-0 text-3xl text-contrast">
$12<span class="text-sm font-normal text-secondary">/month</span>
</h2>
<ButtonStyled color="blue" size="large">
<NuxtLink
v-if="!loggedOut && isSmallAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-blue !font-medium !text-blue"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-blue" />
</NuxtLink>
<button
v-else
class="!bg-highlight-blue !font-medium !text-blue"
@click="selectProduct('small')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-blue" />
</button>
</ButtonStyled>
</li>
<li
style="
background: radial-gradient(
86.12% 101.64% at 95.97% 94.07%,
rgba(27, 217, 106, 0.23) 0%,
rgba(14, 115, 56, 0.2) 100%
);
border: 1px solid rgba(12, 107, 52, 0.55);
box-shadow: 0px 12px 38.1px rgba(27, 217, 106, 0.13);
"
class="flex w-full flex-col gap-4 rounded-2xl bg-bg p-8 text-left lg:w-1/3"
>
<div class="flex flex-row items-center justify-between">
<h1 class="m-0">Medium</h1>
<div
class="grid size-8 place-content-center rounded-full bg-highlight-green text-xs font-bold text-brand"
>
M
<li class="relative flex w-full flex-col justify-between pt-12 lg:w-1/3">
<div
v-if="isMediumLowStock"
class="absolute left-0 right-0 top-[-2px] rounded-t-2xl bg-yellow-500/20 p-4 text-center font-bold"
>
Only {{ capacityStatuses?.medium?.available }} left in stock!
</div>
<div
style="
background: radial-gradient(
86.12% 101.64% at 95.97% 94.07%,
rgba(27, 217, 106, 0.23) 0%,
rgba(14, 115, 56, 0.2) 100%
);
border: 1px solid rgba(12, 107, 52, 0.55);
box-shadow: 0px 12px 38.1px rgba(27, 217, 106, 0.13);
"
class="flex w-full flex-col justify-between gap-4 rounded-2xl p-8 text-left"
:class="{ '!rounded-t-none': isMediumLowStock }"
>
<div class="flex flex-col gap-4">
<div class="flex flex-row items-center justify-between">
<h1 class="m-0">Medium</h1>
<div
class="grid size-8 place-content-center rounded-full bg-highlight-green text-xs font-bold text-brand"
>
M
</div>
</div>
<p class="m-0">Great for modded multiplayer and small communities.</p>
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
<p class="m-0">6 GB RAM</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">6 vCPUs</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">48 GB Storage</p>
</div>
<h2 class="m-0 text-3xl text-contrast">
$18<span class="text-sm font-normal text-secondary">/month</span>
</h2>
</div>
<ButtonStyled color="brand" size="large">
<NuxtLink
v-if="!loggedOut && isMediumAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="flex items-center gap-2 !bg-highlight-green !font-medium !text-green"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-green" />
</NuxtLink>
<button
v-else
class="!bg-highlight-green !font-medium !text-green"
@click="selectProduct('medium')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-green" />
</button>
</ButtonStyled>
</div>
<p class="m-0">Great for modded multiplayer and small communities.</p>
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
<p class="m-0">6 GB RAM</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">6 vCPUs</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">48 GB Storage</p>
</div>
<h2 class="m-0 text-3xl text-contrast">
$18<span class="text-sm font-normal text-secondary">/month</span>
</h2>
<ButtonStyled color="brand" size="large">
<NuxtLink
v-if="!loggedOut && isMediumAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-green !font-medium !text-green"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-green" />
</NuxtLink>
<button
v-else
class="!bg-highlight-green !font-medium !text-green"
@click="selectProduct('medium')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-green" />
</button>
</ButtonStyled>
</li>
<li class="flex w-full flex-col gap-4 rounded-2xl bg-bg p-8 text-left lg:w-1/3">
<div class="flex flex-row items-center justify-between">
<h1 class="m-0">Large</h1>
<div
class="grid size-8 place-content-center rounded-full bg-highlight-purple text-xs font-bold text-purple"
>
L
<li class="relative flex w-full flex-col justify-between pt-12 lg:w-1/3">
<div
v-if="isLargeLowStock"
class="absolute left-0 right-0 top-[-2px] rounded-t-2xl bg-yellow-500/20 p-4 text-center font-bold"
>
Only {{ capacityStatuses?.large?.available }} left in stock!
</div>
<div
class="flex w-full flex-col justify-between gap-4 rounded-2xl bg-bg p-8 text-left"
:class="{ '!rounded-t-none': isLargeLowStock }"
>
<div class="flex flex-col gap-4">
<div class="flex flex-row items-center justify-between">
<h1 class="m-0">Large</h1>
<div
class="grid size-8 place-content-center rounded-full bg-highlight-purple text-xs font-bold text-purple"
>
L
</div>
</div>
<p class="m-0">Ideal for larger communities, modpacks, and heavy modding.</p>
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
<p class="m-0">8 GB RAM</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">8 vCPUs</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">64 GB Storage</p>
</div>
<h2 class="m-0 text-3xl text-contrast">
$24<span class="text-sm font-normal text-secondary">/month</span>
</h2>
</div>
<ButtonStyled color="brand" size="large">
<NuxtLink
v-if="!loggedOut && isLargeAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="flex items-center gap-2 !bg-highlight-purple !font-medium !text-purple"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-purple" />
</NuxtLink>
<button
v-else
class="!bg-highlight-purple !font-medium !text-purple"
@click="selectProduct('large')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-purple" />
</button>
</ButtonStyled>
</div>
<p class="m-0">Ideal for larger communities, modpacks, and heavy modding.</p>
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
<p class="m-0">8 GB RAM</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">8 vCPUs</p>
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
<p class="m-0">64 GB Storage</p>
</div>
<h2 class="m-0 text-3xl text-contrast">
$24<span class="text-sm font-normal text-secondary">/month</span>
</h2>
<ButtonStyled color="brand" size="large">
<NuxtLink
v-if="!loggedOut && isLargeAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-purple !font-medium !text-purple"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-purple" />
</NuxtLink>
<button
v-else
class="!bg-highlight-purple !font-medium !text-purple"
@click="selectProduct('large')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-purple" />
</button>
</ButtonStyled>
</li>
</ul>
@@ -697,6 +821,7 @@ import {
} from "@modrinth/assets";
import { products } from "~/generated/state.json";
import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue";
import Globe from "~/components/ui/servers/Globe.vue";
const pyroProducts = products.filter((p) => p.metadata.type === "pyro");
const pyroPlanProducts = pyroProducts.filter(
@@ -760,9 +885,16 @@ const { data: hasServers } = await useAsyncData("ServerListCountCheck", async ()
async function fetchCapacityStatuses(customProduct = null) {
try {
const productsToCheck = customProduct?.metadata ? [customProduct] : pyroPlanProducts;
const productsToCheck = customProduct?.metadata
? [customProduct]
: [
...pyroPlanProducts,
pyroProducts.reduce((min, product) =>
product.metadata.ram < min.metadata.ram ? product : min,
),
];
const capacityChecks = productsToCheck.map((product) =>
usePyroFetch("capacity", {
usePyroFetch("stock", {
method: "POST",
body: {
cpu: product.metadata.cpu,
@@ -774,6 +906,7 @@ async function fetchCapacityStatuses(customProduct = null) {
);
const results = await Promise.all(capacityChecks);
if (customProduct?.metadata) {
return {
custom: results[0],
@@ -783,6 +916,7 @@ async function fetchCapacityStatuses(customProduct = null) {
small: results[0],
medium: results[1],
large: results[2],
custom: results[3],
};
}
} catch (error) {
@@ -804,6 +938,22 @@ const { data: capacityStatuses, refresh: refreshCapacity } = await useAsyncData(
const isSmallAtCapacity = computed(() => capacityStatuses.value?.small?.available === 0);
const isMediumAtCapacity = computed(() => capacityStatuses.value?.medium?.available === 0);
const isLargeAtCapacity = computed(() => capacityStatuses.value?.large?.available === 0);
const isCustomAtCapacity = computed(() => capacityStatuses.value?.custom?.available === 0);
const isSmallLowStock = computed(() => {
const available = capacityStatuses.value?.small?.available;
return available !== undefined && available > 0 && available < 8;
});
const isMediumLowStock = computed(() => {
const available = capacityStatuses.value?.medium?.available;
return available !== undefined && available > 0 && available < 8;
});
const isLargeLowStock = computed(() => {
const available = capacityStatuses.value?.large?.available;
return available !== undefined && available > 0 && available < 8;
});
const startTyping = () => {
const currentWord = words[currentWordIndex.value];
@@ -907,7 +1057,9 @@ const selectProduct = async (product) => {
}
await refreshCapacity();
if (isAtCapacity.value) {
console.log(capacityStatuses.value);
if ((product === "custom" && isCustomAtCapacity.value) || isAtCapacity.value) {
addNotification({
group: "main",
title: "Server Capacity Full",