feat: migrate Payload CMS to MDX and harden static infrastructure
This commit is contained in:
@@ -1,18 +1,28 @@
|
||||
import React from 'react';
|
||||
import { getPayload } from 'payload';
|
||||
import configPromise from '@payload-config';
|
||||
import { Heading } from '@/components/ui';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
|
||||
export const AgbHistoryBlock: React.FC<{ title: string }> = async ({ title }) => {
|
||||
const payload = await getPayload({ config: configPromise });
|
||||
const agbs: any[] = [];
|
||||
try {
|
||||
const dir = path.join(process.cwd(), 'content', 'agbs');
|
||||
const files = await fs.readdir(dir);
|
||||
for (const f of files) {
|
||||
if (!f.endsWith('.mdx')) continue;
|
||||
const fileContent = await fs.readFile(path.join(dir, f), 'utf-8');
|
||||
const { data } = matter(fileContent);
|
||||
agbs.push({ id: f, ...data });
|
||||
}
|
||||
agbs.sort(
|
||||
(a, b) => new Date(b.versionDate || 0).getTime() - new Date(a.versionDate || 0).getTime(),
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Failed reading AGBs from MDX:', err);
|
||||
}
|
||||
|
||||
const agbs = await payload.find({
|
||||
collection: 'agbs-collection',
|
||||
sort: '-versionDate',
|
||||
limit: 100,
|
||||
});
|
||||
|
||||
if (agbs.totalDocs === 0) {
|
||||
if (agbs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -22,15 +32,15 @@ export const AgbHistoryBlock: React.FC<{ title: string }> = async ({ title }) =>
|
||||
{title || 'Vorherige Versionen'}
|
||||
</Heading>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{agbs.docs.map((agb: any) => {
|
||||
{agbs.map((agb: any) => {
|
||||
const date = new Date(agb.versionDate).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
|
||||
const fileUrl = typeof agb.file === 'object' ? agb.file.url : '';
|
||||
const filename = typeof agb.file === 'object' ? agb.file.filename : 'agb.pdf';
|
||||
const fileUrl = agb.file?.url || agb.fileUrl || '';
|
||||
const filename = agb.file?.filename || agb.filename || 'agb.pdf';
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { AlertCircle, RefreshCw } from 'lucide-react';
|
||||
import { config } from '../lib/config';
|
||||
|
||||
export default function CMSConnectivityNotice() {
|
||||
const [, setStatus] = useState<'checking' | 'ok' | 'error'>('checking');
|
||||
const [errorMsg, setErrorMsg] = useState('');
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Only show if we've detected an issue AND we are in a context where we want to see it
|
||||
const checkCMS = async () => {
|
||||
const isDebug = new URLSearchParams(window.location.search).has('cms_debug');
|
||||
const isLocal = config.isDevelopment;
|
||||
const isTesting = config.isTesting;
|
||||
|
||||
// Only proceed with check if it's developer context (Local or Testing)
|
||||
// Staging and Production should NEVER see this unless forced with ?cms_debug
|
||||
if (!isLocal && !isTesting && !isDebug) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/health/cms');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status !== 'ok') {
|
||||
setStatus('error');
|
||||
setErrorMsg(data.message);
|
||||
setIsVisible(true);
|
||||
} else {
|
||||
setStatus('ok');
|
||||
setIsVisible(false);
|
||||
}
|
||||
} catch {
|
||||
// If it's a connection error, only show if we are really debugging
|
||||
if (isDebug || isLocal) {
|
||||
setStatus('error');
|
||||
setErrorMsg('Could not connect to CMS health endpoint');
|
||||
setIsVisible(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkCMS();
|
||||
}, []);
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 right-4 z-[9999] animate-slide-up">
|
||||
<div className="bg-red-500/90 backdrop-blur-md border border-red-400 text-white p-4 rounded-2xl shadow-2xl max-w-sm">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="bg-white/20 p-2 rounded-lg">
|
||||
<AlertCircle className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-bold text-sm mb-1">CMS Issue Detected</h4>
|
||||
<p className="text-xs opacity-90 leading-relaxed mb-3">
|
||||
{errorMsg === 'relation "products" does not exist'
|
||||
? 'The database schema is missing. Please sync your local data to this environment.'
|
||||
: errorMsg || 'The application cannot connect to the Directus CMS.'}
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="bg-white text-red-600 text-[10px] font-bold uppercase tracking-wider px-3 py-1.5 rounded-lg flex items-center gap-2 hover:bg-neutral-100 transition-colors"
|
||||
>
|
||||
<RefreshCw className="w-3 h-3" />
|
||||
Retry
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsVisible(false)}
|
||||
className="bg-black/20 text-white text-[10px] font-bold uppercase tracking-wider px-3 py-1.5 rounded-lg hover:bg-black/30 transition-colors"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -38,6 +38,7 @@ export default function Footer() {
|
||||
alt="KLZ Vertriebs GmbH"
|
||||
width={150}
|
||||
height={40}
|
||||
sizes="(max-width: 768px) 150px, 150px"
|
||||
style={{ width: 'auto' }}
|
||||
className="h-10 w-auto transition-transform duration-500 group-hover:scale-110"
|
||||
/>
|
||||
|
||||
@@ -168,6 +168,7 @@ export default function Header() {
|
||||
alt={t('home')}
|
||||
width={120}
|
||||
height={120}
|
||||
sizes="(max-width: 768px) 120px, 120px"
|
||||
style={{ width: 'auto' }}
|
||||
className="h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110"
|
||||
priority
|
||||
@@ -463,7 +464,14 @@ export default function Header() {
|
||||
)}
|
||||
style={{ transitionDelay: isMobileMenuOpen ? '800ms' : '0ms' }}
|
||||
>
|
||||
<Image src="/logo-white.svg" alt={t('home')} width={80} height={80} unoptimized />
|
||||
<Image
|
||||
src="/logo-white.svg"
|
||||
alt={t('home')}
|
||||
width={80}
|
||||
height={80}
|
||||
sizes="80px"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
41
components/MDXContent.tsx
Normal file
41
components/MDXContent.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
function ContactSection(props: any) {
|
||||
return (
|
||||
<div className="p-8 border-2 border-dashed border-primary my-8 text-center text-primary font-bold">
|
||||
Contact Section Placeholder
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HeroSection(props: any) {
|
||||
return (
|
||||
<div className="p-16 bg-primary-dark text-white text-center my-8">
|
||||
<h1 className="text-4xl font-bold">{props.title}</h1>
|
||||
<p>{props.subtitle}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Block(props: any) {
|
||||
return (
|
||||
<div className="p-4 border-2 border-dashed border-gray-300">Unknown Block: {props.type}</div>
|
||||
);
|
||||
}
|
||||
|
||||
const components = {
|
||||
ContactSection,
|
||||
HeroSection,
|
||||
Block,
|
||||
// Add other components that could be in MDX here
|
||||
};
|
||||
|
||||
export default function MDXContent({ data, className }: { data: string; className?: string }) {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<MDXRemote source={data} components={components} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user