Strony artykułów, sitemap, GA4, polityka prywatności
- Klikalne kafelki i slideshow → /blog/[slug] - Strona artykułu: cover fullwidth z parallax, tytuł nad obrazkiem - Schema.org BlogPosting na stronach postów - Dynamiczny sitemap.xml (artykuły + produkty z Directus, revalidate=0) - Google Analytics GA4 (G-7067BL7TDN) - Polityka prywatności /polityka-prywatnosci - CookieBanner z linkiem do polityki - Footer: działający link do polityki prywatności Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-9
@@ -1,21 +1,20 @@
|
||||
{
|
||||
"name": "bento-website",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^14.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^10.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"vite": "^5.2.0"
|
||||
"@types/react-dom": "^18.2.22"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { fetchArticle } from '../../../lib/directus'
|
||||
|
||||
const DIRECTUS_URL = 'https://cms.dexterlab.pl'
|
||||
const BASE_URL = 'https://labai.pro'
|
||||
|
||||
function blogPostingSchema(article) {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
'@id': `${BASE_URL}/blog/${article.Slug}`,
|
||||
headline: article.Title,
|
||||
url: `${BASE_URL}/blog/${article.Slug}`,
|
||||
description: article.Excerpt || undefined,
|
||||
datePublished: article.date_published || article.date_created,
|
||||
dateModified: article.date_updated || article.date_published || article.date_created,
|
||||
image: article.Cover_Image ? `${DIRECTUS_URL}/assets/${article.Cover_Image}` : undefined,
|
||||
author: article.Author
|
||||
? { '@type': 'Person', name: article.Author }
|
||||
: { '@type': 'Organization', name: 'Laboratorium AI' },
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
'@id': `${BASE_URL}/#organization`,
|
||||
name: 'Laboratorium AI',
|
||||
},
|
||||
mainEntityOfPage: { '@type': 'WebPage', '@id': `${BASE_URL}/blog/${article.Slug}` },
|
||||
}
|
||||
}
|
||||
|
||||
export const revalidate = 0
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const article = await fetchArticle(params.slug)
|
||||
if (!article) return {}
|
||||
return {
|
||||
title: `${article.Title} — Laboratorium AI`,
|
||||
description: article.Excerpt || article.Title,
|
||||
openGraph: {
|
||||
title: article.Title,
|
||||
images: article.Cover_Image ? [`${DIRECTUS_URL}/assets/${article.Cover_Image}`] : [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function ArticlePage({ params }) {
|
||||
const article = await fetchArticle(params.slug)
|
||||
if (!article) notFound()
|
||||
|
||||
return (
|
||||
<article className="article-page">
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(blogPostingSchema(article)) }}
|
||||
/>
|
||||
<div
|
||||
className="article-cover-parallax"
|
||||
style={article.Cover_Image ? { backgroundImage: `url(${DIRECTUS_URL}/assets/${article.Cover_Image})` } : {}}
|
||||
>
|
||||
<div className="article-header">
|
||||
<div className="article-meta-row">
|
||||
{article.Badge && <span className={`bento-badge badge-${article.Badge.toLowerCase()}`}>{article.Badge}</span>}
|
||||
{article.Category && <span className="article-category">{article.Category}</span>}
|
||||
</div>
|
||||
<h1>{article.Title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
{article.Content && (
|
||||
<div className="article-body">
|
||||
<div className="article-content">
|
||||
<div dangerouslySetInnerHTML={{ __html: article.Content }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
)
|
||||
}
|
||||
@@ -34,6 +34,8 @@ export default function RootLayout({ children }) {
|
||||
<Footer />
|
||||
<CookieBanner />
|
||||
<Script src="https://stat.dexterlab.pl/api/script.js" data-site-id="e7d7e9823a23" strategy="afterInteractive" />
|
||||
<Script src="https://www.googletagmanager.com/gtag/js?id=G-7067BL7TDN" strategy="afterInteractive" />
|
||||
<Script id="ga4" strategy="afterInteractive">{`window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','G-7067BL7TDN');`}</Script>
|
||||
<Script id="libredesk-config" strategy="beforeInteractive">{`window.LibredeskSettings = { baseURL: 'https://pomoc.dexterlab.pl', inboxID: 'bffd72d7-a528-42ca-bfed-377670d49364' };`}</Script>
|
||||
<Script src="https://pomoc.dexterlab.pl/widget.js" strategy="afterInteractive" />
|
||||
</body>
|
||||
|
||||
@@ -49,6 +49,7 @@ export default async function HomePage() {
|
||||
bgImage={article.bgImage}
|
||||
hero={article.hero}
|
||||
style={{ gridColumn: article.gridColumn, gridRow: article.gridRow }}
|
||||
href={article.slug ? `/blog/${article.slug}` : undefined}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
export const metadata = {
|
||||
title: 'Polityka Prywatności — Laboratorium AI',
|
||||
description: 'Informacje o przetwarzaniu danych osobowych i plikach cookie w serwisie Laboratorium AI.',
|
||||
}
|
||||
|
||||
export default function PolitykaPrywatnosci() {
|
||||
return (
|
||||
<div className="article-page">
|
||||
<div className="article-body" style={{ paddingTop: '6rem' }}>
|
||||
<h1 style={{ fontSize: '2.5rem', fontWeight: 800, marginBottom: '2rem' }}>Polityka Prywatności</h1>
|
||||
|
||||
<div className="article-content">
|
||||
<p>Ostatnia aktualizacja: 5 lipca 2026</p>
|
||||
|
||||
<h2>1. Administrator danych</h2>
|
||||
<p>Administratorem danych osobowych jest DexterLab, prowadzący serwis <strong>labai.pro</strong>. Kontakt: <a href="mailto:dexterteam@gmail.com">dexterteam@gmail.com</a></p>
|
||||
|
||||
<h2>2. Jakie dane zbieramy</h2>
|
||||
<p>Zbieramy wyłącznie dane anonimowe lub podane dobrowolnie:</p>
|
||||
<ul>
|
||||
<li>Dane analityczne (anonimowe) — adres IP w formie skróconej, przeglądarka, strony odwiedzane</li>
|
||||
<li>Dane podane w formularzu kontaktowym — imię, adres e-mail, treść wiadomości</li>
|
||||
<li>Dane podane przy zapisie do newslettera — adres e-mail</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Pliki cookie</h2>
|
||||
<p>Używamy następujących plików cookie:</p>
|
||||
<ul>
|
||||
<li><strong>Niezbędne</strong> — zapamiętanie zgody na cookie (<code>dexos-cookie-consent</code>)</li>
|
||||
<li><strong>Analityczne</strong> — Google Analytics 4 (identyfikator: G-7067BL7TDN) oraz Umami Analytics. Dane są anonimizowane przed wysłaniem.</li>
|
||||
<li><strong>Funkcjonalne</strong> — Libredesk (widget czatu), aktywowany wyłącznie po Twojej interakcji</li>
|
||||
</ul>
|
||||
<p>Możesz odrzucić cookie analityczne przy pierwszym wejściu na stronę lub usunąć je z ustawień przeglądarki.</p>
|
||||
|
||||
<h2>4. Podstawa prawna przetwarzania</h2>
|
||||
<ul>
|
||||
<li>Zgoda (art. 6 ust. 1 lit. a RODO) — cookie analityczne i marketingowe</li>
|
||||
<li>Prawnie uzasadniony interes (art. 6 ust. 1 lit. f RODO) — bezpieczeństwo serwisu, cookie niezbędne</li>
|
||||
<li>Wykonanie umowy / działania przedumowne — formularz kontaktowy, newsletter</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Przekazywanie danych</h2>
|
||||
<p>Dane analityczne przekazywane są do Google LLC (USA) na podstawie standardowych klauzul umownych. Google Analytics jest skonfigurowany z anonimizacją IP. Więcej: <a href="https://policies.google.com/privacy" target="_blank" rel="noopener">policies.google.com/privacy</a></p>
|
||||
|
||||
<h2>6. Twoje prawa</h2>
|
||||
<p>Przysługuje Ci prawo do: dostępu do danych, sprostowania, usunięcia, ograniczenia przetwarzania, przenoszenia danych, wniesienia sprzeciwu oraz skargi do Prezesa UODO (<a href="https://uodo.gov.pl" target="_blank" rel="noopener">uodo.gov.pl</a>).</p>
|
||||
|
||||
<h2>7. Okres przechowywania</h2>
|
||||
<p>Dane analityczne przechowywane są przez 14 miesięcy. Dane z formularza kontaktowego — do czasu zakończenia korespondencji, nie dłużej niż 3 lata.</p>
|
||||
|
||||
<h2>8. Kontakt</h2>
|
||||
<p>W sprawach dotyczących prywatności pisz na: <a href="mailto:dexterteam@gmail.com">dexterteam@gmail.com</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
export const revalidate = 0
|
||||
|
||||
const BASE_URL = 'https://labai.pro'
|
||||
const DIRECTUS = 'https://cms.dexterlab.pl'
|
||||
|
||||
async function fetchSlugs() {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${DIRECTUS}/items/LabAI?filter[status][_eq]=published&fields=Slug,date_updated&limit=-1`
|
||||
)
|
||||
return res.ok ? (await res.json()).data ?? [] : []
|
||||
} catch { return [] }
|
||||
}
|
||||
|
||||
async function fetchProducts() {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${DIRECTUS}/items/products?filter[site][_eq]=labai.pro&filter[status][_eq]=published&fields=id,date_updated&limit=-1`
|
||||
)
|
||||
return res.ok ? (await res.json()).data ?? [] : []
|
||||
} catch { return [] }
|
||||
}
|
||||
|
||||
export default async function sitemap() {
|
||||
const [articles, products] = await Promise.all([fetchSlugs(), fetchProducts()])
|
||||
|
||||
const static_pages = [
|
||||
{ url: `${BASE_URL}/`, lastModified: new Date(), changeFrequency: 'daily', priority: 1 },
|
||||
{ url: `${BASE_URL}/produkty`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.8 },
|
||||
{ url: `${BASE_URL}/kontakt`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.5 },
|
||||
]
|
||||
|
||||
const article_pages = articles
|
||||
.filter(a => a.Slug)
|
||||
.map(a => ({
|
||||
url: `${BASE_URL}/blog/${a.Slug}`,
|
||||
lastModified: a.date_updated ? new Date(a.date_updated) : new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.7,
|
||||
}))
|
||||
|
||||
const product_pages = products.map(p => ({
|
||||
url: `${BASE_URL}/produkty#${p.id}`,
|
||||
lastModified: p.date_updated ? new Date(p.date_updated) : new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.6,
|
||||
}))
|
||||
|
||||
return [...static_pages, ...article_pages, ...product_pages]
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
const BentoCard = ({ type, badge, title, category, readTime, bgImage, hero = false, style = {} }) => {
|
||||
return (
|
||||
const BentoCard = ({ type, badge, title, category, readTime, bgImage, hero = false, style = {}, href }) => {
|
||||
const inner = (
|
||||
<article
|
||||
className={`bento-card${hero ? ' hero-article' : ''}`}
|
||||
style={style}
|
||||
tabIndex={0}
|
||||
style={href ? {} : style}
|
||||
tabIndex={href ? -1 : 0}
|
||||
aria-label={`${category}: ${title}`}
|
||||
>
|
||||
<div
|
||||
@@ -32,6 +33,13 @@ const BentoCard = ({ type, badge, title, category, readTime, bgImage, hero = fal
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
|
||||
if (href) return (
|
||||
<Link href={href} style={style} className="bento-card-link">
|
||||
{inner}
|
||||
</Link>
|
||||
);
|
||||
return inner;
|
||||
};
|
||||
|
||||
export default BentoCard;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
'use client'
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
const BentoSlideshow = ({ slides, style = {} }) => {
|
||||
const [current, setCurrent] = useState(0);
|
||||
@@ -40,7 +42,13 @@ const BentoSlideshow = ({ slides, style = {} }) => {
|
||||
aria-hidden={index !== current}
|
||||
>
|
||||
<div className={`bento-badge badge-${slide.type}`}>{slide.badge}</div>
|
||||
{slide.slug ? (
|
||||
<Link href={`/blog/${slide.slug}`} className="slideshow-title-link">
|
||||
<h2 className="article-title slideshow-title">{slide.title}</h2>
|
||||
</Link>
|
||||
) : (
|
||||
<h2 className="article-title slideshow-title">{slide.title}</h2>
|
||||
)}
|
||||
<div className="article-meta">
|
||||
<span>{slide.category}</span>
|
||||
<span className="dot" aria-hidden="true" />
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
const STORAGE_KEY = 'dexos-cookie-consent';
|
||||
|
||||
const CookieBanner = () => {
|
||||
const [visible, setVisible] = useState(() => !localStorage.getItem(STORAGE_KEY));
|
||||
const [visible, setVisible] = useState(false);
|
||||
useEffect(() => { setVisible(!localStorage.getItem(STORAGE_KEY)); }, []);
|
||||
|
||||
const handleConsent = (accepted) => {
|
||||
localStorage.setItem(STORAGE_KEY, accepted ? 'accepted' : 'rejected');
|
||||
@@ -16,7 +19,7 @@ const CookieBanner = () => {
|
||||
<div className="cookie-banner" role="dialog" aria-modal="true" aria-label="Zgoda na pliki cookie">
|
||||
<div className="cookie-content">
|
||||
<h4>Ciasteczka</h4>
|
||||
<p>Używamy plików cookie, aby dostarczać spersonalizowane treści i analizować ruch na stronie.</p>
|
||||
<p>Używamy plików cookie do analizy ruchu i personalizacji treści. <Link href="/polityka-prywatnosci" style={{ color: 'var(--primary)' }}>Polityka prywatności</Link></p>
|
||||
<div className="cookie-actions">
|
||||
<button className="button-secondary" onClick={() => handleConsent(false)}>
|
||||
Odrzuć
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
const Footer = () => (
|
||||
<footer className="main-footer">
|
||||
<div className="footer-container">
|
||||
<div className="footer-brand">
|
||||
<div className="nav-logo">
|
||||
<span className="pulse-dot" aria-hidden="true"></span>
|
||||
DEX_OS
|
||||
</div>
|
||||
<p>Zbudowane przez deweloperów sztucznej inteligencji, dla agentów przyszłości.</p>
|
||||
<div className="footer-social" aria-label="Social media">
|
||||
<a href="https://linkedin.com/company/dexterlab" rel="me noopener" target="_blank" aria-label="LinkedIn" className="footer-social__link">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
|
||||
</a>
|
||||
<a href="https://x.com/dexterlab_pl" rel="me noopener" target="_blank" aria-label="X (Twitter)" className="footer-social__link">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.748l7.73-8.835L1.254 2.25H8.08l4.261 5.636 5.903-5.636zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
|
||||
</a>
|
||||
<a href="https://facebook.com/dexterlab" rel="me noopener" target="_blank" aria-label="Facebook" className="footer-social__link">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer-links">
|
||||
<div className="footer-col">
|
||||
<h4>Platforma</h4>
|
||||
<a href="#">Architektura</a>
|
||||
<a href="#">Agent Swarms</a>
|
||||
<a href="#">Bezpieczeństwo</a>
|
||||
</div>
|
||||
<div className="footer-col">
|
||||
<h4>Firma</h4>
|
||||
<a href="#">O nas</a>
|
||||
<a href="#">Kariera</a>
|
||||
<a href="/kontakt">Kontakt</a>
|
||||
</div>
|
||||
<div className="footer-col">
|
||||
<h4>Zasady</h4>
|
||||
<a href="#">Regulamin</a>
|
||||
<a href="/polityka-prywatnosci">Polityka Prywatności</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer-bottom">
|
||||
<p>© {new Date().getFullYear()} DEX Operating System. Wszelkie prawa zastrzeżone.</p>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
|
||||
export default Footer
|
||||
@@ -1580,3 +1580,102 @@ h1, h2, h3, h4, h5, h6 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Strona artykułu ── */
|
||||
.slideshow-title-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bento-card-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bento-card-link .bento-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.article-page {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.article-meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.article-meta-row .bento-badge {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.article-category {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.article-cover-parallax {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 520px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-attachment: fixed;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.article-header {
|
||||
width: 100%;
|
||||
padding: 2rem 1.5rem;
|
||||
background: rgba(12, 12, 20, 0.8);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.article-header > div,
|
||||
.article-header h1 {
|
||||
max-width: 780px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.article-header h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: var(--text-main);
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.article-body {
|
||||
max-width: 780px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 1.5rem 5rem;
|
||||
}
|
||||
|
||||
.article-content {
|
||||
color: var(--text-muted);
|
||||
line-height: 1.8;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.article-content h2,
|
||||
.article-content h3 {
|
||||
color: var(--text-main);
|
||||
margin: 2rem 0 0.75rem;
|
||||
}
|
||||
|
||||
.article-content p { margin-bottom: 1.25rem; }
|
||||
.article-content img { max-width: 100%; border-radius: 8px; margin: 1.5rem 0; }
|
||||
.article-content a { color: var(--primary); }
|
||||
|
||||
|
||||
@@ -12,6 +12,17 @@ export async function fetchLaboratoriumData() {
|
||||
return result.data
|
||||
}
|
||||
|
||||
export async function fetchArticle(slug) {
|
||||
const url = `${DIRECTUS_URL}/items/${COLLECTION}?filter[Slug][_eq]=${slug}&filter[status][_eq]=published&limit=1`
|
||||
const headers = process.env.DIRECTUS_TOKEN
|
||||
? { Authorization: `Bearer ${process.env.DIRECTUS_TOKEN}` }
|
||||
: {}
|
||||
const response = await fetch(url, { headers })
|
||||
if (!response.ok) return null
|
||||
const result = await response.json()
|
||||
return result.data?.[0] ?? null
|
||||
}
|
||||
|
||||
export function mapDirectusToContent(items) {
|
||||
if (!items || items.length === 0) return null
|
||||
|
||||
@@ -25,6 +36,7 @@ export function mapDirectusToContent(items) {
|
||||
category: item.Category || 'AI',
|
||||
readTime: '5 min read',
|
||||
bgImage: item.Cover_Image ? `${DIRECTUS_URL}/assets/${item.Cover_Image}` : '',
|
||||
slug: item.Slug || null,
|
||||
}))
|
||||
|
||||
const slideshowTitles = new Set(slideshow.map(s => s.title))
|
||||
@@ -39,6 +51,7 @@ export function mapDirectusToContent(items) {
|
||||
category: item.Category || 'AI',
|
||||
readTime: '5 min read',
|
||||
bgImage: item.Cover_Image ? `${DIRECTUS_URL}/assets/${item.Cover_Image}` : '',
|
||||
slug: item.Slug || item.slug || null,
|
||||
gridColumn: getGridColumn(index),
|
||||
gridRow: getGridRow(index),
|
||||
hero: index === 0,
|
||||
|
||||
Reference in New Issue
Block a user