wf4/app/components/Section/Booking.vue
webfussel a86b89dc98 add: extract plain faq
Add Schema Org for FAQ
2025-06-11 08:54:32 +02:00

149 lines
4.5 KiB
Vue
Executable file

<template>
<section id="services" class="Services content">
<h1>Projekt buchen</h1>
<h2>Paketpreise für <Highlight>feste Ergebnisse</Highlight>.</h2>
<p class="margin-top">Manchmal brauchen wir alle einfach nur eine Kleinigkeit und wollen uns nicht lange binden. Das ist natürlich
völlig in Ordnung und genau deshalb biete ich dir die Möglichkeit mich gezielt für <Highlight>kleinere Projekte</Highlight> zu buchen.</p>
<p class="margin-top-small">Hinter diesen Angeboten gibt es <Highlight>keinerlei Abos oder versteckte Kosten</Highlight>.
Aus Transparenzgründen sei aber gesagt, dass sich <Highlight>alle Preise zzgl. 19 % Umsatzsteuer</Highlight> verstehen.</p>
<FreeInfo />
<div class="Pricing margin-top">
<article v-for="(service, index) in oneOff" :class="{ 'z-3-all': index === 1, 'z-2-all': index !== 1}">
<header>
<strong>{{service.title}}</strong>
<p class="claim">{{service.smallClaim}}</p>
<div class="price">
<span v-if="service.price.pre">{{service.price.pre}}</span>
<span>{{typeof service.price.value === 'number' ? intl.format(service.price.value) : service.price.value}}</span>
<span v-if="service.price.post" class="post">{{service.price.post}}</span>
</div>
<div aria-hidden="true" class="bg-icon">
<Icon :name="service.icon" size="1.5em" mode="svg" />
</div>
</header>
<main>
<div class="list-container">
<ul>
<li v-for="point in service.list">
<Icon class="icon yes" name="ph:caret-circle-double-right-duotone" size="1.5em" mode="svg" />
<span class="label">{{ point }}</span>
</li>
</ul>
</div>
</main>
<footer>
<div class="button-wrapper z-2">
<Button :href="service.link" aria-label="Zur externen Seite Terminbuchung" :design="index === 1 ? 'default' : 'white'">
{{ service.button }}
</Button>
</div>
</footer>
</article>
</div>
<div v-if="faq" class="flex-col gap-sm margin-top">
<ContentRenderer :value="faq" :style="{ display: 'contents' }" />
</div>
</section>
</template>
<script setup lang="ts">
type Service = {
title: string
smallClaim: string
price: {
value: number | string
pre?: string
post?: string
}
icon: string
button: string
link: string
list: string[]
}
const intl = new Intl.NumberFormat(
'de-DE',
{
style: 'currency',
currency: 'EUR',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}
)
const oneOff : Service[] = [
{
title: 'Quick Check',
price: {
value: 149,
},
smallClaim: 'A11y, bugs, schlechter Code.',
icon: 'ph:magnifying-glass-thin',
button: 'Jetzt untersuchen',
link: 'https://tidycal.com/webfussel/quick-check',
list: [
'Untersuchung des Quellcodes',
'Untersuchung der Performance',
'Tipps zu CSS und Best Practices',
'Behebung unkompliziert nachbuchen',
'Für selbst gebaute Seiten',
],
}, {
title: 'Projekt',
price: {
pre: 'ab',
value: 999,
},
smallClaim: 'Deine Vision.',
icon: 'ph:trend-up-thin',
button: 'Jetzt durchstarten',
link: 'https://tidycal.com/webfussel/project-booking',
list: [
'Anforderungsanalyse',
'Kontinuierliche Projekt-Updates',
'Fixe Kosten und Feature-Sets',
'Nur 50 % Projektpreis als Anzahlung',
'Gestaffelte Abrechnung nach Milestones',
],
}, {
title: 'Schulung',
price: {
pre: 'ab',
value: 1499,
post: '/ Tag / Person',
},
smallClaim: 'Wenn man\'s selber können muss.',
icon: 'ph:graduation-cap-thin',
button: 'Frag nach!',
link: 'https://tidycal.com/webfussel/teaching',
list: [
'Schulungsinhalte klären',
'Team-Workshops',
'Azubi-Betreuung',
'1:1 Mentoring',
'Abrechnung pro Person und Tag',
],
},
]
const { data: faq } = await useAsyncData('faq', () => queryCollection('faq').path('/snippets/faq/booking').first())
const texts = usePlainFaq(faq.value?.body.value)
if (faq) {
useSchemaOrg({
'@context': 'https://schema.org',
'@type': 'FAQPage',
'mainEntity': texts.map(entity => ({
'@type': 'Question',
'name': entity.question,
'acceptedAnswer': {
'@type': 'Answer',
'text': entity.answer,
},
}))
})
}
</script>