diff --git a/app/composables/useApi.ts b/app/composables/useApi.ts new file mode 100644 index 0000000..8b78d0b --- /dev/null +++ b/app/composables/useApi.ts @@ -0,0 +1,68 @@ +import { useAuthStore } from '~/stores/auth' + +type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' + +interface ApiRequestOptions { + method?: HttpMethod + body?: T + query?: Record + headers?: HeadersInit + auth?: boolean +} + +export function useApi() { + const auth = useAuthStore() + + const execute = async (path: string, options: ApiRequestOptions = {}) => { + const { method = 'GET', body, query, headers = {}, auth: requiresAuth = true } = options + + const computedHeaders: Record = { + Accept: 'application/json', + ...(headers as Record), + } + + if (requiresAuth && auth.accessToken) { + computedHeaders.Authorization = `Bearer ${auth.accessToken}` + } + + const requestOptions: any = { + method, + headers: computedHeaders, + query, + body, + } + + if (body && !(body instanceof FormData)) { + computedHeaders['Content-Type'] = 'application/json' + requestOptions.body = body + } + + try { + return await $fetch(path, requestOptions) + } catch (error: any) { + const status = error?.status || error?.response?.status + if (status === 401 && requiresAuth) { + const refreshed = await auth.tryRefresh() + if (refreshed) { + if (auth.accessToken) { + computedHeaders.Authorization = `Bearer ${auth.accessToken}` + } + return await $fetch(path, requestOptions) + } + await auth.logout() + } + throw error + } + } + + return { + request: execute, + get: (path: string, options: ApiRequestOptions = {}) => execute(path, { ...options, method: 'GET' }), + post: (path: string, body?: any, options: ApiRequestOptions = {}) => + execute(path, { ...options, method: 'POST', body }), + put: (path: string, body?: any, options: ApiRequestOptions = {}) => + execute(path, { ...options, method: 'PUT', body }), + del: (path: string, options: ApiRequestOptions = {}) => execute(path, { ...options, method: 'DELETE' }), + } +} + diff --git a/app/pages/agb.vue b/app/pages/agb.vue new file mode 100644 index 0000000..5a2571f --- /dev/null +++ b/app/pages/agb.vue @@ -0,0 +1,107 @@ + + + + diff --git a/app/pages/api-docs.vue b/app/pages/api-docs.vue new file mode 100644 index 0000000..9a8be9d --- /dev/null +++ b/app/pages/api-docs.vue @@ -0,0 +1,106 @@ + + + + diff --git a/app/pages/datenschutz.vue b/app/pages/datenschutz.vue new file mode 100644 index 0000000..2f57350 --- /dev/null +++ b/app/pages/datenschutz.vue @@ -0,0 +1,86 @@ + + + + + + diff --git a/app/pages/impressum.vue b/app/pages/impressum.vue new file mode 100644 index 0000000..f95a6b8 --- /dev/null +++ b/app/pages/impressum.vue @@ -0,0 +1,57 @@ + + diff --git a/app/pages/index.vue b/app/pages/index.vue index a5e2964..54e6c4b 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -262,17 +262,95 @@ POST /api/route/taxi
-
-
-

Jetzt OpenSquawk ausprobieren

-

Starte kostenlos. Upgrade jederzeit. Keine Bindung.

+
+
+
+

Frühzugang & Warteliste

+

+ Trag dich ein und sichere dir deinen Platz. Aktuell warten + {{ waitlistCountDisplay }} + Pilot:innen auf den nächsten Invite-Drop. +

+
+
+
Lade Warteliste…
+ +
+

Wir senden Einladungen in Batches, priorisieren aktive Wartelistenplätze und verschicken Einladungscodes per E-Mail.

-
- - + +
+ + +