import { computed, reactive, ref, watch } from 'vue' import { normalizeSocialIconKey, simpleIconForKey, socialIconOptionForKey, } from '../../lib/socialIcons' import { privacyContentForStorage, privacyContentToHtml } from '../../lib/privacyContent' import { useAwardsStore } from '../../stores/awards' import type { AdminContentForm, SocialLinkForm } from './adminContentTypes' function createEmptyForm(): AdminContentForm { return { hostDisplayName: '', hostTagline: '', newsletterUrl: '', privacyEmail: '', privacyPolicyContent: '', imprintUrl: '', imprintContent: '', contactUrl: '', contactContent: '', sponsorsUrl: '', sponsorsContent: '', socialLinks: [], faq: [], } } function readFileAsDataUrl(file: File) { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(String(reader.result ?? '')) reader.onerror = () => reject(new Error('Icon konnte nicht gelesen werden.')) reader.readAsDataURL(file) }) } async function validateRasterDimensions(file: File) { if (file.type === 'image/svg+xml') { return } const objectUrl = URL.createObjectURL(file) try { await new Promise((resolve, reject) => { const image = new Image() image.onload = () => { if (image.naturalWidth > 512 || image.naturalHeight > 512) { reject(new Error('Icon ist zu groß. Maximal erlaubt sind 512 x 512 px.')) return } resolve() } image.onerror = () => reject(new Error('Icon-Abmessungen konnten nicht geprüft werden.')) image.src = objectUrl }) } finally { URL.revokeObjectURL(objectUrl) } } export function useAdminContentManager() { const store = useAwardsStore() const form = reactive(createEmptyForm()) const saving = ref(false) const saveMessage = ref('') const saveError = ref('') const privacyPreviewOpen = ref(false) const iconUploadError = ref('') watch( () => store.adminSiteSettings, (settings) => { form.hostDisplayName = settings.hostDisplayName form.hostTagline = settings.hostTagline form.newsletterUrl = settings.newsletterUrl form.privacyEmail = settings.privacyEmail form.privacyPolicyContent = settings.privacyPolicyContent form.imprintUrl = settings.imprintUrl form.imprintContent = settings.imprintContent form.contactUrl = settings.contactUrl form.contactContent = settings.contactContent form.sponsorsUrl = settings.sponsorsUrl form.sponsorsContent = settings.sponsorsContent form.socialLinks = settings.socialLinks.map((item) => ({ label: item.label ?? '', platform: item.platform ?? '', icon: item.icon ?? '', url: item.url ?? '', showOnHost: item.showOnHost ?? true, showOnCommunity: item.showOnCommunity ?? true, })) form.faq = settings.faq.map((item) => ({ question: item.question ?? '', answer: item.answer ?? '', })) }, { immediate: true, deep: true }, ) const privacyPreviewHtml = computed(() => privacyContentToHtml(form.privacyPolicyContent)) const privacyUpdatedLabel = computed(() => { const updatedAt = store.adminSiteSettings.privacyPolicyUpdatedAt if (!updatedAt) { return 'Noch keine Änderung gespeichert' } return new Intl.DateTimeFormat('de-DE', { dateStyle: 'medium', timeStyle: 'short', }).format(new Date(updatedAt)) }) function addSocialLink() { form.socialLinks.push({ label: 'Neu', platform: 'website', icon: '', url: '', showOnHost: true, showOnCommunity: true, }) } function removeSocialLink(index: number) { form.socialLinks.splice(index, 1) } function addFaqItem() { form.faq.push({ question: '', answer: '' }) } function removeFaqItem(index: number) { form.faq.splice(index, 1) } function isUploadedIcon(icon: string) { return icon.startsWith('data:image/') } function socialIconKey(social: SocialLinkForm) { return normalizeSocialIconKey(social.icon || social.platform) } function selectedSocialIconValue(social: SocialLinkForm) { return socialIconOptionForKey(social.platform) ? normalizeSocialIconKey(social.platform) : 'custom' } function handleSocialIconSelection(value: string, index: number) { const social = form.socialLinks[index] if (!social) { return } if (value === 'custom') { if (socialIconOptionForKey(social.platform)) { social.platform = '' } return } const option = socialIconOptionForKey(value) social.platform = value if (!social.label || social.label === 'Neu' || social.label === 'Website / Fallback') { social.label = option?.label ?? value } if (social.icon && !isUploadedIcon(social.icon)) { social.icon = '' } } function hasSocialIconPreview(social: SocialLinkForm) { return Boolean(social.label || social.platform || social.icon) } function socialIconModeLabel(social: SocialLinkForm) { if (isUploadedIcon(social.icon)) { return 'hochgeladenes Custom Icon' } const icon = simpleIconForKey(socialIconKey(social)) if (icon) { return `eingebautes ${icon.title} Icon` } return 'generisches Fallback Icon' } function socialSimpleIconPath(social: SocialLinkForm) { return simpleIconForKey(socialIconKey(social))?.path ?? '' } function socialSimpleIconColor(social: SocialLinkForm) { return `#${simpleIconForKey(socialIconKey(social))?.hex ?? '8b5cf6'}` } async function handleSocialIconUpload(event: Event, index: number) { iconUploadError.value = '' const input = event.target as HTMLInputElement const file = input.files?.[0] input.value = '' if (!file) { return } const allowedTypes = ['image/png', 'image/jpeg', 'image/webp', 'image/svg+xml'] if (!allowedTypes.includes(file.type)) { iconUploadError.value = 'Bitte PNG, JPG, WebP oder SVG hochladen.' return } const maxBytes = 256 * 1024 if (file.size > maxBytes) { iconUploadError.value = 'Icon ist zu groß. Maximal erlaubt sind 256 KB.' return } try { await validateRasterDimensions(file) form.socialLinks[index].icon = await readFileAsDataUrl(file) } catch (error) { iconUploadError.value = error instanceof Error ? error.message : 'Icon konnte nicht hochgeladen werden.' } } function clearSocialIcon(index: number) { form.socialLinks[index].icon = '' } async function saveSiteSettings(sectionLabel = 'Landingpage-Inhalte') { saving.value = true saveMessage.value = '' saveError.value = '' try { await store.updateAdminSiteSettings({ hostDisplayName: form.hostDisplayName, hostTagline: form.hostTagline, newsletterUrl: form.newsletterUrl, privacyEmail: form.privacyEmail, privacyPolicyContent: privacyContentForStorage(form.privacyPolicyContent), imprintUrl: form.imprintUrl, imprintContent: privacyContentForStorage(form.imprintContent), contactUrl: form.contactUrl, contactContent: privacyContentForStorage(form.contactContent), sponsorsUrl: form.sponsorsUrl, sponsorsContent: privacyContentForStorage(form.sponsorsContent), socialLinks: form.socialLinks .map((item) => ({ label: item.label.trim(), platform: item.platform.trim(), icon: item.icon.trim(), url: item.url.trim(), showOnHost: item.showOnHost, showOnCommunity: item.showOnCommunity, })) .filter((item) => item.label && item.platform && item.url), faq: form.faq .map((item) => ({ question: item.question.trim(), answer: item.answer.trim(), })) .filter((item) => item.question && item.answer), }) saveMessage.value = `${sectionLabel} gespeichert und auf der Landingpage aktualisiert.` } catch (error) { saveError.value = error instanceof Error ? error.message : 'Inhalte konnten nicht gespeichert werden.' } finally { saving.value = false } } return { form, saving, saveMessage, saveError, privacyPreviewOpen, iconUploadError, privacyPreviewHtml, privacyUpdatedLabel, addSocialLink, removeSocialLink, addFaqItem, removeFaqItem, isUploadedIcon, selectedSocialIconValue, handleSocialIconSelection, hasSocialIconPreview, socialIconModeLabel, socialSimpleIconPath, socialSimpleIconColor, handleSocialIconUpload, clearSocialIcon, saveSiteSettings, adminSiteSettings: computed(() => store.adminSiteSettings), } }