admin_szolgáltatók_
This commit is contained in:
145
frontend_admin/components/LanguageSwitcher.vue
Normal file
145
frontend_admin/components/LanguageSwitcher.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="relative" ref="dropdownRef">
|
||||
<!-- Trigger Button -->
|
||||
<button
|
||||
class="flex items-center gap-2 px-3 py-1.5 text-xs font-medium rounded-lg transition bg-slate-700/50 hover:bg-slate-700 text-slate-300 hover:text-white border border-slate-600/50"
|
||||
@click="dropdownOpen = !dropdownOpen"
|
||||
>
|
||||
<span class="text-base leading-none">{{ flagFor(currentLocale) }}</span>
|
||||
<span class="hidden sm:inline">{{ labelFor(currentLocale) }}</span>
|
||||
<svg
|
||||
class="w-3.5 h-3.5 text-slate-400 transition-transform duration-200"
|
||||
:class="{ 'rotate-180': dropdownOpen }"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div
|
||||
v-if="dropdownOpen"
|
||||
class="absolute right-0 mt-2 w-44 bg-slate-800 border border-slate-700 rounded-xl shadow-xl py-1 z-50"
|
||||
>
|
||||
<button
|
||||
v-for="loc in availableLocales"
|
||||
:key="loc.code"
|
||||
class="flex items-center gap-3 w-full px-4 py-2.5 text-sm transition"
|
||||
:class="
|
||||
loc.code === currentLocale
|
||||
? 'bg-indigo-600/20 text-indigo-300'
|
||||
: 'text-slate-300 hover:bg-slate-700 hover:text-white'
|
||||
"
|
||||
@click="selectLocale(loc.code)"
|
||||
>
|
||||
<span class="text-lg leading-none">{{ flagFor(loc.code) }}</span>
|
||||
<span>{{ loc.name }}</span>
|
||||
<span v-if="loc.code === currentLocale" class="ml-auto text-indigo-400">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { locale, locales } = useI18n()
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const dropdownRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// ── Locale helpers ──────────────────────────────────────────────
|
||||
|
||||
interface LocaleEntry {
|
||||
code: string
|
||||
name: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const availableLocales = computed(() => locales.value as LocaleEntry[])
|
||||
const currentLocale = computed(() => locale.value)
|
||||
|
||||
/** Map of language codes to flag emojis */
|
||||
const flagMap: Record<string, string> = {
|
||||
hu: '🇭🇺',
|
||||
en: '🇬🇧',
|
||||
}
|
||||
|
||||
/** Map of language codes to display labels (short) */
|
||||
const labelMap: Record<string, string> = {
|
||||
hu: 'Magyar',
|
||||
en: 'English',
|
||||
}
|
||||
|
||||
function flagFor(code: string): string {
|
||||
return flagMap[code] || '🌐'
|
||||
}
|
||||
|
||||
function labelFor(code: string): string {
|
||||
return labelMap[code] || code.toUpperCase()
|
||||
}
|
||||
|
||||
// ── Locale switching ────────────────────────────────────────────
|
||||
|
||||
function getAuthHeaders() {
|
||||
const tokenCookie = useCookie('access_token')
|
||||
const token = tokenCookie.value
|
||||
return token ? { Authorization: `Bearer ${token}` } : {}
|
||||
}
|
||||
|
||||
async function selectLocale(code: string) {
|
||||
if (code === currentLocale.value) {
|
||||
dropdownOpen.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Update vue-i18n locale immediately
|
||||
locale.value = code
|
||||
|
||||
// 2. Persist to localStorage
|
||||
try {
|
||||
localStorage.setItem('preferred_language', code)
|
||||
} catch {
|
||||
// localStorage may be unavailable
|
||||
}
|
||||
|
||||
// 3. Persist to backend
|
||||
try {
|
||||
await $fetch('/api/v1/auth/me/language', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...getAuthHeaders(),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: { preferred_language: code },
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('[LanguageSwitcher] Failed to persist language preference:', err)
|
||||
}
|
||||
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
// ── Close on outside click ──────────────────────────────────────
|
||||
|
||||
function handleClickOutside(e: MouseEvent) {
|
||||
if (dropdownRef.value && !dropdownRef.value.contains(e.target as Node)) {
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user