feat: error pages

This commit is contained in:
limes
2026-08-17 22:53:28 +02:00
parent e89f4b0509
commit a9e3ce1658
6 changed files with 162 additions and 46 deletions

View File

@@ -155,7 +155,5 @@ idk jvs, seems to work on my machine... and yours too now???
- [ ] forgot password shows success toast even w no captcha (doesn't actually submit)
- [ ] signup birthdate editor
- [ ] need error page
- [ ] need 404 page
- [X] need error page
- [X] need 404 page

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -26,11 +26,17 @@ function scoreLocale(cObj: LocaleObject): number {
return 2;
}
}
const sortedLocales = locales.value.sort((a, b) => {
const sA = scoreLocale(a);
const sB = scoreLocale(b);
return sA - sB || (a?.name || '').localeCompare(b?.name || '', locale.value);
const sortedLocales = computed(() => {
const sorted = [...locales.value];
sorted.sort((a, b) => {
const sA = scoreLocale(a);
const sB = scoreLocale(b);
return sA - sB || (a?.name || '').localeCompare(b?.name || '', locale.value);
});
return sorted;
});
const me = useMeStore();

79
src/error.vue Normal file
View File

@@ -0,0 +1,79 @@
<script setup lang="ts">
import DefaultLayout from './layouts/default.vue';
const { error } = defineProps<{ error: any }>();
</script>
<template>
<DefaultLayout>
<div
v-if="error?.statusCode === 404"
class="wrapper"
>
<h1 class="status">
{{ error?.statusCode }}
</h1>
<p class="description">
{{ $t('notfound.description') }}
</p>
<img
src="~/assets/images/err_bandwidthlost.png"
class="bandwidth notfound"
>
</div>
<div
v-else
class="wrapper"
>
<h1 class="status">
{{ error?.statusCode }}
</h1>
<p class="description">
{{ $t('errorPage.500.title') }} {{ $t('errorPage.500.description') }}
</p>
<img
src="/assets/images/shockedbandwidth.svg"
class="bandwidth"
>
</div>
</DefaultLayout>
</template>
<style scoped>
.status {
font-size: 8rem;
text-align: center;
margin: 6rem 0 0;
color: var(--text-shade-3);
}
.description {
font-size: 2rem;
text-align: center;
color: var(--text-shade-1);
margin: 0;
}
.bandwidth {
display: block;
margin: 4rem auto -120px;
height: 24rem;
}
.bandwidth.notfound {
height: 32rem;
margin-bottom: 4em;
}
@media screen and (min-height: 1100px) {
.status {
margin-top: 16rem;
}
.bandwidth {
margin: 16rem auto -120px;
}
.bandwidth.bandwidth.notfound {
margin: 6rem auto 16rem;
}
}
</style>

View File

@@ -341,7 +341,13 @@
"confirm": "Confirm",
"close": "Close"
},
"errorPage": {
"500": {
"title": "Server error!",
"description": "Please try again later."
}
},
"notfound": {
"description": "Oops! We could not find this page."
}
}
}

View File

@@ -51,15 +51,23 @@ watchImmediate(discordLinkQuery, (val) => {
useRouter().replace({ query: {} });
});
const { data: profile, refresh } = await useApiFetch('/api/auth/me');
const { data: connections, refresh: refreshConnections } = await useApiFetch('/api/auth/me-connections');
const selectedServerEnv = ref(profile.value?.serverAccessLevel);
const dialogContainer = ref(null);
const deleteModalOpen = ref(false);
const editModalOpen = ref(false);
const selectedServerEnv = ref<string | undefined>('');
const { data: profile, refresh } = await useApiFetch('/api/auth/me', {
onResponse: (opt) => {
selectedServerEnv.value = opt.response._data?.serverAccessLevel;
}
});
const { data: connections, refresh: refreshConnections } = await useApiFetch(
'/api/auth/me-connections'
);
const { execute: executeUpdateServerEnvironment, isLoading: isLoadingUpdateServerEnv } = useAsync({
const {
execute: executeUpdateServerEnvironment,
isLoading: isLoadingUpdateServerEnv
} = useAsync({
async handler(env: ApiAccountUpdateRequest['environment']) {
await apiFetch('/api/account/update', {
method: 'PATCH',
@@ -131,7 +139,6 @@ const { execute: executeUnlinkDiscord, isLoading: isLoadingUnlink } = useAsync({
useHead({
title: `Account`
});
</script>
<template>
@@ -202,15 +209,17 @@ useHead({
<AlertDialog.Portal :to="dialogContainer ?? undefined">
<AlertDialog.Overlay />
<AlertDialog.Content class="modal">
<AlertDialog.Title>{{ $t("account.settings.delete.modalTitle") }}?</AlertDialog.Title>
<AlertDialog.Title>
{{
$t("account.settings.delete.modalTitle")
}}?
</AlertDialog.Title>
<AlertDialog.Description class="modal-caption">
<p
style="white-space: pre-line;"
>
{{ $t('account.settings.delete.modalDescription') }}
<p style="white-space: pre-line">
{{ $t("account.settings.delete.modalDescription") }}
</p>
<p class="noundo">
{{ $t('account.settings.delete.modalCaution') }}
{{ $t("account.settings.delete.modalCaution") }}
</p>
</AlertDialog.Description>
<div class="modal-button-wrapper">
@@ -225,7 +234,9 @@ useHead({
@click="executeDeleteAccount"
>
<Loader v-if="isLoadingDelete" />
<span v-else>{{ $t("account.settings.delete.modalConfirm") }}</span>
<span v-else>{{
$t("account.settings.delete.modalConfirm")
}}</span>
</button>
</div>
</AlertDialog.Content>
@@ -246,9 +257,7 @@ useHead({
{{ $t("account.settings.settingCards.profile") }}
</h2>
<AlertDialog.Trigger
class="edit"
>
<AlertDialog.Trigger class="edit">
<Icon
name="ph:pencil"
size="26"
@@ -257,10 +266,14 @@ useHead({
<AlertDialog.Portal :to="dialogContainer ?? undefined">
<AlertDialog.Overlay />
<AlertDialog.Content class="modal">
<AlertDialog.Title>{{ $t("account.settings.unavailable") }}.</AlertDialog.Title>
<AlertDialog.Title>
{{ $t("account.settings.unavailable") }}.
</AlertDialog.Title>
<AlertDialog.Description class="modal-caption">
<p>
{{ $t('account.settings.settingCards.no_edit_from_dashboard') }}
{{
$t("account.settings.settingCards.no_edit_from_dashboard")
}}
</p>
</AlertDialog.Description>
<div class="modal-button-wrapper">
@@ -318,7 +331,11 @@ useHead({
<h2 class="header">
{{ $t("account.settings.settingCards.serverEnv") }}
</h2>
<fieldset :disabled="profile.serverAccessLevel === 'prod' && profile.accessLevel < 1">
<fieldset
:disabled="
profile.serverAccessLevel === 'prod' && profile.accessLevel < 1
"
>
<form
id="server"
class="server-selection"
@@ -337,7 +354,10 @@ useHead({
<h2>{{ $t("account.settings.settingCards.production") }}</h2>
</label>
<input
v-if="profile.serverAccessLevel !== 'prod' || profile.accessLevel > 0"
v-if="
profile.serverAccessLevel !== 'prod' ||
profile.accessLevel > 0
"
id="test"
v-model="selectedServerEnv"
type="radio"
@@ -345,7 +365,10 @@ useHead({
>
<label
v-if="profile.serverAccessLevel !== 'prod' || profile.accessLevel > 0"
v-if="
profile.serverAccessLevel !== 'prod' ||
profile.accessLevel > 0
"
for="test"
>
<Icon
@@ -382,7 +405,9 @@ useHead({
"
id="save-server-selection"
class="button secondary"
@click.prevent="() => executeUpdateServerEnvironment(selectedServerEnv)"
@click.prevent="
() => executeUpdateServerEnvironment(selectedServerEnv)
"
>
<Loader v-if="isLoadingUpdateServerEnv" />
<span v-else>Save</span>
@@ -406,9 +431,7 @@ useHead({
<h2 class="header">
{{ $t("account.account") }}
</h2>
<AlertDialog.Trigger
class="edit"
>
<AlertDialog.Trigger class="edit">
<Icon
name="ph:pencil"
size="26"
@@ -459,11 +482,9 @@ useHead({
>
{{ $t("account.settings.settingCards.connectedToDiscord") }}
<img
:style="{ height: '25px', width: '25px', borderRadius: '100px'}"
:style="{ height: '25px', width: '25px', borderRadius: '100px' }"
:src="connections?.discord?.avatarUrl ?? '#'"
>@{{
connections?.discord?.username
}}.
>@{{ connections?.discord?.username }}.
</p>
<button
@@ -473,16 +494,20 @@ useHead({
@click="executeUnlinkDiscord"
>
<Loader v-if="isLoadingUnlink" />
<span v-else>{{ $t("account.settings.settingCards.removeDiscord") }}</span>
<span v-else>{{
$t("account.settings.settingCards.removeDiscord")
}}</span>
</button>
<p v-else>
{{ $t("account.settings.settingCards.noDiscordLinked") }}
<NuxtLink
:style="{cursor: 'pointer'}"
:style="{ cursor: 'pointer' }"
@click="executeLinkDiscord"
>
<Loader v-if="isLoadingLink" />
<span v-else>{{ $t("account.settings.settingCards.linkDiscord") }}</span>
<span v-else>{{
$t("account.settings.settingCards.linkDiscord")
}}</span>
</NuxtLink>
</p>
</div>
@@ -492,7 +517,7 @@ useHead({
{{ $t("account.settings.settingCards.newsletter") }}
</h2>
<p>{{ $t("account.settings.settingCards.no_newsletter_notice") }}</p>
<!--
<!--
<form id="other">
<input type="checkbox" id="marketing" name="marketing" {{#if account.flags.marketing}}checked{{/if}}>
<label for="marketing">{{ locale.account.settings.settingCards.newsletterPrompt }}</label>
@@ -503,7 +528,10 @@ useHead({
</AlertDialog.Root>
<div
id="delete-account"
:class="{ 'modal-wrapper': true, hidden: !(deleteModalOpen || editModalOpen) }"
:class="{
'modal-wrapper': true,
hidden: !(deleteModalOpen || editModalOpen),
}"
>
<div ref="dialogContainer" />
</div>
@@ -651,7 +679,7 @@ useHead({
.modal p.noundo {
font-weight: bold;
color: var(--text-shade-3)
color: var(--text-shade-3);
}
/* Settings */
@@ -810,7 +838,7 @@ fieldset {
width: 100vw;
animation: banner-notice 5s;
z-index: 100;
color: var(--text-shade-3)
color: var(--text-shade-3);
}
.banner-notice div {
padding: 4px 36px;
@@ -822,7 +850,6 @@ fieldset {
}
.banner-notice.error div {
background: var(--red-shade-1);
}
footer {