Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52439cc5aa | |||
| 61bf8517d8 | |||
| 85d9e3a930 | |||
| 2f7d6befe4 | |||
| a6ba484faa | |||
| d7178a9100 | |||
| 1e39dc8567 | |||
| 3c2aa8b71e | |||
| 6e3befdfd9 | |||
| 5377b3c81a | |||
| 941625a4e0 |
@@ -3,7 +3,11 @@
|
||||
import React from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
export const PDFDownloadBlock: React.FC<{ label: string; style: string }> = ({ label, style }) => {
|
||||
export const PDFDownloadBlock: React.FC<{ label: string; style: string; url?: string }> = ({
|
||||
label,
|
||||
style,
|
||||
url,
|
||||
}) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
// Extract slug from pathname
|
||||
@@ -12,7 +16,7 @@ export const PDFDownloadBlock: React.FC<{ label: string; style: string }> = ({ l
|
||||
// We want the page slug.
|
||||
const slug = segments[segments.length - 1] || 'home';
|
||||
|
||||
const href = `/api/pages/${slug}/pdf`;
|
||||
const href = url || `/api/pages/${slug}/pdf`;
|
||||
|
||||
return (
|
||||
<div className="my-8">
|
||||
|
||||
@@ -247,7 +247,7 @@ _status: published
|
||||
|
||||
Stand: April 2026
|
||||
|
||||
<Block type="pdfDownload" data={{"id":"69a9b09ea220fecfb9d54bb8","label":"Als PDF herunterladen","style":"primary","blockName":"","blockType":"pdfDownload"}} />
|
||||
<Block type="pdfDownload" data={{"id":"69a9b09ea220fecfb9d54bb8","label":"Als PDF herunterladen","style":"primary","url":"/AVB-KLZ-4-2026.pdf","blockName":"","blockType":"pdfDownload"}} />
|
||||
|
||||
<Block type="agbHistory" data={{"id":"69a9b09ea220fecfb9d54bb9","blockName":"AGB History","blockType":"agbHistory","title":"Vorherige Versionen"}} />
|
||||
|
||||
|
||||
@@ -215,8 +215,8 @@ const styles = StyleSheet.create({
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '20mm', // Generous area for the footer
|
||||
paddingBottom: '7mm', // 3mm bleed + 4mm safe margin
|
||||
height: '22mm', // Increased height to accommodate more bottom padding
|
||||
paddingBottom: '10mm', // Increased bottom padding to move icons further from edge
|
||||
zIndex: 50,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
@@ -253,7 +253,7 @@ const styles = StyleSheet.create({
|
||||
// -- WHITE PAGE (Vorderseite) --
|
||||
backContainer: {
|
||||
flex: 1,
|
||||
paddingTop: '3mm', // Reduced significantly to pull content up
|
||||
paddingTop: '6mm', // Pushed down as requested
|
||||
paddingLeft: '6mm',
|
||||
paddingRight: '6mm',
|
||||
flexDirection: 'column',
|
||||
@@ -302,7 +302,7 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'column',
|
||||
},
|
||||
nameSection: {
|
||||
marginBottom: '2.5mm', // Clear separation between name block and contact block
|
||||
marginBottom: '2.5mm', // Reduced to fix the 'Lücke' under the role
|
||||
},
|
||||
nameText: {
|
||||
fontSize: 13,
|
||||
@@ -351,10 +351,10 @@ const styles = StyleSheet.create({
|
||||
gap: 3,
|
||||
},
|
||||
qrCodeWrapper: {
|
||||
width: '12mm',
|
||||
height: '12mm',
|
||||
width: '10mm',
|
||||
height: '10mm',
|
||||
border: `0.75pt solid ${COLORS.darkGreen}`,
|
||||
padding: 2.5,
|
||||
padding: 2,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
qrCode: {
|
||||
@@ -815,6 +815,8 @@ export const PDFBusinessCard: React.FC<PDFBusinessCardProps> = ({
|
||||
};
|
||||
|
||||
const getBackLogoPaths = () => {
|
||||
// We use the original black logo to preserve the exact colors of the Truck and KLZ.
|
||||
// The tagline will be hidden via pathOverrides in the JSX.
|
||||
return logoBlackWithTaglinePaths;
|
||||
};
|
||||
|
||||
@@ -925,7 +927,17 @@ export const PDFBusinessCard: React.FC<PDFBusinessCardProps> = ({
|
||||
paths={getBackLogoPaths()}
|
||||
style={styles.backLogo}
|
||||
overrideWhiteColor={COLORS.black}
|
||||
pathOverrides={KLZ_PATHS.reduce((acc, idx) => ({ ...acc, [idx]: 'none' }), {})}
|
||||
pathOverrides={{
|
||||
...(KLZ_PATHS.reduce(
|
||||
(acc: Record<number, string>, idx) => ({ ...acc, [idx]: 'none' }),
|
||||
{} as Record<number, string>,
|
||||
) as any),
|
||||
// Paths 0-24 are the "Cables for a greener tomorrow" tagline in the 40-path logo.
|
||||
...(Array.from({ length: 25 }).reduce(
|
||||
(acc: Record<number, string>, _, i) => ({ ...acc, [i]: 'none' }),
|
||||
{} as Record<number, string>,
|
||||
) as any),
|
||||
}}
|
||||
/>
|
||||
<View style={styles.accentLineHorizontal} />
|
||||
<View style={styles.qrContainer}>
|
||||
|
||||
@@ -329,6 +329,42 @@ const renderLexicalNode = (node: any, idx: number): React.ReactNode => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderMarkdown = (markdown: string) => {
|
||||
const blocks = markdown.split('\n\n').filter((b) => b.trim());
|
||||
return blocks.map((block, idx) => {
|
||||
if (block.startsWith('### ')) {
|
||||
return (
|
||||
<Text key={idx} style={styles.heading3} wrap={false} minPresenceAhead={100}>
|
||||
{block.replace('### ', '').trim()}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
if (block.startsWith('## ')) {
|
||||
return (
|
||||
<Text key={idx} style={styles.heading2} wrap={false} minPresenceAhead={100}>
|
||||
{block.replace('## ', '').trim()}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
if (block.startsWith('# ')) {
|
||||
return (
|
||||
<View key={idx} style={styles.heading1Wrapper} wrap={false} minPresenceAhead={100}>
|
||||
<Text style={styles.heading1}>{block.replace('# ', '').trim()}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Ignore block components like <Block ... />
|
||||
if (block.startsWith('<Block')) return null;
|
||||
|
||||
return (
|
||||
<Text key={idx} style={styles.paragraph} minPresenceAhead={15}>
|
||||
{block.trim()}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
interface PDFPageProps {
|
||||
page: any;
|
||||
locale?: string;
|
||||
@@ -360,9 +396,11 @@ export const PDFPage: React.FC<PDFPageProps> = ({ page, locale = 'de' }) => {
|
||||
|
||||
<View style={styles.content}>
|
||||
<View>
|
||||
{page.content?.root?.children?.map((node: any, i: number) =>
|
||||
renderLexicalNode(node, i),
|
||||
)}
|
||||
{typeof page.content === 'string'
|
||||
? renderMarkdown(page.content)
|
||||
: page.content?.root?.children?.map((node: any, i: number) =>
|
||||
renderLexicalNode(node, i),
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.3.24",
|
||||
"version": "2.3.25",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
10
public/v/johannesgleich.vcf
Normal file
@@ -0,0 +1,10 @@
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
N:Gleich;Johannes;;;
|
||||
FN:Johannes Gleich
|
||||
ORG:KLZ Vertriebs GmbH
|
||||
TITLE:Senior Key Account Manager
|
||||
TEL;TYPE=WORK,VOICE:+49 172 739 1109
|
||||
EMAIL;TYPE=PREF,INTERNET:johannes.gleich@klz-cables.com
|
||||
URL:https://klz-cables.com
|
||||
END:VCARD
|
||||
10
public/v/klausmintel.vcf
Normal file
@@ -0,0 +1,10 @@
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
N:Mintel;Klaus;;;
|
||||
FN:Klaus Mintel
|
||||
ORG:KLZ Vertriebs GmbH
|
||||
TITLE:Managing Director
|
||||
TEL;TYPE=WORK,VOICE:+49 151 1775 2873
|
||||
EMAIL;TYPE=PREF,INTERNET:klaus.mintel@klz-cables.com
|
||||
URL:https://klz-cables.com
|
||||
END:VCARD
|
||||
10
public/v/michaelbodemer.vcf
Normal file
@@ -0,0 +1,10 @@
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
N:Bodemer;Michael;;;
|
||||
FN:Michael Bodemer
|
||||
ORG:KLZ Vertriebs GmbH
|
||||
TITLE:Owner & Managing Director
|
||||
TEL;TYPE=WORK,VOICE:+49 151 462 467 98
|
||||
EMAIL;TYPE=PREF,INTERNET:michael.bodemer@klz-cables.com
|
||||
URL:https://klz-cables.com
|
||||
END:VCARD
|
||||
@@ -52,23 +52,25 @@ async function generateBusinessCards() {
|
||||
for (const person of PEOPLE) {
|
||||
console.log(`\nGenerating Business Card for: ${person.name}`);
|
||||
|
||||
// Generate VCard QR Code
|
||||
const lastName = person.name.split(' ').slice(1).join(' ');
|
||||
const firstName = person.name.split(' ')[0];
|
||||
const vcard = `BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
N:${lastName};${firstName};;;
|
||||
FN:${person.name}
|
||||
ORG:KLZ Vertriebs GmbH
|
||||
TITLE:${person.role}
|
||||
TEL;TYPE=WORK,VOICE:${person.phone}
|
||||
EMAIL;TYPE=PREF,INTERNET:${person.email}
|
||||
URL:https://klz-cables.com
|
||||
END:VCARD`;
|
||||
|
||||
const qrCodeDataUrl = await QRCode.toDataURL(vcard, {
|
||||
// To make the QR code extremely coarse (minimalist), we cannot encode all contact details into it directly.
|
||||
// Instead, we create a physical .vcf file and encode ONLY its URL into the QR Code (e.g. 35 chars vs 140 chars).
|
||||
const shortId = person.name.toLowerCase().replace(/[^a-z0-9]/g, ''); // "michaelbodemer"
|
||||
const vcfUrl = `https://klz-cables.com/v/${shortId}.vcf`;
|
||||
|
||||
const vcardFull = `BEGIN:VCARD\r\nVERSION:3.0\r\nN:${lastName};${firstName};;;\r\nFN:${person.name}\r\nORG:KLZ Vertriebs GmbH\r\nTITLE:${person.role}\r\nTEL;TYPE=WORK,VOICE:${person.phone}\r\nEMAIL;TYPE=PREF,INTERNET:${person.email}\r\nURL:https://klz-cables.com\r\nEND:VCARD`;
|
||||
|
||||
// Save the physical .vcf file so it can be served by Next.js from /public/v/
|
||||
const vcardDir = path.join(process.cwd(), 'public/v');
|
||||
if (!fs.existsSync(vcardDir)) fs.mkdirSync(vcardDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(vcardDir, `${shortId}.vcf`), vcardFull);
|
||||
|
||||
const qrCodeDataUrl = await QRCode.toDataURL(vcfUrl, {
|
||||
width: 400,
|
||||
margin: 0,
|
||||
errorCorrectionLevel: 'L', // Low error correction = less dots
|
||||
color: { dark: '#000000', light: '#ffffff' },
|
||||
});
|
||||
|
||||
|
||||