fix: refactor standorte detail page layout to match standard mdx pages and fix bohrtechnik links
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m10s
Build & Deploy / 🏗️ Build (push) Successful in 2m13s
Build & Deploy / 🚀 Deploy (push) Successful in 27s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 45s
Build & Deploy / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-06-15 11:26:09 +02:00
parent 4c9028f58b
commit aa0dfdf331
15 changed files with 191 additions and 133 deletions

BIN
after-login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

View File

@@ -63,9 +63,9 @@ export default async function StandortDetail(props: { params: Promise<{ locale:
}
return (
<div className="flex flex-col min-h-screen bg-neutral-50 pb-16 md:pb-24">
{/* Industrial Hero Section */}
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-48 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
<div className="flex flex-col min-h-screen bg-white">
{/* Generic Hero Section to match standard pages */}
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
<div className="absolute inset-0 opacity-20 pointer-events-none">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
</div>
@@ -75,139 +75,110 @@ export default async function StandortDetail(props: { params: Promise<{ locale:
<MapPin className="w-4 h-4" />
{standort.type === 'hq' ? (safeLocale === 'de' ? 'Hauptsitz' : 'Headquarters') : (safeLocale === 'de' ? 'Niederlassung' : 'Branch')}
</Badge>
<Heading level={1} variant="white" className="mb-6 drop-shadow-sm">
<Heading level={1} variant="white" className="mb-0 drop-shadow-sm">
{standort.name}
</Heading>
<p className="text-xl md:text-2xl text-white/80 max-w-3xl leading-relaxed font-medium">
{standort.description[safeLocale]}
</p>
</div>
</Container>
</section>
<Container className="relative z-20 mt-12 md:mt-16">
{/* Main Content Area */}
<Container className="py-16 md:py-24">
{/* Excerpt/Lead paragraph */}
<div className="mb-16 max-w-4xl mx-auto">
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
{standort.description[safeLocale]}
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
{/* Main Content (Images & Description) */}
<div className="lg:col-span-8 space-y-8">
{/* Featured Image */}
<div className="relative aspect-video w-full rounded-3xl overflow-hidden bg-neutral-900 shadow-xl border border-neutral-200">
<Image
src={standort.image}
alt={standort.name}
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent" />
</div>
{/* Key Features */}
<div className="bg-white rounded-3xl p-8 md:p-10 shadow-sm border border-neutral-100">
<h2 className="text-2xl font-bold font-heading mb-6 text-neutral-dark">
{safeLocale === 'de' ? 'Unsere Leistungen vor Ort' : 'Our Local Services'}
</h2>
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{standort.keyFeatures[safeLocale].map((feature, idx) => (
<li key={idx} className="flex items-start gap-3">
<CheckCircle2 className="w-5 h-5 text-primary shrink-0 mt-0.5" />
<span className="text-neutral-700 font-medium">{feature}</span>
</li>
))}
</ul>
</div>
{/* Main content in prose style */}
<div className="max-w-4xl mx-auto">
<div className="prose prose-lg md:prose-xl prose-neutral max-w-none prose-headings:font-heading prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-4xl prose-h2:text-3xl prose-h2:mt-12 prose-h2:border-b prose-h2:border-neutral-200 prose-h2:pb-4 prose-h3:text-2xl prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:font-medium prose-a:underline prose-a:underline-offset-4 prose-a:decoration-primary/30 hover:prose-a:decoration-primary prose-strong:text-text-primary prose-strong:font-semibold prose-li:text-text-secondary prose-ul:list-disc prose-ol:list-decimal">
{/* Gallery (if any) */}
<img className="rounded-2xl shadow-2xl my-12 max-w-full h-auto border border-neutral-100" src={standort.image} alt={standort.name} />
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">{safeLocale === 'de' ? 'Unsere Leistungen vor Ort' : 'Our Local Services'}</h2>
<ul className="list-none mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl">
{standort.keyFeatures[safeLocale].map((feature, idx) => (
<li key={idx} className="relative pl-6 before:content-[''] before:absolute before:left-0 before:top-[0.6em] before:w-2 before:h-2 before:bg-primary/50 before:rounded-sm">
{feature}
</li>
))}
</ul>
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">{safeLocale === 'de' ? 'Kontakt & Anfahrt' : 'Contact & Directions'}</h2>
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
<strong>{safeLocale === 'de' ? 'Adresse:' : 'Address:'}</strong><br/>
{standort.address.street}<br/>
{standort.address.city}
</p>
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
<TrackedLink
href={`https://maps.google.com/?q=${encodeURIComponent(`${standort.address.street}, ${standort.address.city}`)}`}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold flex items-center gap-1 w-fit"
eventProperties={{ location: 'standort_maps_link', slug: standort.id }}
>
{safeLocale === 'de' ? 'In Google Maps öffnen' : 'Open in Google Maps'}
<ArrowUpRight className="w-4 h-4 inline-block" />
</TrackedLink>
</p>
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
<strong>{safeLocale === 'de' ? 'Telefon:' : 'Phone:'}</strong><br/>
<a href={`tel:${standort.contact.phone.replace(/[^0-9+]/g, '')}`} className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold">
{standort.contact.phone}
</a>
</p>
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
<strong>Email:</strong><br/>
<a href={`mailto:${standort.contact.email}`} className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold">
{standort.contact.email}
</a>
</p>
{standort.gallery && standort.gallery.length > 0 && (
<div className="grid grid-cols-2 gap-4">
{standort.gallery.map((img, idx) => (
<div key={idx} className="relative aspect-[4/3] rounded-2xl overflow-hidden bg-neutral-100">
<Image src={img} alt={`Gallery ${idx + 1}`} fill className="object-cover" />
</div>
))}
</div>
<>
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">Galerie</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 not-prose my-12">
{standort.gallery.map((img, idx) => (
<div key={idx} className="relative aspect-[4/3] rounded-2xl overflow-hidden bg-neutral-100 border border-neutral-200 shadow-md hover:shadow-xl transition-shadow duration-300">
<Image src={img} alt={`Gallery ${idx + 1}`} fill className="object-cover transition-transform duration-700 hover:scale-105" />
</div>
))}
</div>
</>
)}
</div>
</div>
</Container>
{/* Sidebar (Contact & Address) */}
<div className="lg:col-span-4">
<div className="sticky top-32 space-y-6">
{/* Contact Card */}
<div className="bg-[#050B14] text-white rounded-3xl p-8 shadow-2xl relative overflow-hidden group">
<AnimatedGlossyBorder opacity={0.5} className="z-10" />
<div className="absolute top-0 right-0 w-64 h-full bg-primary/5 blur-3xl -translate-y-1/2 translate-x-1/3" />
<div className="relative z-20">
<h3 className="text-xl font-bold font-heading mb-6 flex items-center gap-2">
<Navigation className="w-5 h-5 text-primary" />
{safeLocale === 'de' ? 'Kontakt & Anfahrt' : 'Contact & Directions'}
</h3>
<div className="space-y-6">
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-full bg-white/5 flex items-center justify-center shrink-0">
<MapPin className="w-5 h-5 text-primary" />
</div>
<div>
<div className="text-xs text-white/50 font-bold uppercase tracking-wider mb-1">
{safeLocale === 'de' ? 'Adresse' : 'Address'}
</div>
<div className="text-white font-medium">
{standort.address.street}<br />
{standort.address.city}
</div>
{(() => {
const baseUrl = 'https://maps.google.com/';
const mapsUrl = `${baseUrl}?q=${encodeURIComponent(`${standort.address.street}, ${standort.address.city}`)}`;
return (
<TrackedLink
href={mapsUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 mt-2 text-primary hover:text-white transition-colors text-sm font-bold"
eventProperties={{ location: 'standort_maps_link', slug: standort.id }}
>
{safeLocale === 'de' ? 'In Google Maps öffnen' : 'Open in Google Maps'}
<ArrowUpRight className="w-3.5 h-3.5" />
</TrackedLink>
);
})()}
</div>
</div>
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-full bg-white/5 flex items-center justify-center shrink-0">
<Phone className="w-5 h-5 text-primary" />
</div>
<div>
<div className="text-xs text-white/50 font-bold uppercase tracking-wider mb-1">
{safeLocale === 'de' ? 'Telefon' : 'Phone'}
</div>
<a href={`tel:${standort.contact.phone.replace(/[^0-9+]/g, '')}`} className="text-white font-medium hover:text-primary transition-colors">
{standort.contact.phone}
</a>
</div>
</div>
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-full bg-white/5 flex items-center justify-center shrink-0">
<Mail className="w-5 h-5 text-primary" />
</div>
<div>
<div className="text-xs text-white/50 font-bold uppercase tracking-wider mb-1">
Email
</div>
<a href={`mailto:${standort.contact.email}`} className="text-white font-medium hover:text-primary transition-colors">
{standort.contact.email}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Support Section */}
<Container>
<div className="mt-12 mb-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group animate-slight-fade-in-from-bottom">
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
<div className="relative z-10 max-w-2xl">
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('needHelp')}</h3>
<p className="text-lg text-white/70 mb-8">{t('supportTeamAvailable')}</p>
<TrackedLink
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
className={getButtonClasses('accent', 'lg')}
eventProperties={{
location: 'standort_page_support_cta',
page_slug: slug,
}}
>
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
{t('contactUs')}
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
&rarr;
</span>
</span>
<ButtonOverlay variant="accent" />
</TrackedLink>
</div>
</div>
</Container>

View File

@@ -7,12 +7,10 @@ import Image from 'next/image';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
import { Location, defaultLocations, minorLocations } from '@/lib/map-data';
import { Location, defaultLocations, minorLocations, projectLocations } from '@/lib/map-data';
import { standorteLocations } from '@/lib/standorte-data';
import { useLocale, useTranslations } from 'next-intl';
const allLocations = [...standorteLocations, ...defaultLocations, ...minorLocations];
interface Stat {
value: string;
suffix?: string;
@@ -34,7 +32,7 @@ export function InteractiveGermanyMap({
title,
description,
stats,
locations = allLocations,
locations = projectLocations,
isHero = false
}: InteractiveGermanyMapProps) {
const [activeLocation, setActiveLocation] = useState<Location | null>(null);

File diff suppressed because one or more lines are too long

1
contact-page-debug.html Normal file

File diff suppressed because one or more lines are too long

BIN
contact.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

View File

@@ -86,7 +86,7 @@ layout: "fullBleed"
title: "E-TIB Bohrtechnik GmbH",
description: "Präzise Horizontalspülbohrungen in allen Bodenklassen für eine oberflächenschonende und grabenlose Leitungsverlegung.",
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
url: "/de/bohrtechnik",
url: "/de/standorte/kirchheilingen",
backgroundImage: "/assets/photos/DSC08653.JPG"
}
]}

View File

@@ -86,7 +86,7 @@ layout: "fullBleed"
title: "E-TIB Bohrtechnik GmbH",
description: "Precise horizontal directional drilling in all soil classes for surface-friendly and trenchless pipe laying.",
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
url: "/en/drilling-technology",
url: "/en/standorte/kirchheilingen",
backgroundImage: "/assets/photos/DSC08653.JPG"
}
]}

View File

@@ -896,3 +896,4 @@ export const minorLocations: Location[] = [
description: 'pv',
},
];
export const projectLocations = [...defaultLocations, ...minorLocations];

1
login-result.html Normal file

File diff suppressed because one or more lines are too long

View File

@@ -29,7 +29,12 @@ async function run() {
console.log(` Waiting for potential Gatekeeper redirect...`);
await new Promise((resolve) => setTimeout(resolve, 3000));
const isGatekeeper = await page.$('input[name="password"]');
let isGatekeeper = null;
try {
isGatekeeper = await page.$('input[name="password"]');
} catch (e) {
console.log('Navigation happened while checking gatekeeper:', e.message);
}
if (isGatekeeper) {
await page.type('input[name="password"]', gatekeeperPassword);
await Promise.all([
@@ -65,7 +70,7 @@ async function run() {
urls.map(async (url) => {
if (url.includes('openstreetmap.org') || url.includes('unpkg.com')) return;
try {
const res = await fetch(url, { method: 'HEAD' });
const res = await fetch(url, { method: 'GET' });
if (res.status >= 400) {
// Diagnostic: check if the raw source is available
const rawUrlMatch = url.match(/[?&]url=([^&]+)/);
@@ -73,7 +78,7 @@ async function run() {
const rawPath = decodeURIComponent(rawUrlMatch[1]);
const baseUrl = new URL(url).origin;
const rawUrl = `${baseUrl}${rawPath}`;
const rawRes = await fetch(rawUrl, { method: 'HEAD' });
const rawRes = await fetch(rawUrl, { method: 'GET' });
broken.push(`${url} (Status: ${res.status}, Raw Status: ${rawRes.status})`);
} else {
broken.push(`${url} (Status: ${res.status})`);

32
test-contact-auth.cjs Normal file
View File

@@ -0,0 +1,32 @@
const puppeteer = require('puppeteer');
(async () => {
const targetUrl = 'https://test.e-tib.com';
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
await new Promise((resolve) => setTimeout(resolve, 3000));
const isGatekeeper = await page.$('input[name="password"]');
if (isGatekeeper) {
await page.type('input[name="password"]', gatekeeperPassword);
await Promise.all([
page.click('button[type="submit"]'),
]);
await new Promise((resolve) => setTimeout(resolve, 5000));
}
await page.screenshot({ path: 'after-login.png' });
await page.goto(`${targetUrl}/de/kontakt`, { waitUntil: 'domcontentloaded' });
await new Promise(r => setTimeout(r, 2000));
const html = await page.content();
const fs = require('fs');
fs.writeFileSync('contact-page-auth-debug.html', html);
await page.screenshot({ path: 'contact.png' });
await browser.close();
})();

12
test-contact.cjs Normal file
View File

@@ -0,0 +1,12 @@
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://test.e-tib.com/de/kontakt', { waitUntil: 'networkidle0' });
const html = await page.content();
const fs = require('fs');
fs.writeFileSync('contact-page-debug.html', html);
await browser.close();
})();

12
test-contact.js Normal file
View File

@@ -0,0 +1,12 @@
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://test.e-tib.com/de/kontakt', { waitUntil: 'networkidle0' });
const html = await page.content();
const fs = require('fs');
fs.writeFileSync('contact-page-debug.html', html);
await browser.close();
})();

24
test-login-error.cjs Normal file
View File

@@ -0,0 +1,24 @@
const puppeteer = require('puppeteer');
(async () => {
const targetUrl = 'https://test.e-tib.com';
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(targetUrl, { waitUntil: 'networkidle0' });
const isGatekeeper = await page.$('input[name="password"]');
if (isGatekeeper) {
await page.type('input[name="password"]', gatekeeperPassword);
await Promise.all([
page.click('button[type="submit"]'),
page.waitForNavigation({ waitUntil: 'networkidle0' }).catch(() => {})
]);
const html = await page.content();
const fs = require('fs');
fs.writeFileSync('login-result.html', html);
} else {
console.log("No gatekeeper found!");
}
await browser.close();
})();