fix(pdf): fix missing logos and product images in datasheets, update pipeline
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Successful in 2m20s
Build & Deploy / 🏗️ Build (push) Successful in 3m56s
Build & Deploy / 🚀 Deploy (push) Successful in 22s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 4m30s
Build & Deploy / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-03-08 01:55:52 +01:00
parent 7583540de2
commit d575e5924a
58 changed files with 67 additions and 25 deletions

View File

@@ -197,6 +197,7 @@ interface ProductData {
applicationHtml?: string; applicationHtml?: string;
images?: string[]; images?: string[];
featuredImage?: string | null; featuredImage?: string | null;
logoDataUrl?: string | null;
categories?: Array<{ name: string }>; categories?: Array<{ name: string }>;
attributes?: Array<{ name: string; options: string[] }>; attributes?: Array<{ name: string; options: string[] }>;
} }
@@ -204,7 +205,7 @@ interface ProductData {
export interface PDFDatasheetProps { export interface PDFDatasheetProps {
product: ProductData; product: ProductData;
locale: 'en' | 'de'; locale: 'en' | 'de';
logoUrl?: string; logoDataUrl?: string | null;
technicalItems?: KeyValueItem[]; technicalItems?: KeyValueItem[];
voltageTables?: DatasheetVoltageTable[]; voltageTables?: DatasheetVoltageTable[];
legendItems?: KeyValueItem[]; legendItems?: KeyValueItem[];
@@ -485,8 +486,15 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
<Page size="A4" style={styles.page}> <Page size="A4" style={styles.page}>
<View style={styles.hero}> <View style={styles.hero}>
<View style={styles.header}> <View style={styles.header}>
<View> <View style={{ width: 80 }}>
<Text style={styles.logoText}>KLZ</Text> {product.logoDataUrl || (product as any).logoDataUrl ? (
<Image
src={product.logoDataUrl || (product as any).logoDataUrl}
style={{ width: '100%', objectFit: 'contain' }}
/>
) : (
<Text style={styles.logoText}>KLZ</Text>
)}
</View> </View>
<Text style={styles.docTitle}>{labels.productDatasheet}</Text> <Text style={styles.docTitle}>{labels.productDatasheet}</Text>
</View> </View>
@@ -586,7 +594,16 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
</View> </View>
<View style={styles.footer} fixed> <View style={styles.footer} fixed>
<Text style={styles.footerBrand}>KLZ CABLES</Text> <View style={{ width: 60 }}>
{product.logoDataUrl || (product as any).logoDataUrl ? (
<Image
src={product.logoDataUrl || (product as any).logoDataUrl}
style={{ width: '100%', objectFit: 'contain' }}
/>
) : (
<Text style={styles.footerBrand}>KLZ CABLES</Text>
)}
</View>
<Text style={styles.footerText}> <Text style={styles.footerText}>
{new Date().toLocaleDateString(locale === 'en' ? 'en-US' : 'de-DE', { {new Date().toLocaleDateString(locale === 'en' ? 'en-US' : 'de-DE', {
year: 'numeric', year: 'numeric',

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -15,6 +15,7 @@ SOURCE_ENV="${1:-}" # local | testing | staging | prod
TARGET_ENV="${2:-}" # testing | staging | prod TARGET_ENV="${2:-}" # testing | staging | prod
SSH_HOST="root@alpha.mintel.me" SSH_HOST="root@alpha.mintel.me"
LOCAL_MEDIA_DIR="./public/media" LOCAL_MEDIA_DIR="./public/media"
LOCAL_DATASHEETS_DIR="./public/datasheets"
DRY_RUN="" DRY_RUN=""
CHECKSUM="" CHECKSUM=""
@@ -38,6 +39,16 @@ get_media_path() {
esac esac
} }
get_datasheets_path() {
case "$1" in
local) echo "$LOCAL_DATASHEETS_DIR" ;;
testing) echo "/home/deploy/sites/testing.klz-cables.com/public/datasheets" ;;
staging) echo "/home/deploy/sites/staging.klz-cables.com/public/datasheets" ;;
prod|production) echo "/home/deploy/sites/klz-cables.com/public/datasheets" ;;
*) echo "❌ Unknown environment: $1"; exit 1 ;;
esac
}
get_app_container() { get_app_container() {
case "$1" in case "$1" in
testing) echo "klz-testing-klz-app-1" ;; testing) echo "klz-testing-klz-app-1" ;;
@@ -52,35 +63,39 @@ TGT_PATH=$(get_media_path "$TARGET_ENV")
TGT_CONTAINER=$(get_app_container "$TARGET_ENV") TGT_CONTAINER=$(get_app_container "$TARGET_ENV")
echo "🚀 Syncing assets: $SOURCE_ENV$TARGET_ENV" echo "🚀 Syncing assets: $SOURCE_ENV$TARGET_ENV"
echo "📂 Source: $SRC_PATH"
echo "📂 Target: $TGT_PATH"
# ── Execution ──────────────────────────────────────────────────────────────
if [[ ! -d "$SRC_PATH" ]] && [[ "$SOURCE_ENV" == "local" ]]; then
echo "❌ Source directory does not exist: $SRC_PATH"
exit 1
fi
# ── Media Sync ─────────────────────────────────────────────────────────────
echo "🖼️ Syncing Media..."
if [[ "$SOURCE_ENV" == "local" ]]; then if [[ "$SOURCE_ENV" == "local" ]]; then
# Local → Remote
echo "📡 Running rsync..."
rsync -avzi $CHECKSUM --delete --progress $DRY_RUN "$SRC_PATH/" "$SSH_HOST:$TGT_PATH/" rsync -avzi $CHECKSUM --delete --progress $DRY_RUN "$SRC_PATH/" "$SSH_HOST:$TGT_PATH/"
elif [[ "$TARGET_ENV" == "local" ]]; then elif [[ "$TARGET_ENV" == "local" ]]; then
# Remote → Local
mkdir -p "$LOCAL_MEDIA_DIR" mkdir -p "$LOCAL_MEDIA_DIR"
echo "📡 Running rsync..."
rsync -avzi $CHECKSUM --delete --progress $DRY_RUN "$SSH_HOST:$SRC_PATH/" "$TGT_PATH/" rsync -avzi $CHECKSUM --delete --progress $DRY_RUN "$SSH_HOST:$SRC_PATH/" "$TGT_PATH/"
else else
# Remote → Remote (e.g., testing → staging)
echo "📡 Running remote rsync..."
ssh "$SSH_HOST" "rsync -avzi $CHECKSUM --delete --progress $DRY_RUN $SRC_PATH/ $TGT_PATH/" ssh "$SSH_HOST" "rsync -avzi $CHECKSUM --delete --progress $DRY_RUN $SRC_PATH/ $TGT_PATH/"
fi fi
# ── Datasheets Sync ────────────────────────────────────────────────────────
echo "📄 Syncing Datasheets..."
SRC_DS_PATH=$(get_datasheets_path "$SOURCE_ENV")
TGT_DS_PATH=$(get_datasheets_path "$TARGET_ENV")
if [[ "$SOURCE_ENV" == "local" ]]; then
ssh "$SSH_HOST" "mkdir -p $TGT_DS_PATH"
rsync -avzi $CHECKSUM --delete --progress $DRY_RUN "$SRC_DS_PATH/" "$SSH_HOST:$TGT_DS_PATH/"
elif [[ "$TARGET_ENV" == "local" ]]; then
mkdir -p "$LOCAL_DATASHEETS_DIR"
rsync -avzi $CHECKSUM --delete --progress $DRY_RUN "$SSH_HOST:$SRC_DS_PATH/" "$TGT_DS_PATH/"
else
ssh "$SSH_HOST" "mkdir -p $TGT_DS_PATH && rsync -avzi $CHECKSUM --delete --progress $DRY_RUN $SRC_DS_PATH/ $TGT_DS_PATH/"
fi
# Fix ownership on remote target if it's not local # Fix ownership on remote target if it's not local
if [[ "$TARGET_ENV" != "local" && -z "$DRY_RUN" ]]; then if [[ "$TARGET_ENV" != "local" && -z "$DRY_RUN" ]]; then
echo "🔑 Fixing media file permissions on $TARGET_ENV..." echo "🔑 Fixing media file permissions on $TARGET_ENV..."
ssh "$SSH_HOST" "docker exec -u 0 $TGT_CONTAINER chown -R 1001:65533 /app/public/media/ 2>/dev/null || true" ssh "$SSH_HOST" "docker exec -u 0 $TGT_CONTAINER chown -R 1001:65533 /app/public/media/ 2>/dev/null || true"
echo "🔑 Fixing datasheet permissions..."
ssh "$SSH_HOST" "chown -R 1001:1001 $TGT_DS_PATH 2>/dev/null || true"
fi fi
echo "✅ Asset sync complete!" echo "✅ Asset sync complete!"

View File

@@ -268,7 +268,10 @@ async function processChunk(
} }
// Load assets as Data URLs for React-PDF // Load assets as Data URLs for React-PDF
const heroDataUrl = await loadImageAsPngDataUrl(model.product.heroSrc); const [heroDataUrl, logoDataUrl] = await Promise.all([
loadImageAsPngDataUrl(model.product.heroSrc),
loadImageAsPngDataUrl('/logo-black.svg'),
]);
const fileName = generateFileName(product, locale); const fileName = generateFileName(product, locale);
const voltageType = (product as any).voltageType || 'other'; const voltageType = (product as any).voltageType || 'other';
@@ -281,14 +284,18 @@ async function processChunk(
// Render using the unified component // Render using the unified component
const element = ( const element = (
<PDFDatasheet <PDFDatasheet
product={{ product={
...model.product, {
featuredImage: heroDataUrl, ...model.product,
}} featuredImage: heroDataUrl,
logoDataUrl,
} as any
}
locale={locale} locale={locale}
technicalItems={model.technicalItems} technicalItems={model.technicalItems}
voltageTables={model.voltageTables} voltageTables={model.voltageTables}
legendItems={model.legendItems} legendItems={model.legendItems}
logoDataUrl={logoDataUrl}
/> />
); );

View File

@@ -147,7 +147,10 @@ function compactCellForDenseTable(
function resolveMediaToLocalPath(urlOrPath: string | null | undefined): string | null { function resolveMediaToLocalPath(urlOrPath: string | null | undefined): string | null {
if (!urlOrPath) return null; if (!urlOrPath) return null;
if (urlOrPath.startsWith('/')) return urlOrPath; if (urlOrPath.startsWith('/')) {
// Handle Payload API URL prefix: /api/media/file/filename.ext -> /media/filename.ext
return urlOrPath.replace(/^\/api\/media\/file\//, '/media/');
}
if (/^media\//i.test(urlOrPath)) return `/${urlOrPath}`; if (/^media\//i.test(urlOrPath)) return `/${urlOrPath}`;
const mapped = ASSET_MAP[urlOrPath]; const mapped = ASSET_MAP[urlOrPath];
if (mapped) { if (mapped) {