--- // src/components/SchemaOrg.astro // JSON-LD (@graph): WebSite + Person (Linus) + ProfessionalService (Images by Olofsson) // + WebPage för aktuell sida (+ FAQPage och BreadcrumbList när det är relevant). // Allt hämtas från src/data/business.ts. null-värden utelämnas helt. import { business, absoluteUrl, genreList } from '../data/business'; import type { FaqItem } from '../data/faq'; import { normalizePath } from '../lib/url'; export interface Props { title: string; description: string; path?: string; pageType?: 'WebPage' | 'AboutPage' | 'ContactPage' | 'CollectionPage'; /** Namn i brödsmulan för undersidor, t.ex. "Tjänster". */ breadcrumb?: string; faq?: FaqItem[]; /** Absolut URL till sidans huvudbild (og:image). */ imageUrl?: string; } const { title, description, path, pageType = 'WebPage', breadcrumb, faq, imageUrl } = Astro.props; const site = (Astro.site ?? new URL(business.url)).toString().replace(/\/$/, ''); const pagePath = normalizePath(path ?? Astro.url.pathname); const pageUrl = absoluteUrl(pagePath); const ids = { website: `${site}/#website`, person: `${site}/#linus-olofsson`, business: `${site}/#images-by-olofsson`, webpage: `${pageUrl}#webpage`, breadcrumb: `${pageUrl}#breadcrumb`, }; // Tar bort null/undefined/tomma listor så att okända fakta aldrig hamnar i JSON-LD. function clean(value: T): T { if (Array.isArray(value)) { return value.map(clean).filter((v) => v !== null && v !== undefined) as unknown as T; } if (value && typeof value === 'object') { const out: Record = {}; for (const [k, v] of Object.entries(value as Record)) { if (v === null || v === undefined) continue; const c = clean(v); if (Array.isArray(c) && c.length === 0) continue; out[k] = c; } return out as T; } return value; } // Landet är känt (Sverige); ort och region läggs till när de fylls i business.ts. const address = { '@type': 'PostalAddress', addressLocality: business.city, addressRegion: business.region, addressCountry: business.country.code, }; const graph: Record[] = [ { '@type': 'WebSite', '@id': ids.website, url: `${site}/`, name: business.brand, inLanguage: 'sv-SE', publisher: { '@id': ids.business }, }, { '@type': 'Person', '@id': ids.person, name: business.person.name, jobTitle: business.person.jobTitle, url: absoluteUrl('/about'), description: `${business.person.jobTitle} baserad i ${business.country.name}. ${business.recognitionThirdPerson}`, knowsAbout: business.genres.map((g) => g.name), worksFor: { '@id': ids.business }, sameAs: [business.social.linkedin, business.social.lightroom], }, { '@type': 'ProfessionalService', '@id': ids.business, name: business.brand, url: `${site}/`, description: `${business.person.name} fotograferar ${genreList()}.`, email: business.email, telephone: business.phone, image: imageUrl, address, areaServed: business.serviceArea, priceRange: business.priceRange, founder: { '@id': ids.person }, employee: { '@id': ids.person }, sameAs: [business.social.instagram.url, business.social.facebook, business.social.flickr, business.social.lightroom], potentialAction: { '@type': 'ReserveAction', name: 'Skicka en bokningsförfrågan', target: business.portal.bookingUrl, }, hasOfferCatalog: { '@type': 'OfferCatalog', name: 'Fotografering', itemListElement: business.genres.map((g) => ({ '@type': 'Offer', priceCurrency: business.priceFromSek != null ? 'SEK' : null, price: business.priceFromSek, itemOffered: { '@type': 'Service', name: g.name, description: g.description, provider: { '@id': ids.business }, }, })), }, }, ]; const webpage: Record = { '@type': faq?.length ? [pageType, 'FAQPage'] : pageType, '@id': ids.webpage, url: pageUrl, name: title, description, inLanguage: 'sv-SE', isPartOf: { '@id': ids.website }, about: { '@id': ids.business }, primaryImageOfPage: imageUrl ? { '@type': 'ImageObject', url: imageUrl } : null, breadcrumb: breadcrumb ? { '@id': ids.breadcrumb } : null, mainEntity: faq?.length ? faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer }, })) : null, }; graph.push(webpage); if (breadcrumb) { graph.push({ '@type': 'BreadcrumbList', '@id': ids.breadcrumb, itemListElement: [ { '@type': 'ListItem', position: 1, name: 'Hem', item: `${site}/` }, { '@type': 'ListItem', position: 2, name: breadcrumb, item: pageUrl }, ], }); } const json = JSON.stringify(clean({ '@context': 'https://schema.org', '@graph': graph })).replace(/