diff --git a/dist/index.html b/dist/index.html
index a54606c..3125b66 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -19,8 +19,8 @@
-
-
+
+
diff --git a/example.jpg b/example.jpg
new file mode 100644
index 0000000..d9bbe09
Binary files /dev/null and b/example.jpg differ
diff --git a/src/App.jsx b/src/App.jsx
index 311a77b..f1e5129 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,9 +1,10 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import './index.css';
import Nav from './components/Nav';
import BentoCard from './components/BentoCard';
import BentoSlideshow from './components/BentoSlideshow';
import CookieBanner from './components/CookieBanner';
+import { fetchLaboratoriumData, mapDirectusToContent } from './lib/directus';
import { slideshowData, articlesData, newsData, videosData } from './data/content';
import Hero from './sections/Hero';
@@ -44,21 +45,59 @@ const SubscribeBox = () => {
};
function App() {
+ const [directusData, setDirectusData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ async function loadData() {
+ try {
+ const items = await fetchLaboratoriumData();
+ const mapped = mapDirectusToContent(items);
+ setDirectusData(mapped);
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ }
+ loadData();
+ }, []);
+
+ // Użyj danych z Directus lub fallback na statyczne
+ const displaySlideshow = directusData?.slideshow || slideshowData;
+ const displayArticles = directusData?.articles || articlesData;
+
+ if (loading) {
+ return (
+
+
+
Ładowanie danych z Laboratorium AI...
+
+ );
+ }
+
return (
<>
+ {error && (
+
+ ⚠️ Nie można połączyć z bazą danych. Wyświetlam dane offline.
+
+ )}
+
{/* CENTER BENTO AREA */}
- {articlesData.map((article, index) => (
+ {displayArticles.map((article, index) => (
* {
- grid-column: auto !important;
- grid-row: auto !important;
- }
- .slideshow-card {
- grid-column: 1 / -1 !important;
- }
- .hero-article {
- grid-column: 1 / -1 !important;
+ grid-template-columns: repeat(4, 1fr);
+ grid-template-rows: 360px repeat(3, 240px);
+ grid-auto-rows: 240px;
}
}
@@ -788,9 +775,34 @@ h1, h2, h3, h4, h5, h6 {
.main-nav {
position: relative;
}
+ .bento-area {
+ grid-template-columns: repeat(2, 1fr);
+ grid-template-rows: 300px auto;
+ grid-auto-rows: 220px;
+ }
+ .bento-area > * {
+ grid-column: auto !important;
+ grid-row: auto !important;
+ }
+ .slideshow-card {
+ grid-column: 1 / -1 !important;
+ }
+ .hero-article {
+ grid-column: 1 / -1 !important;
+ }
}
@media (max-width: 640px) {
+ .hero {
+ padding: 4rem 1.25rem 3rem;
+ }
+ .hero__cta-group {
+ flex-direction: column;
+ align-items: stretch;
+ }
+ .hero__btn-primary {
+ justify-content: center;
+ }
.bento-area {
grid-template-columns: 1fr;
grid-auto-rows: 200px;
@@ -806,6 +818,7 @@ h1, h2, h3, h4, h5, h6 {
padding: 0.75rem;
gap: 0.75rem;
}
+}
.cookie-banner {
bottom: 1rem;
right: 1rem;
@@ -1149,3 +1162,42 @@ h1, h2, h3, h4, h5, h6 {
}
}
+/* ===== LOADING & ERROR STATES ===== */
+.loading-screen {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ background: var(--bg-color);
+ gap: 1.5rem;
+}
+
+.loading-spinner {
+ width: 48px;
+ height: 48px;
+ border: 3px solid var(--border-color);
+ border-top-color: var(--accent-color);
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.loading-screen p {
+ color: var(--text-muted);
+ font-size: 1rem;
+}
+
+.error-banner {
+ background: rgba(239, 68, 68, 0.1);
+ border: 1px solid rgba(239, 68, 68, 0.3);
+ color: #fca5a5;
+ padding: 1rem 2rem;
+ text-align: center;
+}
+
diff --git a/src/lib/directus.js b/src/lib/directus.js
new file mode 100644
index 0000000..fa8b58c
--- /dev/null
+++ b/src/lib/directus.js
@@ -0,0 +1,145 @@
+/**
+ * Directus API Client
+ * Źródło danych: https://cms.dexterlab.pl/ - kolekcja Laboratorium_AI
+ */
+
+const DIRECTUS_URL = import.meta.env.DEV ? '/api' : 'https://cms.dexterlab.pl';
+const COLLECTION = 'Laboratorium_AI';
+
+// Cache storage
+let dataCache = null;
+let cacheTimestamp = 0;
+const CACHE_DURATION = 5 * 60 * 1000; // 5 minut
+
+/**
+ * Pobiera wszystkie elementy z kolekcji Laboratorium_AI
+ */
+export async function fetchLaboratoriumData() {
+ // Sprawdź cache
+ if (dataCache && Date.now() - cacheTimestamp < CACHE_DURATION) {
+ return dataCache;
+ }
+
+ try {
+ const response = await fetch(
+ `${DIRECTUS_URL}/items/${COLLECTION}?sort=-date_published&limit=-1`,
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Origin': window.location.origin,
+ },
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const result = await response.json();
+
+ dataCache = result.data;
+ cacheTimestamp = Date.now();
+
+ return result.data;
+ } catch (error) {
+ console.error('Błąd pobierania danych z Directus:', error);
+ return [];
+ }
+}
+
+/**
+ * Pobiera pojedynczy element po ID
+ */
+export async function fetchLaboratoriumItem(id) {
+ try {
+ const response = await fetch(`${DIRECTUS_URL}/items/${COLLECTION}/${id}`);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const result = await response.json();
+ return result.data;
+ } catch (error) {
+ console.error(`Błąd pobierania elementu ${id}:`, error);
+ return null;
+ }
+}
+
+/**
+ * Czyści cache - wymusza odświeżenie
+ */
+export function clearCache() {
+ dataCache = null;
+ cacheTimestamp = 0;
+}
+
+/**
+ * Mapuje dane z Directus na format strony
+ */
+export function mapDirectusToContent(items) {
+ if (!items || items.length === 0) return null;
+
+ // Preferuj published, fallback na wszystkie
+ const published = items.filter(item => item.status === 'published');
+ const sourceItems = published.length > 0 ? published : items;
+
+ // Slideshow - featured lub pierwsze 3
+ const featured = sourceItems.filter(item => item.is_featured === true);
+ const slideshow = (featured.length > 0 ? featured : sourceItems.slice(0, 3)).map(item => ({
+ type: 'hot',
+ badge: item.badge || null,
+ title: item.title,
+ category: item.category || 'AI',
+ readTime: '5 min read',
+ bgImage: item.cover_image ? `${DIRECTUS_URL}/assets/${item.cover_image}` : '',
+ }));
+
+ const slideshowTitles = new Set(slideshow.map(s => s.title));
+
+ // Pozostałe jako articles (max 8)
+ const articles = sourceItems
+ .filter(item => !slideshowTitles.has(item.title))
+ .slice(0, 8)
+ .map((item, index) => ({
+ type: item.badge?.toLowerCase() || 'article',
+ badge: item.badge || null,
+ title: item.title,
+ category: item.category || 'AI',
+ readTime: item.read_time || '5 min read',
+ bgImage: item.cover_image ? `${DIRECTUS_URL}/assets/${item.cover_image}` : '',
+ gridColumn: getGridColumn(index),
+ gridRow: getGridRow(index),
+ hero: index === 0,
+ }));
+
+ return { slideshow, articles };
+}
+
+function getGridColumn(index) {
+ // Jasny układ dla 4-kolumnowej siatki
+ // 1: 2/3 (2 kol), 2: 3/4 (1 kol), 3: 4/5 (1 kol), 4: 4/5 (1 kol)
+ // 5: 1/2 (1 kol), 6: 2/3 (1 kol), 7: 3/4 (1 kol), 8: 4/5 (1 kol)
+ if (index === 0) return '1 / 3'; // 2 kolumny
+ if (index === 1) return '3 / 4'; // 1 kolumna
+ if (index === 2) return '4 / 5'; // 1 kolumna
+ if (index === 3) return '4 / 5'; // 1 kolumna
+ if (index === 4) return '1 / 2'; // 1 kolumna
+ if (index === 5) return '2 / 3'; // 1 kolumna
+ if (index === 6) return '3 / 4'; // 1 kolumna
+ if (index === 7) return '4 / 5'; // 1 kolumna
+ return '1 / 2';
+}
+
+function getGridRow(index) {
+ // Wiersze dla układu: 2 duże na górze (2/4), reszta po 1 (3/4, 4/5)
+ if (index === 0) return '2 / 4'; // 2 rzędy
+ if (index === 1) return '2 / 4'; // 2 rzędy
+ if (index === 2) return '2 / 3'; // 1 rząd
+ if (index === 3) return '3 / 4'; // 1 rząd
+ if (index === 4) return '4 / 5'; // 1 rząd
+ if (index === 5) return '4 / 5'; // 1 rząd
+ if (index === 6) return '4 / 5'; // 1 rząd
+ if (index === 7) return '4 / 5'; // 1 rząd
+ return '4 / 5';
+}
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
index 5a33944..5791f92 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -4,4 +4,13 @@ import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
-})
+ server: {
+ proxy: {
+ '/api': {
+ target: 'https://cms.dexterlab.pl',
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, ''),
+ },
+ },
+ },
+})
\ No newline at end of file