Dodano obsługę Directus CMS, układ Bento Grid 2x2+1x2+1x1 z danymi z Laboratorium_AI
This commit is contained in:
Vendored
+2
-2
@@ -19,8 +19,8 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||
<script type="module" crossorigin src="/assets/index-D2vGqyJ4.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CKyw65S0.css">
|
||||
<script type="module" crossorigin src="/assets/index-DVtSebH9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-wZazZb-P.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 270 KiB |
+42
-3
@@ -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 (
|
||||
<div className="loading-screen">
|
||||
<div className="loading-spinner" />
|
||||
<p>Ładowanie danych z Laboratorium AI...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<Hero />
|
||||
|
||||
{error && (
|
||||
<div className="error-banner">
|
||||
⚠️ Nie można połączyć z bazą danych. Wyświetlam dane offline.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="page-wrapper">
|
||||
|
||||
{/* CENTER BENTO AREA */}
|
||||
<main className="bento-area" aria-label="Artykuły">
|
||||
<BentoSlideshow
|
||||
slides={slideshowData}
|
||||
slides={displaySlideshow}
|
||||
style={{ gridColumn: '1 / 5', gridRow: '1 / 2' }}
|
||||
/>
|
||||
|
||||
{articlesData.map((article, index) => (
|
||||
{displayArticles.map((article, index) => (
|
||||
<BentoCard
|
||||
key={index}
|
||||
type={article.type}
|
||||
|
||||
+71
-19
@@ -722,35 +722,22 @@ h1, h2, h3, h4, h5, h6 {
|
||||
/* ===== RESPONSYWNOŚĆ ===== */
|
||||
@media (max-width: 1400px) {
|
||||
.page-wrapper {
|
||||
grid-template-columns: 250px 1fr;
|
||||
}
|
||||
.sidebar-right {
|
||||
display: none;
|
||||
grid-template-columns: 1fr 280px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
@media (max-width: 1100px) {
|
||||
.page-wrapper {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 1rem;
|
||||
}
|
||||
.sidebar-left {
|
||||
.sidebar-right {
|
||||
display: none;
|
||||
}
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user