chore(ci/routing): merge v1.1.7 fixes into main (routing fix & pipeline optimizations)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 29s
Build & Deploy / 🏗️ Build (push) Successful in 2m52s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / ♿ WCAG (push) Has been cancelled
Build & Deploy / 🛡️ Quality Gates (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 29s
Build & Deploy / 🏗️ Build (push) Successful in 2m52s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / ♿ WCAG (push) Has been cancelled
Build & Deploy / 🛡️ Quality Gates (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
This commit is contained in:
@@ -34,6 +34,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: 🔐 Configure Private Registry
|
||||
run: |
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
run: |
|
||||
echo "Purging old build layers and dangling images..."
|
||||
docker image prune -f
|
||||
docker builder prune -f --filter "until=6h"
|
||||
docker builder prune -f --filter "until=24h"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -165,6 +165,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc
|
||||
@@ -193,7 +194,7 @@ jobs:
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
build:
|
||||
name: 🏗️ Build
|
||||
needs: [prepare, qa]
|
||||
needs: [prepare]
|
||||
if: needs.prepare.outputs.target != 'skip'
|
||||
runs-on: docker
|
||||
container:
|
||||
@@ -392,7 +393,7 @@ jobs:
|
||||
name: 🧪 Smoke Test
|
||||
needs: [prepare, deploy]
|
||||
continue-on-error: true
|
||||
if: needs.deploy.result == 'success' && github.ref_type != 'tag'
|
||||
if: needs.deploy.result == 'success' && needs.prepare.outputs.target != 'branch'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -425,7 +426,7 @@ jobs:
|
||||
name: ⚡ Lighthouse
|
||||
needs: [prepare, deploy]
|
||||
continue-on-error: true
|
||||
if: success() && needs.prepare.outputs.target != 'skip' && github.ref_type != 'tag'
|
||||
if: success() && needs.prepare.outputs.target != 'skip' && needs.prepare.outputs.target != 'branch'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -440,6 +441,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc
|
||||
@@ -500,7 +502,7 @@ jobs:
|
||||
name: ♿ WCAG
|
||||
needs: [prepare, deploy, smoke_test]
|
||||
continue-on-error: true
|
||||
if: success() && needs.prepare.outputs.target != 'skip' && github.ref_type != 'tag'
|
||||
if: success() && needs.prepare.outputs.target != 'skip' && needs.prepare.outputs.target != 'branch'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -522,6 +524,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc
|
||||
@@ -582,7 +585,7 @@ jobs:
|
||||
name: 🛡️ Quality Gates
|
||||
needs: [prepare, deploy, smoke_test]
|
||||
continue-on-error: true
|
||||
if: success() && needs.prepare.outputs.target != 'skip' && github.ref_type != 'tag'
|
||||
if: success() && needs.prepare.outputs.target != 'skip' && needs.prepare.outputs.target != 'branch'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -597,6 +600,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc
|
||||
@@ -630,6 +634,11 @@ jobs:
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: 🔔 Gotify
|
||||
run: |
|
||||
STATUS="${{ needs.deploy.result }}"
|
||||
|
||||
@@ -53,11 +53,11 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
||||
title: categoryTitle,
|
||||
description: categoryDesc,
|
||||
alternates: {
|
||||
canonical: `${SITE_URL}/${locale}/products/${productSlug}`,
|
||||
canonical: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${productSlug}`,
|
||||
languages: {
|
||||
de: `${SITE_URL}/de/products/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
||||
en: `${SITE_URL}/en/products/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
'x-default': `${SITE_URL}/en/products/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
de: `${SITE_URL}/de/${await mapFileSlugToTranslated('products', 'de')}/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
||||
en: `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
'x-default': `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
@@ -81,11 +81,11 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
||||
title: product.frontmatter.title,
|
||||
description: product.frontmatter.description,
|
||||
alternates: {
|
||||
canonical: `${SITE_URL}/${locale}/products/${slug.join('/')}`,
|
||||
canonical: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
|
||||
languages: {
|
||||
de: `${SITE_URL}/de/products/${await mapFileSlugToTranslated(slug[0], 'de')}/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
||||
en: `${SITE_URL}/en/products/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
'x-default': `${SITE_URL}/en/products/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
de: `${SITE_URL}/de/${await mapFileSlugToTranslated('products', 'de')}/${await mapFileSlugToTranslated(slug[0], 'de')}/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
||||
en: `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
'x-default': `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
@@ -179,6 +179,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
setRequestLocale(locale);
|
||||
const productSlug = slug[slug.length - 1];
|
||||
const t = await getTranslations('Products');
|
||||
const productsSlug = await mapFileSlugToTranslated('products', locale);
|
||||
|
||||
// Check if it's a category page
|
||||
const categories = [
|
||||
@@ -220,7 +221,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
<div className="max-w-4xl animate-slide-up">
|
||||
<nav className="flex items-center mb-8 text-white/40 text-sm font-bold uppercase tracking-widest">
|
||||
<Link
|
||||
href={`/${locale}/${await mapFileSlugToTranslated('products', locale)}`}
|
||||
href={`/${locale}/${productsSlug}`}
|
||||
className="hover:text-accent transition-colors"
|
||||
>
|
||||
{t.has('breadcrumb') ? t('breadcrumb') : 'Products'}
|
||||
@@ -242,7 +243,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
{productsWithTranslatedSlugs.map((product) => (
|
||||
<Link
|
||||
key={product.slug}
|
||||
href={`/${locale}/products/${productSlug}/${product.translatedSlug}`}
|
||||
href={`/${locale}/${productsSlug}/${productSlug}/${product.translatedSlug}`}
|
||||
className="group block bg-white rounded-[32px] overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-500 border border-neutral-dark/5"
|
||||
>
|
||||
<Card tag="article" className="premium-card-reset">
|
||||
@@ -381,14 +382,14 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
<div className="max-w-4xl animate-slide-up">
|
||||
<nav className="flex items-center mb-12 text-white/40 text-[10px] font-black uppercase tracking-[0.2em]">
|
||||
<Link
|
||||
href={`/${locale}/${await mapFileSlugToTranslated('products', locale)}`}
|
||||
href={`/${locale}/${productsSlug}`}
|
||||
className="hover:text-accent transition-colors"
|
||||
>
|
||||
{t.has('breadcrumb') ? t('breadcrumb') : 'Products'}
|
||||
</Link>
|
||||
<span className="mx-4 opacity-20">/</span>
|
||||
<Link
|
||||
href={`/${locale}/products/${categorySlug}`}
|
||||
href={`/${locale}/${productsSlug}/${categorySlug}`}
|
||||
className="hover:text-accent transition-colors"
|
||||
>
|
||||
{categoryTitle}
|
||||
@@ -511,7 +512,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
'@type': 'Offer',
|
||||
availability: 'https://schema.org/InStock',
|
||||
priceCurrency: 'EUR',
|
||||
url: `${SITE_URL}/${locale}/products/${slug.join('/')}`,
|
||||
url: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
|
||||
itemCondition: 'https://schema.org/NewCondition',
|
||||
},
|
||||
additionalProperty: technicalItems.map((item: any) => ({
|
||||
@@ -522,7 +523,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
category: product.frontmatter.categories.join(', '),
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': `${SITE_URL}/${locale}/products/${slug.join('/')}`,
|
||||
'@id': `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
|
||||
},
|
||||
} as any
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getAllProducts } from '@/lib/mdx';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
import { RelatedProductLink } from './RelatedProductLink';
|
||||
import { mapFileSlugToTranslated } from '@/lib/slugs';
|
||||
|
||||
interface RelatedProductsProps {
|
||||
currentSlug: string;
|
||||
@@ -16,6 +17,7 @@ export default async function RelatedProducts({
|
||||
}: RelatedProductsProps) {
|
||||
const products = await getAllProducts(locale);
|
||||
const t = await getTranslations('Products');
|
||||
const productsSlug = await mapFileSlugToTranslated('products', locale);
|
||||
|
||||
// Filter products: same category, not current product
|
||||
const related = products
|
||||
@@ -27,6 +29,34 @@ export default async function RelatedProducts({
|
||||
|
||||
if (related.length === 0) return null;
|
||||
|
||||
// Pre-calculate translated slugs for related products
|
||||
const relatedWithTranslatedSlugs = await Promise.all(
|
||||
related.map(async (product) => {
|
||||
// Find the category slug for the link
|
||||
const categorySlugs = [
|
||||
'low-voltage-cables',
|
||||
'medium-voltage-cables',
|
||||
'high-voltage-cables',
|
||||
'solar-cables',
|
||||
];
|
||||
const catFileSlug =
|
||||
categorySlugs.find((slug) => {
|
||||
const key = slug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
const title = t(`categories.${key}.title`);
|
||||
return product.frontmatter.categories.some(
|
||||
(cat) => cat.toLowerCase().replace(/\s+/g, '-') === slug || cat === title,
|
||||
);
|
||||
}) || 'low-voltage-cables';
|
||||
|
||||
const catSlug = await mapFileSlugToTranslated(catFileSlug, locale);
|
||||
|
||||
return {
|
||||
...product,
|
||||
catSlug,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="flex items-end justify-between mb-12">
|
||||
@@ -39,29 +69,11 @@ export default async function RelatedProducts({
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{related.map((product) => {
|
||||
// Find the category slug for the link
|
||||
const categorySlugs = [
|
||||
'low-voltage-cables',
|
||||
'medium-voltage-cables',
|
||||
'high-voltage-cables',
|
||||
'solar-cables',
|
||||
];
|
||||
const catSlug =
|
||||
categorySlugs.find((slug) => {
|
||||
const key = slug
|
||||
.replace(/-cables$/, '')
|
||||
.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
const title = t(`categories.${key}.title`);
|
||||
return product.frontmatter.categories.some(
|
||||
(cat) => cat.toLowerCase().replace(/\s+/g, '-') === slug || cat === title,
|
||||
);
|
||||
}) || 'low-voltage-cables';
|
||||
|
||||
{relatedWithTranslatedSlugs.map((product) => {
|
||||
return (
|
||||
<RelatedProductLink
|
||||
key={product.slug}
|
||||
href={`/${locale}/products/${catSlug}/${product.slug}`}
|
||||
href={`/${locale}/${productsSlug}/${product.catSlug}/${product.slug}`}
|
||||
productSlug={product.slug}
|
||||
productTitle={product.frontmatter.title}
|
||||
className="group block bg-white rounded-[32px] overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-500 border border-neutral-dark/5"
|
||||
|
||||
@@ -405,6 +405,10 @@ const nextConfig = {
|
||||
source: '/de/produkte',
|
||||
destination: '/de/products',
|
||||
},
|
||||
{
|
||||
source: '/de/produkte/:path*',
|
||||
destination: '/de/products/:path*',
|
||||
},
|
||||
{
|
||||
source: '/cms/:path*',
|
||||
destination: `${directusUrl}/:path*`,
|
||||
|
||||
Reference in New Issue
Block a user