You've already forked AstralRinth
forked from didirus/AstralRinth
Handle downtime errors, give more information on error pages. (#3402)
Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
@@ -126,6 +126,7 @@ export default defineNuxtConfig({
|
||||
homePageSearch?: any[];
|
||||
homePageNotifs?: any[];
|
||||
products?: any[];
|
||||
errors?: number[];
|
||||
} = {};
|
||||
|
||||
try {
|
||||
@@ -157,6 +158,14 @@ export default defineNuxtConfig({
|
||||
},
|
||||
};
|
||||
|
||||
const caughtErrorCodes = new Set<number>();
|
||||
|
||||
function handleFetchError(err: any, defaultValue: any) {
|
||||
console.error("Error generating state: ", err);
|
||||
caughtErrorCodes.add(err.status);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const [
|
||||
categories,
|
||||
loaders,
|
||||
@@ -168,15 +177,25 @@ export default defineNuxtConfig({
|
||||
homePageNotifs,
|
||||
products,
|
||||
] = await Promise.all([
|
||||
$fetch(`${API_URL}tag/category`, headers),
|
||||
$fetch(`${API_URL}tag/loader`, headers),
|
||||
$fetch(`${API_URL}tag/game_version`, headers),
|
||||
$fetch(`${API_URL}tag/donation_platform`, headers),
|
||||
$fetch(`${API_URL}tag/report_type`, headers),
|
||||
$fetch(`${API_URL}projects_random?count=60`, headers),
|
||||
$fetch(`${API_URL}search?limit=3&query=leave&index=relevance`, headers),
|
||||
$fetch(`${API_URL}search?limit=3&query=&index=updated`, headers),
|
||||
$fetch(`${API_URL.replace("/v2/", "/_internal/")}billing/products`, headers),
|
||||
$fetch(`${API_URL}tag/category`, headers).catch((err) => handleFetchError(err, [])),
|
||||
$fetch(`${API_URL}tag/loader`, headers).catch((err) => handleFetchError(err, [])),
|
||||
$fetch(`${API_URL}tag/game_version`, headers).catch((err) => handleFetchError(err, [])),
|
||||
$fetch(`${API_URL}tag/donation_platform`, headers).catch((err) =>
|
||||
handleFetchError(err, []),
|
||||
),
|
||||
$fetch(`${API_URL}tag/report_type`, headers).catch((err) => handleFetchError(err, [])),
|
||||
$fetch(`${API_URL}projects_random?count=60`, headers).catch((err) =>
|
||||
handleFetchError(err, []),
|
||||
),
|
||||
$fetch(`${API_URL}search?limit=3&query=leave&index=relevance`, headers).catch((err) =>
|
||||
handleFetchError(err, {}),
|
||||
),
|
||||
$fetch(`${API_URL}search?limit=3&query=&index=updated`, headers).catch((err) =>
|
||||
handleFetchError(err, {}),
|
||||
),
|
||||
$fetch(`${API_URL.replace("/v2/", "/_internal/")}billing/products`, headers).catch((err) =>
|
||||
handleFetchError(err, []),
|
||||
),
|
||||
]);
|
||||
|
||||
state.categories = categories;
|
||||
@@ -188,6 +207,7 @@ export default defineNuxtConfig({
|
||||
state.homePageSearch = homePageSearch;
|
||||
state.homePageNotifs = homePageNotifs;
|
||||
state.products = products;
|
||||
state.errors = [...caughtErrorCodes];
|
||||
|
||||
await fs.writeFile("./src/generated/state.json", JSON.stringify(state));
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
max-width: 80rem;
|
||||
column-gap: 0.75rem;
|
||||
padding: 0 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
|
||||
grid-template:
|
||||
"header"
|
||||
|
||||
@@ -1,21 +1,52 @@
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<div class="main">
|
||||
<div class="error">
|
||||
<Logo404 v-if="error.statusCode === 404" />
|
||||
<h1 v-else>An error occurred!</h1>
|
||||
<p>{{ error.message }}</p>
|
||||
<div class="button-group">
|
||||
<nuxt-link to="/" class="iconified-button raised-button brand-button">
|
||||
Go home
|
||||
</nuxt-link>
|
||||
<a
|
||||
href="https://discord.modrinth.com"
|
||||
class="iconified-button raised-button"
|
||||
rel="noopener"
|
||||
>
|
||||
Get help on Discord
|
||||
</a>
|
||||
<div class="main experimental-styles-within">
|
||||
<div v-if="is404" class="error-graphic">
|
||||
<Logo404 />
|
||||
</div>
|
||||
<div class="error-box" :class="{ 'has-bot': !is404 }">
|
||||
<img
|
||||
v-if="!is404"
|
||||
src="https://cdn-raw.modrinth.com/sad-bot.webp"
|
||||
alt="Sad Modrinth bot"
|
||||
class="error-box__sad-bot"
|
||||
/>
|
||||
<div v-if="!is404" class="error-box__top-glow" />
|
||||
<div class="error-box__body">
|
||||
<h1 class="error-box__title">{{ formatMessage(errorMessages.title) }}</h1>
|
||||
<p v-if="errorMessages.subtitle" class="error-box__subtitle">
|
||||
{{ formatMessage(errorMessages.subtitle) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="error-box__body">
|
||||
<p v-if="errorMessages.list_title" class="error-box__list-title">
|
||||
{{ formatMessage(errorMessages.list_title) }}
|
||||
</p>
|
||||
<ul v-if="errorMessages.list_items" class="error-box__list">
|
||||
<li v-for="item in errorMessages.list_items" :key="item">
|
||||
<IntlFormatted :message-id="item">
|
||||
<template #status-link="{ children }">
|
||||
<a href="https://status.modrinth.com" target="_blank" rel="noopener">
|
||||
<component :is="() => children" />
|
||||
</a>
|
||||
</template>
|
||||
<template #discord-link="{ children }">
|
||||
<a href="https://discord.modrinth.com" target="_blank" rel="noopener">
|
||||
<component :is="() => children" />
|
||||
</a>
|
||||
</template>
|
||||
<template #tou-link="{ children }">
|
||||
<nuxt-link :to="`/legal/terms`" target="_blank" rel="noopener">
|
||||
<component :is="() => children" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="!is404" class="error-box__details">
|
||||
<p>Error {{ error.statusCode }}</p>
|
||||
<p>{{ error.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,9 +54,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineMessage, useVIntl } from "@vintl/vintl";
|
||||
import Logo404 from "~/assets/images/404.svg";
|
||||
|
||||
defineProps({
|
||||
const { formatMessage } = useVIntl();
|
||||
|
||||
const props = defineProps({
|
||||
error: {
|
||||
type: Object,
|
||||
default() {
|
||||
@@ -36,29 +70,316 @@ defineProps({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const is404 = computed(() => props.error.statusCode === 404);
|
||||
const errorMessages = computed(
|
||||
() =>
|
||||
routeMessages.find((x) => x.match(route))?.messages[props.error.statusCode] ??
|
||||
messages[props.error.statusCode] ??
|
||||
messages.default,
|
||||
);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
watch(route, () => {
|
||||
console.log(route);
|
||||
});
|
||||
|
||||
const messages = {
|
||||
404: {
|
||||
title: defineMessage({
|
||||
id: "error.generic.404.title",
|
||||
defaultMessage: "Page not found",
|
||||
}),
|
||||
subtitle: defineMessage({
|
||||
id: "error.generic.404.subtitle",
|
||||
defaultMessage: "The page you were looking for doesn't seem to exist.",
|
||||
}),
|
||||
},
|
||||
default: {
|
||||
title: defineMessage({
|
||||
id: "error.generic.default.title",
|
||||
defaultMessage: "Uh oh!",
|
||||
}),
|
||||
subtitle: defineMessage({
|
||||
id: "error.generic.default.subtitle",
|
||||
defaultMessage: "Something went wrong.",
|
||||
}),
|
||||
list_title: defineMessage({
|
||||
id: "error.generic.default.list_title",
|
||||
defaultMessage: "Please try again in a few minutes.",
|
||||
}),
|
||||
list_items: [
|
||||
defineMessage({
|
||||
id: "error.generic.default.list_item.1",
|
||||
defaultMessage: "Check if Modrinth is down on our <status-link>Status page</status-link>.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.generic.default.list_item.2",
|
||||
defaultMessage:
|
||||
"If this keeps happening, you may want to let the Modrinth Team know by joining our <discord-link>Discord server</discord-link>.",
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const PROJECT_PATH_PREFIXES = [
|
||||
"/mod/",
|
||||
"/datapack/",
|
||||
"/resourcepack/",
|
||||
"/plugin/",
|
||||
"/shader/",
|
||||
"/modpack/",
|
||||
"/project/",
|
||||
];
|
||||
|
||||
const routeMessages = [
|
||||
{
|
||||
match: (route) => PROJECT_PATH_PREFIXES.some((prefix) => route.path.startsWith(prefix)),
|
||||
messages: {
|
||||
404: {
|
||||
title: defineMessage({
|
||||
id: "error.project.404.title",
|
||||
defaultMessage: "Project not found",
|
||||
}),
|
||||
list_title: defineMessage({
|
||||
id: "error.project.404.list_title",
|
||||
defaultMessage: "Why?",
|
||||
}),
|
||||
list_items: [
|
||||
defineMessage({
|
||||
id: "error.project.404.list_item.1",
|
||||
defaultMessage: "You may have mistyped the project's URL.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.project.404.list_item.2",
|
||||
defaultMessage:
|
||||
"The project's owner may have changed the URL, made the project private, or deleted it.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.project.404.list_item.3",
|
||||
defaultMessage:
|
||||
"The project may have been taken down by Modrinth's moderation team for violating our <tou-link>Terms of Use</tou-link>.",
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
match: (route) => route.path.startsWith("/user/"),
|
||||
messages: {
|
||||
404: {
|
||||
title: defineMessage({
|
||||
id: "error.user.404.title",
|
||||
defaultMessage: "User not found",
|
||||
}),
|
||||
list_title: defineMessage({
|
||||
id: "error.user.404.list_title",
|
||||
defaultMessage: "Why?",
|
||||
}),
|
||||
list_items: [
|
||||
defineMessage({
|
||||
id: "error.user.404.list_item.1",
|
||||
defaultMessage: "You may have mistyped the user's username.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.user.404.list_item.2",
|
||||
defaultMessage: "The user may have changed their username or deleted their account.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.user.404.list_item.3",
|
||||
defaultMessage:
|
||||
"The user's account may have been terminated for violating Modrinth's <tou-link>Terms of Use</tou-link>.",
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
match: (route) => route.path.startsWith("/organization/"),
|
||||
messages: {
|
||||
404: {
|
||||
title: defineMessage({
|
||||
id: "error.organization.404.title",
|
||||
defaultMessage: "Organization not found",
|
||||
}),
|
||||
list_title: defineMessage({
|
||||
id: "error.organization.404.list_title",
|
||||
defaultMessage: "Why?",
|
||||
}),
|
||||
list_items: [
|
||||
defineMessage({
|
||||
id: "error.organization.404.list_item.1",
|
||||
defaultMessage: "You may have mistyped the organization's URL.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.organization.404.list_item.2",
|
||||
defaultMessage: "The organization's owner may have changed the URL or deleted it.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.organization.404.list_item.3",
|
||||
defaultMessage:
|
||||
"The organization may have been removed by Modrinth's moderation team for violating our <tou-link>Terms of Use</tou-link>.",
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
match: (route) => route.path.startsWith("/collection/"),
|
||||
messages: {
|
||||
404: {
|
||||
title: defineMessage({
|
||||
id: "error.collection.404.title",
|
||||
defaultMessage: "Collection not found",
|
||||
}),
|
||||
list_title: defineMessage({
|
||||
id: "error.collection.404.list_title",
|
||||
defaultMessage: "Why?",
|
||||
}),
|
||||
list_items: [
|
||||
defineMessage({
|
||||
id: "error.collection.404.list_item.1",
|
||||
defaultMessage: "You may have mistyped the collection's URL.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.collection.404.list_item.2",
|
||||
defaultMessage: "The collection may be private.",
|
||||
}),
|
||||
defineMessage({
|
||||
id: "error.collection.404.list_item.3",
|
||||
defaultMessage:
|
||||
"The collection may have been taken down by Modrinth's moderation team for violating our <tou-link>Terms of Use</tou-link>.",
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main {
|
||||
margin: var(--spacing-card-lg) auto;
|
||||
width: calc(100% - 4rem);
|
||||
min-height: min(90vh, 30rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
@media screen and (min-width: 800px) {
|
||||
width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
h1 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.error-box {
|
||||
background-color: var(--color-raised-bg);
|
||||
border-radius: 1.25rem;
|
||||
padding: 1.75rem 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
box-shadow: var(--shadow-card);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--color-text);
|
||||
color: var(--color-text);
|
||||
.error-box.has-bot {
|
||||
margin-block: 120px;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.error-box p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.error-box a {
|
||||
color: var(--color-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error-box a:hover,
|
||||
.error-box a:focus {
|
||||
filter: brightness(1.125);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.error-graphic {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-graphic svg {
|
||||
fill: var(--color-text);
|
||||
color: var(--color-text);
|
||||
|
||||
width: min(15rem, 100%);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.error-box__sad-bot {
|
||||
--_bot-height: 112px;
|
||||
position: absolute;
|
||||
top: calc(-1 * var(--_bot-height));
|
||||
right: 5rem;
|
||||
width: auto;
|
||||
height: var(--_bot-height);
|
||||
}
|
||||
|
||||
.error-box__top-glow {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent 2rem,
|
||||
var(--color-green) calc(100% - 13rem),
|
||||
var(--color-green) calc(100% - 5rem),
|
||||
transparent calc(100% - 2rem)
|
||||
);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.error-box__title {
|
||||
font-size: 2rem;
|
||||
font-weight: 900;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.error-box__subtitle {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error-box__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.error-box__list-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error-box__list {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.error-box li {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.error-box__details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--color-secondary);
|
||||
gap: 0.25rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -80,6 +80,23 @@
|
||||
/></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="generatedStateErrors && generatedStateErrors.length > 0"
|
||||
class="site-banner site-banner--warning [&>*]:z-[6]"
|
||||
>
|
||||
<div class="site-banner__title">
|
||||
<IssuesIcon aria-hidden="true" />
|
||||
<span>{{ formatMessage(failedToBuildBannerMessages.title) }}</span>
|
||||
</div>
|
||||
<div class="site-banner__description">
|
||||
{{
|
||||
formatMessage(failedToBuildBannerMessages.description, {
|
||||
errors: generatedStateErrors,
|
||||
url: config.public.apiBaseUrl,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<header
|
||||
class="experimental-styles-within desktop-only relative z-[5] mx-auto grid max-w-[1280px] grid-cols-[1fr_auto] items-center gap-2 px-6 py-4 lg:grid-cols-[auto_1fr_auto]"
|
||||
>
|
||||
@@ -538,7 +555,7 @@
|
||||
<slot id="main" />
|
||||
</main>
|
||||
<footer
|
||||
class="footer-brand-background experimental-styles-within mt-6 border-0 border-t-[1px] border-solid"
|
||||
class="footer-brand-background experimental-styles-within border-0 border-t-[1px] border-solid"
|
||||
>
|
||||
<div class="mx-auto flex max-w-screen-xl flex-col gap-6 p-6 pb-20 sm:px-12 md:py-12">
|
||||
<div
|
||||
@@ -667,8 +684,9 @@ import {
|
||||
ScaleIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { Button, ButtonStyled, OverflowMenu, Avatar, commonMessages } from "@modrinth/ui";
|
||||
|
||||
import { isAdmin, isStaff } from "@modrinth/utils";
|
||||
import { errors as generatedStateErrors } from "~/generated/state.json";
|
||||
|
||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||
import { getProjectTypeMessage } from "~/utils/i18n-project-type.ts";
|
||||
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
||||
@@ -737,6 +755,18 @@ const stagingBannerMessages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const failedToBuildBannerMessages = defineMessages({
|
||||
title: {
|
||||
id: "layout.banner.build-fail.title",
|
||||
defaultMessage: "Error generating state from API when building.",
|
||||
},
|
||||
description: {
|
||||
id: "layout.banner.build-fail.description",
|
||||
defaultMessage:
|
||||
"This deploy of Modrinth's frontend failed to generate state from the API. This may be due to an outage or an error in configuration. Rebuild when the API is available. Error codes: {errors}; Current API URL is: {url}",
|
||||
},
|
||||
});
|
||||
|
||||
const navMenuMessages = defineMessages({
|
||||
home: {
|
||||
id: "layout.nav.home",
|
||||
|
||||
@@ -269,6 +269,12 @@
|
||||
"layout.banner.add-email.title": {
|
||||
"message": "For security purposes, please enter your email on Modrinth."
|
||||
},
|
||||
"layout.banner.build-fail.description": {
|
||||
"message": "This deploy of Modrinth's frontend failed to generate state from the API. This may be due to an outage or an error in configuration. Rebuild when the API is available. Error codes: {errors}; Current API URL is: {url}"
|
||||
},
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Error generating state from API when building."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "The staging environment is completely separate from the production Modrinth database. This is used for testing and debugging purposes, and may be running in-development versions of the Modrinth backend or frontend newer than the production instance."
|
||||
},
|
||||
|
||||
@@ -1105,14 +1105,19 @@ let project,
|
||||
featuredVersions,
|
||||
versions,
|
||||
organization,
|
||||
resetOrganization;
|
||||
resetOrganization,
|
||||
projectError,
|
||||
membersError,
|
||||
dependenciesError,
|
||||
featuredVersionsError,
|
||||
versionsError;
|
||||
try {
|
||||
[
|
||||
{ data: project, refresh: resetProject },
|
||||
{ data: allMembers, refresh: resetMembers },
|
||||
{ data: dependencies },
|
||||
{ data: featuredVersions },
|
||||
{ data: versions },
|
||||
{ data: project, error: projectError, refresh: resetProject },
|
||||
{ data: allMembers, error: membersError, refresh: resetMembers },
|
||||
{ data: dependencies, error: dependenciesError },
|
||||
{ data: featuredVersions, error: featuredVersionsError },
|
||||
{ data: versions, error: versionsError },
|
||||
{ data: organization, refresh: resetOrganization },
|
||||
] = await Promise.all([
|
||||
useAsyncData(`project/${route.params.id}`, () => useBaseFetch(`project/${route.params.id}`), {
|
||||
@@ -1159,14 +1164,30 @@ try {
|
||||
|
||||
versions = shallowRef(toRaw(versions));
|
||||
featuredVersions = shallowRef(toRaw(featuredVersions));
|
||||
} catch {
|
||||
} catch (err) {
|
||||
throw createError({
|
||||
fatal: true,
|
||||
statusCode: 404,
|
||||
message: "Project not found",
|
||||
statusCode: err.statusCode ?? 500,
|
||||
message: "Error loading project data" + (err.message ? `: ${err.message}` : ""),
|
||||
});
|
||||
}
|
||||
|
||||
function handleError(err, project = false) {
|
||||
if (err.value && err.value.statusCode) {
|
||||
throw createError({
|
||||
fatal: true,
|
||||
statusCode: err.value.statusCode,
|
||||
message: err.value.statusCode === 404 && project ? "Project not found" : err.value.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleError(projectError, true);
|
||||
handleError(membersError);
|
||||
handleError(dependenciesError);
|
||||
handleError(featuredVersionsError);
|
||||
handleError(versionsError);
|
||||
|
||||
if (!project.value) {
|
||||
throw createError({
|
||||
fatal: true,
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="users-section-outer">
|
||||
<div class="projects-showcase">
|
||||
<div v-if="rows" class="projects-showcase">
|
||||
<div v-for="(row, index) in rows" :key="index" class="row">
|
||||
<div v-for="n in 2" :key="n" class="row__content" :class="{ offset: index % 2 }">
|
||||
<nuxt-link
|
||||
@@ -61,6 +61,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="relative z-[10] w-full text-center text-xl font-bold text-contrast">
|
||||
Failed to load random projects :(
|
||||
</div>
|
||||
<div class="projects-transition" />
|
||||
<div class="users-section">
|
||||
<div class="section-header">
|
||||
@@ -535,23 +538,27 @@ const sortType = ref("relevance");
|
||||
const auth = await useAuth();
|
||||
const tags = useTags();
|
||||
|
||||
const newProjects = homePageProjects.slice(0, 40);
|
||||
const val = Math.ceil(newProjects.length / 3);
|
||||
const rows = ref([
|
||||
newProjects.slice(0, val),
|
||||
newProjects.slice(val, val * 2),
|
||||
newProjects.slice(val * 2, val * 3),
|
||||
]);
|
||||
const newProjects = homePageProjects?.slice(0, 40);
|
||||
const val = Math.ceil(newProjects?.length / 3);
|
||||
const rows = ref(
|
||||
newProjects.length > 0
|
||||
? [
|
||||
newProjects.slice(0, val),
|
||||
newProjects.slice(val, val * 2),
|
||||
newProjects.slice(val * 2, val * 3),
|
||||
]
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const notifications = ref(homePageNotifs.hits ?? []);
|
||||
const searchProjects = ref(homePageSearch.hits ?? []);
|
||||
const notifications = ref(homePageNotifs?.hits ?? []);
|
||||
const searchProjects = ref(homePageSearch?.hits ?? []);
|
||||
|
||||
async function updateSearchProjects() {
|
||||
const res = await useBaseFetch(
|
||||
`search?limit=3&query=${searchQuery.value}&index=${sortType.value}`,
|
||||
);
|
||||
|
||||
searchProjects.value = res.hits ?? [];
|
||||
searchProjects.value = res?.hits ?? [];
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user