Skip to content

Commit 26a2470

Browse files
committed
ui(errors): add transparent glass box to 403 page (centered, glow/blur)
1 parent d1d9f1d commit 26a2470

3 files changed

Lines changed: 76 additions & 0 deletions

File tree

ui/src/lib/api.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {
1010
import axios from "axios"
1111
import { getToken } from "./keycloak"
1212
import keycloak from "./keycloak"
13+
import router from "@/router"
14+
1315
// ===== BASE URL dynamisch bestimmen =====
1416
let BASE = import.meta.env.VITE_API_URL?.trim()
1517
if (!BASE) {
@@ -30,6 +32,7 @@ try {
3032
export const http = axios.create({
3133
baseURL: BASE,
3234
withCredentials: false,
35+
headers: { "Content-Type": "application/json" },
3336
})
3437

3538
// ===== INTERCEPTOR: fügt automatisch das Keycloak-Token hinzu =====
@@ -55,6 +58,32 @@ function clean<T extends Record<string, any>>(o: T): Partial<T> {
5558
Object.entries(o).filter(([, v]) => v !== undefined && v !== "" && v !== null)
5659
) as Partial<T>
5760
}
61+
// ============================================================
62+
// 🧭 Response-Interceptor → zentrales Error-Handling
63+
// ============================================================h
64+
http.interceptors.response.use(
65+
(response) => response,
66+
(error) => {
67+
const status = error.response?.status
68+
69+
if (status === 401) {
70+
console.warn("🔒 Unauthorized – redirecting to /login")
71+
router.push("/login")
72+
}
73+
74+
if (status === 403) {
75+
console.warn("🚫 Access denied – redirecting to /403")
76+
router.push("/403")
77+
}
78+
79+
if (status >= 500) {
80+
console.error("💥 Server error:", error.response)
81+
}
82+
83+
return Promise.reject(error)
84+
}
85+
)
86+
5887

5988
// ===== TYPES =====
6089
export type EmployeeDto = {

ui/src/router/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export default createRouter({
6161
meta: { title: "HR Dashboard", requiresAuth: true },
6262
},
6363

64+
//----- Error ------
65+
{
66+
path: "/403",
67+
name: "Forbidden",
68+
component: () => import("@/views/error/403.vue"),
69+
},
6470
// --- Fallback ---
6571
{ path: "/:pathMatch(.*)*", redirect: "/dashboard" },
6672
],

ui/src/views/error/403.vue

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<script setup lang="ts">
2+
import { RouterLink } from "vue-router"
3+
</script>
4+
5+
<template>
6+
<!-- Wrapper: nutzt globalen Hintergrund -->
7+
<div class="flex items-center justify-center min-h-[calc(100vh-96px)] px-4">
8+
<!-- 🔳 Glass-Box -->
9+
<div
10+
class="w-full max-w-md rounded-2xl border border-white/10 bg-white/5 backdrop-blur-md
11+
shadow-[0_8px_25px_rgba(0,0,0,0.6),0_0_25px_rgba(255,145,0,0.18)]
12+
p-10 text-center animate-fadeIn"
13+
>
14+
<h1 class="text-[96px] leading-none font-black text-rose-500 drop-shadow-[0_0_15px_rgba(255,0,0,0.25)]">
15+
403
16+
</h1>
17+
<h2 class="mt-2 text-2xl font-semibold text-white">Zugriff verweigert</h2>
18+
<p class="mt-4 text-sm leading-relaxed text-white/60">
19+
Du hast keine Berechtigung, auf diese Seite zuzugreifen.<br />
20+
Bitte kontaktiere dein Management oder kehre zum Dashboard zurück.
21+
</p>
22+
23+
<RouterLink
24+
to="/dashboard"
25+
class="mt-8 inline-flex items-center gap-2 px-6 py-3 rounded-lg font-semibold
26+
bg-[#ff9100] text-black hover:bg-[#ffb74d] transition
27+
shadow-[0_0_20px_rgba(255,145,0,0.35)] hover:scale-105"
28+
>
29+
🏠 Zurück zum Dashboard
30+
</RouterLink>
31+
</div>
32+
</div>
33+
</template>
34+
35+
<style scoped>
36+
@keyframes fadeIn {
37+
from { opacity: 0; transform: translateY(16px) scale(0.98); }
38+
to { opacity: 1; transform: translateY(0) scale(1); }
39+
}
40+
.animate-fadeIn { animation: fadeIn .5s ease-out both; }
41+
</style>

0 commit comments

Comments
 (0)