Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f7a985074 | |||
| bfccd57849 |
@@ -102,6 +102,8 @@ jobs:
|
|||||||
echo "directus_url=https://cms.$PRIMARY_HOST"
|
echo "directus_url=https://cms.$PRIMARY_HOST"
|
||||||
if [[ "$TARGET" == "production" ]]; then
|
if [[ "$TARGET" == "production" ]]; then
|
||||||
echo "project_name=klz-cablescom"
|
echo "project_name=klz-cablescom"
|
||||||
|
elif [[ "$TARGET" == "branch" ]]; then
|
||||||
|
echo "project_name=$PRJ-branch-$SLUG"
|
||||||
else
|
else
|
||||||
echo "project_name=$PRJ-$TARGET"
|
echo "project_name=$PRJ-$TARGET"
|
||||||
fi
|
fi
|
||||||
@@ -354,7 +356,13 @@ jobs:
|
|||||||
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
|
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
# Transfer and Restart
|
# Transfer and Restart
|
||||||
SITE_DIR="/home/deploy/sites/klz-cables.com"
|
if [[ "$TARGET" == "production" ]]; then
|
||||||
|
SITE_DIR="/home/deploy/sites/klz-cables.com"
|
||||||
|
elif [[ "$TARGET" == "testing" ]]; then
|
||||||
|
SITE_DIR="/home/deploy/sites/testing.klz-cables.com"
|
||||||
|
else
|
||||||
|
SITE_DIR="/home/deploy/sites/branch.klz-cables.com/${SLUG:-unknown}"
|
||||||
|
fi
|
||||||
ssh root@alpha.mintel.me "mkdir -p $SITE_DIR/directus/schema $SITE_DIR/directus/uploads $SITE_DIR/directus/extensions"
|
ssh root@alpha.mintel.me "mkdir -p $SITE_DIR/directus/schema $SITE_DIR/directus/uploads $SITE_DIR/directus/extensions"
|
||||||
|
|
||||||
scp .env.deploy root@alpha.mintel.me:$SITE_DIR/$ENV_FILE
|
scp .env.deploy root@alpha.mintel.me:$SITE_DIR/$ENV_FILE
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { mapFileSlugToTranslated, mapSlugToFileSlug } from '@/lib/slugs';
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||||
import { getProductOGImageMetadata } from '@/lib/metadata';
|
import { getProductOGImageMetadata } from '@/lib/metadata';
|
||||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
@@ -103,70 +103,70 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const components = {
|
// const components = {
|
||||||
ProductTechnicalData,
|
// ProductTechnicalData,
|
||||||
ProductTabs,
|
// ProductTabs,
|
||||||
p: (props: any) => (
|
// p: (props: any) => (
|
||||||
<p
|
// <p
|
||||||
{...props}
|
// {...props}
|
||||||
className="text-lg md:text-xl text-text-secondary leading-relaxed mb-8 font-medium"
|
// className="text-lg md:text-xl text-text-secondary leading-relaxed mb-8 font-medium"
|
||||||
/>
|
// />
|
||||||
),
|
// ),
|
||||||
h2: (props: any) => (
|
// h2: (props: any) => (
|
||||||
<div className="relative mb-16">
|
// <div className="relative mb-16">
|
||||||
<h2
|
// <h2
|
||||||
{...props}
|
// {...props}
|
||||||
className="text-3xl md:text-4xl font-black text-primary tracking-tighter uppercase mb-6"
|
// className="text-3xl md:text-4xl font-black text-primary tracking-tighter uppercase mb-6"
|
||||||
/>
|
// />
|
||||||
<div className="w-20 h-1.5 bg-accent rounded-full" />
|
// <div className="w-20 h-1.5 bg-accent rounded-full" />
|
||||||
</div>
|
// </div>
|
||||||
),
|
// ),
|
||||||
h3: (props: any) => (
|
// h3: (props: any) => (
|
||||||
<h3
|
// <h3
|
||||||
{...props}
|
// {...props}
|
||||||
className="text-2xl md:text-3xl font-black text-primary mb-10 tracking-tight uppercase"
|
// className="text-2xl md:text-3xl font-black text-primary mb-10 tracking-tight uppercase"
|
||||||
/>
|
// />
|
||||||
),
|
// ),
|
||||||
ul: (props: any) => <ul {...props} className="list-none pl-0 mb-10" />,
|
// ul: (props: any) => <ul {...props} className="list-none pl-0 mb-10" />,
|
||||||
section: (props: any) => <div {...props} className="block" />,
|
// section: (props: any) => <div {...props} className="block" />,
|
||||||
li: (props: any) => (
|
// li: (props: any) => (
|
||||||
<li className="flex items-start gap-4 group mb-4 last:mb-0">
|
// <li className="flex items-start gap-4 group mb-4 last:mb-0">
|
||||||
<div className="mt-2.5 w-2 h-2 rounded-full bg-accent flex-shrink-0 group-hover:scale-125 transition-transform" />
|
// <div className="mt-2.5 w-2 h-2 rounded-full bg-accent flex-shrink-0 group-hover:scale-125 transition-transform" />
|
||||||
<span
|
// <span
|
||||||
{...props}
|
// {...props}
|
||||||
className="text-lg md:text-xl text-text-secondary leading-relaxed font-medium"
|
// className="text-lg md:text-xl text-text-secondary leading-relaxed font-medium"
|
||||||
/>
|
// />
|
||||||
</li>
|
// </li>
|
||||||
),
|
// ),
|
||||||
strong: (props: any) => <strong {...props} className="font-black text-primary" />,
|
// strong: (props: any) => <strong {...props} className="font-black text-primary" />,
|
||||||
table: (props: any) => (
|
// table: (props: any) => (
|
||||||
<div className="overflow-x-auto my-20 rounded-[32px] border border-neutral-dark/10 shadow-xl bg-white p-1">
|
// <div className="overflow-x-auto my-20 rounded-[32px] border border-neutral-dark/10 shadow-xl bg-white p-1">
|
||||||
<table {...props} className="min-w-full divide-y divide-neutral-dark/10" />
|
// <table {...props} className="min-w-full divide-y divide-neutral-dark/10" />
|
||||||
</div>
|
// </div>
|
||||||
),
|
// ),
|
||||||
th: (props: any) => (
|
// th: (props: any) => (
|
||||||
<th
|
// <th
|
||||||
{...props}
|
// {...props}
|
||||||
className="px-8 py-6 bg-neutral-light/50 text-left text-[10px] font-black uppercase tracking-[0.25em] text-primary/60"
|
// className="px-8 py-6 bg-neutral-light/50 text-left text-[10px] font-black uppercase tracking-[0.25em] text-primary/60"
|
||||||
/>
|
// />
|
||||||
),
|
// ),
|
||||||
td: (props: any) => (
|
// td: (props: any) => (
|
||||||
<td
|
// <td
|
||||||
{...props}
|
// {...props}
|
||||||
className="px-8 py-6 text-text-secondary border-t border-neutral-dark/5 text-lg md:text-xl font-medium"
|
// className="px-8 py-6 text-text-secondary border-t border-neutral-dark/5 text-lg md:text-xl font-medium"
|
||||||
/>
|
// />
|
||||||
),
|
// ),
|
||||||
hr: () => <hr className="my-24 border-t-2 border-neutral-dark/5" />,
|
// hr: () => <hr className="my-24 border-t-2 border-neutral-dark/5" />,
|
||||||
blockquote: (props: any) => (
|
// blockquote: (props: any) => (
|
||||||
<div className="my-20 p-10 md:p-16 bg-primary-dark rounded-[40px] relative overflow-hidden group">
|
// <div className="my-20 p-10 md:p-16 bg-primary-dark rounded-[40px] relative overflow-hidden group">
|
||||||
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl group-hover:bg-accent/20 transition-colors duration-700" />
|
// <div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl group-hover:bg-accent/20 transition-colors duration-700" />
|
||||||
<div
|
// <div
|
||||||
className="relative z-10 italic text-2xl md:text-3xl text-white/90 leading-relaxed font-black tracking-tight"
|
// className="relative z-10 italic text-2xl md:text-3xl text-white/90 leading-relaxed font-black tracking-tight"
|
||||||
{...props}
|
// {...props}
|
||||||
/>
|
// />
|
||||||
</div>
|
// </div>
|
||||||
),
|
// ),
|
||||||
};
|
// };
|
||||||
|
|
||||||
export default async function ProductPage({ params }: ProductPageProps) {
|
export default async function ProductPage({ params }: ProductPageProps) {
|
||||||
const { locale, slug } = await params;
|
const { locale, slug } = await params;
|
||||||
@@ -307,20 +307,25 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract technical data for schema
|
// Extract technical data from MDX content (bypass MDXRemote which crashes on complex JSX-in-MDX)
|
||||||
const technicalDataMatch = product.content.match(
|
const technicalDataMatch = product.content.match(
|
||||||
/technicalData=\{<ProductTechnicalData data=\{(.*?)\}\s*\/>\}/s,
|
/technicalData=\{<ProductTechnicalData data=\{(.*?)\}\s*\/>\}/s,
|
||||||
);
|
);
|
||||||
let technicalItems = [];
|
let technicalData: any = null;
|
||||||
|
let technicalItems: any[] = [];
|
||||||
if (technicalDataMatch) {
|
if (technicalDataMatch) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(technicalDataMatch[1]);
|
technicalData = JSON.parse(technicalDataMatch[1]);
|
||||||
technicalItems = data.technicalItems || [];
|
technicalItems = technicalData.technicalItems || [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse technical data for schema', e);
|
console.error('Failed to parse technical data', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract section HTML content
|
||||||
|
const sectionMatch = product.content.match(/<section>([\s\S]*?)<\/section>/);
|
||||||
|
const sectionHtml = sectionMatch ? sectionMatch[1] : '';
|
||||||
|
|
||||||
const datasheetPath = getDatasheetPath(productSlug, locale);
|
const datasheetPath = getDatasheetPath(productSlug, locale);
|
||||||
const isFallback = (product.frontmatter as any).isFallback;
|
const isFallback = (product.frontmatter as any).isFallback;
|
||||||
const categorySlug = slug[0];
|
const categorySlug = slug[0];
|
||||||
@@ -340,22 +345,6 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const productComponents = {
|
|
||||||
...components,
|
|
||||||
ProductTabs: (props: any) => <ProductTabs {...props} sidebar={sidebar} />,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pre-process content to convert raw HTML tags to Markdown so they use our custom components
|
|
||||||
const processedContent = product.content
|
|
||||||
.replace(/<h2[^>]*>(.*?)<\/h2>/g, '\n## $1\n')
|
|
||||||
.replace(/<h3[^>]*>(.*?)<\/h3>/g, '\n### $1\n')
|
|
||||||
.replace(/<p[^>]*>(.*?)<\/p>/g, '\n$1\n')
|
|
||||||
.replace(/<ul[^>]*>(.*?)<\/ul>/gs, '\n$1\n')
|
|
||||||
.replace(/<li[^>]*>(.*?)<\/li>/g, '\n- $1\n')
|
|
||||||
.replace(/<strong[^>]*>(.*?)<\/strong>/g, '**$1**')
|
|
||||||
.replace(/<section[^>]*>/g, '')
|
|
||||||
.replace(/<\/section>/g, '');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen bg-white relative">
|
<div className="flex flex-col min-h-screen bg-white relative">
|
||||||
{/* Product Hero */}
|
{/* Product Hero */}
|
||||||
@@ -465,9 +454,25 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
|||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area - Direct rendering (bypasses MDXRemote crash) */}
|
||||||
<div className="max-w-none">
|
<div className="max-w-none">
|
||||||
<MDXRemote source={processedContent} components={productComponents} />
|
<ProductTabs
|
||||||
|
technicalData={
|
||||||
|
technicalData ? <ProductTechnicalData data={technicalData} /> : null
|
||||||
|
}
|
||||||
|
sidebar={sidebar}
|
||||||
|
>
|
||||||
|
{sectionHtml && (
|
||||||
|
<div
|
||||||
|
className="prose prose-lg max-w-none
|
||||||
|
[&_h2]:text-3xl [&_h2]:md:text-4xl [&_h2]:font-black [&_h2]:text-primary [&_h2]:tracking-tighter [&_h2]:uppercase [&_h2]:mb-6
|
||||||
|
[&_h3]:text-2xl [&_h3]:md:text-3xl [&_h3]:font-black [&_h3]:text-primary [&_h3]:mb-10 [&_h3]:tracking-tight [&_h3]:uppercase
|
||||||
|
[&_p]:text-lg [&_p]:md:text-xl [&_p]:text-text-secondary [&_p]:leading-relaxed [&_p]:mb-8 [&_p]:font-medium
|
||||||
|
[&_strong]:font-black [&_strong]:text-primary"
|
||||||
|
dangerouslySetInnerHTML={{ __html: sectionHtml }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ProductTabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Datasheet Download Section - Only for Medium Voltage for now */}
|
{/* Datasheet Download Section - Only for Medium Voltage for now */}
|
||||||
|
|||||||
@@ -45,7 +45,11 @@ export default function middleware(request: NextRequest) {
|
|||||||
const hostHeader =
|
const hostHeader =
|
||||||
headers.get('x-forwarded-host') || headers.get('host') || 'testing.klz-cables.com';
|
headers.get('x-forwarded-host') || headers.get('host') || 'testing.klz-cables.com';
|
||||||
|
|
||||||
|
const [publicHostname] = hostHeader.split(':');
|
||||||
|
|
||||||
urlObj.protocol = proto;
|
urlObj.protocol = proto;
|
||||||
|
urlObj.hostname = publicHostname;
|
||||||
|
urlObj.port = ''; // Explicitly clear internal port (3000)
|
||||||
|
|
||||||
effectiveRequest = new NextRequest(urlObj, {
|
effectiveRequest = new NextRequest(urlObj, {
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
@@ -95,8 +99,7 @@ export default function middleware(request: NextRequest) {
|
|||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: [
|
||||||
'/((?!api|_next/static|_next/image|_img|favicon.ico|manifest.webmanifest|.*\\.(?:svg|png|jpg|jpeg|gif|webp|pdf|txt|vcf|xml|webm|mp4|map)$).*)',
|
'/((?!api|_next/static|_next/image|_img|favicon.ico|admin|manifest.webmanifest|.*\\.(?:svg|png|jpg|jpeg|gif|webp|pdf|txt|vcf|xml|webm|mp4|map)$).*)',
|
||||||
'/(de|en)/:path*',
|
|
||||||
'/(de|en)/:path*',
|
'/(de|en)/:path*',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docker network create infra 2>/dev/null || true && echo '\\n🚀 Development Environment Starting...\\n\\n📱 App (Next.js): http://localhost:3000\\n📱 App (Traefik): http://klz.localhost\\n🗄️ CMS: http://cms.klz.localhost/admin\\n🚦 Traefik: http://localhost:8080\\n\\n(Press Ctrl+C to stop)\\n' && docker-compose down --remove-orphans && docker-compose up --build klz-app klz-cms klz-db klz-gatekeeper",
|
"dev": "docker network create infra 2>/dev/null || true && echo '\\n🚀 Development Environment Starting...\\n\\n📱 App (Next.js): http://localhost:3000\\n📱 App (Traefik): http://klz.localhost\\n🗄️ CMS: http://cms.klz.localhost/admin\\n🚦 Traefik: http://localhost:8080\\n\\n(Press Ctrl+C to stop)\\n' && docker-compose down --remove-orphans && docker-compose up --build klz-app klz-cms klz-db klz-gatekeeper",
|
||||||
"dev:infra": "docker network create infra 2>/dev/null || true && docker-compose up -d klz-cms klz-db klz-gatekeeper",
|
"dev:infra": "docker network create infra 2>/dev/null || true && docker-compose up -d klz-cms klz-db klz-gatekeeper",
|
||||||
"dev:local": "next dev",
|
"dev:local": "next dev --no-turbo",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
|||||||
Reference in New Issue
Block a user