215 lines
6.3 KiB
Vue
215 lines
6.3 KiB
Vue
|
|
<template>
|
||
|
|
<div class="error-page">
|
||
|
|
<v-app>
|
||
|
|
<v-main>
|
||
|
|
<v-container class="fill-height">
|
||
|
|
<v-row justify="center" align="center" class="fill-height">
|
||
|
|
<v-col cols="12" md="8" lg="6" class="text-center">
|
||
|
|
<!-- Logo -->
|
||
|
|
<v-img
|
||
|
|
src="/MONACOUSA-Flags_376x376.png"
|
||
|
|
width="120"
|
||
|
|
height="120"
|
||
|
|
class="mx-auto mb-6"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<!-- Error Code -->
|
||
|
|
<h1 class="text-h1 font-weight-bold mb-4" style="color: #a31515;">
|
||
|
|
{{ error.statusCode }}
|
||
|
|
</h1>
|
||
|
|
|
||
|
|
<!-- Error Title -->
|
||
|
|
<h2 class="text-h3 mb-4 text-grey-darken-2">
|
||
|
|
{{ getErrorTitle(error.statusCode) }}
|
||
|
|
</h2>
|
||
|
|
|
||
|
|
<!-- Error Message -->
|
||
|
|
<p class="text-h6 mb-6 text-medium-emphasis" style="max-width: 600px; margin: 0 auto;">
|
||
|
|
{{ getErrorMessage(error.statusCode) }}
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<!-- Additional Info for 403 -->
|
||
|
|
<v-alert
|
||
|
|
v-if="error.statusCode === 403"
|
||
|
|
type="warning"
|
||
|
|
variant="tonal"
|
||
|
|
class="mb-6 text-left"
|
||
|
|
style="max-width: 500px; margin: 0 auto;"
|
||
|
|
>
|
||
|
|
<v-alert-title>Access Restricted</v-alert-title>
|
||
|
|
<p class="mb-2">This resource requires specific permissions:</p>
|
||
|
|
<ul class="ml-4">
|
||
|
|
<li v-if="error.statusMessage?.includes('Board')">Board membership required</li>
|
||
|
|
<li v-if="error.statusMessage?.includes('Admin')">Administrator privileges required</li>
|
||
|
|
<li v-if="!error.statusMessage?.includes('Board') && !error.statusMessage?.includes('Admin')">
|
||
|
|
Higher access level required
|
||
|
|
</li>
|
||
|
|
</ul>
|
||
|
|
</v-alert>
|
||
|
|
|
||
|
|
<!-- Action Buttons -->
|
||
|
|
<div class="d-flex flex-column flex-sm-row justify-center gap-4 mb-6">
|
||
|
|
<v-btn
|
||
|
|
color="primary"
|
||
|
|
size="large"
|
||
|
|
style="background-color: #a31515;"
|
||
|
|
@click="goHome"
|
||
|
|
>
|
||
|
|
<v-icon start>mdi-home</v-icon>
|
||
|
|
Go to Dashboard
|
||
|
|
</v-btn>
|
||
|
|
|
||
|
|
<v-btn
|
||
|
|
variant="outlined"
|
||
|
|
size="large"
|
||
|
|
style="border-color: #a31515; color: #a31515;"
|
||
|
|
@click="goBack"
|
||
|
|
>
|
||
|
|
<v-icon start>mdi-arrow-left</v-icon>
|
||
|
|
Go Back
|
||
|
|
</v-btn>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Contact Support for 403 -->
|
||
|
|
<div v-if="error.statusCode === 403" class="mt-8">
|
||
|
|
<v-divider class="mb-4" />
|
||
|
|
<p class="text-body-2 text-medium-emphasis mb-3">
|
||
|
|
Need access to this resource?
|
||
|
|
</p>
|
||
|
|
<v-btn
|
||
|
|
variant="text"
|
||
|
|
color="primary"
|
||
|
|
@click="contactSupport"
|
||
|
|
>
|
||
|
|
<v-icon start>mdi-email</v-icon>
|
||
|
|
Contact Administrator
|
||
|
|
</v-btn>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Debug Info (development only) -->
|
||
|
|
<div v-if="isDevelopment" class="mt-8 pa-4 bg-grey-lighten-4 rounded">
|
||
|
|
<p class="text-caption text-grey-darken-1 mb-2">Debug Information:</p>
|
||
|
|
<p class="text-caption font-mono">{{ error.statusMessage }}</p>
|
||
|
|
<p class="text-caption font-mono">{{ error.url }}</p>
|
||
|
|
</div>
|
||
|
|
</v-col>
|
||
|
|
</v-row>
|
||
|
|
</v-container>
|
||
|
|
</v-main>
|
||
|
|
</v-app>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
interface ErrorProps {
|
||
|
|
error: {
|
||
|
|
statusCode: number;
|
||
|
|
statusMessage: string;
|
||
|
|
url?: string;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
const props = defineProps<ErrorProps>();
|
||
|
|
|
||
|
|
// Check if we're in development mode
|
||
|
|
const isDevelopment = process.dev;
|
||
|
|
|
||
|
|
// Error title mapping
|
||
|
|
const getErrorTitle = (code: number): string => {
|
||
|
|
switch (code) {
|
||
|
|
case 403: return 'Access Denied';
|
||
|
|
case 404: return 'Page Not Found';
|
||
|
|
case 500: return 'Server Error';
|
||
|
|
case 401: return 'Unauthorized';
|
||
|
|
default: return 'Something Went Wrong';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Error message mapping
|
||
|
|
const getErrorMessage = (code: number): string => {
|
||
|
|
switch (code) {
|
||
|
|
case 403:
|
||
|
|
return 'You do not have the required permissions to access this resource. Please contact your administrator if you believe this is an error.';
|
||
|
|
case 404:
|
||
|
|
return 'The page you are looking for could not be found. It may have been moved, deleted, or you may have entered the wrong URL.';
|
||
|
|
case 500:
|
||
|
|
return 'An internal server error occurred. Our team has been notified and is working to resolve the issue. Please try again later.';
|
||
|
|
case 401:
|
||
|
|
return 'You need to be logged in to access this resource. Please sign in and try again.';
|
||
|
|
default:
|
||
|
|
return 'An unexpected error occurred. Please try refreshing the page or contact support if the problem persists.';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Navigation methods
|
||
|
|
const goHome = () => {
|
||
|
|
navigateTo('/dashboard');
|
||
|
|
};
|
||
|
|
|
||
|
|
const goBack = () => {
|
||
|
|
if (window.history.length > 1) {
|
||
|
|
window.history.back();
|
||
|
|
} else {
|
||
|
|
navigateTo('/dashboard');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const contactSupport = () => {
|
||
|
|
// TODO: Implement support contact (email, help desk, etc.)
|
||
|
|
window.location.href = 'mailto:support@monacousa.org?subject=Access Request&body=I need access to a restricted resource.';
|
||
|
|
};
|
||
|
|
|
||
|
|
// Set page title
|
||
|
|
useHead({
|
||
|
|
title: `Error ${props.error.statusCode} - MonacoUSA Portal`,
|
||
|
|
meta: [
|
||
|
|
{ name: 'robots', content: 'noindex' }
|
||
|
|
]
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.error-page {
|
||
|
|
min-height: 100vh;
|
||
|
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||
|
|
}
|
||
|
|
|
||
|
|
.v-main {
|
||
|
|
background: transparent !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.font-mono {
|
||
|
|
font-family: 'Courier New', monospace;
|
||
|
|
}
|
||
|
|
|
||
|
|
.v-btn {
|
||
|
|
text-transform: none !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.v-alert {
|
||
|
|
text-align: left;
|
||
|
|
}
|
||
|
|
|
||
|
|
.v-alert ul {
|
||
|
|
margin-bottom: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.v-alert li {
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 600px) {
|
||
|
|
.text-h1 {
|
||
|
|
font-size: 4rem !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.text-h3 {
|
||
|
|
font-size: 1.75rem !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.text-h6 {
|
||
|
|
font-size: 1.1rem !important;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|