feat(task-12): link Bohrtechnik to service pages and remove reference detailed links
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 36s
Build & Deploy / 🧪 QA (push) Successful in 2m7s
CI - Lint, Typecheck & Test / quality-assurance (pull_request) Successful in 3m31s
Build & Deploy / 🏗️ Build (push) Successful in 3m45s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s

This commit is contained in:
2026-06-10 11:39:38 +02:00
parent 0105c8c9a3
commit 9134d6b070
7 changed files with 57 additions and 72 deletions

View File

@@ -3,7 +3,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server';
import { Metadata } from 'next';
import { getAllReferences } from '@/lib/references';
import TrackedLink from '@/components/analytics/TrackedLink';
import { MapPin, Calendar, Briefcase, ArrowUpRight } from 'lucide-react';
import { MapPin, Calendar, Briefcase } from 'lucide-react';
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
import { SITE_URL } from '@/lib/schema';
import Image from 'next/image';
@@ -85,14 +85,9 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
<Container className="relative z-20 mt-16 md:mt-24">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
{references.map((ref) => (
<TrackedLink
<div
key={ref.slug}
href={`/${locale}/referenzen/${ref.slug}`}
className="group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2"
eventProperties={{
location: 'referenzen_grid',
target_slug: ref.slug,
}}
className="group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 cursor-default"
>
<div className="flex flex-col h-full relative">
{/* Image Section */}
@@ -102,14 +97,14 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
src={ref.frontmatter.featuredImage}
alt={ref.frontmatter.title}
fill
className="object-cover group-hover:scale-105 transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%] group-hover:grayscale-0"
className="object-cover transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%]"
/>
) : (
<div className="absolute inset-0 bg-[#f4f4f5] flex items-center justify-center">
<Image src="/assets/logo.png" alt="E-TIB Logo" width={80} height={80} className="opacity-20 grayscale" />
</div>
)}
<div className="absolute inset-0 bg-black/5 group-hover:bg-transparent transition-colors duration-500" />
<div className="absolute inset-0 bg-black/5 transition-colors duration-500" />
{/* Location Badge */}
<div className="absolute top-4 left-4 z-10">
@@ -122,12 +117,12 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
{/* Content Section */}
<div className="flex flex-col flex-grow p-6 md:p-8 bg-white">
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 group-hover:text-primary transition-colors line-clamp-3 leading-[1.2]">
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 transition-colors line-clamp-3 leading-[1.2]">
{ref.frontmatter.title}
</h3>
{/* Technical Meta Data */}
<div className="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 pr-16 md:pr-20 relative z-10">
<div className="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 relative z-10">
<div className="min-w-0">
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">{t('client')}</span>
<span className="flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight">
@@ -144,13 +139,8 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
</div>
</div>
</div>
{/* Industrial Hover Indicator */}
<div className="absolute right-6 bottom-6 md:right-8 md:bottom-8 w-10 h-10 bg-neutral-50 border border-neutral-200 rounded flex items-center justify-center transform group-hover:bg-primary group-hover:border-primary transition-colors duration-300">
<ArrowUpRight className="w-5 h-5 text-neutral-dark group-hover:text-white transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform duration-300" />
</div>
</div>
</TrackedLink>
</div>
))}
</div>

View File

@@ -1,8 +1,7 @@
import * as React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { ReferencesSlider, Reference } from './ReferencesSlider';
import { NextIntlClientProvider } from 'next-intl';
// Mock next/link to render standard <a> tag
vi.mock('next/link', () => ({
@@ -52,45 +51,14 @@ describe('ReferencesSlider TDD', () => {
expect(screen.getByText('Referenz Projekt Zwei')).toBeTruthy();
});
it('wraps each tile in a link to /referenzen/[slug]', () => {
it('renders each tile as a static block and NOT wrapped in a link', () => {
render(<ReferencesSlider references={mockReferences} />);
const links = screen.getAllByTestId('reference-tile-link');
expect(links).toHaveLength(2);
expect(links[0].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-eins');
expect(links[1].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-zwei');
});
const links = screen.queryAllByTestId('reference-tile-link');
expect(links).toHaveLength(0);
it('allows navigation on simple click (no drag)', () => {
render(<ReferencesSlider references={mockReferences} />);
const firstLink = screen.getAllByTestId('reference-tile-link')[0];
// Simulate simple click sequence
fireEvent.mouseDown(firstLink, { pageX: 100, clientX: 100 });
fireEvent.mouseUp(firstLink);
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
firstLink.dispatchEvent(clickEvent);
expect(clickEvent.defaultPrevented).toBe(false);
});
it('prevents navigation if mouse was dragged', () => {
render(<ReferencesSlider references={mockReferences} />);
const firstLink = screen.getAllByTestId('reference-tile-link')[0];
// Simulate drag sequence
fireEvent.mouseDown(firstLink, { pageX: 100, clientX: 100 });
// Simulate mouse move of 20px
fireEvent.mouseMove(firstLink, { pageX: 120, clientX: 120 });
fireEvent.mouseUp(firstLink);
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
firstLink.dispatchEvent(clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
const tiles = screen.getAllByTestId('reference-tile');
expect(tiles).toHaveLength(2);
});
});

View File

@@ -92,11 +92,6 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
dragDistanceRef.current = Math.abs(x - startXRef.current);
};
const handleTileClick = (e: React.MouseEvent) => {
if (dragDistanceRef.current > 10) {
e.preventDefault();
}
};
return (
<section id="referenzen" className="py-24 bg-neutral-dark text-white relative overflow-hidden">
@@ -135,10 +130,8 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
transition={{ delay: i * 0.1, duration: 0.6, ease: "easeOut" }}
className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto"
>
<Link
href={`/${locale}/referenzen/${ref.slug}`}
onClick={handleTileClick}
data-testid="reference-tile-link"
<div
data-testid="reference-tile"
className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
>
<Image
@@ -158,7 +151,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
</span>
<h4 className="font-heading text-xl md:text-2xl font-bold leading-tight break-words">{ref.title}</h4>
</div>
</Link>
</div>
</motion.div>
);
})}

View File

@@ -39,6 +39,7 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
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",
backgroundImage: "/assets/photos/DSC08653.JPG"
}
]}

View File

@@ -39,6 +39,7 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
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",
backgroundImage: "/assets/photos/DSC08653.JPG"
}
]}

View File

@@ -45,7 +45,6 @@ export const defaultLocations: Location[] = [
x: 66.38,
y: 14.04,
description: 'Netzanbindung Solarpark',
href: '/referenzen/dettmannsdorf',
},
{
id: 'proetzel',
@@ -54,7 +53,6 @@ export const defaultLocations: Location[] = [
x: 78.84,
y: 32.5,
description: 'Kabeltrasse Solarpark',
href: '/referenzen/proetzel',
},
{
id: 'ostbevern',
@@ -63,7 +61,6 @@ export const defaultLocations: Location[] = [
x: 26.64,
y: 39.84,
description: 'Netzanbindung Windpark',
href: '/referenzen/ostbevern',
},
{
id: 'petershagen',
@@ -72,7 +69,6 @@ export const defaultLocations: Location[] = [
x: 36.26,
y: 35.79,
description: 'Netzanbindung Windpark',
href: '/referenzen/petershagen',
},
{
id: 'ketzin',
@@ -81,7 +77,6 @@ export const defaultLocations: Location[] = [
x: 72,
y: 34,
description: 'Netzanbindung Windpark',
href: '/referenzen/ketzin',
},
{
id: 'jevenstedt',
@@ -90,7 +85,6 @@ export const defaultLocations: Location[] = [
x: 43,
y: 9,
description: 'Netzanbindung Windpark',
href: '/referenzen/jevenstedt',
}
];

View File

@@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
import { defaultLocations } from '../lib/map-data';
describe('Task 12 TDD - Bohrtechnik and Reference Links', () => {
const deHomePath = path.join(process.cwd(), 'content', 'de', 'home.mdx');
const enHomePath = path.join(process.cwd(), 'content', 'en', 'home.mdx');
it('should verify E-TIB Bohrtechnik GmbH has the correct page link in DE and EN home.mdx', () => {
const deContent = fs.readFileSync(deHomePath, 'utf8');
const enContent = fs.readFileSync(enHomePath, 'utf8');
// Assert that E-TIB Bohrtechnik GmbH has url: "/de/bohrtechnik" in German content
expect(deContent).toContain('title: "E-TIB Bohrtechnik GmbH"');
// We expect it to have the url parameter added
const bohrtechnikDeBlock = deContent.substring(deContent.indexOf('title: "E-TIB Bohrtechnik GmbH"'));
const urlDeIndex = bohrtechnikDeBlock.indexOf('url:');
const nextItemDeIndex = bohrtechnikDeBlock.indexOf('title:', 10);
// Ensure "url:" exists within the E-TIB Bohrtechnik block and points to "/de/bohrtechnik"
expect(deContent).toMatch(/title:\s*"E-TIB Bohrtechnik GmbH"[\s\S]*?url:\s*["']\/de\/bohrtechnik["']/);
// Assert that E-TIB Bohrtechnik GmbH has url: "/en/drilling-technology" in English content
expect(enContent).toContain('title: "E-TIB Bohrtechnik GmbH"');
expect(enContent).toMatch(/title:\s*"E-TIB Bohrtechnik GmbH"[\s\S]*?url:\s*["']\/en\/drilling-technology["']/);
});
it('should verify that all reference projects in map-data have no href links', () => {
// Check that all defaultLocations of type 'project' have href undefined or removed
const projects = defaultLocations.filter(loc => loc.type === 'project');
expect(projects.length).toBeGreaterThan(0);
projects.forEach(project => {
expect(project.href).toBeUndefined();
});
});
});