- Add ThemeToggle component (Auto / Dark / Light) to all views - Store preference per browser and detect system preference live via prefers-color-scheme - Apply theme before first render in index.html to prevent flash - Extend CSS with light theme variables and refactor glass styles
151 lines
4.9 KiB
Vue
151 lines
4.9 KiB
Vue
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { useAuth } from '../stores/auth'
|
||
import { useToast } from '../stores/toast'
|
||
import LogoMark from '../components/LogoMark.vue'
|
||
import ThemeToggle from '../components/ThemeToggle.vue'
|
||
import ShareLinkCard from '../components/ShareLinkCard.vue'
|
||
import SectionConnections from '../components/SectionConnections.vue'
|
||
import SectionRequests from '../components/SectionRequests.vue'
|
||
import SectionSettings from '../components/SectionSettings.vue'
|
||
|
||
const auth = useAuth()
|
||
const toast = useToast()
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
|
||
const tab = ref<'overview' | 'requests' | 'calendars' | 'settings'>('overview')
|
||
const loading = ref(true)
|
||
|
||
const tabs = [
|
||
{ value: 'overview', label: 'Übersicht' },
|
||
{ value: 'requests', label: 'Anfragen' },
|
||
{ value: 'calendars', label: 'Kalender' },
|
||
{ value: 'settings', label: 'Einstellungen' },
|
||
] as const
|
||
|
||
onMounted(async () => {
|
||
await auth.init()
|
||
if (!auth.user) {
|
||
auth.logout()
|
||
router.push({ name: 'login' })
|
||
return
|
||
}
|
||
loading.value = false
|
||
|
||
if (route.query.google === 'ok') {
|
||
toast.ok('Google Kalender verbunden 🎉')
|
||
tab.value = 'calendars'
|
||
} else if (route.query.google === 'error') {
|
||
toast.error('Google-Verbindung fehlgeschlagen: ' + (route.query.reason || 'unbekannter Fehler'))
|
||
tab.value = 'calendars'
|
||
} else if (typeof route.query.tab === 'string') {
|
||
tab.value = route.query.tab as typeof tab.value
|
||
}
|
||
if (Object.keys(route.query).length > 0) {
|
||
router.replace({ query: {} })
|
||
}
|
||
})
|
||
|
||
function logout() {
|
||
auth.logout()
|
||
router.push('/')
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<main class="page dash" v-if="!loading && auth.user">
|
||
<header class="glass dash-header">
|
||
<div class="row">
|
||
<LogoMark />
|
||
<strong class="brand">WannPassts</strong>
|
||
</div>
|
||
<div class="row">
|
||
<span class="muted small hide-sm">{{ auth.user.email }}</span>
|
||
<ThemeToggle />
|
||
<button class="btn btn-sm" @click="logout">Abmelden</button>
|
||
</div>
|
||
</header>
|
||
|
||
<nav class="tabs">
|
||
<button
|
||
v-for="t in tabs"
|
||
:key="t.value"
|
||
class="tab"
|
||
:class="{ active: tab === t.value }"
|
||
@click="tab = t.value"
|
||
>
|
||
{{ t.label }}
|
||
</button>
|
||
</nav>
|
||
|
||
<div v-if="tab === 'overview'" class="col overview">
|
||
<ShareLinkCard />
|
||
<div class="overview-grid">
|
||
<router-link :to="{ query: { tab: 'calendars' } }" class="glass tile" @click="tab = 'calendars'">
|
||
<span class="tile-icon">📆</span>
|
||
<strong>Kalender verbinden</strong>
|
||
<span class="muted small">Google, iCloud/CalDAV oder ICS-Link hinzufügen</span>
|
||
</router-link>
|
||
<router-link :to="{ query: { tab: 'requests' } }" class="glass tile" @click="tab = 'requests'">
|
||
<span class="tile-icon">📨</span>
|
||
<strong>Anfragen prüfen</strong>
|
||
<span class="muted small">Buchungsanfragen annehmen oder ablehnen</span>
|
||
</router-link>
|
||
<router-link :to="{ query: { tab: 'settings' } }" class="glass tile" @click="tab = 'settings'">
|
||
<span class="tile-icon">⚙️</span>
|
||
<strong>Buchungsregeln</strong>
|
||
<span class="muted small">Zeitfenster, Dauern, Slot-Größe festlegen</span>
|
||
</router-link>
|
||
</div>
|
||
<section class="glass privacy">
|
||
<strong>🔒 Datenschutz by Design</strong>
|
||
<p class="muted small" style="margin: 6px 0 0">
|
||
Besucher deiner Buchungsseite sehen ausschließlich <em>frei</em> oder <em>belegt</em> –
|
||
niemals Titel, Ort oder Beschreibung deiner Termine. Google-Anbindungen nutzen die
|
||
FreeBusy-API, die strukturell keine Termindetails liefert.
|
||
</p>
|
||
</section>
|
||
</div>
|
||
|
||
<SectionRequests v-else-if="tab === 'requests'" />
|
||
<SectionConnections v-else-if="tab === 'calendars'" />
|
||
<SectionSettings v-else />
|
||
</main>
|
||
|
||
<main v-else class="page" style="display: flex; justify-content: center; padding-top: 20vh">
|
||
<div class="skeleton" style="width: 320px; height: 120px"></div>
|
||
</main>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.dash { display: flex; flex-direction: column; gap: 18px; }
|
||
.dash-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 20px;
|
||
}
|
||
.brand { font-size: 17px; letter-spacing: -0.01em; }
|
||
.overview { gap: 18px; }
|
||
.overview-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||
gap: 14px;
|
||
}
|
||
.tile {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
padding: 22px;
|
||
text-decoration: none;
|
||
color: var(--text);
|
||
transition: transform 0.15s ease, border-color 0.15s ease;
|
||
cursor: pointer;
|
||
}
|
||
.tile:hover { transform: translateY(-2px); border-color: var(--border-strong); }
|
||
.tile-icon { font-size: 26px; }
|
||
.privacy { padding: 20px 24px; }
|
||
@media (max-width: 520px) { .hide-sm { display: none; } }
|
||
</style>
|