added theme toggle,

fixed create entityies in API
This commit is contained in:
2026-02-14 08:01:07 +01:00
parent 9b2f2c4e91
commit 276cc21c5a
13 changed files with 141 additions and 46 deletions

View File

@ -405,6 +405,12 @@ select {
color: var(--color-muted);
}
.app-topbar__actions {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
}
.app-content {
padding: var(--space-lg) var(--space-xl);
}
@ -609,6 +615,24 @@ select {
margin-bottom: var(--space-md);
}
.settings-card {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.settings-card__theme {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-sm);
}
.settings-card__theme span {
color: var(--color-muted);
font-size: var(--fs-sm);
}
.app-bottom-tabs {
display: none;
}
@ -671,4 +695,8 @@ select {
color: var(--color-green);
font-weight: 700;
}
.app-topbar__actions {
gap: var(--space-xs);
}
}

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons'
import { useThemeStore } from '@/stores/theme'
const props = withDefaults(defineProps<{
showLabel?: boolean
}>(), {
showLabel: true,
})
const { t } = useI18n()
const themeStore = useThemeStore()
themeStore.initialize()
const icon = computed(() => (themeStore.isDarkMode ? faMoon : faSun))
const themeLabel = computed(() => t(themeStore.currentThemeLabelKey))
</script>
<template>
<button
type="button"
class="theme-btn"
:aria-label="t('theme.toggle')"
@click="themeStore.toggle"
>
<font-awesome-icon :icon="icon" />
<span v-if="props.showLabel">{{ themeLabel }}</span>
</button>
</template>

View File

@ -2,6 +2,7 @@
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import ThemeToggle from '@/components/common/ThemeToggle.vue'
import { useAuthStore } from '@/stores/auth'
const route = useRoute()
@ -33,8 +34,11 @@ const title = computed(() => {
<div>
<h1>{{ title }}</h1>
</div>
<div class="app-topbar__user" v-if="auth.userEmail">
{{ auth.userEmail }}
<div class="app-topbar__actions">
<ThemeToggle :show-label="false" />
<div class="app-topbar__user" v-if="auth.userEmail">
{{ auth.userEmail }}
</div>
</div>
</header>
</template>

View File

@ -29,7 +29,8 @@
},
"theme": {
"light": "Světlý režim",
"dark": "Tmavý režim"
"dark": "Tmavý režim",
"toggle": "Přepnout režim"
},
"locale": {
"sk": "Slovenština",
@ -133,6 +134,7 @@
"settings": {
"accountTitle": "Nastavení účtu",
"loggedInAs": "Přihlášený uživatel",
"themeTitle": "Vzhled",
"logout": "Odhlásit se"
}
}

View File

@ -29,7 +29,8 @@
},
"theme": {
"light": "Heller Modus",
"dark": "Dunkler Modus"
"dark": "Dunkler Modus",
"toggle": "Modus wechseln"
},
"locale": {
"sk": "Slowakisch",
@ -133,6 +134,7 @@
"settings": {
"accountTitle": "Kontoeinstellungen",
"loggedInAs": "Angemeldet als",
"themeTitle": "Darstellung",
"logout": "Abmelden"
}
}

View File

@ -29,7 +29,8 @@
},
"theme": {
"light": "Light mode",
"dark": "Dark mode"
"dark": "Dark mode",
"toggle": "Toggle mode"
},
"locale": {
"sk": "Slovak",
@ -133,6 +134,7 @@
"settings": {
"accountTitle": "Account settings",
"loggedInAs": "Logged in as",
"themeTitle": "Appearance",
"logout": "Log out"
}
}

View File

@ -29,7 +29,8 @@
},
"theme": {
"light": "Modo claro",
"dark": "Modo oscuro"
"dark": "Modo oscuro",
"toggle": "Cambiar modo"
},
"locale": {
"sk": "Eslovaco",
@ -133,6 +134,7 @@
"settings": {
"accountTitle": "Ajustes de cuenta",
"loggedInAs": "Sesión iniciada como",
"themeTitle": "Apariencia",
"logout": "Cerrar sesión"
}
}

View File

@ -29,7 +29,8 @@
},
"theme": {
"light": "Svetlý režim",
"dark": "Tmavý režim"
"dark": "Tmavý režim",
"toggle": "Prepnúť režim"
},
"locale": {
"sk": "Slovenčina",
@ -133,6 +134,7 @@
"settings": {
"accountTitle": "Nastavenia účtu",
"loggedInAs": "Prihlásený používateľ",
"themeTitle": "Vzhľad",
"logout": "Odhlásiť sa"
}
}

View File

@ -5,8 +5,11 @@ import App from './App.vue'
import i18n from './i18n'
import router from './router'
import { pinia } from './stores'
import { useThemeStore } from './stores/theme'
const app = createApp(App)
const themeStore = useThemeStore(pinia)
themeStore.initialize()
app.component('font-awesome-icon', FontAwesomeIcon)
app.use(pinia)

View File

@ -0,0 +1,44 @@
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
export type ThemeMode = 'light' | 'dark'
const THEME_KEY = 'theme'
const isThemeMode = (value: unknown): value is ThemeMode => value === 'light' || value === 'dark'
export const useThemeStore = defineStore('theme', () => {
const mode = ref<ThemeMode>('light')
const initialized = ref(false)
const applyTheme = (nextMode: ThemeMode) => {
mode.value = nextMode
document.documentElement.setAttribute('data-theme', nextMode)
localStorage.setItem(THEME_KEY, nextMode)
}
const initialize = () => {
if (initialized.value) {
return
}
const storedTheme = localStorage.getItem(THEME_KEY)
applyTheme(isThemeMode(storedTheme) ? storedTheme : 'light')
initialized.value = true
}
const toggle = () => {
applyTheme(mode.value === 'dark' ? 'light' : 'dark')
}
const isDarkMode = computed(() => mode.value === 'dark')
const currentThemeLabelKey = computed(() => (isDarkMode.value ? 'theme.dark' : 'theme.light'))
return {
mode,
isDarkMode,
currentThemeLabelKey,
initialize,
applyTheme,
toggle,
}
})

View File

@ -8,17 +8,14 @@ import {
faEyeSlash,
faGlobe,
faLock,
faMoon,
faRightToBracket,
faSun,
} from '@fortawesome/free-solid-svg-icons'
import ThemeToggle from '@/components/common/ThemeToggle.vue'
import i18n from '@/i18n'
import { SUPPORTED_LOCALES, type AppLocale } from '@/i18n'
import { useAuthStore } from '@/stores/auth'
type ThemeMode = 'light' | 'dark'
const { t } = useI18n({ useScope: 'global' })
const router = useRouter()
const route = useRoute()
@ -57,31 +54,6 @@ watch(
{ immediate: true },
)
const getInitialTheme = (): ThemeMode => {
const storedTheme = localStorage.getItem('theme')
return storedTheme === 'dark' ? 'dark' : 'light'
}
const theme = ref<ThemeMode>(getInitialTheme())
const applyTheme = (nextTheme: ThemeMode) => {
theme.value = nextTheme
document.documentElement.setAttribute('data-theme', nextTheme)
localStorage.setItem('theme', nextTheme)
}
applyTheme(theme.value)
const toggleTheme = () => {
applyTheme(theme.value === 'dark' ? 'light' : 'dark')
}
const isDarkMode = computed(() => theme.value === 'dark')
const themeLabel = computed(() => {
return isDarkMode.value ? t('theme.dark') : t('theme.light')
})
const submitLabel = computed(() => {
return isLoading.value ? t('auth.submitting') : t('auth.submit')
})
@ -166,10 +138,7 @@ const submitForm = async () => {
</select>
</label>
<button type="button" class="theme-btn" :aria-label="t('auth.themeToggle')" @click="toggleTheme">
<font-awesome-icon :icon="isDarkMode ? faMoon : faSun" />
<span>{{ themeLabel }}</span>
</button>
<ThemeToggle />
</div>
<h2>{{ t('auth.title') }}</h2>

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import ThemeToggle from '@/components/common/ThemeToggle.vue'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
@ -20,6 +21,10 @@ const onLogout = async () => {
<p v-if="auth.userEmail">
{{ t('settings.loggedInAs') }}: <strong>{{ auth.userEmail }}</strong>
</p>
<div class="settings-card__theme">
<span>{{ t('settings.themeTitle') }}</span>
<ThemeToggle />
</div>
<button class="btn btn-danger" type="button" @click="onLogout">{{ t('settings.logout') }}</button>
</section>
</section>