Refine admin workflows and team access

This commit is contained in:
AzuTear
2026-06-26 18:09:29 +02:00
parent b7804e10ea
commit 8b69dfbafb
71 changed files with 5066 additions and 751 deletions
@@ -40,6 +40,9 @@ const props = defineProps<{
const activeFooterLink = ref<FooterLink | null>(null)
const activeFooterHtml = computed(() => privacyContentToHtml(activeFooterLink.value?.content || ''))
const safeNewsletterUrl = computed(() =>
isSafePublicUrl(props.siteContent.newsletterUrl) ? props.siteContent.newsletterUrl : '',
)
function openFooterLink(event: Event, link: FooterLink) {
if (!link.content.trim()) {
@@ -56,6 +59,17 @@ function openFooterLink(event: Event, link: FooterLink) {
function closeFooterLink() {
activeFooterLink.value = null
}
function isSafePublicUrl(value: string | null | undefined) {
if (!value) return false
try {
const url = new URL(value)
return url.protocol === 'http:' || url.protocol === 'https:'
} catch {
return false
}
}
</script>
<template>
@@ -98,7 +112,7 @@ function closeFooterLink() {
</template>
</a>
</div>
<a :href="props.siteContent.newsletterUrl" target="_blank" rel="noopener" style="display:inline-flex;align-items:center;gap:11px;padding:15px 26px;border-radius:14px;background:#fff;border:1px solid #e3d8f5;color:#5f44ad;text-decoration:none;font-family:'Outfit',sans-serif;font-weight:600;font-size:16px;box-shadow:0 8px 22px rgba(124,86,196,.1);" style-hover="transform:translateY(-2px);border-color:#c9b6f0;"><svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#8b6cdb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>Newsletter abonnieren</a>
<a v-if="safeNewsletterUrl" :href="safeNewsletterUrl" target="_blank" rel="noopener" style="display:inline-flex;align-items:center;gap:11px;padding:15px 26px;border-radius:14px;background:#fff;border:1px solid #e3d8f5;color:#5f44ad;text-decoration:none;font-family:'Outfit',sans-serif;font-weight:600;font-size:16px;box-shadow:0 8px 22px rgba(124,86,196,.1);" style-hover="transform:translateY(-2px);border-color:#c9b6f0;"><svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#8b6cdb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>Newsletter abonnieren</a>
</div>
<img class="home-community-card__image" src="/assets/jayu-hero.png" alt="Jayuhime" style="position:absolute;z-index:1;right:-26px;bottom:0;height:430px;width:auto;pointer-events:none;filter:drop-shadow(0 18px 36px rgba(120,80,180,.22));" />
</div>
@@ -7,7 +7,7 @@ import type { OverviewResponse } from '../../types/awards'
export function useHomeSocialPresentation(siteContent: ComputedRef<OverviewResponse['siteContent']>) {
const siteSocialLinks = computed(() =>
(siteContent.value.socialLinks ?? [])
.filter((social) => social?.url && social.platform)
.filter((social) => isSafePublicUrl(social?.url) && social.platform)
.map((social) => ({
label: social.label || social.platform || 'Social Link',
platform: social.platform || 'link',
@@ -27,7 +27,7 @@ export function useHomeSocialPresentation(siteContent: ComputedRef<OverviewRespo
)
const footerLinks = computed(() =>
(siteContent.value.footerLinks ?? []).filter((link) => link?.label && (link.url || link.content)),
(siteContent.value.footerLinks ?? []).filter((link) => link?.label && (isSafePublicUrl(link.url) || link.content)),
)
const privacyContentHtml = computed(() => privacyContentToHtml(siteContent.value.privacyPolicyContent || ''))
@@ -48,6 +48,17 @@ export function useHomeSocialPresentation(siteContent: ComputedRef<OverviewRespo
return `#${simpleIconForKey(platform)?.hex ?? '5f44ad'}`
}
function isSafePublicUrl(value: string | null | undefined) {
if (!value) return false
try {
const url = new URL(value)
return url.protocol === 'http:' || url.protocol === 'https:'
} catch {
return false
}
}
return {
hostSocialLinks,
communitySocialLinks,