feat(contacts): ContactCard + ContactsBlock-Komponente, BlockRenderer registriert
Deploy / verify (push) Successful in 1m8s
Deploy / deploy (push) Successful in 1m13s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-06-04 08:49:23 +02:00
parent 1827d9772a
commit 7bd62b75b0
4 changed files with 78 additions and 0 deletions
+18
View File
@@ -331,6 +331,24 @@ export interface OrganisationsBlockData {
layout?: BlockLayout;
}
export interface ContactEntry {
_type?: "contact";
_slug?: string;
name?: string;
role?: string;
email?: string;
phone?: string;
note?: string;
}
export interface ContactsBlockData {
_type?: "contacts_block";
_slug?: string;
headline?: string;
contacts?: (string | ContactEntry)[];
layout?: BlockLayout;
}
/** Live-Strommix-Widget (_type: "live_strommix"). Editorial-Konfig + Platzierungs-Instanz.
* Fetched live data from /api/strommix (aggregator) and renders status bucket
* derived from `(wind_onshore + wind_offshore + solar) / load * 100`. */
+4
View File
@@ -16,6 +16,7 @@
import CalendarBlock from "./blocks/CalendarBlock.svelte";
import CalendarCompactBlock from "./blocks/CalendarCompactBlock.svelte";
import OrganisationsBlock from "./blocks/OrganisationsBlock.svelte";
import ContactsBlock from "./blocks/ContactsBlock.svelte";
import OpnFormBlock from "./blocks/OpnFormBlock.svelte";
import InternalComponentBlock from "./blocks/InternalComponentBlock.svelte";
import DeadlineBannerBlock from "./blocks/DeadlineBannerBlock.svelte";
@@ -39,6 +40,7 @@
SearchableTextBlockData,
CalendarBlockData,
OrganisationsBlockData,
ContactsBlockData,
OpnFormBlockData,
InternalComponentBlockData,
DeadlineBannerBlockData,
@@ -91,6 +93,8 @@
{/if}
{:else if type === "organisations"}
<OrganisationsBlock block={block as OrganisationsBlockData} />
{:else if type === "contacts_block"}
<ContactsBlock block={block as ContactsBlockData} />
{:else if type === "opnform"}
<OpnFormBlock block={block as OpnFormBlockData} />
{:else if type === "internal_component"}
+31
View File
@@ -0,0 +1,31 @@
<script lang="ts">
import Icon from "@iconify/svelte";
import "$lib/iconify-offline";
import type { ContactEntry } from "$lib/block-types";
let { contact }: { contact: ContactEntry } = $props();
</script>
<div class="flex flex-col gap-1.5 rounded-lg border border-stein-200 bg-white px-4 py-3">
<div>
<span class="text-sm font-semibold text-stein-900">{contact.name ?? ""}</span>
{#if contact.role}
<span class="ml-1.5 text-xs text-stein-500">{contact.role}</span>
{/if}
</div>
{#if contact.phone}
<a href="tel:{contact.phone.replace(/\s/g, '')}" class="inline-flex items-center gap-1.5 text-xs text-stein-700 no-underline hover:text-wald-700">
<Icon icon="mdi:phone-outline" class="size-3.5 shrink-0 text-stein-400" />
{contact.phone}
</a>
{/if}
{#if contact.email}
<a href="mailto:{contact.email}" class="inline-flex items-center gap-1.5 text-xs text-stein-700 no-underline hover:text-wald-700">
<Icon icon="mdi:email-outline" class="size-3.5 shrink-0 text-stein-400" />
{contact.email}
</a>
{/if}
{#if contact.note}
<p class="text-[11px] text-stein-400">{contact.note}</p>
{/if}
</div>
@@ -0,0 +1,25 @@
<script lang="ts">
import ContactCard from "$lib/components/ContactCard.svelte";
import type { ContactsBlockData, ContactEntry } from "$lib/block-types";
let { block }: { block: ContactsBlockData } = $props();
const contacts = $derived(
(block.contacts ?? []).filter((c): c is ContactEntry => typeof c === "object" && c !== null)
);
</script>
{#if contacts.length > 0}
<div>
{#if block.headline}
<h3 class="mb-3 text-sm font-semibold text-stein-700">{block.headline}</h3>
{/if}
<div class="flex flex-col gap-2 sm:flex-row sm:flex-wrap">
{#each contacts as contact}
<div class="sm:min-w-[220px] sm:max-w-xs">
<ContactCard {contact} />
</div>
{/each}
</div>
</div>
{/if}