feat: Add organization logo support

- Add Organization interface to auth store
- Fetch org info including logo_url on login
- Update sidebar to show org logo or name
- Add logo upload in Settings (chef only)
- Support FormData uploads in API client
This commit is contained in:
2026-03-13 05:09:05 +00:00
parent 0768696ead
commit bd0051561a
4 changed files with 236 additions and 9 deletions

View File

@@ -14,6 +14,14 @@ const message = ref('')
const error = ref('')
const showSecuritySettings = ref(false)
// Logo upload
const logoFile = ref<File | null>(null)
const logoPreview = ref<string | null>(null)
const logoLoading = ref(false)
const logoMessage = ref('')
const logoError = ref('')
const fileInput = ref<HTMLInputElement | null>(null)
// App lock status
const lockMethod = ref<string | null>(null)
@@ -23,8 +31,91 @@ onMounted(() => {
if (authStore.user?.email) {
localStorage.setItem('userEmail', authStore.user.email)
}
// Set logo preview from current org logo
if (authStore.orgLogo) {
logoPreview.value = authStore.orgLogo
}
})
function handleLogoSelect(event: Event) {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
// Validate file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml']
if (!allowedTypes.includes(file.type)) {
logoError.value = 'Nur JPG, PNG, WebP oder SVG erlaubt'
return
}
// Validate file size (5MB)
if (file.size > 5 * 1024 * 1024) {
logoError.value = 'Datei zu groß (max 5MB)'
return
}
logoFile.value = file
logoError.value = ''
// Create preview
const reader = new FileReader()
reader.onload = (e) => {
logoPreview.value = e.target?.result as string
}
reader.readAsDataURL(file)
}
async function uploadLogo() {
if (!logoFile.value) return
logoLoading.value = true
logoError.value = ''
logoMessage.value = ''
try {
const formData = new FormData()
formData.append('logo', logoFile.value)
const response = await api.post('/uploads/logo', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
logoMessage.value = 'Logo erfolgreich hochgeladen'
logoFile.value = null
// Update store with new logo URL
authStore.setOrgLogo(response.data.logo_url)
logoPreview.value = response.data.logo_url
} catch (e) {
logoError.value = e instanceof Error ? e.message : 'Fehler beim Hochladen'
} finally {
logoLoading.value = false
}
}
async function deleteLogo() {
if (!confirm('Logo wirklich löschen?')) return
logoLoading.value = true
logoError.value = ''
try {
await api.delete('/uploads/logo')
logoMessage.value = 'Logo gelöscht'
logoPreview.value = null
logoFile.value = null
authStore.setOrgLogo(null)
} catch (e) {
logoError.value = e instanceof Error ? e.message : 'Fehler beim Löschen'
} finally {
logoLoading.value = false
}
}
const lockStatusText = computed(() => {
if (!lockMethod.value || lockMethod.value === 'none') return 'Deaktiviert'
if (lockMethod.value === 'biometric') return 'Fingerabdruck / Face ID'
@@ -96,6 +187,84 @@ function handleSecurityClose() {
<label class="block text-sm text-gray-500">Rolle</label>
<p class="font-medium capitalize">{{ authStore.user?.role }}</p>
</div>
<div>
<label class="block text-sm text-gray-500">Organisation</label>
<p class="font-medium">{{ authStore.orgName }}</p>
</div>
</div>
</div>
<!-- Organization Logo (Chef only) -->
<div v-if="authStore.isChef" class="card">
<h2 class="text-lg font-semibold mb-4">🏢 Firmenlogo</h2>
<p class="text-gray-600 text-sm mb-4">
Lade ein Logo für deine Organisation hoch. Deine Mitarbeiter sehen es in der Sidebar.
</p>
<div class="flex items-start gap-6">
<!-- Logo Preview -->
<div class="flex-shrink-0">
<div class="w-32 h-32 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg flex items-center justify-center overflow-hidden bg-gray-50 dark:bg-gray-700">
<img
v-if="logoPreview"
:src="logoPreview"
alt="Logo Vorschau"
class="max-w-full max-h-full object-contain"
>
<span v-else class="text-4xl text-gray-400">🏢</span>
</div>
</div>
<!-- Upload Controls -->
<div class="flex-1 space-y-4">
<div>
<input
ref="fileInput"
type="file"
accept="image/jpeg,image/png,image/webp,image/svg+xml"
class="hidden"
@change="handleLogoSelect"
>
<button
type="button"
class="btn btn-secondary"
@click="fileInput?.click()"
>
📁 Datei auswählen
</button>
<span v-if="logoFile" class="ml-3 text-sm text-gray-600 dark:text-gray-400">
{{ logoFile.name }}
</span>
</div>
<p class="text-xs text-gray-500">
JPG, PNG, WebP oder SVG max. 5MB empfohlen: 200x80px
</p>
<div class="flex gap-2">
<button
v-if="logoFile"
type="button"
:disabled="logoLoading"
class="btn btn-primary"
@click="uploadLogo"
>
{{ logoLoading ? 'Hochladen...' : '⬆️ Hochladen' }}
</button>
<button
v-if="authStore.orgLogo"
type="button"
:disabled="logoLoading"
class="btn btn-secondary text-red-600 hover:bg-red-50"
@click="deleteLogo"
>
🗑 Logo löschen
</button>
</div>
<div v-if="logoError" class="text-red-600 text-sm">{{ logoError }}</div>
<div v-if="logoMessage" class="text-green-600 text-sm">{{ logoMessage }}</div>
</div>
</div>
</div>