feat: implement AGB History with dynamic CMS fetching

This commit is contained in:
2026-04-27 12:11:11 +02:00
parent 24a19adf19
commit 909bad573b
7 changed files with 181 additions and 2 deletions

View File

@@ -11,8 +11,38 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ slug
try {
const { slug } = await params;
// Hardcoded bypass for AGBs to use the original uploaded PDF.
// Handle AGBs specifically - either fetch from collection or use fallback file
if (slug === 'agbs') {
const payload = await getPayload({ config: configPromise });
const currentAgbs = await payload.find({
collection: 'agbs-collection',
where: {
isCurrent: { equals: true },
},
limit: 1,
});
if (currentAgbs.totalDocs > 0) {
const agb = currentAgbs.docs[0];
const media = agb.file as any;
if (media && media.url) {
// If it's a remote URL or absolute path, we might need to fetch it
// For now assume it's a local file in public/media
const filePath = path.join(process.cwd(), 'public', media.url);
if (fs.existsSync(filePath)) {
const fileBuffer = fs.readFileSync(filePath);
return new NextResponse(fileBuffer, {
status: 200,
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${media.filename}"`,
},
});
}
}
}
// Fallback for hardcoded legacy AGB
const filePath = path.join(process.cwd(), 'public', 'AVB-KLZ-4-2026.pdf');
if (fs.existsSync(filePath)) {
const fileBuffer = fs.readFileSync(filePath);

View File

@@ -0,0 +1,76 @@
import React from 'react';
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import { Container, Card, Heading } from '@/components/ui';
export const AgbHistoryBlock: React.FC<{ title: string }> = async ({ title }) => {
const payload = await getPayload({ config: configPromise });
const agbs = await payload.find({
collection: 'agbs-collection',
sort: '-versionDate',
limit: 100,
});
if (agbs.totalDocs === 0) {
return null;
}
return (
<div className="my-16 p-8 md:p-12 bg-neutral-light rounded-3xl shadow-sm border border-neutral-medium">
<Heading level={3} className="mb-8 text-saturated">
{title || 'Vorherige Versionen'}
</Heading>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{agbs.docs.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';
return (
<div
key={agb.id}
className="flex items-center justify-between p-6 bg-white rounded-2xl shadow-sm border border-neutral-medium hover:border-primary transition-all group"
>
<div>
<h4 className="font-bold text-saturated group-hover:text-primary transition-colors">
{agb.title}
</h4>
<p className="text-sm text-text-secondary mt-1">Gültig ab {date}</p>
</div>
{fileUrl && (
<a
href={fileUrl}
download={filename}
className="p-3 bg-neutral-light text-primary rounded-full hover:bg-primary hover:text-white transition-all shadow-sm"
title="PDF herunterladen"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" x2="12" y1="15" y2="3" />
</svg>
</a>
)}
</div>
);
})}
</div>
</div>
);
};

View File

@@ -38,6 +38,7 @@ import GallerySection from '@/components/home/GallerySection';
import VideoSection from '@/components/home/VideoSection';
import CTA from '@/components/home/CTA';
import { PDFDownloadBlock } from '@/components/PDFDownloadBlock';
import { AgbHistoryBlock } from '@/components/AgbHistoryBlock';
/**
* Splits a text string on \n and intersperses <br /> elements.
@@ -436,6 +437,8 @@ const jsxConverters: JSXConverters = {
'block-pdfDownload': ({ node }: any) => (
<PDFDownloadBlock label={node.fields.label} style={node.fields.style} />
),
agbHistory: ({ node }: any) => <AgbHistoryBlock title={node.fields.title} />,
'block-agbHistory': ({ node }: any) => <AgbHistoryBlock title={node.fields.title} />,
// ─── New Page Blocks ───────────────────────────────────────────
heroSection: ({ node }: any) => {
const f = node.fields;

View File

@@ -21,6 +21,7 @@ import { Posts } from './src/payload/collections/Posts';
import { FormSubmissions } from './src/payload/collections/FormSubmissions';
import { Products } from './src/payload/collections/Products';
import { Pages } from './src/payload/collections/Pages';
import { Agbs } from './src/payload/collections/Agbs';
import { seedDatabase } from './src/payload/seed';
const filename = fileURLToPath(import.meta.url);
@@ -56,7 +57,7 @@ export default buildConfig({
defaultLocale: 'de',
fallback: true,
},
collections: [Users, Media, Posts, FormSubmissions, Products, Pages],
collections: [Users, Media, Posts, FormSubmissions, Products, Pages, Agbs],
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,

View File

@@ -0,0 +1,18 @@
import { Block } from 'payload';
export const AgbHistory: Block = {
slug: 'agbHistory',
labels: {
singular: 'AGB Historie',
plural: 'AGB Historien',
},
fields: [
{
name: 'title',
type: 'text',
label: 'Titel',
localized: true,
defaultValue: 'Vorherige Versionen',
},
],
};

View File

@@ -17,6 +17,7 @@ import { TeamProfile } from './TeamProfile';
import { TechnicalGrid } from './TechnicalGrid';
import { VisualLinkPreview } from './VisualLinkPreview';
import { PDFDownload } from './PDFDownload';
import { AgbHistory } from './AgbHistory';
import { homeBlocksArray } from './HomeBlocks';
export const payloadBlocks = [
@@ -40,4 +41,5 @@ export const payloadBlocks = [
TechnicalGrid,
VisualLinkPreview,
PDFDownload,
AgbHistory,
];

View File

@@ -0,0 +1,49 @@
import { CollectionConfig } from 'payload';
export const Agbs: CollectionConfig = {
slug: 'agbs-collection',
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'versionDate', 'updatedAt'],
group: 'Rechtliches',
},
access: {
read: () => true,
},
fields: [
{
name: 'title',
type: 'text',
required: true,
localized: true,
label: 'Titel (z.B. AGB April 2026)',
},
{
name: 'versionDate',
type: 'date',
required: true,
label: 'Gültig ab',
admin: {
date: {
pickerAppearance: 'dayOnly',
},
},
},
{
name: 'file',
type: 'upload',
relationTo: 'media',
required: true,
label: 'PDF Datei',
},
{
name: 'isCurrent',
type: 'checkbox',
label: 'Aktuelle Version',
defaultValue: false,
admin: {
position: 'sidebar',
},
},
],
};