feat: auto-opening brochure modal with mintel/mail delivery
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m53s
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

- implemented BrochureDeliveryEmail template

- created AutoBrochureModal wrapper with 5s delay

- updated layout.tsx and BrochureCTA to use new success state

- added tests/brochure-modal.test.ts e2e test
This commit is contained in:
2026-03-02 23:08:05 +01:00
parent d2418b5720
commit 02be8e59b2
62 changed files with 4474 additions and 975 deletions

View File

@@ -1,13 +1,5 @@
import * as React from 'react';
import {
Document,
Page,
View,
Text,
Image,
StyleSheet,
Font,
} from '@react-pdf/renderer';
import { Document, Page, View, Text, Image, StyleSheet, Font } from '@react-pdf/renderer';
// Register fonts (using system fonts for now, can be customized)
Font.register({
@@ -18,27 +10,43 @@ Font.register({
],
});
// Industrial/technical/restrained design - STYLEGUIDE.md compliant
// ─── Brand Tokens (matching brochure) ────────────────────────────────────────
const C = {
navy: '#001a4d',
navyDeep: '#000d26',
green: '#4da612',
greenLight: '#e8f5d8',
white: '#FFFFFF',
offWhite: '#f8f9fa',
gray100: '#f3f4f6',
gray200: '#e5e7eb',
gray300: '#d1d5db',
gray400: '#9ca3af',
gray600: '#4b5563',
gray900: '#111827',
};
const MARGIN = 56;
const styles = StyleSheet.create({
page: {
color: '#111827', // Text Primary
color: C.gray900,
lineHeight: 1.5,
backgroundColor: '#FFFFFF',
backgroundColor: C.white,
paddingTop: 0,
paddingBottom: 100,
paddingBottom: 80,
fontFamily: 'Helvetica',
},
// Hero-style header
hero: {
backgroundColor: '#FFFFFF',
backgroundColor: C.white,
paddingTop: 24,
paddingBottom: 0,
paddingHorizontal: 72,
paddingHorizontal: MARGIN,
marginBottom: 20,
position: 'relative',
borderBottomWidth: 0,
borderBottomColor: '#e5e7eb',
},
header: {
@@ -49,17 +57,17 @@ const styles = StyleSheet.create({
},
logoText: {
fontSize: 24,
fontSize: 22,
fontWeight: 700,
color: '#000d26',
letterSpacing: 1,
color: C.navyDeep,
letterSpacing: 2,
textTransform: 'uppercase',
},
docTitle: {
fontSize: 10,
fontSize: 8,
fontWeight: 700,
color: '#001a4d',
color: C.green,
letterSpacing: 2,
textTransform: 'uppercase',
},
@@ -78,10 +86,10 @@ const styles = StyleSheet.create({
height: 120,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
borderRadius: 4,
borderWidth: 1,
borderColor: '#e5e7eb',
backgroundColor: '#FFFFFF',
borderColor: C.gray200,
backgroundColor: C.white,
overflow: 'hidden',
},
@@ -93,7 +101,7 @@ const styles = StyleSheet.create({
productName: {
fontSize: 24,
fontWeight: 700,
color: '#000d26',
color: C.navyDeep,
marginBottom: 0,
textTransform: 'uppercase',
letterSpacing: -0.5,
@@ -101,7 +109,7 @@ const styles = StyleSheet.create({
productMeta: {
fontSize: 10,
color: '#4b5563',
color: C.gray600,
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: 1,
@@ -115,13 +123,13 @@ const styles = StyleSheet.create({
noImage: {
fontSize: 8,
color: '#9ca3af',
color: C.gray400,
textAlign: 'center',
},
// Content Area
content: {
paddingHorizontal: 72,
paddingHorizontal: MARGIN,
},
// Content sections
@@ -130,40 +138,40 @@ const styles = StyleSheet.create({
},
sectionTitle: {
fontSize: 14,
fontSize: 8,
fontWeight: 700,
color: '#000d26', // Primary Dark
marginBottom: 8,
color: C.green,
marginBottom: 6,
textTransform: 'uppercase',
letterSpacing: -0.2,
letterSpacing: 1.5,
},
sectionAccent: {
width: 30,
height: 3,
backgroundColor: '#82ed20', // Accent Green
height: 2,
backgroundColor: C.green,
marginBottom: 8,
borderRadius: 1.5,
borderRadius: 1,
},
description: {
fontSize: 11,
fontSize: 10,
lineHeight: 1.7,
color: '#4b5563', // Text Secondary
color: C.gray600,
},
// Technical data table
specsTable: {
marginTop: 8,
border: '1px solid #e5e7eb',
borderRadius: 8,
marginTop: 4,
borderWidth: 0,
borderRadius: 0,
overflow: 'hidden',
},
specsTableRow: {
flexDirection: 'row',
borderBottomWidth: 1,
borderBottomColor: '#e5e7eb',
borderBottomWidth: 0.5,
borderBottomColor: C.gray200,
},
specsTableRowLast: {
@@ -172,83 +180,85 @@ const styles = StyleSheet.create({
specsTableLabelCell: {
flex: 1,
paddingVertical: 4,
paddingHorizontal: 16,
backgroundColor: '#f8f9fa',
borderRightWidth: 1,
borderRightColor: '#e5e7eb',
paddingVertical: 5,
paddingHorizontal: 12,
backgroundColor: C.offWhite,
borderRightWidth: 0.5,
borderRightColor: C.gray200,
},
specsTableValueCell: {
flex: 1,
paddingVertical: 4,
paddingHorizontal: 16,
paddingVertical: 5,
paddingHorizontal: 12,
},
specsTableLabelText: {
fontSize: 9,
fontSize: 8,
fontWeight: 700,
color: '#000d26',
color: C.navyDeep,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
specsTableValueText: {
fontSize: 10,
color: '#111827',
fontWeight: 500,
fontSize: 9,
color: C.gray900,
fontWeight: 400,
},
// Categories
categories: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
gap: 6,
},
categoryTag: {
backgroundColor: '#f8f9fa',
paddingHorizontal: 12,
paddingVertical: 6,
border: '1px solid #e5e7eb',
borderRadius: 100,
backgroundColor: C.offWhite,
paddingHorizontal: 10,
paddingVertical: 4,
borderWidth: 0.5,
borderColor: C.gray200,
borderRadius: 3,
},
categoryText: {
fontSize: 8,
color: '#4b5563',
fontSize: 7,
color: C.gray600,
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
// Footer
// Footer — matches brochure style
footer: {
position: 'absolute',
bottom: 40,
left: 72,
right: 72,
bottom: 28,
left: MARGIN,
right: MARGIN,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: 24,
borderTop: '1px solid #e5e7eb',
paddingTop: 12,
borderTopWidth: 2,
borderTopColor: C.green,
},
footerText: {
fontSize: 8,
color: '#9ca3af',
fontWeight: 500,
fontSize: 7,
color: C.gray400,
fontWeight: 400,
textTransform: 'uppercase',
letterSpacing: 1,
letterSpacing: 0.8,
},
footerBrand: {
fontSize: 10,
fontSize: 9,
fontWeight: 700,
color: '#000d26',
color: C.navyDeep,
textTransform: 'uppercase',
letterSpacing: 1,
letterSpacing: 1.5,
},
});
@@ -302,10 +312,7 @@ const getLabels = (locale: 'en' | 'de') => {
return labels[locale];
};
export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
product,
locale,
}) => {
export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({ product, locale }) => {
const labels = getLabels(locale);
return (
@@ -317,9 +324,7 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
<View>
<Text style={styles.logoText}>KLZ</Text>
</View>
<Text style={styles.docTitle}>
{labels.productDatasheet}
</Text>
<Text style={styles.docTitle}>{labels.productDatasheet}</Text>
</View>
<View style={styles.productRow}>
@@ -328,7 +333,8 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
<View style={styles.categories}>
{product.categories.map((cat, index) => (
<Text key={index} style={styles.productMeta}>
{cat.name}{index < product.categories.length - 1 ? ' • ' : ''}
{cat.name}
{index < product.categories.length - 1 ? ' • ' : ''}
</Text>
))}
</View>
@@ -337,12 +343,8 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
</View>
<View style={styles.productImageCol}>
{product.featuredImage ? (
<Image
src={product.featuredImage}
style={styles.heroImage}
/>
<Image src={product.featuredImage} style={styles.heroImage} />
) : (
<Text style={styles.noImage}>{labels.noImage}</Text>
)}
</View>
@@ -356,7 +358,11 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
<Text style={styles.sectionTitle}>{labels.description}</Text>
<View style={styles.sectionAccent} />
<Text style={styles.description}>
{stripHtml(product.applicationHtml || product.shortDescriptionHtml || product.descriptionHtml)}
{stripHtml(
product.applicationHtml ||
product.shortDescriptionHtml ||
product.descriptionHtml,
)}
</Text>
</View>
)}
@@ -372,17 +378,14 @@ export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
key={index}
style={[
styles.specsTableRow,
index === product.attributes.length - 1 &&
styles.specsTableRowLast,
index === product.attributes.length - 1 && styles.specsTableRowLast,
]}
>
<View style={styles.specsTableLabelCell}>
<Text style={styles.specsTableLabelText}>{attr.name}</Text>
</View>
<View style={styles.specsTableValueCell}>
<Text style={styles.specsTableValueText}>
{attr.options.join(', ')}
</Text>
<Text style={styles.specsTableValueText}>{attr.options.join(', ')}</Text>
</View>
</View>
))}