Integracja Directus LabAI, naprawa Schema.org, poprawki UI

- Zmiana kolekcji Directus z Laboratorium_AI na LabAI
- Usunięcie fallbacku na fake dane — brak wpisów = komunikat
- revalidate=0 na stronie głównej (zawsze świeże dane)
- BlogPosting przeniesiony do @graph (poprawna spec Schema.org)
- Inbox Libredesk zaktualizowany
- Hero image-wrap powiększony (scale 1.56)
- Dokumentacja projektu w CLAUDE.md

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paweł Domański
2026-07-05 13:14:43 +02:00
parent f39551d0d0
commit eb09da9e4d
9 changed files with 943 additions and 1909 deletions
+104
View File
@@ -0,0 +1,104 @@
const DIRECTUS_URL = 'https://cms.dexterlab.pl'
const DOMAIN = 'labai.pro'
const BASE_URL = `https://${DOMAIN}`
const FALLBACK = {
name: 'Laboratorium AI',
description: 'Automatyzacja procesów biznesowych bez programowania. Wdrażamy AI i no-code w 48h.',
logo: `${BASE_URL}/logo.webp`,
sameAs: [
'https://linkedin.com/company/dexterlab',
'https://x.com/dexterlab_pl',
'https://facebook.com/dexterlab',
],
}
async function fetchSiteData() {
try {
const res = await fetch(
`${DIRECTUS_URL}/items/sites?filter[domain][_eq]=${DOMAIN}`,
{ next: { revalidate: 3600 } }
)
if (!res.ok) return null
const json = await res.json()
return json.data?.[0] ?? null
} catch {
return null
}
}
async function fetchArticles() {
try {
const res = await fetch(
`${DIRECTUS_URL}/items/LabAI?sort=-date_published&limit=5&fields=Title,Slug`,
{ next: { revalidate: 300 } }
)
if (!res.ok) return []
const json = await res.json()
return json.data ?? []
} catch {
return []
}
}
function buildSchema(site, articles) {
const s = site ?? {}
const baseUrl = (s.url ?? BASE_URL).replace(/\/$/, '')
const name = s.site_name ?? FALLBACK.name
const description = s.site_description ?? FALLBACK.description
const logo = s.logo ?? { '@type': 'ImageObject', url: FALLBACK.logo }
const sameAs = Array.isArray(s.social_links) && s.social_links.length
? s.social_links
: FALLBACK.sameAs
const blogPostings = articles.map((a) => ({
'@type': 'BlogPosting',
'@id': `${baseUrl}/blog/${a.Slug}`,
headline: a.Title,
url: `${baseUrl}/blog/${a.Slug}`,
isPartOf: { '@id': `${baseUrl}/#webpage` },
}))
return {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'WebSite',
'@id': `${baseUrl}/#website`,
url: `${baseUrl}/`,
name,
publisher: { '@id': `${baseUrl}/#organization` },
},
{
'@type': 'WebPage',
'@id': `${baseUrl}/#webpage`,
url: `${baseUrl}/`,
name,
isPartOf: { '@id': `${baseUrl}/#website` },
description,
},
{
'@type': 'Organization',
'@id': `${baseUrl}/#organization`,
name: s.company_name ?? name,
url: `${baseUrl}/`,
logo,
sameAs,
...(s.contact && { contactPoint: s.contact }),
},
...blogPostings,
],
}
}
export default async function SchemaOrg() {
const [site, articles] = await Promise.all([fetchSiteData(), fetchArticles()])
const schema = buildSchema(site, articles)
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
)
}