Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76cbdaa37e | ||
|
|
5b1a2eff7f | ||
|
|
60422da644 | ||
|
|
fc6da4e9aa | ||
|
|
f7411d3dc4 | ||
|
|
7be2ec605c | ||
|
|
bd00175912 | ||
|
|
9c9663eb88 | ||
|
|
402b5c30c6 | ||
|
|
2bb0381de4 | ||
|
|
614b0a5ffd | ||
|
|
b1569a1ce2 | ||
|
|
2c35c6b8aa | ||
|
|
bff150b1a7 |
@@ -28,3 +28,4 @@ backups/
|
|||||||
|
|
||||||
# Local data / backups
|
# Local data / backups
|
||||||
.data/
|
.data/
|
||||||
|
scratch/
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getPageBySlug } from '@/lib/pages';
|
import { getPageBySlug } from '@/lib/pages';
|
||||||
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
|
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
|
||||||
|
|
||||||
export const size = OG_IMAGE_SIZE;
|
export const size = OG_IMAGE_SIZE;
|
||||||
export const contentType = 'image/png';
|
export const contentType = 'image/png';
|
||||||
@@ -20,9 +20,12 @@ export default async function Image({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fonts = await getOgFonts();
|
const fonts = await getOgFonts();
|
||||||
|
const { logo, bgImage } = getOgAssets();
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<OGImageTemplate
|
<OGImageTemplate
|
||||||
|
image={bgImage}
|
||||||
|
logo={logo}
|
||||||
title={pageData.frontmatter.title}
|
title={pageData.frontmatter.title}
|
||||||
description={pageData.frontmatter.excerpt}
|
description={pageData.frontmatter.excerpt}
|
||||||
label="Information"
|
label="Information"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getPostBySlug } from '@/lib/blog';
|
import { getPostBySlug } from '@/lib/blog';
|
||||||
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
|
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
|
||||||
import { SITE_URL } from '@/lib/schema';
|
import { SITE_URL } from '@/lib/schema';
|
||||||
|
|
||||||
export const size = OG_IMAGE_SIZE;
|
export const size = OG_IMAGE_SIZE;
|
||||||
@@ -35,6 +35,7 @@ export default async function Image({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fonts = await getOgFonts();
|
const fonts = await getOgFonts();
|
||||||
|
const { logo, bgImage } = getOgAssets();
|
||||||
|
|
||||||
// We don't have request.url here, but we can assume the domain from SITE_URL or config
|
// We don't have request.url here, but we can assume the domain from SITE_URL or config
|
||||||
// For local images during dev, relative paths in <img> might not work in Satori
|
// For local images during dev, relative paths in <img> might not work in Satori
|
||||||
@@ -55,10 +56,11 @@ export default async function Image({
|
|||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<OGImageTemplate
|
<OGImageTemplate
|
||||||
|
logo={logo}
|
||||||
title={post.frontmatter.title}
|
title={post.frontmatter.title}
|
||||||
description={post.frontmatter.excerpt}
|
description={post.frontmatter.excerpt}
|
||||||
label={post.frontmatter.category || 'Blog'}
|
label={post.frontmatter.category || 'Blog'}
|
||||||
image={base64Image || featuredImage}
|
image={base64Image || featuredImage || bgImage}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
...OG_IMAGE_SIZE,
|
...OG_IMAGE_SIZE,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
|
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
|
||||||
|
|
||||||
export const size = OG_IMAGE_SIZE;
|
export const size = OG_IMAGE_SIZE;
|
||||||
export const contentType = 'image/png';
|
export const contentType = 'image/png';
|
||||||
@@ -11,9 +11,16 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
|
|||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
const t = await getTranslations({ locale, namespace: 'Blog.meta' });
|
const t = await getTranslations({ locale, namespace: 'Blog.meta' });
|
||||||
const fonts = await getOgFonts();
|
const fonts = await getOgFonts();
|
||||||
|
const { logo, bgImage } = getOgAssets();
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<OGImageTemplate title={t('title')} description={t('description')} label="Blog" />,
|
<OGImageTemplate
|
||||||
|
image={bgImage}
|
||||||
|
logo={logo}
|
||||||
|
title={t('title')}
|
||||||
|
description={t('description')}
|
||||||
|
label="Blog"
|
||||||
|
/>,
|
||||||
{
|
{
|
||||||
...OG_IMAGE_SIZE,
|
...OG_IMAGE_SIZE,
|
||||||
fonts,
|
fonts,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
|
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
|
||||||
|
|
||||||
export const size = OG_IMAGE_SIZE;
|
export const size = OG_IMAGE_SIZE;
|
||||||
export const contentType = 'image/png';
|
export const contentType = 'image/png';
|
||||||
@@ -11,12 +11,19 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
|
|||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
const t = await getTranslations({ locale, namespace: 'Contact' });
|
const t = await getTranslations({ locale, namespace: 'Contact' });
|
||||||
const fonts = await getOgFonts();
|
const fonts = await getOgFonts();
|
||||||
|
const { logo, bgImage } = getOgAssets();
|
||||||
|
|
||||||
const title = t('meta.title') || t('title');
|
const title = t('meta.title') || t('title');
|
||||||
const description = t('meta.description') || t('subtitle');
|
const description = t('meta.description') || t('subtitle');
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<OGImageTemplate title={title} description={description} label="Contact" />,
|
<OGImageTemplate
|
||||||
|
image={bgImage}
|
||||||
|
logo={logo}
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
label="Contact"
|
||||||
|
/>,
|
||||||
{
|
{
|
||||||
...OG_IMAGE_SIZE,
|
...OG_IMAGE_SIZE,
|
||||||
fonts,
|
fonts,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
|
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
|
||||||
|
|
||||||
export const size = OG_IMAGE_SIZE;
|
export const size = OG_IMAGE_SIZE;
|
||||||
export const contentType = 'image/png';
|
export const contentType = 'image/png';
|
||||||
@@ -11,9 +11,12 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
|
|||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
const t = await getTranslations({ locale, namespace: 'Index.meta' });
|
const t = await getTranslations({ locale, namespace: 'Index.meta' });
|
||||||
const fonts = await getOgFonts();
|
const fonts = await getOgFonts();
|
||||||
|
const { logo, bgImage } = getOgAssets();
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<OGImageTemplate
|
<OGImageTemplate
|
||||||
|
image={bgImage}
|
||||||
|
logo={logo}
|
||||||
title={t('title')}
|
title={t('title')}
|
||||||
description={t('description')}
|
description={t('description')}
|
||||||
label="Reliable Energy Infrastructure"
|
label="Reliable Energy Infrastructure"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
|
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
|
||||||
|
|
||||||
export const size = OG_IMAGE_SIZE;
|
export const size = OG_IMAGE_SIZE;
|
||||||
export const contentType = 'image/png';
|
export const contentType = 'image/png';
|
||||||
@@ -11,6 +11,7 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
|
|||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
const t = await getTranslations({ locale, namespace: 'Products' });
|
const t = await getTranslations({ locale, namespace: 'Products' });
|
||||||
const fonts = await getOgFonts();
|
const fonts = await getOgFonts();
|
||||||
|
const { logo, bgImage } = getOgAssets();
|
||||||
|
|
||||||
const title = t.has('meta.title')
|
const title = t.has('meta.title')
|
||||||
? t('meta.title')
|
? t('meta.title')
|
||||||
@@ -20,7 +21,13 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
|
|||||||
const description = t('meta.description') || t('subtitle');
|
const description = t('meta.description') || t('subtitle');
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<OGImageTemplate title={title} description={description} label="Products" />,
|
<OGImageTemplate
|
||||||
|
image={bgImage}
|
||||||
|
logo={logo}
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
label="Products"
|
||||||
|
/>,
|
||||||
{
|
{
|
||||||
...OG_IMAGE_SIZE,
|
...OG_IMAGE_SIZE,
|
||||||
fonts,
|
fonts,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
|
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
|
||||||
|
|
||||||
export const size = OG_IMAGE_SIZE;
|
export const size = OG_IMAGE_SIZE;
|
||||||
export const contentType = 'image/png';
|
export const contentType = 'image/png';
|
||||||
@@ -11,12 +11,19 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
|
|||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
const t = await getTranslations({ locale, namespace: 'Team' });
|
const t = await getTranslations({ locale, namespace: 'Team' });
|
||||||
const fonts = await getOgFonts();
|
const fonts = await getOgFonts();
|
||||||
|
const { logo, bgImage } = getOgAssets();
|
||||||
|
|
||||||
const title = t('meta.title') || t('hero.subtitle');
|
const title = t('meta.title') || t('hero.subtitle');
|
||||||
const description = t('meta.description') || t('hero.title');
|
const description = t('meta.description') || t('hero.title');
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<OGImageTemplate title={title} description={description} label="Our Team" />,
|
<OGImageTemplate
|
||||||
|
image={bgImage}
|
||||||
|
logo={logo}
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
label="Our Team"
|
||||||
|
/>,
|
||||||
{
|
{
|
||||||
...OG_IMAGE_SIZE,
|
...OG_IMAGE_SIZE,
|
||||||
fonts,
|
fonts,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export async function sendContactFormAction(formData: FormData) {
|
|||||||
productName,
|
productName,
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
fs.appendFileSync(backupFile, JSON.stringify(leadData) + '\\n');
|
fs.appendFileSync(backupFile, JSON.stringify(leadData) + '\n');
|
||||||
logger.info('Successfully saved lead to local backup file', { backupFile });
|
logger.info('Successfully saved lead to local backup file', { backupFile });
|
||||||
} catch (backupError) {
|
} catch (backupError) {
|
||||||
logger.error('Failed to write to local leads backup', { error: String(backupError) });
|
logger.error('Failed to write to local leads backup', { error: String(backupError) });
|
||||||
@@ -156,7 +156,7 @@ export async function sendContactFormAction(formData: FormData) {
|
|||||||
|
|
||||||
// Notify via Gotify (Internal)
|
// Notify via Gotify (Internal)
|
||||||
await services.notifications.notify({
|
await services.notifications.notify({
|
||||||
title: `📩 ${notificationSubject}`,
|
title: `📩 [KLZ] ${notificationSubject}`,
|
||||||
message: `New message from ${name} (${email}):\n\n${message}`,
|
message: `New message from ${name} (${email}):\n\n${message}`,
|
||||||
priority: 5,
|
priority: 5,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import VideoSection from './home/VideoSection';
|
|||||||
import CTA from './home/CTA';
|
import CTA from './home/CTA';
|
||||||
import { AgbHistoryBlock } from './AgbHistoryBlock';
|
import { AgbHistoryBlock } from './AgbHistoryBlock';
|
||||||
import { PDFDownloadBlock } from './PDFDownloadBlock';
|
import { PDFDownloadBlock } from './PDFDownloadBlock';
|
||||||
|
import ProductTechnicalData from './ProductTechnicalData';
|
||||||
function ContactSection(props: any) {
|
function ContactSection(props: any) {
|
||||||
return (
|
return (
|
||||||
<div className="p-8 border-2 border-dashed border-primary my-8 text-center text-primary font-bold">
|
<div className="p-8 border-2 border-dashed border-primary my-8 text-center text-primary font-bold">
|
||||||
@@ -100,6 +101,8 @@ async function Block(props: any) {
|
|||||||
return <AgbHistoryBlock {...data} />;
|
return <AgbHistoryBlock {...data} />;
|
||||||
case 'pdfDownload':
|
case 'pdfDownload':
|
||||||
return <PDFDownloadBlock {...data} />;
|
return <PDFDownloadBlock {...data} />;
|
||||||
|
case 'productTabs':
|
||||||
|
return <ProductTechnicalData data={data} />;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ interface OGImageTemplateProps {
|
|||||||
description?: string;
|
description?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
logo?: string;
|
||||||
mode?: 'dark' | 'light' | 'image';
|
mode?: 'dark' | 'light' | 'image';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ export function OGImageTemplate({
|
|||||||
description,
|
description,
|
||||||
label,
|
label,
|
||||||
image,
|
image,
|
||||||
|
logo,
|
||||||
mode = 'dark',
|
mode = 'dark',
|
||||||
}: OGImageTemplateProps) {
|
}: OGImageTemplateProps) {
|
||||||
const primaryBlue = '#001a4d';
|
const primaryBlue = '#001a4d';
|
||||||
@@ -152,21 +154,26 @@ export function OGImageTemplate({
|
|||||||
height: '6px',
|
height: '6px',
|
||||||
backgroundColor: accentGreen,
|
backgroundColor: accentGreen,
|
||||||
borderRadius: '3px',
|
borderRadius: '3px',
|
||||||
marginRight: '24px',
|
marginRight: '32px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div
|
{logo ? (
|
||||||
style={{
|
/* eslint-disable-next-line @next/next/no-img-element */
|
||||||
fontSize: '24px',
|
<img src={logo} alt="KLZ Cables" height="40" style={{ objectFit: 'contain' }} />
|
||||||
fontWeight: 700,
|
) : (
|
||||||
color: 'white',
|
<div
|
||||||
textTransform: 'uppercase',
|
style={{
|
||||||
letterSpacing: '0.15em',
|
fontSize: '24px',
|
||||||
display: 'flex',
|
fontWeight: 700,
|
||||||
}}
|
color: 'white',
|
||||||
>
|
textTransform: 'uppercase',
|
||||||
KLZ Cables
|
letterSpacing: '0.15em',
|
||||||
</div>
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
KLZ Cables
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Saturated Blue Brand Strip */}
|
{/* Saturated Blue Brand Strip */}
|
||||||
|
|||||||
+106
-106
File diff suppressed because one or more lines are too long
+105
-105
File diff suppressed because one or more lines are too long
+107
-107
File diff suppressed because one or more lines are too long
+106
-106
File diff suppressed because one or more lines are too long
+106
-106
File diff suppressed because one or more lines are too long
+2
-5
@@ -65,11 +65,8 @@ export async function getPostBySlug(slug: string, locale: string): Promise<PostD
|
|||||||
const fileContent = await fs.readFile(filePath, 'utf-8');
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
||||||
const { data, content } = matter(fileContent);
|
const { data, content } = matter(fileContent);
|
||||||
|
|
||||||
// Fix MDX data props dropped by next-mdx-remote
|
const { fixMdxDataProps } = await import('./mdx-utils');
|
||||||
// Payload serializes as data={{"items":...}} which Acorn treats as a Block statement.
|
const fixedContent = fixMdxDataProps(content);
|
||||||
const fixedContent = content.replace(/data=\{\{([\s\S]*?)\}\}/g, (match, p1) => {
|
|
||||||
return 'data="{' + p1.replace(/"/g, '"') + '}"';
|
|
||||||
});
|
|
||||||
|
|
||||||
let parsedContent = fixedContent;
|
let parsedContent = fixedContent;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
export function fixMdxDataProps(content: string): string {
|
||||||
|
let fixedContent = content;
|
||||||
|
let dataIndex = fixedContent.indexOf('data={{');
|
||||||
|
|
||||||
|
while (dataIndex !== -1) {
|
||||||
|
let openCount = 0;
|
||||||
|
let endIndex = -1;
|
||||||
|
const startObj = dataIndex + 6; // index of the first '{' in 'data={{'
|
||||||
|
|
||||||
|
let inString = false;
|
||||||
|
let isEscaped = false;
|
||||||
|
|
||||||
|
for (let i = startObj; i < fixedContent.length; i++) {
|
||||||
|
const char = fixedContent[i];
|
||||||
|
|
||||||
|
if (isEscaped) {
|
||||||
|
isEscaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char === '\\') {
|
||||||
|
isEscaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char === '"') {
|
||||||
|
inString = !inString;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inString) {
|
||||||
|
if (char === '{') {
|
||||||
|
openCount++;
|
||||||
|
} else if (char === '}') {
|
||||||
|
openCount--;
|
||||||
|
if (openCount === 0) {
|
||||||
|
endIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endIndex !== -1) {
|
||||||
|
// jsonStr is the content INSIDE the outer curly braces
|
||||||
|
const jsonStr = fixedContent.substring(startObj + 1, endIndex);
|
||||||
|
const safeJsonStr = jsonStr.replace(/"/g, '"');
|
||||||
|
const replacement = `data="{${safeJsonStr}}"`;
|
||||||
|
|
||||||
|
// We also need to consume the closing `}` of the `data={{`
|
||||||
|
const nextCharIndex = endIndex + 1;
|
||||||
|
const skipChars =
|
||||||
|
nextCharIndex < fixedContent.length && fixedContent[nextCharIndex] === '}' ? 2 : 1;
|
||||||
|
|
||||||
|
fixedContent =
|
||||||
|
fixedContent.substring(0, dataIndex) +
|
||||||
|
replacement +
|
||||||
|
fixedContent.substring(endIndex + skipChars);
|
||||||
|
|
||||||
|
dataIndex = fixedContent.indexOf('data={{', dataIndex + replacement.length);
|
||||||
|
} else {
|
||||||
|
// Malformed braces, prevent infinite loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixedContent;
|
||||||
|
}
|
||||||
@@ -55,3 +55,36 @@ export const OG_IMAGE_SIZE = {
|
|||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a local image and converts it to a base64 data URI for Satori
|
||||||
|
*/
|
||||||
|
export function getLocalImageBase64(filename: string): string | undefined {
|
||||||
|
try {
|
||||||
|
const filePath = join(process.cwd(), 'public', filename);
|
||||||
|
const buffer = readFileSync(filePath);
|
||||||
|
const ext = filename.split('.').pop()?.toLowerCase();
|
||||||
|
const mimeType =
|
||||||
|
ext === 'png'
|
||||||
|
? 'image/png'
|
||||||
|
: ext === 'jpg' || ext === 'jpeg'
|
||||||
|
? 'image/jpeg'
|
||||||
|
: 'image/svg+xml';
|
||||||
|
return `data:${mimeType};base64,${buffer.toString('base64')}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[OG] Failed to load local image ${filename}:`, error);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns standard assets for OG images
|
||||||
|
*/
|
||||||
|
export function getOgAssets() {
|
||||||
|
return {
|
||||||
|
logo: getLocalImageBase64('logo-white.png'),
|
||||||
|
bgImage: getLocalImageBase64(
|
||||||
|
'media/aerial-view-of-electricity-station-surrounded-with-2023-11-27-05-33-40-utc-scaled.jpg',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
+2
-5
@@ -53,11 +53,8 @@ export async function getPageBySlug(slug: string, locale: string): Promise<PageD
|
|||||||
const fileContent = await fs.readFile(filePath, 'utf-8');
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
||||||
const { data, content } = matter(fileContent);
|
const { data, content } = matter(fileContent);
|
||||||
|
|
||||||
// Fix MDX data props dropped by next-mdx-remote
|
const { fixMdxDataProps } = await import('./mdx-utils');
|
||||||
// Payload serializes as data={{"items":...}} which Acorn treats as a Block statement.
|
const fixedContent = fixMdxDataProps(content);
|
||||||
const fixedContent = content.replace(/data=\{\{([\s\S]*?)\}\}/g, (match, p1) => {
|
|
||||||
return 'data="{' + p1.replace(/"/g, '"') + '}"';
|
|
||||||
});
|
|
||||||
|
|
||||||
let parsedContent = fixedContent;
|
let parsedContent = fixedContent;
|
||||||
try {
|
try {
|
||||||
|
|||||||
+2
-5
@@ -39,11 +39,8 @@ export async function getProductBySlug(slug: string, locale: string): Promise<Pr
|
|||||||
const fileContent = await fs.readFile(filePath, 'utf-8');
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
||||||
const { data, content } = matter(fileContent);
|
const { data, content } = matter(fileContent);
|
||||||
|
|
||||||
// Fix MDX data props dropped by next-mdx-remote
|
const { fixMdxDataProps } = await import('./mdx-utils');
|
||||||
// Payload serializes as data={{"items":...}} which Acorn treats as a Block statement.
|
const fixedContent = fixMdxDataProps(content);
|
||||||
const fixedContent = content.replace(/data=\{\{([\s\S]*?)\}\}/g, (match, p1) => {
|
|
||||||
return 'data="{' + p1.replace(/"/g, '"') + '}"';
|
|
||||||
});
|
|
||||||
|
|
||||||
let parsedContent = fixedContent;
|
let parsedContent = fixedContent;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ export class GlitchtipErrorReportingService implements ErrorReportingService {
|
|||||||
tracesSampleRate: this.options.tracesSampleRate ?? 0.1,
|
tracesSampleRate: this.options.tracesSampleRate ?? 0.1,
|
||||||
replaysOnErrorSampleRate: 1.0,
|
replaysOnErrorSampleRate: 1.0,
|
||||||
replaysSessionSampleRate: 0.1,
|
replaysSessionSampleRate: 0.1,
|
||||||
|
ignoreErrors: [
|
||||||
|
'ChunkLoadError',
|
||||||
|
'Failed to fetch dynamically imported module',
|
||||||
|
'Failed to find Server Action',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Sentry;
|
return Sentry;
|
||||||
|
|||||||
+1
-1
@@ -116,7 +116,7 @@
|
|||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"preinstall": "npx only-allow pnpm"
|
"preinstall": "npx only-allow pnpm"
|
||||||
},
|
},
|
||||||
"version": "2.3.28",
|
"version": "2.3.32",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@parcel/watcher",
|
"@parcel/watcher",
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -12,4 +12,5 @@ Sentry.init({
|
|||||||
|
|
||||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||||
debug: false,
|
debug: false,
|
||||||
|
ignoreErrors: ['failed to pipe response'],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ Sentry.init({
|
|||||||
|
|
||||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||||
debug: false,
|
debug: false,
|
||||||
|
ignoreErrors: ['failed to pipe response'],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { GlitchtipErrorReportingService } from '../lib/services/errors/glitchtip-error-reporting-service';
|
||||||
|
|
||||||
|
// Mock the LoggerService
|
||||||
|
const mockLogger = {
|
||||||
|
child: vi.fn().mockReturnThis(),
|
||||||
|
info: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
warn: vi.fn(),
|
||||||
|
debug: vi.fn(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const mockSentryInit = vi.fn();
|
||||||
|
|
||||||
|
vi.mock('@sentry/nextjs', () => ({
|
||||||
|
init: (...args: any[]) => mockSentryInit(...args),
|
||||||
|
captureException: vi.fn(),
|
||||||
|
captureMessage: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('GlitchtipErrorReportingService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore version drift errors (ChunkLoadError and Server Action) in Sentry config', async () => {
|
||||||
|
// Simulate window to force client-side init behavior instantly
|
||||||
|
const originalWindow = global.window;
|
||||||
|
global.window = {
|
||||||
|
requestIdleCallback: (cb: Function) => cb(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const service = new GlitchtipErrorReportingService({ enabled: true }, mockLogger);
|
||||||
|
|
||||||
|
// Give the dynamic import a moment to resolve
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
expect(mockSentryInit).toHaveBeenCalledOnce();
|
||||||
|
const initCallArgs = mockSentryInit.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(initCallArgs.ignoreErrors).toBeDefined();
|
||||||
|
expect(initCallArgs.ignoreErrors).toContain('ChunkLoadError');
|
||||||
|
expect(initCallArgs.ignoreErrors).toContain('Failed to find Server Action');
|
||||||
|
|
||||||
|
global.window = originalWindow;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
describe('MDXContent Mapping', () => {
|
||||||
|
it('should have productTabs in the switch statement to prevent Unknown Block Type errors', () => {
|
||||||
|
const filePath = path.join(process.cwd(), 'components/MDXContent.tsx');
|
||||||
|
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
|
||||||
|
// Check if the switch case for productTabs exists
|
||||||
|
expect(fileContent).toContain("case 'productTabs':");
|
||||||
|
expect(fileContent).toContain('<ProductTechnicalData');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { fixMdxDataProps } from '../lib/mdx-utils';
|
||||||
|
|
||||||
|
describe('MDX Data Props Fixer', () => {
|
||||||
|
it('should correctly parse MDX with nested JSON containing }}', () => {
|
||||||
|
// This string simulates the exact structure that causes the bug in n2x2y.mdx
|
||||||
|
const mdxInput = `<Block type="productTabs" data={{"content":{"root":{"children":[]}},"id":"123"}} />`;
|
||||||
|
|
||||||
|
const result = fixMdxDataProps(mdxInput);
|
||||||
|
|
||||||
|
const expected = `<Block type="productTabs" data="{"content":{"root":{"children":[]}},"id":"123"}" />`;
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not prematurely truncate if } appears inside a string literal', () => {
|
||||||
|
const mdxInput = `<Block type="productTabs" data={{"content":{"text":"}>"}}} />`;
|
||||||
|
const result = fixMdxDataProps(mdxInput);
|
||||||
|
const expected = `<Block type="productTabs" data="{"content":{"text":"}>"}}" />`;
|
||||||
|
expect(result).toBe(expected.replace('>', '>'));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user