wannpassts/frontend/src/views/DashboardView.vue
Tronax f220d96e3b
feat(frontend): add dark/light theme toggle with system detection
- 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
2026-08-25 18:59:12 +02:00

151 lines
4.9 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>