Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
CI - Lint, Typecheck & Test / quality-assurance (pull_request) Failing after 1m57s
Build & Deploy / 🧪 QA (push) Failing after 2m3s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
263 lines
10 KiB
TypeScript
263 lines
10 KiB
TypeScript
import ContactForm from '@/components/ContactForm';
|
|
import JsonLd from '@/components/JsonLd';
|
|
import Reveal from '@/components/Reveal';
|
|
import { Container, Heading, Section } from '@/components/ui';
|
|
import { Metadata } from 'next';
|
|
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
|
import { SITE_URL } from '@/lib/schema';
|
|
|
|
import { Suspense } from 'react';
|
|
import ContactMap from '@/components/ContactMap';
|
|
import ObfuscatedEmail from '@/components/ObfuscatedEmail';
|
|
|
|
interface ContactPageProps {
|
|
params: Promise<{
|
|
locale: string;
|
|
}>;
|
|
}
|
|
|
|
export async function generateMetadata({ params }: ContactPageProps): Promise<Metadata> {
|
|
const { locale } = await params;
|
|
const t = await getTranslations({ locale, namespace: 'Contact' });
|
|
const title = t('meta.title') || t('title');
|
|
const description = t('meta.description') || t('subtitle');
|
|
return {
|
|
title,
|
|
description,
|
|
alternates: {
|
|
canonical: `${SITE_URL}/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`,
|
|
languages: {
|
|
de: `${SITE_URL}/de/kontakt`,
|
|
en: `${SITE_URL}/en/contact`,
|
|
'x-default': `${SITE_URL}/en/contact`,
|
|
},
|
|
},
|
|
openGraph: {
|
|
title: `${title} | KLZ Cables`,
|
|
description,
|
|
url: `${SITE_URL}/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`,
|
|
siteName: 'KLZ Cables',
|
|
locale: `${locale.toUpperCase()}_DE`,
|
|
type: 'website',
|
|
},
|
|
twitter: {
|
|
card: 'summary_large_image',
|
|
title: `${title} | KLZ Cables`,
|
|
description,
|
|
},
|
|
robots: {
|
|
index: true,
|
|
follow: true,
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function generateStaticParams() {
|
|
return [{ locale: 'de' }, { locale: 'en' }];
|
|
}
|
|
|
|
export default async function ContactPage({ params }: ContactPageProps) {
|
|
const { locale } = await params;
|
|
setRequestLocale(locale);
|
|
const t = await getTranslations({ locale, namespace: 'Contact' });
|
|
|
|
// Get translated slug to redirect if user used incorrect static slug
|
|
const { headers } = await import('next/headers');
|
|
const headersList = await headers();
|
|
const urlPath = headersList.get('x-invoke-path') || '';
|
|
const currentSlug = urlPath.split('/').pop();
|
|
|
|
if (currentSlug) {
|
|
const contactSlugDe = locale === 'de' ? 'kontakt' : 'contact';
|
|
if (currentSlug !== contactSlugDe && (currentSlug === 'kontakt' || currentSlug === 'contact')) {
|
|
const { redirect } = await import('next/navigation');
|
|
redirect(`/${locale}/${contactSlugDe}`);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col min-h-screen bg-neutral-light">
|
|
<JsonLd
|
|
id="breadcrumb-contact"
|
|
data={{
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement: [
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 1,
|
|
name: t('title'),
|
|
item: `${SITE_URL}/${locale}/contact`,
|
|
},
|
|
],
|
|
}}
|
|
/>
|
|
<JsonLd
|
|
id="local-business-contact"
|
|
data={{
|
|
'@context': 'https://schema.org',
|
|
'@type': 'LocalBusiness',
|
|
name: 'KLZ Cables',
|
|
image: `${SITE_URL}/logo.png`,
|
|
'@id': SITE_URL,
|
|
url: SITE_URL,
|
|
address: {
|
|
'@type': 'PostalAddress',
|
|
streetAddress: 'Raiffeisenstraße 22',
|
|
addressLocality: 'Remshalden',
|
|
postalCode: '73630',
|
|
addressCountry: 'DE',
|
|
},
|
|
geo: {
|
|
'@type': 'GeoCoordinates',
|
|
latitude: 48.8144,
|
|
longitude: 9.4144,
|
|
},
|
|
openingHoursSpecification: [
|
|
{
|
|
'@type': 'OpeningHoursSpecification',
|
|
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
|
opens: '08:00',
|
|
closes: '17:00',
|
|
},
|
|
],
|
|
sameAs: ['https://www.linkedin.com/company/klz-cables'],
|
|
}}
|
|
/>
|
|
{/* Hero Section */}
|
|
<Reveal>
|
|
<section className="bg-primary-dark text-white py-20 md:py-32 relative overflow-hidden">
|
|
<div className="absolute inset-0 opacity-20">
|
|
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent via-transparent to-transparent" />
|
|
</div>
|
|
<Container className="relative z-10">
|
|
<div className="max-w-4xl">
|
|
<Heading level={1} subtitle={t('heroSubtitle')} className="text-white mb-4 md:mb-6">
|
|
<span className="text-white">{t('title')}</span>
|
|
</Heading>
|
|
<p className="text-lg md:text-xl text-white/70 leading-relaxed max-w-2xl">
|
|
{t('subtitle')}
|
|
</p>
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
</Reveal>
|
|
|
|
<Section className="bg-neutral-light -mt-8 md:-mt-20 relative z-20 py-12 md:py-28">
|
|
<Container>
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16">
|
|
{/* Contact Info */}
|
|
<div className="lg:col-span-5 space-y-6 md:space-y-12">
|
|
<div className="animate-fade-in">
|
|
<Heading level={3} subtitle={t('info.subtitle')} className="mb-6 md:mb-8">
|
|
{t('info.howToReachUs')}
|
|
</Heading>
|
|
<address className="space-y-4 md:space-y-8 not-italic">
|
|
<div className="flex items-start gap-4 md:gap-6 group">
|
|
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-saturated/10 flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
|
|
<svg
|
|
className="w-5 h-5 md:w-7 md:h-7"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
|
|
/>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h4 className="text-base md:text-xl font-bold text-primary mb-1 md:mb-2">
|
|
{t('info.office')}
|
|
</h4>
|
|
<p className="text-sm md:text-lg text-text-secondary leading-relaxed whitespace-pre-line">
|
|
{t('info.address')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-start gap-4 md:gap-6 group">
|
|
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-saturated/10 flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
|
|
<svg
|
|
className="w-5 h-5 md:w-7 md:h-7"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h4 className="text-base md:text-xl font-bold text-primary mb-1 md:mb-2">
|
|
{t('info.email')}
|
|
</h4>
|
|
<ObfuscatedEmail
|
|
email="info@klz-cables.com"
|
|
className="text-sm md:text-lg text-text-secondary hover:text-primary transition-colors font-medium touch-target"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</address>
|
|
</div>
|
|
|
|
<div className="p-6 md:p-10 bg-white rounded-2xl md:rounded-3xl border border-neutral-medium shadow-sm animate-fade-in">
|
|
<Heading level={4} className="mb-4 md:mb-6">
|
|
{t('hours.title')}
|
|
</Heading>
|
|
<ul className="space-y-2 md:space-y-4 list-none m-0 p-0">
|
|
<li className="flex justify-between items-center pb-2 md:pb-4 border-b border-neutral-medium text-sm md:text-base">
|
|
<span className="font-bold text-primary">{t('hours.weekdays')}</span>
|
|
<span className="text-text-secondary">{t('hours.weekdaysTime')}</span>
|
|
</li>
|
|
<li className="flex justify-between items-center text-sm md:text-base">
|
|
<span className="font-bold text-primary">{t('hours.weekend')}</span>
|
|
<span className="text-accent-dark font-bold">{t('hours.closed')}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Contact Form */}
|
|
<div className="lg:col-span-7">
|
|
<Suspense
|
|
fallback={
|
|
<div className="animate-pulse bg-neutral-medium h-96 rounded-2xl md:rounded-3xl"></div>
|
|
}
|
|
>
|
|
<ContactForm />
|
|
</Suspense>
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
</Section>
|
|
|
|
{/* Map Section */}
|
|
<section className="h-[300px] md:h-[500px] bg-neutral-medium relative overflow-hidden grayscale hover:grayscale-0 transition-all duration-1000">
|
|
<Suspense
|
|
fallback={
|
|
<div className="h-full w-full bg-neutral-medium animate-pulse flex items-center justify-center">
|
|
<div className="text-primary font-medium">Loading Map...</div>
|
|
</div>
|
|
}
|
|
>
|
|
<ContactMap address={t('info.address')} lat={48.8144} lng={9.4144} />
|
|
</Suspense>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|