Kumasy-Marketing/app/lib/structured-data.ts

104 lines
3.3 KiB
TypeScript

import { FAQ_ENTRIES } from "./faq";
import {
CONTACT_EMAIL,
PRICE_CURRENCY,
PRICE_MONTHLY,
SITE_NAME,
SITE_URL,
} from "./site";
/**
* Structured data for the landing page, emitted as a single @graph so the
* entities can reference each other by @id instead of repeating themselves.
*
* Deliberately omitted:
* - AggregateRating / Review — we have no reviews. Inventing them is a
* manual-action risk, not a ranking shortcut.
* - Organization address / telephone — not published anywhere on the site
* yet. Schema must describe what the page actually shows.
*/
export function buildStructuredData() {
const organizationId = `${SITE_URL}/#organization`;
const websiteId = `${SITE_URL}/#website`;
return {
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": organizationId,
name: SITE_NAME,
url: SITE_URL,
logo: {
"@type": "ImageObject",
url: `${SITE_URL}/icons/icon-512.png`,
width: 512,
height: 512,
},
email: CONTACT_EMAIL,
contactPoint: {
"@type": "ContactPoint",
email: CONTACT_EMAIL,
contactType: "customer support",
availableLanguage: ["de"],
},
},
{
"@type": "WebSite",
"@id": websiteId,
url: SITE_URL,
name: SITE_NAME,
inLanguage: "de-DE",
publisher: { "@id": organizationId },
},
{
"@type": "SoftwareApplication",
"@id": `${SITE_URL}/#software`,
name: SITE_NAME,
applicationCategory: "BusinessApplication",
applicationSubCategory: "Kursverwaltung und Buchungssystem",
operatingSystem: "Web",
url: SITE_URL,
inLanguage: "de-DE",
publisher: { "@id": organizationId },
description:
"Kumasy bündelt Kurse, Räume, Trainer, Buchungen und Zahlungen für Kursbetriebe in einer Oberfläche. Teilnehmer:innen buchen und bezahlen selbst — mit öffentlicher Buchungsseite, Warteliste und Stripe-Zahlungen.",
featureList: [
"Kurse und Serientermine verwalten",
"Öffentliche Buchungsseite mit Gastbuchung",
"Zahlungen über Stripe",
"Warteliste mit automatischem Nachrücken",
"Räume- und Trainerverwaltung",
"Rabattcodes und Stornoregeln",
"GoBD-konforme Rechnungen",
],
offers: {
"@type": "Offer",
price: PRICE_MONTHLY,
priceCurrency: PRICE_CURRENCY,
// Schema.org has no "per month" on Offer itself; the
// UnitPriceSpecification carries the billing period.
priceSpecification: {
"@type": "UnitPriceSpecification",
price: PRICE_MONTHLY,
priceCurrency: PRICE_CURRENCY,
unitCode: "MON",
billingIncrement: 1,
},
availability: "https://schema.org/PreOrder",
url: `${SITE_URL}/#preis`,
},
},
{
"@type": "FAQPage",
"@id": `${SITE_URL}/#faq`,
inLanguage: "de-DE",
mainEntity: FAQ_ENTRIES.map(({ question, answer }) => ({
"@type": "Question",
name: question,
acceptedAnswer: { "@type": "Answer", text: answer },
})),
},
],
};
}