Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f80e72c1d | |||
| d9334f558d | |||
| cb436d31d0 | |||
| 4b3ef49522 | |||
| 301e112488 |
@@ -576,6 +576,11 @@ jobs:
|
|||||||
Deploy: $DEPLOY | Smoke: $SMOKE | Perf: $PERF
|
Deploy: $DEPLOY | Smoke: $SMOKE | Perf: $PERF
|
||||||
$URL"
|
$URL"
|
||||||
|
|
||||||
|
if [[ -z "${{ secrets.GOTIFY_URL }}" || -z "${{ secrets.GOTIFY_TOKEN }}" ]]; then
|
||||||
|
echo "⚠️ Gotify credentials missing, skipping notification."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
||||||
-F "title=$TITLE" \
|
-F "title=$TITLE" \
|
||||||
-F "message=$MESSAGE" \
|
-F "message=$MESSAGE" \
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
name: Nightly QA
|
name: Nightly QA
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 3 * * *'
|
- cron: '0 3 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -227,6 +225,11 @@ jobs:
|
|||||||
MESSAGE="Static: $STATIC | A11y: $A11Y | Lighthouse: $LIGHTHOUSE | Links: $LINKS
|
MESSAGE="Static: $STATIC | A11y: $A11Y | Lighthouse: $LIGHTHOUSE | Links: $LINKS
|
||||||
${{ env.TARGET_URL }}"
|
${{ env.TARGET_URL }}"
|
||||||
|
|
||||||
|
if [[ -z "${{ secrets.GOTIFY_URL }}" || -z "${{ secrets.GOTIFY_TOKEN }}" ]]; then
|
||||||
|
echo "⚠️ Gotify credentials missing, skipping notification."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
||||||
-F "title=$TITLE" \
|
-F "title=$TITLE" \
|
||||||
-F "message=$MESSAGE" \
|
-F "message=$MESSAGE" \
|
||||||
|
|||||||
34
components/PDFDownloadBlock.tsx
Normal file
34
components/PDFDownloadBlock.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
export const PDFDownloadBlock: React.FC<{ label: string; style: string }> = ({ label, style }) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
// Extract slug from pathname
|
||||||
|
const segments = pathname.split('/').filter(Boolean);
|
||||||
|
// Pathname is usually /[locale]/[slug] or /[locale]/products/[slug]
|
||||||
|
// We want the page slug.
|
||||||
|
const slug = segments[segments.length - 1] || 'home';
|
||||||
|
|
||||||
|
const href = `/api/pages/${slug}/pdf`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-8">
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
className={`inline-flex items-center px-8 py-3.5 font-bold rounded-full transition-all duration-300 shadow-lg hover:shadow-xl group ${
|
||||||
|
style === 'primary'
|
||||||
|
? 'bg-primary text-white hover:bg-primary-dark'
|
||||||
|
: style === 'secondary'
|
||||||
|
? 'bg-accent text-primary-dark hover:bg-neutral-light'
|
||||||
|
: 'border-2 border-primary text-primary hover:bg-primary hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="mr-3 transition-transform group-hover:scale-12 bit-bounce">📄</span>
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -37,6 +37,7 @@ import MeetTheTeam from '@/components/home/MeetTheTeam';
|
|||||||
import GallerySection from '@/components/home/GallerySection';
|
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits a text string on \n and intersperses <br /> elements.
|
* Splits a text string on \n and intersperses <br /> elements.
|
||||||
@@ -429,6 +430,12 @@ const jsxConverters: JSXConverters = {
|
|||||||
{node.fields.content && <RichText data={node.fields.content} converters={jsxConverters} />}
|
{node.fields.content && <RichText data={node.fields.content} converters={jsxConverters} />}
|
||||||
</ProductTabs>
|
</ProductTabs>
|
||||||
),
|
),
|
||||||
|
pdfDownload: ({ node }: any) => (
|
||||||
|
<PDFDownloadBlock label={node.fields.label} style={node.fields.style} />
|
||||||
|
),
|
||||||
|
'block-pdfDownload': ({ node }: any) => (
|
||||||
|
<PDFDownloadBlock label={node.fields.label} style={node.fields.style} />
|
||||||
|
),
|
||||||
// ─── New Page Blocks ───────────────────────────────────────────
|
// ─── New Page Blocks ───────────────────────────────────────────
|
||||||
heroSection: ({ node }: any) => {
|
heroSection: ({ node }: any) => {
|
||||||
const f = node.fields;
|
const f = node.fields;
|
||||||
|
|||||||
180
lib/pdf-page.tsx
180
lib/pdf-page.tsx
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Document, Page, View, Text, Image, StyleSheet, Font, Link } from '@react-pdf/renderer';
|
import { Document, Page, View, Text, StyleSheet, Font, Link } from '@react-pdf/renderer';
|
||||||
|
|
||||||
// Register fonts (using system fonts for now, can be customized)
|
// Register fonts (using system fonts for now, can be customized)
|
||||||
Font.register({
|
Font.register({
|
||||||
@@ -10,120 +10,120 @@ Font.register({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Brand Tokens ────────────────────────────────────────
|
// ─── Brand Tokens (matching datasheet) ──────────────────────────────────
|
||||||
|
|
||||||
const C = {
|
const C = {
|
||||||
navy: '#001a4d',
|
navy: '#001a4d',
|
||||||
navyDeep: '#000d26',
|
navyDeep: '#000d26',
|
||||||
green: '#4da612',
|
green: '#4da612',
|
||||||
|
greenLight: '#e8f5d8',
|
||||||
white: '#FFFFFF',
|
white: '#FFFFFF',
|
||||||
offWhite: '#f8f9fa',
|
offWhite: '#f8f9fa',
|
||||||
|
gray100: '#f3f4f6',
|
||||||
gray200: '#e5e7eb',
|
gray200: '#e5e7eb',
|
||||||
|
gray300: '#d1d5db',
|
||||||
gray400: '#9ca3af',
|
gray400: '#9ca3af',
|
||||||
gray600: '#4b5563',
|
gray600: '#4b5563',
|
||||||
gray900: '#111827',
|
gray900: '#111827',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MARGIN = 56;
|
const MARGIN = 56;
|
||||||
const HEADER_H = 52;
|
|
||||||
const FOOTER_H = 48;
|
|
||||||
const BODY_TOP = HEADER_H + 20;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
page: {
|
page: {
|
||||||
color: C.gray900,
|
color: C.gray900,
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
backgroundColor: C.white,
|
backgroundColor: C.white,
|
||||||
paddingTop: BODY_TOP,
|
paddingTop: 0,
|
||||||
paddingBottom: FOOTER_H + 40,
|
paddingBottom: 80,
|
||||||
paddingHorizontal: MARGIN,
|
|
||||||
fontFamily: 'Helvetica',
|
fontFamily: 'Helvetica',
|
||||||
},
|
},
|
||||||
header: {
|
|
||||||
position: 'absolute',
|
// Hero-style header
|
||||||
top: 0,
|
hero: {
|
||||||
left: 0,
|
backgroundColor: C.white,
|
||||||
right: 0,
|
paddingTop: 24,
|
||||||
height: HEADER_H,
|
paddingBottom: 0,
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'flex-end',
|
|
||||||
paddingHorizontal: MARGIN,
|
paddingHorizontal: MARGIN,
|
||||||
paddingBottom: 12,
|
marginBottom: 20,
|
||||||
|
position: 'relative',
|
||||||
borderBottomWidth: 0,
|
borderBottomWidth: 0,
|
||||||
},
|
},
|
||||||
logoText: {
|
|
||||||
fontSize: 16,
|
header: {
|
||||||
fontWeight: 700,
|
|
||||||
color: C.navy,
|
|
||||||
},
|
|
||||||
docTitleLabel: {
|
|
||||||
fontSize: 7,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: C.gray400,
|
|
||||||
letterSpacing: 1.2,
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 20,
|
|
||||||
left: MARGIN,
|
|
||||||
right: MARGIN,
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderTopWidth: 0.5,
|
marginBottom: 16,
|
||||||
borderTopColor: C.gray200,
|
|
||||||
paddingTop: 8,
|
|
||||||
},
|
},
|
||||||
footerText: {
|
|
||||||
fontSize: 7,
|
logoText: {
|
||||||
color: C.gray400,
|
fontSize: 22,
|
||||||
letterSpacing: 0.8,
|
fontWeight: 700,
|
||||||
|
color: C.navyDeep,
|
||||||
|
letterSpacing: 2,
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
docTitle: {
|
||||||
|
fontSize: 8,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: C.green,
|
||||||
|
letterSpacing: 2,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content Area
|
||||||
|
content: {
|
||||||
|
paddingHorizontal: MARGIN,
|
||||||
|
},
|
||||||
|
|
||||||
pageTitle: {
|
pageTitle: {
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: C.navyDeep,
|
color: C.navyDeep,
|
||||||
marginBottom: 16,
|
marginBottom: 8,
|
||||||
|
marginTop: 10,
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
letterSpacing: -0.5,
|
letterSpacing: -0.5,
|
||||||
},
|
},
|
||||||
|
|
||||||
accentBar: {
|
accentBar: {
|
||||||
width: 40,
|
width: 30,
|
||||||
height: 3,
|
height: 2,
|
||||||
backgroundColor: C.green,
|
backgroundColor: C.green,
|
||||||
marginBottom: 24,
|
marginBottom: 20,
|
||||||
|
borderRadius: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Lexical Elements
|
// Lexical Elements
|
||||||
paragraph: {
|
paragraph: {
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: C.gray600,
|
color: C.gray600,
|
||||||
lineHeight: 1.6,
|
lineHeight: 1.7,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
heading1: {
|
heading1: {
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: C.navyDeep,
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 10,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
heading2: {
|
||||||
|
fontSize: 12,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: C.navyDeep,
|
color: C.navyDeep,
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
heading2: {
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: C.navyDeep,
|
|
||||||
marginTop: 14,
|
|
||||||
marginBottom: 6,
|
|
||||||
},
|
|
||||||
heading3: {
|
heading3: {
|
||||||
fontSize: 12,
|
fontSize: 10,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: C.navyDeep,
|
color: C.navyDeep,
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
marginBottom: 4,
|
marginBottom: 6,
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
@@ -137,12 +137,13 @@ const styles = StyleSheet.create({
|
|||||||
width: 12,
|
width: 12,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: C.green,
|
color: C.green,
|
||||||
|
fontWeight: 700,
|
||||||
},
|
},
|
||||||
listItemContent: {
|
listItemContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: C.gray600,
|
color: C.gray600,
|
||||||
lineHeight: 1.6,
|
lineHeight: 1.7,
|
||||||
},
|
},
|
||||||
link: {
|
link: {
|
||||||
color: C.green,
|
color: C.green,
|
||||||
@@ -154,7 +155,37 @@ const styles = StyleSheet.create({
|
|||||||
color: C.navyDeep,
|
color: C.navyDeep,
|
||||||
},
|
},
|
||||||
textItalic: {
|
textItalic: {
|
||||||
fontStyle: 'italic', // Not actually working without proper font set, but we will fallback
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Footer — matches brochure style
|
||||||
|
footer: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 28,
|
||||||
|
left: MARGIN,
|
||||||
|
right: MARGIN,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: 12,
|
||||||
|
borderTopWidth: 2,
|
||||||
|
borderTopColor: C.green,
|
||||||
|
},
|
||||||
|
|
||||||
|
footerText: {
|
||||||
|
fontSize: 7,
|
||||||
|
color: C.gray400,
|
||||||
|
fontWeight: 400,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.8,
|
||||||
|
},
|
||||||
|
|
||||||
|
footerBrand: {
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: C.navyDeep,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 1.5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -232,11 +263,10 @@ const renderLexicalNode = (node: any, idx: number): React.ReactNode => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'linebreak': {
|
case 'linebreak': {
|
||||||
// React-PDF doesn't handle `<br/>`, but a newline char usually works inside `<Text>`.
|
|
||||||
return <Text key={idx}>{'\n'}</Text>;
|
return <Text key={idx}>{'\n'}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore payload blocks recursively to avoid crashing, as pages should mainly use rich text
|
// Ignore payload blocks recursively to avoid crashing
|
||||||
case 'block':
|
case 'block':
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -267,20 +297,30 @@ export const PDFPage: React.FC<PDFPageProps> = ({ page, locale = 'de' }) => {
|
|||||||
return (
|
return (
|
||||||
<Document>
|
<Document>
|
||||||
<Page size="A4" style={styles.page}>
|
<Page size="A4" style={styles.page}>
|
||||||
<View style={styles.header} fixed>
|
{/* Hero Header */}
|
||||||
<Text style={styles.logoText}>KLZ</Text>
|
<View style={styles.hero} fixed>
|
||||||
<Text style={styles.docTitleLabel}>{locale === 'en' ? 'Document' : 'Dokument'}</Text>
|
<View style={styles.header}>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.logoText}>KLZ</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.docTitle}>{locale === 'en' ? 'Document' : 'Dokument'}</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.pageTitle}>{page.title}</Text>
|
<View style={styles.content}>
|
||||||
<View style={styles.accentBar} />
|
<Text style={styles.pageTitle}>{page.title}</Text>
|
||||||
|
<View style={styles.accentBar} />
|
||||||
|
|
||||||
<View>
|
<View>
|
||||||
{page.content?.root?.children?.map((node: any, i: number) => renderLexicalNode(node, i))}
|
{page.content?.root?.children?.map((node: any, i: number) =>
|
||||||
|
renderLexicalNode(node, i),
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Minimal footer */}
|
||||||
<View style={styles.footer} fixed>
|
<View style={styles.footer} fixed>
|
||||||
<Text style={styles.footerText}>KLZ CABLES</Text>
|
<Text style={styles.footerBrand}>KLZ CABLES</Text>
|
||||||
<Text style={styles.footerText}>{dateStr}</Text>
|
<Text style={styles.footerText}>{dateStr}</Text>
|
||||||
</View>
|
</View>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ export default buildConfig({
|
|||||||
},
|
},
|
||||||
db: postgresAdapter({
|
db: postgresAdapter({
|
||||||
prodMigrations: migrations,
|
prodMigrations: migrations,
|
||||||
|
migrationDir:
|
||||||
|
process.env.NODE_ENV === 'production' ? undefined : path.resolve(dirname, 'src/migrations'),
|
||||||
pool: {
|
pool: {
|
||||||
connectionString:
|
connectionString:
|
||||||
process.env.DATABASE_URI ||
|
process.env.DATABASE_URI ||
|
||||||
|
|||||||
52
src/migrations/20260305_215000_products_featured_image.ts
Normal file
52
src/migrations/20260305_215000_products_featured_image.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
|
||||||
|
|
||||||
|
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||||
|
// Add featured_image_id to products and _products_v
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "products" ADD COLUMN IF NOT EXISTS "featured_image_id" integer;
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "_products_v" ADD COLUMN IF NOT EXISTS "version_featured_image_id" integer;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Add foreign key constraints
|
||||||
|
await db.execute(sql`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "products" ADD CONSTRAINT "products_featured_image_id_media_id_fk" FOREIGN KEY ("featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN null; END $$;
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_products_v" ADD CONSTRAINT "_products_v_version_featured_image_id_media_id_fk" FOREIGN KEY ("version_featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN null; END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Add indexes
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE INDEX IF NOT EXISTS "products_featured_image_idx" ON "products" USING btree ("featured_image_id");
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE INDEX IF NOT EXISTS "_products_v_version_version_featured_image_idx" ON "_products_v" USING btree ("version_featured_image_id");
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "products" DROP CONSTRAINT IF EXISTS "products_featured_image_id_media_id_fk";
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "_products_v" DROP CONSTRAINT IF EXISTS "_products_v_version_featured_image_id_media_id_fk";
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
DROP INDEX IF EXISTS "products_featured_image_idx";
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
DROP INDEX IF EXISTS "_products_v_version_version_featured_image_idx";
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "products" DROP COLUMN IF EXISTS "featured_image_id";
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "_products_v" DROP COLUMN IF EXISTS "version_featured_image_id";
|
||||||
|
`);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import * as migration_20260223_195005_products_collection from './20260223_19500
|
|||||||
import * as migration_20260223_195151_remove_sku_unique from './20260223_195151_remove_sku_unique';
|
import * as migration_20260223_195151_remove_sku_unique from './20260223_195151_remove_sku_unique';
|
||||||
import * as migration_20260225_003500_add_pages_collection from './20260225_003500_add_pages_collection';
|
import * as migration_20260225_003500_add_pages_collection from './20260225_003500_add_pages_collection';
|
||||||
import * as migration_20260225_175000_native_localization from './20260225_175000_native_localization';
|
import * as migration_20260225_175000_native_localization from './20260225_175000_native_localization';
|
||||||
|
import * as migration_20260305_215000_products_featured_image from './20260305_215000_products_featured_image';
|
||||||
|
|
||||||
export const migrations = [
|
export const migrations = [
|
||||||
{
|
{
|
||||||
@@ -24,4 +25,9 @@ export const migrations = [
|
|||||||
down: migration_20260225_175000_native_localization.down,
|
down: migration_20260225_175000_native_localization.down,
|
||||||
name: '20260225_175000_native_localization',
|
name: '20260225_175000_native_localization',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
up: migration_20260305_215000_products_featured_image.up,
|
||||||
|
down: migration_20260305_215000_products_featured_image.down,
|
||||||
|
name: '20260305_215000_products_featured_image',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
30
src/payload/blocks/PDFDownload.ts
Normal file
30
src/payload/blocks/PDFDownload.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Block } from 'payload';
|
||||||
|
|
||||||
|
export const PDFDownload: Block = {
|
||||||
|
slug: 'pdfDownload',
|
||||||
|
labels: {
|
||||||
|
singular: 'PDF Download',
|
||||||
|
plural: 'PDF Downloads',
|
||||||
|
},
|
||||||
|
admin: {},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'label',
|
||||||
|
type: 'text',
|
||||||
|
label: 'Button Beschriftung',
|
||||||
|
required: true,
|
||||||
|
localized: true,
|
||||||
|
defaultValue: 'Als PDF herunterladen',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'style',
|
||||||
|
type: 'select',
|
||||||
|
defaultValue: 'primary',
|
||||||
|
options: [
|
||||||
|
{ label: 'Primary', value: 'primary' },
|
||||||
|
{ label: 'Secondary', value: 'secondary' },
|
||||||
|
{ label: 'Outline', value: 'outline' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ import { StickyNarrative } from './StickyNarrative';
|
|||||||
import { TeamProfile } from './TeamProfile';
|
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 { homeBlocksArray } from './HomeBlocks';
|
import { homeBlocksArray } from './HomeBlocks';
|
||||||
|
|
||||||
export const payloadBlocks = [
|
export const payloadBlocks = [
|
||||||
@@ -38,4 +39,5 @@ export const payloadBlocks = [
|
|||||||
TeamProfile,
|
TeamProfile,
|
||||||
TechnicalGrid,
|
TechnicalGrid,
|
||||||
VisualLinkPreview,
|
VisualLinkPreview,
|
||||||
|
PDFDownload,
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user