fix: match customer draft layout and correctly use PNG assets for QR Code and Logos
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Successful in 53s
Build & Deploy / 🏗️ Build (push) Successful in 1m59s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Successful in 53s
Build & Deploy / 🏗️ Build (push) Successful in 1m59s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
This commit is contained in:
@@ -1,154 +1,201 @@
|
|||||||
import * as React from 'react';
|
import React from 'react';
|
||||||
import { Document, Page, View, Text, Image, StyleSheet, Font } from '@react-pdf/renderer';
|
import { Document, Page, Text, View, StyleSheet, Font, Image } from '@react-pdf/renderer';
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
// Wir nutzen Standard-Helvetica, da das Projekt Helvetica für sauberen Tech-Look vorsieht.
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
// Size incl. 3mm bleed on all sides
|
|
||||||
// 85x55 -> 91x61 -> 257.95 x 172.91 pt
|
|
||||||
pageFront: {
|
|
||||||
backgroundColor: '#001a4d', // KLZ Navy
|
|
||||||
width: 257.95,
|
|
||||||
height: 172.91,
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
position: 'relative',
|
|
||||||
fontFamily: 'Helvetica',
|
|
||||||
},
|
|
||||||
pageBack: {
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
width: 257.95,
|
|
||||||
height: 172.91,
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: 24,
|
|
||||||
position: 'relative',
|
|
||||||
fontFamily: 'Helvetica',
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
width: 100, // Logo breite
|
|
||||||
},
|
|
||||||
accentLine: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: 8,
|
|
||||||
backgroundColor: '#82ed20', // KLZ Green
|
|
||||||
},
|
|
||||||
accentLineTop: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: 8,
|
|
||||||
backgroundColor: '#82ed20', // KLZ Green
|
|
||||||
},
|
|
||||||
personInfo: {
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#000d26',
|
|
||||||
letterSpacing: -0.5,
|
|
||||||
marginBottom: 2,
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
fontSize: 8,
|
|
||||||
color: '#82ed20', // Highlight im Text
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: 1,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
contactGrid: {
|
|
||||||
marginTop: 10,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 4,
|
|
||||||
},
|
|
||||||
contactRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
contactLabel: {
|
|
||||||
width: 40,
|
|
||||||
fontSize: 7,
|
|
||||||
color: '#4b5563',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
},
|
|
||||||
contactValue: {
|
|
||||||
fontSize: 8,
|
|
||||||
color: '#111827',
|
|
||||||
},
|
|
||||||
logoTextWhite: {
|
|
||||||
fontSize: 32,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: '#FFFFFF',
|
|
||||||
letterSpacing: 4,
|
|
||||||
marginBottom: 4,
|
|
||||||
},
|
|
||||||
logoTextBlue: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 24,
|
|
||||||
right: 24,
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: '#001a4d',
|
|
||||||
letterSpacing: 2,
|
|
||||||
opacity: 0.3,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface PersonData {
|
export interface PersonData {
|
||||||
name: string;
|
name: string;
|
||||||
position: string;
|
role: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
email: string;
|
email: string;
|
||||||
website: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PDFBusinessCardProps {
|
interface PDFBusinessCardProps {
|
||||||
person: PersonData;
|
person: PersonData;
|
||||||
|
logoWhitePath: string;
|
||||||
|
logoBluePath: string;
|
||||||
|
logoFullPath: string;
|
||||||
|
truckImagePath: string;
|
||||||
|
qrCodeDataUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PDFBusinessCard: React.FC<PDFBusinessCardProps> = ({ person }) => {
|
const styles = StyleSheet.create({
|
||||||
|
page: {
|
||||||
|
width: '85mm',
|
||||||
|
height: '55mm',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
position: 'relative',
|
||||||
|
fontFamily: 'Helvetica',
|
||||||
|
},
|
||||||
|
pageBleed: {
|
||||||
|
// 85x55mm + 3mm bleed on each side = 91x61mm
|
||||||
|
width: '91mm',
|
||||||
|
height: '61mm',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
position: 'relative',
|
||||||
|
fontFamily: 'Helvetica',
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
// FRONT PAGE
|
||||||
|
frontContainer: {
|
||||||
|
flex: 1,
|
||||||
|
position: 'relative',
|
||||||
|
padding: '8mm',
|
||||||
|
},
|
||||||
|
frontLogo: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '5mm',
|
||||||
|
right: '5mm',
|
||||||
|
width: '25mm',
|
||||||
|
},
|
||||||
|
truckImage: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '-2mm',
|
||||||
|
left: '-5mm',
|
||||||
|
width: '100mm', // cover bleed width
|
||||||
|
height: '50mm',
|
||||||
|
objectFit: 'contain',
|
||||||
|
},
|
||||||
|
// BACK PAGE
|
||||||
|
backContainer: {
|
||||||
|
flex: 1,
|
||||||
|
position: 'relative',
|
||||||
|
padding: '6mm 6mm',
|
||||||
|
},
|
||||||
|
backLogo: {
|
||||||
|
width: '20mm',
|
||||||
|
marginBottom: '8mm',
|
||||||
|
},
|
||||||
|
backMiddle: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '8mm',
|
||||||
|
},
|
||||||
|
nameSection: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '35%',
|
||||||
|
},
|
||||||
|
nameText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: '#000000',
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
roleText: {
|
||||||
|
fontSize: 8,
|
||||||
|
fontWeight: 400,
|
||||||
|
color: '#333333',
|
||||||
|
},
|
||||||
|
contactSection: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '45%',
|
||||||
|
paddingLeft: '2mm',
|
||||||
|
},
|
||||||
|
contactRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 3,
|
||||||
|
},
|
||||||
|
iconCircle: {
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
borderRadius: 6,
|
||||||
|
backgroundColor: '#82ed20',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 4,
|
||||||
|
},
|
||||||
|
contactText: {
|
||||||
|
fontSize: 6.5,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
qrSection: {
|
||||||
|
width: '20%',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
qrCode: {
|
||||||
|
width: '18mm',
|
||||||
|
height: '18mm',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '5mm',
|
||||||
|
left: '6mm',
|
||||||
|
right: '6mm',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
footerText: {
|
||||||
|
fontSize: 6.5,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
pipe: {
|
||||||
|
fontSize: 6.5,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#82ed20',
|
||||||
|
marginHorizontal: 3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PDFBusinessCard: React.FC<PDFBusinessCardProps> = ({
|
||||||
|
person,
|
||||||
|
logoFullPath,
|
||||||
|
truckImagePath,
|
||||||
|
qrCodeDataUrl,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Document>
|
<Document
|
||||||
{/* Front: Logo and Navy Background */}
|
title={`Business Card - ${person.name}`}
|
||||||
<Page size={[257.95, 172.91]} style={styles.pageFront}>
|
author="KLZ Cables"
|
||||||
<Text style={styles.logoTextWhite}>KLZ</Text>
|
creator="KLZ React-PDF Engine"
|
||||||
<View style={styles.accentLine} />
|
>
|
||||||
|
{/* FRONT - 91x61mm with Bleed */}
|
||||||
|
<Page size={[258, 173]} style={styles.pageBleed}>
|
||||||
|
<Image src={truckImagePath} style={styles.truckImage} />
|
||||||
|
<Image src={logoFullPath} style={styles.frontLogo} />
|
||||||
</Page>
|
</Page>
|
||||||
|
|
||||||
{/* Back: Contact Details */}
|
{/* BACK - 91x61mm with Bleed */}
|
||||||
<Page size={[257.95, 172.91]} style={styles.pageBack}>
|
<Page size={[258, 173]} style={styles.pageBleed}>
|
||||||
<View style={styles.accentLineTop} />
|
<View style={styles.backContainer}>
|
||||||
|
<Image src={logoFullPath} style={styles.backLogo} />
|
||||||
|
|
||||||
<View style={styles.personInfo}>
|
<View style={styles.backMiddle}>
|
||||||
<Text style={styles.name}>{person.name}</Text>
|
<View style={styles.nameSection}>
|
||||||
<Text style={styles.position}>{person.position}</Text>
|
<Text style={styles.nameText}>{person.name}</Text>
|
||||||
</View>
|
<Text style={styles.roleText}>{person.role}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.contactGrid}>
|
<View style={styles.contactSection}>
|
||||||
<View style={styles.contactRow}>
|
<View style={styles.contactRow}>
|
||||||
<Text style={styles.contactLabel}>Phone</Text>
|
<View style={styles.iconCircle} />
|
||||||
<Text style={styles.contactValue}>{person.phone}</Text>
|
<Text style={styles.contactText}>{person.phone}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.contactRow}>
|
||||||
|
<View style={styles.iconCircle} />
|
||||||
|
<Text style={styles.contactText}>{person.email}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.contactRow}>
|
||||||
|
<View style={styles.iconCircle} />
|
||||||
|
<Text style={styles.contactText}>klz-cables.com</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.qrSection}>
|
||||||
|
<Image src={qrCodeDataUrl} style={styles.qrCode} />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.contactRow}>
|
|
||||||
<Text style={styles.contactLabel}>Email</Text>
|
<View style={styles.footer}>
|
||||||
<Text style={styles.contactValue}>{person.email}</Text>
|
<Text style={styles.footerText}>KLZ Vertriebs GmbH</Text>
|
||||||
</View>
|
<Text style={styles.pipe}>|</Text>
|
||||||
<View style={styles.contactRow}>
|
<Text style={styles.footerText}>Raiffeisenstraße 22</Text>
|
||||||
<Text style={styles.contactLabel}>Web</Text>
|
<Text style={styles.pipe}>|</Text>
|
||||||
<Text style={styles.contactValue}>{person.website}</Text>
|
<Text style={styles.footerText}>73630 Remshalden</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.logoTextBlue}>KLZ</Text>
|
|
||||||
</Page>
|
</Page>
|
||||||
</Document>
|
</Document>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pino": "^10.3.0",
|
"pino": "^10.3.0",
|
||||||
"pino-pretty": "^13.1.3",
|
"pino-pretty": "^13.1.3",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"react-email": "^5.2.5",
|
"react-email": "^5.2.5",
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"@types/node": "^22.19.3",
|
"@types/node": "^22.19.3",
|
||||||
"@types/nodemailer": "^7.0.5",
|
"@types/nodemailer": "^7.0.5",
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/sharp": "^0.31.1",
|
"@types/sharp": "^0.31.1",
|
||||||
|
|||||||
43
pnpm-lock.yaml
generated
43
pnpm-lock.yaml
generated
@@ -84,6 +84,9 @@ importers:
|
|||||||
pino-pretty:
|
pino-pretty:
|
||||||
specifier: ^13.1.3
|
specifier: ^13.1.3
|
||||||
version: 13.1.3
|
version: 13.1.3
|
||||||
|
qrcode:
|
||||||
|
specifier: ^1.5.4
|
||||||
|
version: 1.5.4
|
||||||
react:
|
react:
|
||||||
specifier: ^19.2.4
|
specifier: ^19.2.4
|
||||||
version: 19.2.4
|
version: 19.2.4
|
||||||
@@ -163,6 +166,9 @@ importers:
|
|||||||
'@types/nodemailer':
|
'@types/nodemailer':
|
||||||
specifier: ^7.0.5
|
specifier: ^7.0.5
|
||||||
version: 7.0.11
|
version: 7.0.11
|
||||||
|
'@types/qrcode':
|
||||||
|
specifier: ^1.5.6
|
||||||
|
version: 1.5.6
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.2.7
|
specifier: ^19.2.7
|
||||||
version: 19.2.14
|
version: 19.2.14
|
||||||
@@ -2801,6 +2807,9 @@ packages:
|
|||||||
'@types/pg@8.15.6':
|
'@types/pg@8.15.6':
|
||||||
resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==}
|
resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==}
|
||||||
|
|
||||||
|
'@types/qrcode@1.5.6':
|
||||||
|
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
||||||
|
|
||||||
'@types/react-dom@19.2.3':
|
'@types/react-dom@19.2.3':
|
||||||
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3990,6 +3999,9 @@ packages:
|
|||||||
dfa@1.2.0:
|
dfa@1.2.0:
|
||||||
resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
|
resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
|
||||||
|
|
||||||
|
dijkstrajs@1.0.3:
|
||||||
|
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||||
|
|
||||||
doctrine@2.1.0:
|
doctrine@2.1.0:
|
||||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -6167,6 +6179,10 @@ packages:
|
|||||||
png-js@1.0.0:
|
png-js@1.0.0:
|
||||||
resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
|
resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
|
||||||
|
|
||||||
|
pngjs@5.0.0:
|
||||||
|
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
po-parser@2.1.1:
|
po-parser@2.1.1:
|
||||||
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
|
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
|
||||||
|
|
||||||
@@ -6286,6 +6302,11 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
qrcode@1.5.4:
|
||||||
|
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
qs@6.14.2:
|
qs@6.14.2:
|
||||||
resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
|
resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@@ -6964,8 +6985,8 @@ packages:
|
|||||||
third-party-web@0.26.7:
|
third-party-web@0.26.7:
|
||||||
resolution: {integrity: sha512-buUzX4sXC4efFX6xg2bw6/eZsCUh8qQwSavC4D9HpONMFlRbcHhD8Je5qwYdCpViR6q0qla2wPP+t91a2vgolg==}
|
resolution: {integrity: sha512-buUzX4sXC4efFX6xg2bw6/eZsCUh8qQwSavC4D9HpONMFlRbcHhD8Je5qwYdCpViR6q0qla2wPP+t91a2vgolg==}
|
||||||
|
|
||||||
third-party-web@0.29.0:
|
third-party-web@0.29.2:
|
||||||
resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==}
|
resolution: {integrity: sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==}
|
||||||
|
|
||||||
thread-stream@4.0.0:
|
thread-stream@4.0.0:
|
||||||
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
|
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
|
||||||
@@ -9121,7 +9142,7 @@ snapshots:
|
|||||||
'@paulirish/trace_engine@0.0.53':
|
'@paulirish/trace_engine@0.0.53':
|
||||||
dependencies:
|
dependencies:
|
||||||
legacy-javascript: 0.0.1
|
legacy-javascript: 0.0.1
|
||||||
third-party-web: 0.29.0
|
third-party-web: 0.29.2
|
||||||
|
|
||||||
'@pdf-lib/standard-fonts@1.0.0':
|
'@pdf-lib/standard-fonts@1.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10152,6 +10173,10 @@ snapshots:
|
|||||||
pg-protocol: 1.13.0
|
pg-protocol: 1.13.0
|
||||||
pg-types: 2.2.0
|
pg-types: 2.2.0
|
||||||
|
|
||||||
|
'@types/qrcode@1.5.6':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.19.15
|
||||||
|
|
||||||
'@types/react-dom@19.2.3(@types/react@19.2.14)':
|
'@types/react-dom@19.2.3(@types/react@19.2.14)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
@@ -11442,6 +11467,8 @@ snapshots:
|
|||||||
|
|
||||||
dfa@1.2.0: {}
|
dfa@1.2.0: {}
|
||||||
|
|
||||||
|
dijkstrajs@1.0.3: {}
|
||||||
|
|
||||||
doctrine@2.1.0:
|
doctrine@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
@@ -14186,6 +14213,8 @@ snapshots:
|
|||||||
|
|
||||||
png-js@1.0.0: {}
|
png-js@1.0.0: {}
|
||||||
|
|
||||||
|
pngjs@5.0.0: {}
|
||||||
|
|
||||||
po-parser@2.1.1: {}
|
po-parser@2.1.1: {}
|
||||||
|
|
||||||
possible-typed-array-names@1.1.0: {}
|
possible-typed-array-names@1.1.0: {}
|
||||||
@@ -14317,6 +14346,12 @@ snapshots:
|
|||||||
- typescript
|
- typescript
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
|
qrcode@1.5.4:
|
||||||
|
dependencies:
|
||||||
|
dijkstrajs: 1.0.3
|
||||||
|
pngjs: 5.0.0
|
||||||
|
yargs: 15.4.1
|
||||||
|
|
||||||
qs@6.14.2:
|
qs@6.14.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
@@ -15196,7 +15231,7 @@ snapshots:
|
|||||||
|
|
||||||
third-party-web@0.26.7: {}
|
third-party-web@0.26.7: {}
|
||||||
|
|
||||||
third-party-web@0.29.0: {}
|
third-party-web@0.29.2: {}
|
||||||
|
|
||||||
thread-stream@4.0.0:
|
thread-stream@4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
BIN
public/logo-blue.png
Normal file
BIN
public/logo-blue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
public/logo-white.png
Normal file
BIN
public/logo-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
BIN
public/truck.png
Normal file
BIN
public/truck.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 MiB |
@@ -9,45 +9,34 @@ import * as React from 'react';
|
|||||||
import { renderToBuffer } from '@react-pdf/renderer';
|
import { renderToBuffer } from '@react-pdf/renderer';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { PDFBusinessCard, PersonData } from '../lib/pdf-business-card';
|
import { PDFBusinessCard, PersonData } from '../lib/pdf-business-card';
|
||||||
|
import QRCode from 'qrcode';
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
outputDir: path.join(process.cwd(), 'public/downloads/business-cards'),
|
outputDir: path.join(process.cwd(), 'public/downloads/business-cards'),
|
||||||
|
logoWhite: path.join(process.cwd(), 'public/logo-white.png'),
|
||||||
|
logoBlue: path.join(process.cwd(), 'public/logo-blue.png'),
|
||||||
|
logoFull: path.join(process.cwd(), 'public/logo-full.png'),
|
||||||
|
truckImage: path.join(process.cwd(), 'public/truck.png'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const PEOPLE: PersonData[] = [
|
const PEOPLE: PersonData[] = [
|
||||||
{
|
{
|
||||||
name: 'Michael Bodemer',
|
name: 'Michael Bodemer',
|
||||||
position: 'Owner & Managing Director',
|
role: 'Managing Director',
|
||||||
phone: '+49 151 462 467 98',
|
phone: '+49 171 785 2420',
|
||||||
email: 'michael.bodemer@klz-cables.com',
|
email: 'michael.bodemer@klz-cables.com',
|
||||||
website: 'www.klz-cables.com',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Klaus Mintel',
|
name: 'Klaus Mintel',
|
||||||
position: 'Managing Director',
|
role: 'Managing Director',
|
||||||
phone: '+49 151 1775 2873',
|
phone: '+49 151 1775 2873',
|
||||||
email: 'klaus.mintel@klz-cables.com',
|
email: 'klaus.mintel@klz-cables.com',
|
||||||
website: 'www.klz-cables.com',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
async function ensureOutputDir() {
|
|
||||||
if (!fs.existsSync(CONFIG.outputDir)) {
|
|
||||||
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Konvertiert ein RGB PDF zu CMYK via Ghostscript.
|
|
||||||
*/
|
|
||||||
function convertToCMYK(inputPath: string, outputPath: string) {
|
function convertToCMYK(inputPath: string, outputPath: string) {
|
||||||
console.log(`- Converting to CMYK via Ghostscript...`);
|
console.log(`- Converting to CMYK via Ghostscript...`);
|
||||||
try {
|
try {
|
||||||
// Basic CMYK conversion via Ghostscript.
|
|
||||||
// -dSAFER -dBATCH -dNOPAUSE
|
|
||||||
// -sDEVICE=pdfwrite
|
|
||||||
// -sColorConversionStrategy=CMYK
|
|
||||||
// -dProcessColorModel=/DeviceCMYK
|
|
||||||
const gsCommand = `gs -dSAFER -dBATCH -dNOPAUSE -dNOCACHE -sDEVICE=pdfwrite -sColorConversionStrategy=CMYK -dProcessColorModel=/DeviceCMYK -dAutoFilterColorImages=false -dColorImageFilter=/FlateEncode -sOutputFile="${outputPath}" "${inputPath}"`;
|
const gsCommand = `gs -dSAFER -dBATCH -dNOPAUSE -dNOCACHE -sDEVICE=pdfwrite -sColorConversionStrategy=CMYK -dProcessColorModel=/DeviceCMYK -dAutoFilterColorImages=false -dColorImageFilter=/FlateEncode -sOutputFile="${outputPath}" "${inputPath}"`;
|
||||||
|
|
||||||
execSync(gsCommand, { stdio: 'pipe' });
|
execSync(gsCommand, { stdio: 'pipe' });
|
||||||
@@ -59,11 +48,33 @@ function convertToCMYK(inputPath: string, outputPath: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function generateBusinessCards() {
|
async function generateBusinessCards() {
|
||||||
await ensureOutputDir();
|
if (!fs.existsSync(CONFIG.outputDir)) {
|
||||||
|
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
for (const person of PEOPLE) {
|
for (const person of PEOPLE) {
|
||||||
console.log(`\nGenerating Business Card for: ${person.name}`);
|
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, {
|
||||||
|
width: 400,
|
||||||
|
margin: 0,
|
||||||
|
color: { dark: '#000000', light: '#ffffff' },
|
||||||
|
});
|
||||||
|
|
||||||
const safeName = person.name.replace(/\s+/g, '_');
|
const safeName = person.name.replace(/\s+/g, '_');
|
||||||
const rgbFileName = `KLZ_BusinessCard_${safeName}_RGB.pdf`;
|
const rgbFileName = `KLZ_BusinessCard_${safeName}_RGB.pdf`;
|
||||||
const cmykFileName = `KLZ_BusinessCard_${safeName}_CMYK_Print.pdf`;
|
const cmykFileName = `KLZ_BusinessCard_${safeName}_CMYK_Print.pdf`;
|
||||||
@@ -75,6 +86,11 @@ async function generateBusinessCards() {
|
|||||||
const buffer = await renderToBuffer(
|
const buffer = await renderToBuffer(
|
||||||
React.createElement(PDFBusinessCard, {
|
React.createElement(PDFBusinessCard, {
|
||||||
person,
|
person,
|
||||||
|
logoWhitePath: CONFIG.logoWhite,
|
||||||
|
logoBluePath: CONFIG.logoBlue,
|
||||||
|
logoFullPath: CONFIG.logoFull,
|
||||||
|
truckImagePath: CONFIG.truckImage,
|
||||||
|
qrCodeDataUrl,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -93,7 +109,7 @@ async function main() {
|
|||||||
await generateBusinessCards();
|
await generateBusinessCards();
|
||||||
console.log(`\n✅ Finished in ${((Date.now() - start) / 1000).toFixed(2)}s`);
|
console.log(`\n✅ Finished in ${((Date.now() - start) / 1000).toFixed(2)}s`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('\\n❌ Fatal error:', error);
|
console.error('\n❌ Fatal error:', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user