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:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user