Compare commits

...

17 Commits

Author SHA1 Message Date
1db7f3af6c 2.3.22-rc.2
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🧪 QA (push) Failing after 1m15s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-27 12:11:15 +02:00
909bad573b feat: implement AGB History with dynamic CMS fetching 2026-04-27 12:11:11 +02:00
24a19adf19 chore: trigger rebuild to verify disk space fix
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 58s
Build & Deploy / 🏗️ Build (push) Successful in 2m21s
Build & Deploy / 🚀 Deploy (push) Successful in 17s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 5m0s
Build & Deploy / 🔔 Notify (push) Successful in 3s
Nightly QA / 🔗 Links & Deps (push) Successful in 2m13s
Nightly QA / 🎭 Lighthouse (push) Successful in 3m20s
Nightly QA / ♿ Accessibility (push) Successful in 5m39s
Nightly QA / 🔍 Static Analysis (push) Successful in 6m50s
Nightly QA / 🔔 Notify (push) Successful in 3s
2026-04-22 10:24:12 +02:00
3313206734 fix: emergency route path to bypass payload catch-all
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 55s
Build & Deploy / 🏗️ Build (push) Failing after 2m27s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-04-22 08:59:54 +02:00
ec989690ce chore: move emergency route to public path
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 56s
Build & Deploy / 🏗️ Build (push) Successful in 2m12s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 4m42s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / ♿ Accessibility (push) Failing after 1m24s
Nightly QA / 🎭 Lighthouse (push) Failing after 1m23s
Nightly QA / 🔗 Links & Deps (push) Failing after 1m21s
Nightly QA / 🔍 Static Analysis (push) Successful in 6m8s
Nightly QA / 🔔 Notify (push) Successful in 1s
2026-04-21 22:59:47 +02:00
37807079cd chore: add emergency avb fix route
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 54s
Build & Deploy / 🏗️ Build (push) Successful in 2m10s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 4m43s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-21 21:47:34 +02:00
af4213ad59 fix(avb): clear legacy excerpt in CMS update script
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 56s
Build & Deploy / 🏗️ Build (push) Successful in 2m11s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 4m40s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-21 21:37:50 +02:00
dc1ba4def3 chore: bump version to 2.3.22-rc.1
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🧪 QA (push) Successful in 1m15s
Build & Deploy / 🚀 Deploy (push) Successful in 19s
Build & Deploy / 🏗️ Build (push) Successful in 4m47s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 4m57s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-21 19:26:30 +02:00
f87c714402 fix(e2e): implement automatic cleanup of test form submissions with authorized secret header 2026-04-21 19:26:06 +02:00
f989e0604f 2.3.21
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 1m18s
Build & Deploy / 🏗️ Build (push) Successful in 3m57s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 6m9s
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-04-21 19:19:08 +02:00
4a2d094cbd fix(i18n): update navigation term from AGB to AVB 2026-04-21 19:19:06 +02:00
77181fc983 2.3.20
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 1m43s
Build & Deploy / 🏗️ Build (push) Successful in 3m6s
Build & Deploy / 🚀 Deploy (push) Successful in 16s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 6m23s
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-04-21 18:57:38 +02:00
32b56696a6 feat(pdf): use static avb pdf for agbs route 2026-04-21 18:57:35 +02:00
9b28dd20d9 chore: update avb assets for april 2026
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Successful in 1m59s
Build & Deploy / 🏗️ Build (push) Successful in 3m1s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 5m9s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-21 18:47:48 +02:00
1970ae310f 2.3.18
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 1m17s
Build & Deploy / 🏗️ Build (push) Successful in 3m47s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 5m37s
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-04-21 17:38:49 +02:00
7bc8811a60 2.3.18-rc.8
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 1m19s
Build & Deploy / 🏗️ Build (push) Successful in 3m38s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 5m10s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-21 17:20:45 +02:00
530503fa09 fix(pdf): layout bleeding, margins, and titles 2026-04-21 17:20:42 +02:00
19 changed files with 287 additions and 22 deletions

View File

@@ -532,6 +532,7 @@ jobs:
env: env:
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
PAYLOAD_SECRET: ${{ secrets.PAYLOAD_SECRET || vars.PAYLOAD_SECRET }}
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
run: pnpm run check:forms run: pnpm run check:forms

View File

@@ -0,0 +1,25 @@
import { getPayload } from 'payload';
import config from '@/payload.config';
import { NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
export async function GET() {
try {
const payload = await getPayload({ config });
// Clear the excerpt for page ID 6
await payload.update({
collection: 'pages',
id: 6,
data: {
excerpt: '',
},
});
return NextResponse.json({ success: true, message: 'AVB excerpt cleared successfully on production!' });
} catch (error: any) {
console.error('Error fixing AVB:', error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}

View File

@@ -4,11 +4,58 @@ import configPromise from '@payload-config';
import { renderToStream } from '@react-pdf/renderer'; import { renderToStream } from '@react-pdf/renderer';
import React from 'react'; import React from 'react';
import { PDFPage } from '@/lib/pdf-page'; import { PDFPage } from '@/lib/pdf-page';
import fs from 'fs';
import path from 'path';
export async function GET(req: NextRequest, { params }: { params: Promise<{ slug: string }> }) { export async function GET(req: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
try { try {
const { slug } = await params; const { slug } = await params;
// 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);
return new NextResponse(fileBuffer, {
status: 200,
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="AVB-KLZ-4-2026.pdf"`,
},
});
}
}
// Get Payload App // Get Payload App
const payload = await getPayload({ config: configPromise }); const payload = await getPayload({ config: configPromise });

View File

@@ -0,0 +1,16 @@
import { getPayload } from 'payload'
import config from '@payload-config'
export async function GET() {
const payload = await getPayload({ config })
await payload.update({
collection: 'pages',
id: 6,
data: {
excerpt: '',
},
})
return Response.json({ success: true, message: 'AVB excerpt cleared' })
}

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

View File

@@ -27,7 +27,7 @@ const styles = StyleSheet.create({
color: C.gray900, color: C.gray900,
lineHeight: 1.6, lineHeight: 1.6,
backgroundColor: C.white, backgroundColor: C.white,
paddingTop: 0, paddingTop: 50,
paddingBottom: 80, paddingBottom: 80,
fontFamily: 'Helvetica', fontFamily: 'Helvetica',
}, },
@@ -35,10 +35,11 @@ const styles = StyleSheet.create({
// Premium Header Layout // Premium Header Layout
hero: { hero: {
backgroundColor: C.offWhite, backgroundColor: C.offWhite,
paddingTop: 40, paddingTop: 24,
paddingBottom: 32, paddingBottom: 24,
paddingHorizontal: MARGIN, paddingHorizontal: MARGIN,
marginBottom: 40, marginBottom: 40,
marginTop: -50, // Counters the page padding to achieve full-bleed top
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: C.gray200, borderBottomColor: C.gray200,
position: 'relative', position: 'relative',
@@ -71,12 +72,12 @@ const styles = StyleSheet.create({
}, },
pageTitle: { pageTitle: {
fontSize: 28, fontSize: 16,
fontWeight: 700, fontWeight: 700,
color: C.navyDeep, color: C.navyDeep,
marginBottom: 4, marginBottom: 4,
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: -0.5, letterSpacing: 0,
}, },
accentBar: { accentBar: {
@@ -366,7 +367,7 @@ export const PDFPage: React.FC<PDFPageProps> = ({ page, locale = 'de' }) => {
</View> </View>
{/* Industrial footer with page numbers */} {/* Industrial footer with page numbers */}
<View style={{ ...styles.footer, position: 'absolute', bottom: 30, height: 40 }} fixed> <View style={{ ...styles.footer, position: 'absolute', bottom: 40, height: 40 }} fixed>
<View style={styles.footerInfo}> <View style={styles.footerInfo}>
<Text style={styles.footerBrand}>KLZ VERTRIEBS GMBH</Text> <Text style={styles.footerBrand}>KLZ VERTRIEBS GMBH</Text>
<Text style={styles.footerText}> <Text style={styles.footerText}>

View File

@@ -73,7 +73,7 @@
"legalNoticeSlug": "impressum", "legalNoticeSlug": "impressum",
"privacyPolicy": "Datenschutz", "privacyPolicy": "Datenschutz",
"privacyPolicySlug": "datenschutz", "privacyPolicySlug": "datenschutz",
"terms": "AGB", "terms": "AVB",
"termsSlug": "terms", "termsSlug": "terms",
"products": "Produkte", "products": "Produkte",
"lowVoltage": "Niederspannungskabel", "lowVoltage": "Niederspannungskabel",

View File

@@ -139,7 +139,7 @@
"prepare": "husky", "prepare": "husky",
"preinstall": "npx only-allow pnpm" "preinstall": "npx only-allow pnpm"
}, },
"version": "2.3.18-rc.7", "version": "2.3.22-rc.2",
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@parcel/watcher", "@parcel/watcher",

View File

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

BIN
public/AVB-KLZ-4-2026.docx Normal file

Binary file not shown.

BIN
public/AVB-KLZ-4-2026.pdf Normal file

Binary file not shown.

View File

@@ -348,13 +348,23 @@ async function main() {
// 5. Cleanup: Delete test submissions from Payload CMS // 5. Cleanup: Delete test submissions from Payload CMS
console.log(`\n🧹 Starting cleanup of test submissions...`); console.log(`\n🧹 Starting cleanup of test submissions...`);
const payloadSecret = process.env.PAYLOAD_SECRET;
if (!payloadSecret) {
console.warn(` ⚠️ PAYLOAD_SECRET not found in environment. Cleanup will likely fail with 403.`);
}
try { try {
const apiUrl = `${targetUrl.replace(/\/$/, '')}/api/form-submissions`; const apiUrl = `${targetUrl.replace(/\/$/, '')}/api/form-submissions`;
const searchUrl = `${apiUrl}?where[email][equals]=testing@mintel.me`; // We fetch ALL submissions matching the test email to clean up potential lefovers from previous runs
const searchUrl = `${apiUrl}?where[email][equals]=testing@mintel.me&limit=100`;
// Fetch test submissions // Fetch test submissions with bypass header
const searchResponse = await axios.get(searchUrl, { const searchResponse = await axios.get(searchUrl, {
headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, headers: {
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`,
'x-e2e-secret': payloadSecret || '',
},
}); });
const testSubmissions = searchResponse.data.docs || []; const testSubmissions = searchResponse.data.docs || [];
@@ -363,25 +373,30 @@ async function main() {
for (const doc of testSubmissions) { for (const doc of testSubmissions) {
try { try {
await axios.delete(`${apiUrl}/${doc.id}`, { await axios.delete(`${apiUrl}/${doc.id}`, {
headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, headers: {
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`,
'x-e2e-secret': payloadSecret || '',
},
}); });
console.log(` ✅ Deleted submission: ${doc.id}`); console.log(` ✅ Deleted submission: ${doc.id} (${doc.name})`);
} catch (delErr: any) { } catch (delErr: any) {
// Log but don't fail, 403s on Directus / Payload APIs for guest Gatekeeper sessions are normal
console.warn( console.warn(
` ⚠️ Cleanup attempt on ${doc.id} returned an error, typically due to API Auth separation: ${delErr.message}`, ` ⚠️ Cleanup attempt on ${doc.id} failed: ${delErr.message}`,
); );
} }
} }
if (testSubmissions.length > 0) {
console.log(`✅ Cleanup completed successfully.`);
}
} catch (err: any) { } catch (err: any) {
if (err.response?.status === 403) { if (err.response?.status === 403) {
console.warn( console.error(
` ⚠️ Cleanup fetch failed with 403 Forbidden. This is expected if the runner lacks admin API credentials. Test submissions remain in the database.`, ` Cleanup failed with 403 Forbidden. Ensure FormSubmissions access control and PAYLOAD_SECRET are aligned.`,
); );
} else { } else {
console.error(` ❌ Cleanup fetch failed: ${err.message}`); console.error(` ❌ Cleanup fetch failed: ${err.message}`);
} }
// Don't mark the whole test as failed just because cleanup failed
} }
await browser.close(); await browser.close();

View File

@@ -22,6 +22,7 @@ async function run() {
id: 6, id: 6,
data: { data: {
title: 'Allgemeine Verkaufsbedingungen (AVB)', title: 'Allgemeine Verkaufsbedingungen (AVB)',
excerpt: '',
content: lexicalContent, content: lexicalContent,
}, },
}); });

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 { TechnicalGrid } from './TechnicalGrid';
import { VisualLinkPreview } from './VisualLinkPreview'; import { VisualLinkPreview } from './VisualLinkPreview';
import { PDFDownload } from './PDFDownload'; import { PDFDownload } from './PDFDownload';
import { AgbHistory } from './AgbHistory';
import { homeBlocksArray } from './HomeBlocks'; import { homeBlocksArray } from './HomeBlocks';
export const payloadBlocks = [ export const payloadBlocks = [
@@ -40,4 +41,5 @@ export const payloadBlocks = [
TechnicalGrid, TechnicalGrid,
VisualLinkPreview, VisualLinkPreview,
PDFDownload, 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',
},
},
],
};

View File

@@ -9,9 +9,19 @@ export const FormSubmissions: CollectionConfig = {
}, },
access: { access: {
// Only Admins can view and delete leads via dashboard. // Only Admins can view and delete leads via dashboard.
read: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', // E2E scripts can bypass if they provide the correct secret header.
update: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', read: ({ req }) => {
delete: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET;
return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development';
},
update: ({ req }) => {
const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET;
return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development';
},
delete: ({ req }) => {
const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET;
return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development';
},
// Next.js server actions handle secure inserts natively. No public client create access. // Next.js server actions handle secure inserts natively. No public client create access.
create: () => false, create: () => false,
}, },

Binary file not shown.