diff --git a/frontend/app/app.vue b/frontend/app/app.vue
index ea4ef5c..a156593 100644
--- a/frontend/app/app.vue
+++ b/frontend/app/app.vue
@@ -1,3 +1,5 @@
+
+
diff --git a/frontend/app/composables/useAPI.ts b/frontend/app/composables/useAPI.ts
new file mode 100644
index 0000000..65a6300
--- /dev/null
+++ b/frontend/app/composables/useAPI.ts
@@ -0,0 +1,59 @@
+export function useApi() {
+ const config = useRuntimeConfig()
+ const { token, logout } = useAuth()
+
+ async function apiFetch(url: string, options: any = {}) {
+ try {
+ return await $fetch(url, {
+ baseURL: config.public.apiBase,
+ headers: {
+ ...(options.headers || {}),
+ ...(token.value ? { Authorization: `Bearer ${token.value}` } : {})
+ },
+ ...options
+ })
+ } catch (error: any) {
+ // Netzwerkfehler oder Server nicht erreichbar
+ if (!error.response) {
+ console.error("API unreachable:", error)
+ throw new Error("Der Server ist nicht erreichbar.")
+ }
+
+ const status = error.response.status
+ const data = error.response._data
+
+ // 401 → Token ungültig oder abgelaufen
+ if (status === 401) {
+ console.warn("API returned 401 — logging out")
+ logout()
+ throw new Error("Deine Sitzung ist abgelaufen. Bitte erneut einloggen.")
+ }
+
+ // 403 → keine Berechtigung
+ if (status === 403) {
+ throw new Error("Du hast keine Berechtigung für diese Aktion.")
+ }
+
+ // 400 → Validierungsfehler
+ if (status === 400) {
+ throw new Error(data?.detail || "Ungültige Eingabe.")
+ }
+
+ // 404 → nicht gefunden
+ if (status === 404) {
+ throw new Error("Die angeforderte Ressource wurde nicht gefunden.")
+ }
+
+ // 500 → Serverfehler
+ if (status >= 500) {
+ console.error("Server error:", data)
+ throw new Error("Interner Serverfehler. Bitte später erneut versuchen.")
+ }
+
+ // Fallback
+ throw new Error(data?.detail || "Unbekannter Fehler.")
+ }
+ }
+
+ return { apiFetch }
+}
diff --git a/frontend/app/composables/useAuth.ts b/frontend/app/composables/useAuth.ts
index 3acb2c5..15e3cd2 100644
--- a/frontend/app/composables/useAuth.ts
+++ b/frontend/app/composables/useAuth.ts
@@ -1,15 +1,46 @@
+function decodeJwt(token: string) {
+ try {
+ const payload = token.split(".")[1]
+ const decoded = JSON.parse(atob(payload))
+ return decoded
+ } catch {
+ return null
+ }
+}
export function useAuth() {
- const token = useState("token", () => null)
+ const token = useState("token", () => null)
function setToken(t: string) {
token.value = t
- localStorage.setItem("token", t)
+
+ if (typeof window !== "undefined") {
+ localStorage.setItem("token", t)
+ }
}
function loadToken() {
- token.value = localStorage.getItem("token")
+ if (typeof window !== "undefined") {
+ const stored = localStorage.getItem("token")
+ if (!stored) return
+
+ const payload = decodeJwt(stored)
+ if( !payload || !payload.exp || payload.exp * 1000 < Date.now()) {
+ console.info("Token expired - removing it from storage")
+ localStorage.removeItem("token")
+ token.value = null
+ }
+ token.value = stored
+
+ }
}
- return { token, setToken, loadToken }
-}
+ function logout() {
+ token.value = null
+ if (typeof window !== "undefined") {
+ localStorage.removeItem("token")
+ }
+ }
+
+ return { token, setToken, loadToken, logout }
+}
diff --git a/frontend/app/layouts/default.vue b/frontend/app/layouts/default.vue
index 1a115bb..6b9cc50 100644
--- a/frontend/app/layouts/default.vue
+++ b/frontend/app/layouts/default.vue
@@ -1,12 +1,34 @@
+
+
-
-
+
+
+
+
+
+
@@ -14,4 +36,3 @@
-
diff --git a/frontend/app/middleware/auth.global.ts b/frontend/app/middleware/auth.global.ts
new file mode 100644
index 0000000..9e713e2
--- /dev/null
+++ b/frontend/app/middleware/auth.global.ts
@@ -0,0 +1,11 @@
+export default defineNuxtRouteMiddleware((to) => {
+ if (import.meta.server) return // SSR ignorieren
+ // allow login-page
+
+ const { token, loadToken } = useAuth()
+ loadToken()
+
+ if (!token.value && to.path !== "/login") {
+ return navigateTo("/login")
+ }
+})
diff --git a/frontend/app/pages/login.vue b/frontend/app/pages/login.vue
new file mode 100644
index 0000000..dce2885
--- /dev/null
+++ b/frontend/app/pages/login.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
Login
+
+
+
+
+
+
+
+
+
{{ error }}
+
+
+
diff --git a/frontend/app/pages/owners.vue b/frontend/app/pages/owners.vue
deleted file mode 100644
index 1a6bf69..0000000
--- a/frontend/app/pages/owners.vue
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
diff --git a/frontend/app/pages/owners/index.vue b/frontend/app/pages/owners/index.vue
new file mode 100644
index 0000000..7ee4eaa
--- /dev/null
+++ b/frontend/app/pages/owners/index.vue
@@ -0,0 +1,97 @@
+
+
+
+
+
Owner Verwaltung
+
+
+
Neuen Owner anlegen
+
+
+
+
+
+
+
+
+
+
Owner Liste
+
+
Lade Owner…
+
{{ error }}
+
+
+
+
+ | ID |
+ Name |
+
+
+
+
+ | {{ owner.id }} |
+ {{ owner.name }} |
+
+
+
+
+
+